@gibs/bridge-sdk 1.9.1 → 1.12.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.
@@ -0,0 +1,2754 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ // Mock viem functions
4
+ vi.mock('viem', async () => {
5
+ const actual = await vi.importActual<typeof import('viem')>('viem');
6
+ return {
7
+ ...actual,
8
+ encodeFunctionData: vi.fn(() => '0xMOCKED_FUNCTION_DATA' as any),
9
+ encodeAbiParameters: vi.fn(() => '0xMOCKED_ABI_PARAMS' as any),
10
+ concatHex: vi.fn((parts: any[]) => parts.join('') as any),
11
+ };
12
+ });
13
+
14
+ import { getAddress, type Hex, zeroAddress } from 'viem';
15
+ import { Chains, nativeAssetOut, type Pathway } from './config';
16
+ import { FeeType } from './fee-type';
17
+ import {
18
+ amountAfterBridgeFee,
19
+ amountToBridge,
20
+ assetOut,
21
+ assetOutKey,
22
+ availableCompensationMaximum,
23
+ basisPoints,
24
+ bridgeCost,
25
+ bridgePathway,
26
+ desiredCompensationRatio,
27
+ desiredExcessCompensationBasisPoints,
28
+ destinationDataParam,
29
+ estimatedAmountOut,
30
+ estimatedCost,
31
+ estimatedFee,
32
+ estimatedNativeNetworkCost,
33
+ estimatedTokenNetworkCost,
34
+ feeDirectorStructEncoded,
35
+ interactingWithBridgeToken,
36
+ isUndercompensated,
37
+ limit,
38
+ networkSwitchAssetOutAddress,
39
+ oneEther,
40
+ oneTokenInt,
41
+ reasonableFixedFee,
42
+ reasonablePercentFee,
43
+ reasonablePercentOnGasLimit,
44
+ reasonablePercentOnTopOfGasFee,
45
+ transactionInputs,
46
+ } from './settings';
47
+ import type { BridgeKey, Token } from './types';
48
+
49
+ describe('settings', () => {
50
+ describe('constants', () => {
51
+ it('should have basisPoints as 10000', () => {
52
+ expect(basisPoints).toBe(10_000n);
53
+ });
54
+
55
+ it('should have oneEther as 10^18', () => {
56
+ expect(oneEther).toBe(1000000000000000000n);
57
+ expect(oneEther).toBe(10n ** 18n);
58
+ });
59
+ });
60
+
61
+ describe('limit', () => {
62
+ it('should return 0 for null', () => {
63
+ expect(limit(null)).toBe(0n);
64
+ });
65
+
66
+ it('should return 0 for falsy bigint', () => {
67
+ expect(limit(0n)).toBe(0n);
68
+ });
69
+
70
+ it('should return the value for positive bigint', () => {
71
+ expect(limit(1000n)).toBe(1000n);
72
+ expect(limit(12345678901234567890n)).toBe(12345678901234567890n);
73
+ });
74
+ });
75
+
76
+ describe('oneTokenInt', () => {
77
+ it('should return 1n when assetIn is null', () => {
78
+ expect(oneTokenInt({ assetIn: null })).toBe(1n);
79
+ });
80
+
81
+ it('should calculate 10^decimals for token', () => {
82
+ const usdt = { decimals: 6, address: '0x123', chainId: 1, name: 'USDT', symbol: 'USDT' };
83
+ expect(oneTokenInt({ assetIn: usdt })).toBe(1000000n); // 10^6
84
+ });
85
+
86
+ it('should handle 18 decimal tokens (standard ERC20)', () => {
87
+ const dai = { decimals: 18, address: '0x456', chainId: 1, name: 'DAI', symbol: 'DAI' };
88
+ expect(oneTokenInt({ assetIn: dai })).toBe(10n ** 18n);
89
+ });
90
+
91
+ it('should handle tokens with 0 decimals', () => {
92
+ const token = { decimals: 0, address: '0x789', chainId: 1, name: 'TOKEN', symbol: 'TKN' };
93
+ expect(oneTokenInt({ assetIn: token })).toBe(1n);
94
+ });
95
+
96
+ it('should handle tokens with large decimal values', () => {
97
+ const token = { decimals: 24, address: '0xabc', chainId: 1, name: 'TOKEN', symbol: 'TKN' };
98
+ expect(oneTokenInt({ assetIn: token })).toBe(10n ** 24n);
99
+ });
100
+ });
101
+
102
+ describe('interactingWithBridgeToken', () => {
103
+ const foreignAddress = '0xdAC17F958D2ee523a2206206994597C13D831ec7';
104
+ const homeAddress = '0xA1077a294dDE1B09bB078844df40758a5D0f9a27';
105
+
106
+ it('should return false when assetInAddress is null', () => {
107
+ const result = interactingWithBridgeToken({
108
+ assetLink: null,
109
+ assetInAddress: null,
110
+ });
111
+ expect(result).toBe(false);
112
+ });
113
+
114
+ it('should return false when assetLink is null', () => {
115
+ const result = interactingWithBridgeToken({
116
+ assetLink: null,
117
+ assetInAddress: foreignAddress,
118
+ });
119
+ expect(result).toBe(false);
120
+ });
121
+
122
+ it('should return true when assetInAddress matches foreign address', () => {
123
+ const assetLink = {
124
+ toForeign: { foreign: foreignAddress },
125
+ toHome: { home: homeAddress },
126
+ };
127
+ const result = interactingWithBridgeToken({
128
+ assetLink: assetLink as any,
129
+ assetInAddress: foreignAddress,
130
+ });
131
+ expect(result).toBe(true);
132
+ });
133
+
134
+ it('should return true when assetInAddress matches home address', () => {
135
+ const assetLink = {
136
+ toForeign: { foreign: foreignAddress },
137
+ toHome: { home: homeAddress },
138
+ };
139
+ const result = interactingWithBridgeToken({
140
+ assetLink: assetLink as any,
141
+ assetInAddress: homeAddress,
142
+ });
143
+ expect(result).toBe(true);
144
+ });
145
+
146
+ it('should handle checksummed addresses correctly', () => {
147
+ const assetLink = {
148
+ toForeign: { foreign: foreignAddress.toLowerCase() },
149
+ toHome: { home: homeAddress.toLowerCase() },
150
+ };
151
+ // Should match even with different casing due to getAddress checksumming
152
+ const result = interactingWithBridgeToken({
153
+ assetLink: assetLink as any,
154
+ assetInAddress: foreignAddress, // checksummed
155
+ });
156
+ expect(result).toBe(true);
157
+ });
158
+
159
+ it('should return false when assetInAddress does not match either address', () => {
160
+ const assetLink = {
161
+ toForeign: { foreign: foreignAddress },
162
+ toHome: { home: homeAddress },
163
+ };
164
+ const result = interactingWithBridgeToken({
165
+ assetLink: assetLink as any,
166
+ assetInAddress: '0x0000000000000000000000000000000000000000',
167
+ });
168
+ expect(result).toBe(false);
169
+ });
170
+
171
+ it('should return false when toForeign is missing', () => {
172
+ const assetLink = {
173
+ toHome: { home: homeAddress },
174
+ };
175
+ const result = interactingWithBridgeToken({
176
+ assetLink: assetLink as any,
177
+ assetInAddress: foreignAddress,
178
+ });
179
+ expect(result).toBe(false);
180
+ });
181
+
182
+ it('should return false when toHome is missing', () => {
183
+ const assetLink = {
184
+ toForeign: { foreign: foreignAddress },
185
+ };
186
+ const result = interactingWithBridgeToken({
187
+ assetLink: assetLink as any,
188
+ assetInAddress: homeAddress,
189
+ });
190
+ expect(result).toBe(false);
191
+ });
192
+ });
193
+
194
+ describe('amountAfterBridgeFee', () => {
195
+ it('should return null when bridgeCost is null', () => {
196
+ const result = amountAfterBridgeFee({
197
+ amountToBridge: 1000n,
198
+ bridgeCost: null,
199
+ });
200
+ expect(result).toBeNull();
201
+ });
202
+
203
+ it('should calculate amount after fee correctly', () => {
204
+ const result = amountAfterBridgeFee({
205
+ amountToBridge: 1000n,
206
+ bridgeCost: 100n,
207
+ });
208
+ expect(result).toBe(900n);
209
+ });
210
+
211
+ it('should return 0 when fee exceeds amount', () => {
212
+ const result = amountAfterBridgeFee({
213
+ amountToBridge: 100n,
214
+ bridgeCost: 200n,
215
+ });
216
+ expect(result).toBe(0n);
217
+ });
218
+
219
+ it('should handle zero fee', () => {
220
+ const result = amountAfterBridgeFee({
221
+ amountToBridge: 1000n,
222
+ bridgeCost: 0n,
223
+ });
224
+ expect(result).toBe(1000n);
225
+ });
226
+
227
+ it('should handle exact fee amount', () => {
228
+ const result = amountAfterBridgeFee({
229
+ amountToBridge: 1000n,
230
+ bridgeCost: 1000n,
231
+ });
232
+ expect(result).toBe(0n);
233
+ });
234
+
235
+ it('should handle large amounts', () => {
236
+ const largeAmount = 1000000000000000000n; // 1 ETH in wei
237
+ const largeFee = 10000000000000000n; // 0.01 ETH
238
+ const result = amountAfterBridgeFee({
239
+ amountToBridge: largeAmount,
240
+ bridgeCost: largeFee,
241
+ });
242
+ expect(result).toBe(largeAmount - largeFee);
243
+ });
244
+
245
+ it('should maintain precision with bigint', () => {
246
+ const amount = 123456789012345678n;
247
+ const fee = 1n;
248
+ const result = amountAfterBridgeFee({
249
+ amountToBridge: amount,
250
+ bridgeCost: fee,
251
+ });
252
+ expect(result).toBe(123456789012345677n);
253
+ });
254
+ });
255
+
256
+ describe('assetOutKey', () => {
257
+ it('should return null when bridgeKeyPath is empty', () => {
258
+ expect(
259
+ assetOutKey({
260
+ bridgeKeyPath: '',
261
+ assetInAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
262
+ unwrap: false,
263
+ }),
264
+ ).toBeNull();
265
+ });
266
+
267
+ it('should return null when assetInAddress is null', () => {
268
+ expect(
269
+ assetOutKey({
270
+ bridgeKeyPath: 'pulsechain/0x1/0x171',
271
+ assetInAddress: null,
272
+ unwrap: false,
273
+ }),
274
+ ).toBeNull();
275
+ });
276
+
277
+ it('should return null when assetInAddress is undefined', () => {
278
+ expect(
279
+ assetOutKey({
280
+ bridgeKeyPath: 'pulsechain/0x1/0x171',
281
+ assetInAddress: undefined,
282
+ unwrap: false,
283
+ }),
284
+ ).toBeNull();
285
+ });
286
+
287
+ it('should create key for ERC20 asset', () => {
288
+ const result = assetOutKey({
289
+ bridgeKeyPath: 'pulsechain/0x1/0x171',
290
+ assetInAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
291
+ unwrap: false,
292
+ });
293
+ expect(result).toBe('pulsechain/0x1/0x171/erc20/0xdac17f958d2ee523a2206206994597c13d831ec7');
294
+ });
295
+
296
+ it('should create key for native (unwrapped) asset', () => {
297
+ const result = assetOutKey({
298
+ bridgeKeyPath: 'pulsechain/0x1/0x171',
299
+ assetInAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
300
+ unwrap: true,
301
+ });
302
+ expect(result).toBe('pulsechain/0x1/0x171/native/0xdac17f958d2ee523a2206206994597c13d831ec7');
303
+ });
304
+
305
+ it('should lowercase the address', () => {
306
+ const result = assetOutKey({
307
+ bridgeKeyPath: 'pulsechain/0x1/0x171',
308
+ assetInAddress: '0xDAC17F958D2EE523A2206206994597C13D831EC7', // uppercase
309
+ unwrap: false,
310
+ });
311
+ expect(result).toContain('0xdac17f958d2ee523a2206206994597c13d831ec7');
312
+ });
313
+
314
+ it('should handle different bridge paths', () => {
315
+ const key1 = assetOutKey({
316
+ bridgeKeyPath: 'pulsechain/0x1/0x171',
317
+ assetInAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
318
+ unwrap: false,
319
+ });
320
+ const key2 = assetOutKey({
321
+ bridgeKeyPath: 'tokensex/0x38/0x171',
322
+ assetInAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
323
+ unwrap: false,
324
+ });
325
+ expect(key1).not.toBe(key2);
326
+ expect(key1).toContain('pulsechain');
327
+ expect(key2).toContain('tokensex');
328
+ });
329
+
330
+ it('should differentiate between native and erc20 for same asset', () => {
331
+ const erc20Key = assetOutKey({
332
+ bridgeKeyPath: 'pulsechain/0x1/0x171',
333
+ assetInAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
334
+ unwrap: false,
335
+ });
336
+ const nativeKey = assetOutKey({
337
+ bridgeKeyPath: 'pulsechain/0x1/0x171',
338
+ assetInAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
339
+ unwrap: true,
340
+ });
341
+ expect(erc20Key).not.toBe(nativeKey);
342
+ expect(erc20Key).toContain('/erc20/');
343
+ expect(nativeKey).toContain('/native/');
344
+ });
345
+ });
346
+
347
+ describe('desiredCompensationRatio', () => {
348
+ it('should return oneEther when excess is 0', () => {
349
+ const result = desiredCompensationRatio({
350
+ desiredExcessCompensationBasisPoints: 0n,
351
+ });
352
+ expect(result).toBe(oneEther);
353
+ });
354
+
355
+ it('should calculate ratio for 10% excess (1000 basis points)', () => {
356
+ const result = desiredCompensationRatio({
357
+ desiredExcessCompensationBasisPoints: 1_000n,
358
+ });
359
+ // 1 ETH + (1000 * 1 ETH / 10000) = 1 ETH + 0.1 ETH = 1.1 ETH
360
+ expect(result).toBe(oneEther + (1_000n * oneEther) / basisPoints);
361
+ expect(result).toBe(1100000000000000000n); // 1.1 ETH
362
+ });
363
+
364
+ it('should calculate ratio for 50% excess (5000 basis points)', () => {
365
+ const result = desiredCompensationRatio({
366
+ desiredExcessCompensationBasisPoints: 5_000n,
367
+ });
368
+ // 1 ETH + (5000 * 1 ETH / 10000) = 1 ETH + 0.5 ETH = 1.5 ETH
369
+ expect(result).toBe(1500000000000000000n); // 1.5 ETH
370
+ });
371
+
372
+ it('should calculate ratio for 100% excess (10000 basis points)', () => {
373
+ const result = desiredCompensationRatio({
374
+ desiredExcessCompensationBasisPoints: 10_000n,
375
+ });
376
+ // 1 ETH + (10000 * 1 ETH / 10000) = 1 ETH + 1 ETH = 2 ETH
377
+ expect(result).toBe(2000000000000000000n); // 2 ETH
378
+ });
379
+
380
+ it('should handle small excess amounts (1 basis point)', () => {
381
+ const result = desiredCompensationRatio({
382
+ desiredExcessCompensationBasisPoints: 1n,
383
+ });
384
+ // 1 ETH + (1 * 1 ETH / 10000) = 1 ETH + 0.0001 ETH
385
+ expect(result).toBe(1000100000000000000n);
386
+ });
387
+
388
+ it('should use correct formula with oneEther and basisPoints', () => {
389
+ const excess = 2_500n; // 25%
390
+ const result = desiredCompensationRatio({
391
+ desiredExcessCompensationBasisPoints: excess,
392
+ });
393
+ const expected = oneEther + (excess * oneEther) / basisPoints;
394
+ expect(result).toBe(expected);
395
+ });
396
+
397
+ it('should maintain precision with bigint arithmetic', () => {
398
+ const excess = 3_333n; // 33.33%
399
+ const result = desiredCompensationRatio({
400
+ desiredExcessCompensationBasisPoints: excess,
401
+ });
402
+ // Should not lose precision
403
+ expect(result).toBe(oneEther + (3_333n * oneEther) / basisPoints);
404
+ });
405
+ });
406
+
407
+ describe('amountToBridge', () => {
408
+ const mockToken = {
409
+ address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
410
+ chainId: 1,
411
+ name: 'USDT',
412
+ symbol: 'USDT',
413
+ decimals: 6,
414
+ };
415
+
416
+ it('should return amountIn when valid', () => {
417
+ const result = amountToBridge({
418
+ amountIn: 1000n,
419
+ assetIn: mockToken,
420
+ });
421
+ expect(result).toBe(1000n);
422
+ });
423
+
424
+ it('should return 0n when amountIn is null', () => {
425
+ const result = amountToBridge({
426
+ amountIn: null,
427
+ assetIn: mockToken,
428
+ });
429
+ expect(result).toBe(0n);
430
+ });
431
+
432
+ it('should return 0n when amountIn is 0n', () => {
433
+ const result = amountToBridge({
434
+ amountIn: 0n,
435
+ assetIn: mockToken,
436
+ });
437
+ expect(result).toBe(0n);
438
+ });
439
+
440
+ it('should return 0n when assetIn is null', () => {
441
+ const result = amountToBridge({
442
+ amountIn: 1000n,
443
+ assetIn: null,
444
+ });
445
+ expect(result).toBe(0n);
446
+ });
447
+
448
+ it('should return 0n when both are null', () => {
449
+ const result = amountToBridge({
450
+ amountIn: null,
451
+ assetIn: null,
452
+ });
453
+ expect(result).toBe(0n);
454
+ });
455
+
456
+ it('should handle large amounts', () => {
457
+ const largeAmount = 1000000000000000000n; // 1 ETH in wei
458
+ const result = amountToBridge({
459
+ amountIn: largeAmount,
460
+ assetIn: mockToken,
461
+ });
462
+ expect(result).toBe(largeAmount);
463
+ });
464
+
465
+ it('should validate both amountIn and assetIn together', () => {
466
+ // Only returns the amount if BOTH are valid
467
+ expect(amountToBridge({ amountIn: 100n, assetIn: mockToken })).toBe(100n);
468
+ expect(amountToBridge({ amountIn: null, assetIn: mockToken })).toBe(0n);
469
+ expect(amountToBridge({ amountIn: 100n, assetIn: null })).toBe(0n);
470
+ expect(amountToBridge({ amountIn: 0n, assetIn: mockToken })).toBe(0n);
471
+ });
472
+ });
473
+
474
+ describe('bridgeCost', () => {
475
+ it('should return null when bridgeFee is null', () => {
476
+ const result = bridgeCost({
477
+ amountToBridge: 1000n,
478
+ bridgeFee: null,
479
+ });
480
+ expect(result).toBeNull();
481
+ });
482
+
483
+ it('should calculate cost correctly with percentage fee', () => {
484
+ const amount = oneEther; // 1 ETH
485
+ const fee = oneEther / 100n; // 1% = 0.01 ETH
486
+ const result = bridgeCost({
487
+ amountToBridge: amount,
488
+ bridgeFee: fee,
489
+ });
490
+ // (1 ETH * 0.01 ETH) / 1 ETH = 0.01 ETH
491
+ expect(result).toBe((amount * fee) / oneEther);
492
+ });
493
+
494
+ it('should handle zero fee', () => {
495
+ const result = bridgeCost({
496
+ amountToBridge: 1000n,
497
+ bridgeFee: 0n,
498
+ });
499
+ expect(result).toBe(0n);
500
+ });
501
+
502
+ it('should calculate with small fee', () => {
503
+ const amount = 1000000n; // 1 USDT (6 decimals)
504
+ const fee = oneEther / 1000n; // 0.1% fee
505
+ const result = bridgeCost({
506
+ amountToBridge: amount,
507
+ bridgeFee: fee,
508
+ });
509
+ expect(result).toBe((amount * fee) / oneEther);
510
+ });
511
+
512
+ it('should maintain precision with large numbers', () => {
513
+ const amount = 123456789012345678n;
514
+ const fee = oneEther / 50n; // 2% fee
515
+ const result = bridgeCost({
516
+ amountToBridge: amount,
517
+ bridgeFee: fee,
518
+ });
519
+ const expected = (amount * fee) / oneEther;
520
+ expect(result).toBe(expected);
521
+ });
522
+
523
+ it('should use oneEther as divisor for percentage calculation', () => {
524
+ const amount = 500n * oneEther; // 500 ETH
525
+ const fee = oneEther / 200n; // 0.5% = 0.005 ETH per 1 ETH
526
+ const result = bridgeCost({
527
+ amountToBridge: amount,
528
+ bridgeFee: fee,
529
+ });
530
+ // (500 ETH * 0.005) / 1 = 2.5 ETH
531
+ expect(result).toBe((500n * oneEther * fee) / oneEther);
532
+ });
533
+
534
+ it('should handle fee equal to oneEther (100%)', () => {
535
+ const amount = 1000n * oneEther;
536
+ const fee = oneEther; // 100% fee
537
+ const result = bridgeCost({
538
+ amountToBridge: amount,
539
+ bridgeFee: fee,
540
+ });
541
+ // (1000 ETH * 1 ETH) / 1 ETH = 1000 ETH (entire amount)
542
+ expect(result).toBe(amount);
543
+ });
544
+ });
545
+
546
+ describe('networkSwitchAssetOutAddress', () => {
547
+ const mockToken = {
548
+ address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
549
+ chainId: 1,
550
+ name: 'USDT',
551
+ symbol: 'USDT',
552
+ decimals: 6,
553
+ };
554
+
555
+ const wrappedPLS = {
556
+ address: nativeAssetOut[Chains.PLS],
557
+ chainId: 369,
558
+ name: 'WPLS',
559
+ symbol: 'WPLS',
560
+ decimals: 18,
561
+ };
562
+
563
+ it('should return null when assetOut is null', () => {
564
+ const result = networkSwitchAssetOutAddress({
565
+ assetOut: null,
566
+ unwrap: false,
567
+ toChain: Chains.ETH,
568
+ });
569
+ expect(result).toBeNull();
570
+ });
571
+
572
+ it('should return assetOut address when not unwrapping', () => {
573
+ const result = networkSwitchAssetOutAddress({
574
+ assetOut: mockToken,
575
+ unwrap: false,
576
+ toChain: Chains.ETH,
577
+ });
578
+ expect(result).toBe(mockToken.address);
579
+ });
580
+
581
+ it('should return zeroAddress when unwrapping native asset', () => {
582
+ const result = networkSwitchAssetOutAddress({
583
+ assetOut: wrappedPLS,
584
+ unwrap: true,
585
+ toChain: Chains.PLS,
586
+ });
587
+ expect(result).toBe(zeroAddress);
588
+ });
589
+
590
+ it('should return assetOut address when unwrapping non-native asset', () => {
591
+ const result = networkSwitchAssetOutAddress({
592
+ assetOut: mockToken,
593
+ unwrap: true,
594
+ toChain: Chains.ETH,
595
+ });
596
+ expect(result).toBe(mockToken.address);
597
+ });
598
+
599
+ it('should check native asset out per chain', () => {
600
+ const wrappedETH = {
601
+ address: nativeAssetOut[Chains.ETH],
602
+ chainId: 1,
603
+ name: 'WETH',
604
+ symbol: 'WETH',
605
+ decimals: 18,
606
+ };
607
+
608
+ const result = networkSwitchAssetOutAddress({
609
+ assetOut: wrappedETH,
610
+ unwrap: true,
611
+ toChain: Chains.ETH,
612
+ });
613
+ expect(result).toBe(zeroAddress);
614
+ });
615
+
616
+ it('should not unwrap if chain does not match', () => {
617
+ // WPLS address but different chain
618
+ const result = networkSwitchAssetOutAddress({
619
+ assetOut: wrappedPLS,
620
+ unwrap: true,
621
+ toChain: Chains.ETH, // Different chain
622
+ });
623
+ // Should NOT unwrap because nativeAssetOut[ETH] !== WPLS address
624
+ expect(result).toBe(wrappedPLS.address);
625
+ expect(result).not.toBe(zeroAddress);
626
+ });
627
+
628
+ it('should handle all supported chains', () => {
629
+ Object.entries(nativeAssetOut).forEach(([chain, wrappedAddress]) => {
630
+ const token = {
631
+ address: wrappedAddress,
632
+ chainId: Number(chain),
633
+ name: 'Wrapped',
634
+ symbol: 'W',
635
+ decimals: 18,
636
+ };
637
+
638
+ const result = networkSwitchAssetOutAddress({
639
+ assetOut: token,
640
+ unwrap: true,
641
+ toChain: chain as Chains,
642
+ });
643
+ expect(result).toBe(zeroAddress);
644
+ });
645
+ });
646
+ });
647
+
648
+ describe('estimatedAmountOut', () => {
649
+ it('should return null when fee is not a bigint', () => {
650
+ expect(
651
+ estimatedAmountOut({
652
+ amountAfterBridgeFee: 1000n,
653
+ fee: null,
654
+ }),
655
+ ).toBeNull();
656
+
657
+ expect(
658
+ estimatedAmountOut({
659
+ amountAfterBridgeFee: 1000n,
660
+ fee: undefined as any,
661
+ }),
662
+ ).toBeNull();
663
+ });
664
+
665
+ it('should return null when amountAfterBridgeFee is null', () => {
666
+ expect(
667
+ estimatedAmountOut({
668
+ amountAfterBridgeFee: null,
669
+ fee: 100n,
670
+ }),
671
+ ).toBeNull();
672
+ });
673
+
674
+ it('should return 0n when fee exceeds amount', () => {
675
+ expect(
676
+ estimatedAmountOut({
677
+ amountAfterBridgeFee: 100n,
678
+ fee: 200n,
679
+ }),
680
+ ).toBe(0n);
681
+
682
+ expect(
683
+ estimatedAmountOut({
684
+ amountAfterBridgeFee: 50n,
685
+ fee: 51n,
686
+ }),
687
+ ).toBe(0n);
688
+ });
689
+
690
+ it('should calculate amount out correctly', () => {
691
+ expect(
692
+ estimatedAmountOut({
693
+ amountAfterBridgeFee: 1000n,
694
+ fee: 100n,
695
+ }),
696
+ ).toBe(900n);
697
+
698
+ expect(
699
+ estimatedAmountOut({
700
+ amountAfterBridgeFee: 500000n,
701
+ fee: 2500n,
702
+ }),
703
+ ).toBe(497500n);
704
+ });
705
+
706
+ it('should handle zero fee', () => {
707
+ expect(
708
+ estimatedAmountOut({
709
+ amountAfterBridgeFee: 1000n,
710
+ fee: 0n,
711
+ }),
712
+ ).toBe(1000n);
713
+ });
714
+
715
+ it('should handle exact fee amount', () => {
716
+ expect(
717
+ estimatedAmountOut({
718
+ amountAfterBridgeFee: 1000n,
719
+ fee: 1000n,
720
+ }),
721
+ ).toBe(0n);
722
+ });
723
+
724
+ it('should handle large amounts', () => {
725
+ const largeAmount = oneEther * 1000n; // 1000 ETH
726
+ const largeFee = oneEther; // 1 ETH
727
+ expect(
728
+ estimatedAmountOut({
729
+ amountAfterBridgeFee: largeAmount,
730
+ fee: largeFee,
731
+ }),
732
+ ).toBe(largeAmount - largeFee);
733
+ });
734
+
735
+ it('should maintain precision with bigint', () => {
736
+ const amount = 123456789012345678n;
737
+ const fee = 1n;
738
+ expect(
739
+ estimatedAmountOut({
740
+ amountAfterBridgeFee: amount,
741
+ fee: fee,
742
+ }),
743
+ ).toBe(123456789012345677n);
744
+ });
745
+ });
746
+
747
+ describe('estimatedFee', () => {
748
+ const baseParams = {
749
+ amountAfterBridgeFee: 1000n * oneEther,
750
+ fee: 50n * oneEther,
751
+ percentFee: oneEther / 100n, // 1%
752
+ limit: 21000n,
753
+ };
754
+
755
+ it('should return 0n when not delivering', () => {
756
+ expect(
757
+ estimatedFee({
758
+ ...baseParams,
759
+ feeType: FeeType.FIXED,
760
+ costsToDeliver: false,
761
+ }),
762
+ ).toBe(0n);
763
+
764
+ expect(
765
+ estimatedFee({
766
+ ...baseParams,
767
+ feeType: FeeType.PERCENT,
768
+ costsToDeliver: false,
769
+ }),
770
+ ).toBe(0n);
771
+ });
772
+
773
+ it('should return fixed fee for FIXED type', () => {
774
+ expect(
775
+ estimatedFee({
776
+ ...baseParams,
777
+ feeType: FeeType.FIXED,
778
+ costsToDeliver: true,
779
+ }),
780
+ ).toBe(50n * oneEther);
781
+ });
782
+
783
+ it('should calculate percent fee for PERCENT type', () => {
784
+ const result = estimatedFee({
785
+ ...baseParams,
786
+ feeType: FeeType.PERCENT,
787
+ costsToDeliver: true,
788
+ });
789
+ // 1000 ETH * 1% = 10 ETH
790
+ const expected = (1000n * oneEther * (oneEther / 100n)) / oneEther;
791
+ expect(result).toBe(expected);
792
+ expect(result).toBe(10n * oneEther);
793
+ });
794
+
795
+ it('should return limit for GAS_TIP type', () => {
796
+ expect(
797
+ estimatedFee({
798
+ ...baseParams,
799
+ feeType: FeeType.GAS_TIP,
800
+ costsToDeliver: true,
801
+ }),
802
+ ).toBe(21000n);
803
+ });
804
+
805
+ it('should return null for PERCENT when amountAfterBridgeFee is null', () => {
806
+ expect(
807
+ estimatedFee({
808
+ ...baseParams,
809
+ amountAfterBridgeFee: null,
810
+ feeType: FeeType.PERCENT,
811
+ costsToDeliver: true,
812
+ }),
813
+ ).toBeNull();
814
+ });
815
+
816
+ it('should handle zero percent fee', () => {
817
+ const result = estimatedFee({
818
+ ...baseParams,
819
+ percentFee: 0n,
820
+ feeType: FeeType.PERCENT,
821
+ costsToDeliver: true,
822
+ });
823
+ expect(result).toBe(0n);
824
+ });
825
+
826
+ it('should handle high percent fee', () => {
827
+ const result = estimatedFee({
828
+ ...baseParams,
829
+ percentFee: oneEther / 10n, // 10%
830
+ feeType: FeeType.PERCENT,
831
+ costsToDeliver: true,
832
+ });
833
+ // 1000 ETH * 10% = 100 ETH
834
+ expect(result).toBe(100n * oneEther);
835
+ });
836
+
837
+ it('should return null when amountAfterBridgeFee is null with fixedFee calculation', () => {
838
+ const result = estimatedFee({
839
+ ...baseParams,
840
+ amountAfterBridgeFee: null,
841
+ feeType: FeeType.PERCENT,
842
+ costsToDeliver: true,
843
+ });
844
+ expect(result).toBeNull();
845
+ });
846
+
847
+ it('should handle all FeeType enum values', () => {
848
+ // FIXED
849
+ const fixed = estimatedFee({
850
+ ...baseParams,
851
+ feeType: FeeType.FIXED,
852
+ costsToDeliver: true,
853
+ });
854
+ expect(fixed).toBe(baseParams.fee);
855
+
856
+ // PERCENT
857
+ const percent = estimatedFee({
858
+ ...baseParams,
859
+ feeType: FeeType.PERCENT,
860
+ costsToDeliver: true,
861
+ });
862
+ expect(percent).toBeDefined();
863
+ expect(typeof percent).toBe('bigint');
864
+
865
+ // GAS_TIP
866
+ const gasTip = estimatedFee({
867
+ ...baseParams,
868
+ feeType: FeeType.GAS_TIP,
869
+ costsToDeliver: true,
870
+ });
871
+ expect(gasTip).toBe(baseParams.limit);
872
+ });
873
+ });
874
+
875
+ describe('assetOut', () => {
876
+ const mockToken1 = {
877
+ address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
878
+ chainId: 1,
879
+ name: 'USDT',
880
+ symbol: 'USDT',
881
+ decimals: 6,
882
+ };
883
+
884
+ const mockToken2 = {
885
+ address: '0xA1077a294dDE1B09bB078844df40758a5D0f9a27',
886
+ chainId: 369,
887
+ name: 'WPLS',
888
+ symbol: 'WPLS',
889
+ decimals: 18,
890
+ };
891
+
892
+ it('should return null when assetInAddress is null', () => {
893
+ const assetsOut = new Map();
894
+ expect(
895
+ assetOut({
896
+ assetInAddress: null,
897
+ assetsOut,
898
+ bridgeKeyPath: 'pulsechain/0x1/0x171',
899
+ unwrap: false,
900
+ }),
901
+ ).toBeNull();
902
+ });
903
+
904
+ it('should return null when assetOutKey returns null', () => {
905
+ const assetsOut = new Map();
906
+ expect(
907
+ assetOut({
908
+ assetInAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
909
+ assetsOut,
910
+ bridgeKeyPath: '', // Empty bridge path causes assetOutKey to return null
911
+ unwrap: false,
912
+ }),
913
+ ).toBeNull();
914
+ });
915
+
916
+ it('should return token from map when key exists', () => {
917
+ const assetsOut = new Map();
918
+ const key = 'pulsechain/0x1/0x171/erc20/0xdac17f958d2ee523a2206206994597c13d831ec7';
919
+ assetsOut.set(key, mockToken1);
920
+
921
+ const result = assetOut({
922
+ assetInAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
923
+ assetsOut,
924
+ bridgeKeyPath: 'pulsechain/0x1/0x171',
925
+ unwrap: false,
926
+ });
927
+
928
+ expect(result).toEqual(mockToken1);
929
+ });
930
+
931
+ it('should return null when key does not exist in map', () => {
932
+ const assetsOut = new Map();
933
+
934
+ const result = assetOut({
935
+ assetInAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
936
+ assetsOut,
937
+ bridgeKeyPath: 'pulsechain/0x1/0x171',
938
+ unwrap: false,
939
+ });
940
+
941
+ expect(result).toBeNull();
942
+ });
943
+
944
+ it('should handle unwrap flag correctly', () => {
945
+ const assetsOut = new Map();
946
+ const erc20Key = 'pulsechain/0x1/0x171/erc20/0xdac17f958d2ee523a2206206994597c13d831ec7';
947
+ const nativeKey = 'pulsechain/0x1/0x171/native/0xdac17f958d2ee523a2206206994597c13d831ec7';
948
+
949
+ assetsOut.set(erc20Key, mockToken1);
950
+ assetsOut.set(nativeKey, mockToken2);
951
+
952
+ // Without unwrap - should look for erc20 key
953
+ const erc20Result = assetOut({
954
+ assetInAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
955
+ assetsOut,
956
+ bridgeKeyPath: 'pulsechain/0x1/0x171',
957
+ unwrap: false,
958
+ });
959
+ expect(erc20Result).toEqual(mockToken1);
960
+
961
+ // With unwrap - should look for native key
962
+ const nativeResult = assetOut({
963
+ assetInAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
964
+ assetsOut,
965
+ bridgeKeyPath: 'pulsechain/0x1/0x171',
966
+ unwrap: true,
967
+ });
968
+ expect(nativeResult).toEqual(mockToken2);
969
+ });
970
+
971
+ it('should handle different bridge paths', () => {
972
+ const assetsOut = new Map();
973
+ const key1 = 'pulsechain/0x1/0x171/erc20/0xdac17f958d2ee523a2206206994597c13d831ec7';
974
+ const key2 = 'tokensex/0x38/0x171/erc20/0xdac17f958d2ee523a2206206994597c13d831ec7';
975
+
976
+ assetsOut.set(key1, mockToken1);
977
+ assetsOut.set(key2, mockToken2);
978
+
979
+ const result1 = assetOut({
980
+ assetInAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
981
+ assetsOut,
982
+ bridgeKeyPath: 'pulsechain/0x1/0x171',
983
+ unwrap: false,
984
+ });
985
+ expect(result1).toEqual(mockToken1);
986
+
987
+ const result2 = assetOut({
988
+ assetInAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
989
+ assetsOut,
990
+ bridgeKeyPath: 'tokensex/0x38/0x171',
991
+ unwrap: false,
992
+ });
993
+ expect(result2).toEqual(mockToken2);
994
+ });
995
+
996
+ it('should handle case-insensitive address matching via assetOutKey', () => {
997
+ const assetsOut = new Map();
998
+ // Key is always lowercase due to assetOutKey
999
+ const key = 'pulsechain/0x1/0x171/erc20/0xdac17f958d2ee523a2206206994597c13d831ec7';
1000
+ assetsOut.set(key, mockToken1);
1001
+
1002
+ // Should work with uppercase address
1003
+ const result = assetOut({
1004
+ assetInAddress: '0xDAC17F958D2EE523A2206206994597C13D831EC7',
1005
+ assetsOut,
1006
+ bridgeKeyPath: 'pulsechain/0x1/0x171',
1007
+ unwrap: false,
1008
+ });
1009
+
1010
+ expect(result).toEqual(mockToken1);
1011
+ });
1012
+
1013
+ it('should return null when map has other keys but not the requested one', () => {
1014
+ const assetsOut = new Map();
1015
+ assetsOut.set('some/other/key', mockToken1);
1016
+ assetsOut.set('another/different/key', mockToken2);
1017
+
1018
+ const result = assetOut({
1019
+ assetInAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
1020
+ assetsOut,
1021
+ bridgeKeyPath: 'pulsechain/0x1/0x171',
1022
+ unwrap: false,
1023
+ });
1024
+
1025
+ expect(result).toBeNull();
1026
+ });
1027
+ });
1028
+
1029
+ describe('estimatedNativeNetworkCost', () => {
1030
+ it('should return null when estimatedGas is null', () => {
1031
+ expect(
1032
+ estimatedNativeNetworkCost({
1033
+ estimatedGas: null,
1034
+ latestBaseFeePerGas: 30n * 10n ** 9n, // 30 gwei
1035
+ requiresDelivery: true,
1036
+ }),
1037
+ ).toBeNull();
1038
+ });
1039
+
1040
+ it('should return null when latestBaseFeePerGas is null', () => {
1041
+ expect(
1042
+ estimatedNativeNetworkCost({
1043
+ estimatedGas: 21000n,
1044
+ latestBaseFeePerGas: null,
1045
+ requiresDelivery: true,
1046
+ }),
1047
+ ).toBeNull();
1048
+ });
1049
+
1050
+ it('should return null when requiresDelivery is false', () => {
1051
+ expect(
1052
+ estimatedNativeNetworkCost({
1053
+ estimatedGas: 21000n,
1054
+ latestBaseFeePerGas: 30n * 10n ** 9n,
1055
+ requiresDelivery: false,
1056
+ }),
1057
+ ).toBeNull();
1058
+ });
1059
+
1060
+ it('should calculate cost correctly', () => {
1061
+ const gas = 21000n;
1062
+ const baseFee = 30n * 10n ** 9n; // 30 gwei
1063
+ const result = estimatedNativeNetworkCost({
1064
+ estimatedGas: gas,
1065
+ latestBaseFeePerGas: baseFee,
1066
+ requiresDelivery: true,
1067
+ });
1068
+ expect(result).toBe(gas * baseFee);
1069
+ });
1070
+
1071
+ it('should handle high gas costs', () => {
1072
+ const gas = 500000n; // Complex transaction
1073
+ const baseFee = 100n * 10n ** 9n; // 100 gwei
1074
+ const result = estimatedNativeNetworkCost({
1075
+ estimatedGas: gas,
1076
+ latestBaseFeePerGas: baseFee,
1077
+ requiresDelivery: true,
1078
+ });
1079
+ expect(result).toBe(gas * baseFee);
1080
+ expect(result).toBe(50000000000000000n); // 0.05 ETH
1081
+ });
1082
+
1083
+ it('should handle low gas costs', () => {
1084
+ const gas = 21000n;
1085
+ const baseFee = 1n * 10n ** 9n; // 1 gwei
1086
+ const result = estimatedNativeNetworkCost({
1087
+ estimatedGas: gas,
1088
+ latestBaseFeePerGas: baseFee,
1089
+ requiresDelivery: true,
1090
+ });
1091
+ expect(result).toBe(21000000000000n); // 0.000021 ETH
1092
+ });
1093
+
1094
+ it('should return BigInt type', () => {
1095
+ const result = estimatedNativeNetworkCost({
1096
+ estimatedGas: 21000n,
1097
+ latestBaseFeePerGas: 30n * 10n ** 9n,
1098
+ requiresDelivery: true,
1099
+ });
1100
+ expect(typeof result).toBe('bigint');
1101
+ });
1102
+
1103
+ it('should handle zero values gracefully', () => {
1104
+ // Zero gas should return null (falsy check)
1105
+ expect(
1106
+ estimatedNativeNetworkCost({
1107
+ estimatedGas: 0n,
1108
+ latestBaseFeePerGas: 30n * 10n ** 9n,
1109
+ requiresDelivery: true,
1110
+ }),
1111
+ ).toBeNull();
1112
+
1113
+ // Zero base fee should return null (falsy check)
1114
+ expect(
1115
+ estimatedNativeNetworkCost({
1116
+ estimatedGas: 21000n,
1117
+ latestBaseFeePerGas: 0n,
1118
+ requiresDelivery: true,
1119
+ }),
1120
+ ).toBeNull();
1121
+ });
1122
+
1123
+ it('should require all three parameters to be valid', () => {
1124
+ // All valid - should calculate
1125
+ expect(
1126
+ estimatedNativeNetworkCost({
1127
+ estimatedGas: 21000n,
1128
+ latestBaseFeePerGas: 30n * 10n ** 9n,
1129
+ requiresDelivery: true,
1130
+ }),
1131
+ ).not.toBeNull();
1132
+
1133
+ // Any invalid - should return null
1134
+ expect(
1135
+ estimatedNativeNetworkCost({
1136
+ estimatedGas: null,
1137
+ latestBaseFeePerGas: 30n * 10n ** 9n,
1138
+ requiresDelivery: true,
1139
+ }),
1140
+ ).toBeNull();
1141
+
1142
+ expect(
1143
+ estimatedNativeNetworkCost({
1144
+ estimatedGas: 21000n,
1145
+ latestBaseFeePerGas: null,
1146
+ requiresDelivery: true,
1147
+ }),
1148
+ ).toBeNull();
1149
+
1150
+ expect(
1151
+ estimatedNativeNetworkCost({
1152
+ estimatedGas: 21000n,
1153
+ latestBaseFeePerGas: 30n * 10n ** 9n,
1154
+ requiresDelivery: false,
1155
+ }),
1156
+ ).toBeNull();
1157
+ });
1158
+ });
1159
+
1160
+ describe('reasonablePercentOnGasLimit', () => {
1161
+ it('should calculate with both parameters provided', () => {
1162
+ const result = reasonablePercentOnGasLimit({
1163
+ gasTipFee: oneEther / 100n, // 1% tip
1164
+ estimatedTokenNetworkCost: 10n * oneEther, // 10 tokens
1165
+ });
1166
+ // Formula: (25000 * ((1 ETH + 0.01 ETH) * 10 ETH)) / (1 ETH * 10000)
1167
+ const expected =
1168
+ (25_000n * ((oneEther + oneEther / 100n) * 10n * oneEther)) / (oneEther * basisPoints);
1169
+ expect(result).toBe(expected);
1170
+ });
1171
+
1172
+ it('should handle null gasTipFee (treats as 0)', () => {
1173
+ const result = reasonablePercentOnGasLimit({
1174
+ gasTipFee: null,
1175
+ estimatedTokenNetworkCost: 10n * oneEther,
1176
+ });
1177
+ // Formula with gasTipFee as 0: (25000 * ((1 ETH + 0) * 10 ETH)) / (1 ETH * 10000)
1178
+ const expected = (25_000n * ((oneEther + 0n) * 10n * oneEther)) / (oneEther * basisPoints);
1179
+ expect(result).toBe(expected);
1180
+ });
1181
+
1182
+ it('should handle null estimatedTokenNetworkCost (treats as 0)', () => {
1183
+ const result = reasonablePercentOnGasLimit({
1184
+ gasTipFee: oneEther / 100n,
1185
+ estimatedTokenNetworkCost: null,
1186
+ });
1187
+ // Formula with cost as 0: (25000 * ((1.01 ETH) * 0)) / (1 ETH * 10000) = 0
1188
+ expect(result).toBe(0n);
1189
+ });
1190
+
1191
+ it('should handle both parameters as null', () => {
1192
+ const result = reasonablePercentOnGasLimit({
1193
+ gasTipFee: null,
1194
+ estimatedTokenNetworkCost: null,
1195
+ });
1196
+ expect(result).toBe(0n);
1197
+ });
1198
+
1199
+ it('should handle zero gasTipFee', () => {
1200
+ const result = reasonablePercentOnGasLimit({
1201
+ gasTipFee: 0n,
1202
+ estimatedTokenNetworkCost: 10n * oneEther,
1203
+ });
1204
+ const expected = (25_000n * oneEther * 10n * oneEther) / (oneEther * basisPoints);
1205
+ expect(result).toBe(expected);
1206
+ });
1207
+
1208
+ it('should handle zero estimatedTokenNetworkCost', () => {
1209
+ const result = reasonablePercentOnGasLimit({
1210
+ gasTipFee: oneEther / 100n,
1211
+ estimatedTokenNetworkCost: 0n,
1212
+ });
1213
+ expect(result).toBe(0n);
1214
+ });
1215
+
1216
+ it('should handle large values', () => {
1217
+ const largeTip = oneEther; // 100% tip
1218
+ const largeCost = 1000n * oneEther; // 1000 tokens
1219
+ const result = reasonablePercentOnGasLimit({
1220
+ gasTipFee: largeTip,
1221
+ estimatedTokenNetworkCost: largeCost,
1222
+ });
1223
+ const expected = (25_000n * ((oneEther + largeTip) * largeCost)) / (oneEther * basisPoints);
1224
+ expect(result).toBe(expected);
1225
+ });
1226
+
1227
+ it('should use correct constants (25000 and basisPoints)', () => {
1228
+ const result = reasonablePercentOnGasLimit({
1229
+ gasTipFee: oneEther / 10n, // 10%
1230
+ estimatedTokenNetworkCost: 100n * oneEther,
1231
+ });
1232
+ // Verify formula uses 25000 and basisPoints
1233
+ const expected =
1234
+ (25_000n * ((oneEther + oneEther / 10n) * 100n * oneEther)) / (oneEther * basisPoints);
1235
+ expect(result).toBe(expected);
1236
+ expect(basisPoints).toBe(10_000n); // Verify constant
1237
+ });
1238
+ });
1239
+
1240
+ describe('reasonablePercentOnTopOfGasFee', () => {
1241
+ it('should calculate percent from basis points', () => {
1242
+ const result = reasonablePercentOnTopOfGasFee({
1243
+ desiredExcessCompensationBasisPoints: 1000n, // 10%
1244
+ });
1245
+ // Formula: 1000 * (1 ETH / 10000) = 0.1 ETH
1246
+ expect(result).toBe(1000n * (oneEther / basisPoints));
1247
+ expect(result).toBe(oneEther / 10n);
1248
+ });
1249
+
1250
+ it('should handle zero basis points', () => {
1251
+ const result = reasonablePercentOnTopOfGasFee({
1252
+ desiredExcessCompensationBasisPoints: 0n,
1253
+ });
1254
+ expect(result).toBe(0n);
1255
+ });
1256
+
1257
+ it('should handle 100% (10000 basis points)', () => {
1258
+ const result = reasonablePercentOnTopOfGasFee({
1259
+ desiredExcessCompensationBasisPoints: 10_000n,
1260
+ });
1261
+ // 10000 * (1 ETH / 10000) = 1 ETH
1262
+ expect(result).toBe(oneEther);
1263
+ });
1264
+
1265
+ it('should handle 50% (5000 basis points)', () => {
1266
+ const result = reasonablePercentOnTopOfGasFee({
1267
+ desiredExcessCompensationBasisPoints: 5_000n,
1268
+ });
1269
+ // 5000 * (1 ETH / 10000) = 0.5 ETH
1270
+ expect(result).toBe(oneEther / 2n);
1271
+ });
1272
+
1273
+ it('should handle 1% (100 basis points)', () => {
1274
+ const result = reasonablePercentOnTopOfGasFee({
1275
+ desiredExcessCompensationBasisPoints: 100n,
1276
+ });
1277
+ // 100 * (1 ETH / 10000) = 0.01 ETH
1278
+ expect(result).toBe(oneEther / 100n);
1279
+ });
1280
+
1281
+ it('should handle 0.01% (1 basis point)', () => {
1282
+ const result = reasonablePercentOnTopOfGasFee({
1283
+ desiredExcessCompensationBasisPoints: 1n,
1284
+ });
1285
+ // 1 * (1 ETH / 10000) = 0.0001 ETH
1286
+ expect(result).toBe(oneEther / 10_000n);
1287
+ });
1288
+
1289
+ it('should maintain precision with bigint', () => {
1290
+ const result = reasonablePercentOnTopOfGasFee({
1291
+ desiredExcessCompensationBasisPoints: 3_333n, // 33.33%
1292
+ });
1293
+ const expected = 3_333n * (oneEther / basisPoints);
1294
+ expect(result).toBe(expected);
1295
+ // Should not lose precision
1296
+ expect(result).not.toBe(oneEther / 3n); // Not exactly 1/3 due to rounding
1297
+ });
1298
+
1299
+ it('should use oneEther and basisPoints constants', () => {
1300
+ const testBps = 2_500n; // 25%
1301
+ const result = reasonablePercentOnTopOfGasFee({
1302
+ desiredExcessCompensationBasisPoints: testBps,
1303
+ });
1304
+ expect(result).toBe(testBps * (oneEther / basisPoints));
1305
+ expect(oneEther).toBe(10n ** 18n);
1306
+ expect(basisPoints).toBe(10_000n);
1307
+ });
1308
+ });
1309
+
1310
+ describe('estimatedTokenNetworkCost', () => {
1311
+ const baseParams = {
1312
+ estimatedNativeNetworkCost: 630000000000000n, // 0.00063 ETH
1313
+ priceCorrective: oneEther / 1000n, // Token worth 0.001 ETH
1314
+ decimals: 18,
1315
+ oneTokenInt: oneEther,
1316
+ };
1317
+
1318
+ it('should return null when priceCorrective is null', () => {
1319
+ expect(
1320
+ estimatedTokenNetworkCost({
1321
+ ...baseParams,
1322
+ priceCorrective: null,
1323
+ }),
1324
+ ).toBeNull();
1325
+ });
1326
+
1327
+ it('should return null when oneTokenInt is null', () => {
1328
+ expect(
1329
+ estimatedTokenNetworkCost({
1330
+ ...baseParams,
1331
+ oneTokenInt: null,
1332
+ }),
1333
+ ).toBeNull();
1334
+ });
1335
+
1336
+ it('should return null when estimatedNativeNetworkCost is null', () => {
1337
+ expect(
1338
+ estimatedTokenNetworkCost({
1339
+ ...baseParams,
1340
+ estimatedNativeNetworkCost: null,
1341
+ }),
1342
+ ).toBeNull();
1343
+ });
1344
+
1345
+ it('should return null when decimals is null', () => {
1346
+ expect(
1347
+ estimatedTokenNetworkCost({
1348
+ ...baseParams,
1349
+ decimals: null,
1350
+ }),
1351
+ ).toBeNull();
1352
+ });
1353
+
1354
+ it('should calculate token cost correctly', () => {
1355
+ const result = estimatedTokenNetworkCost(baseParams);
1356
+ // (0.00063 ETH * 1 ETH) / 0.001 ETH = 0.63 tokens
1357
+ const expected =
1358
+ (baseParams.estimatedNativeNetworkCost * baseParams.oneTokenInt) /
1359
+ baseParams.priceCorrective;
1360
+ expect(result).toBe(expected);
1361
+ });
1362
+
1363
+ it('should handle expensive tokens (high price corrective)', () => {
1364
+ const result = estimatedTokenNetworkCost({
1365
+ estimatedNativeNetworkCost: oneEther / 100n, // 0.01 ETH
1366
+ priceCorrective: 100n * oneEther, // Token worth 100 ETH
1367
+ decimals: 18,
1368
+ oneTokenInt: oneEther,
1369
+ });
1370
+ // (0.01 ETH * 1 ETH) / 100 ETH = 0.0001 tokens
1371
+ const expected = ((oneEther / 100n) * oneEther) / (100n * oneEther);
1372
+ expect(result).toBe(expected);
1373
+ });
1374
+
1375
+ it('should handle cheap tokens (low price corrective)', () => {
1376
+ const result = estimatedTokenNetworkCost({
1377
+ estimatedNativeNetworkCost: oneEther / 1000n, // 0.001 ETH
1378
+ priceCorrective: oneEther / 1000000n, // Token worth 0.000001 ETH
1379
+ decimals: 18,
1380
+ oneTokenInt: oneEther,
1381
+ });
1382
+ // (0.001 ETH * 1 ETH) / 0.000001 ETH = 1000 tokens
1383
+ const expected = ((oneEther / 1000n) * oneEther) / (oneEther / 1000000n);
1384
+ expect(result).toBe(expected);
1385
+ });
1386
+
1387
+ it('should handle different token decimals', () => {
1388
+ // USDT with 6 decimals
1389
+ const usdtResult = estimatedTokenNetworkCost({
1390
+ estimatedNativeNetworkCost: oneEther / 100n,
1391
+ priceCorrective: oneEther / 3000n, // $3000 per ETH
1392
+ decimals: 6,
1393
+ oneTokenInt: 10n ** 6n, // 1 USDT
1394
+ });
1395
+ expect(usdtResult).toBeDefined();
1396
+ const expected = ((oneEther / 100n) * 10n ** 6n) / (oneEther / 3000n);
1397
+ expect(usdtResult).toBe(expected);
1398
+ });
1399
+
1400
+ it('should return null when priceCorrective is 0', () => {
1401
+ expect(
1402
+ estimatedTokenNetworkCost({
1403
+ ...baseParams,
1404
+ priceCorrective: 0n,
1405
+ }),
1406
+ ).toBeNull();
1407
+ });
1408
+
1409
+ it('should return null when oneTokenInt is 0', () => {
1410
+ expect(
1411
+ estimatedTokenNetworkCost({
1412
+ ...baseParams,
1413
+ oneTokenInt: 0n,
1414
+ }),
1415
+ ).toBeNull();
1416
+ });
1417
+
1418
+ it('should return null when estimatedNativeNetworkCost is 0', () => {
1419
+ expect(
1420
+ estimatedTokenNetworkCost({
1421
+ ...baseParams,
1422
+ estimatedNativeNetworkCost: 0n,
1423
+ }),
1424
+ ).toBeNull();
1425
+ });
1426
+
1427
+ it('should require all four parameters to be valid', () => {
1428
+ // All valid - should calculate
1429
+ expect(estimatedTokenNetworkCost(baseParams)).not.toBeNull();
1430
+
1431
+ // Any null - should return null
1432
+ expect(
1433
+ estimatedTokenNetworkCost({
1434
+ ...baseParams,
1435
+ priceCorrective: null,
1436
+ }),
1437
+ ).toBeNull();
1438
+
1439
+ expect(
1440
+ estimatedTokenNetworkCost({
1441
+ ...baseParams,
1442
+ oneTokenInt: null,
1443
+ }),
1444
+ ).toBeNull();
1445
+
1446
+ expect(
1447
+ estimatedTokenNetworkCost({
1448
+ ...baseParams,
1449
+ estimatedNativeNetworkCost: null,
1450
+ }),
1451
+ ).toBeNull();
1452
+
1453
+ expect(
1454
+ estimatedTokenNetworkCost({
1455
+ ...baseParams,
1456
+ decimals: null,
1457
+ }),
1458
+ ).toBeNull();
1459
+ });
1460
+
1461
+ it('should maintain bigint precision', () => {
1462
+ const result = estimatedTokenNetworkCost({
1463
+ estimatedNativeNetworkCost: 123456789012345n,
1464
+ priceCorrective: 987654321098765n,
1465
+ decimals: 18,
1466
+ oneTokenInt: oneEther,
1467
+ });
1468
+ const expected = (123456789012345n * oneEther) / 987654321098765n;
1469
+ expect(result).toBe(expected);
1470
+ });
1471
+
1472
+ it('should use formula: (nativeCost * oneToken) / priceCorrective', () => {
1473
+ const nativeCost = (5n * oneEther) / 10n; // 0.5 ETH
1474
+ const price = 2n * oneEther; // 2 ETH per token
1475
+ const tokenInt = oneEther;
1476
+
1477
+ const result = estimatedTokenNetworkCost({
1478
+ estimatedNativeNetworkCost: nativeCost,
1479
+ priceCorrective: price,
1480
+ decimals: 18,
1481
+ oneTokenInt: tokenInt,
1482
+ });
1483
+
1484
+ // Verify formula
1485
+ expect(result).toBe((nativeCost * tokenInt) / price);
1486
+ // 0.5 ETH / (2 ETH per token) = 0.25 tokens
1487
+ expect(result).toBe(oneEther / 4n);
1488
+ });
1489
+ });
1490
+ });
1491
+
1492
+ describe('desiredExcessCompensationBasisPoints', () => {
1493
+ const mockBridgeKey: BridgeKey = ['pulsechain', Chains.PLS, Chains.ETH];
1494
+ const mockToken: Token = {
1495
+ address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
1496
+ decimals: 6,
1497
+ name: 'USD Coin',
1498
+ symbol: 'USDC',
1499
+ chainId: parseInt(Chains.PLS, 16),
1500
+ };
1501
+ const mockPathway: Pathway = {
1502
+ from: '0xFrom' as Hex,
1503
+ requiresDelivery: true,
1504
+ feeManager: 'from',
1505
+ nativeRouter: '0xNative' as Hex,
1506
+ usesExtraParam: false,
1507
+ };
1508
+
1509
+ it('should return 0n when path does not require delivery', () => {
1510
+ const result = desiredExcessCompensationBasisPoints({
1511
+ path: { ...mockPathway, requiresDelivery: false },
1512
+ assetIn: mockToken,
1513
+ assetOut: mockToken,
1514
+ bridgeKey: mockBridgeKey,
1515
+ feeType: FeeType.FIXED,
1516
+ list: new Set(),
1517
+ assetOutAddress: mockToken.address,
1518
+ });
1519
+ expect(result).toBe(0n);
1520
+ });
1521
+
1522
+ it('should return 0n when assetIn is null', () => {
1523
+ const result = desiredExcessCompensationBasisPoints({
1524
+ path: mockPathway,
1525
+ assetIn: null,
1526
+ assetOut: mockToken,
1527
+ bridgeKey: mockBridgeKey,
1528
+ feeType: FeeType.FIXED,
1529
+ list: new Set(),
1530
+ assetOutAddress: mockToken.address,
1531
+ });
1532
+ expect(result).toBe(0n);
1533
+ });
1534
+
1535
+ it('should return 1_000n for PERCENT fee type', () => {
1536
+ const result = desiredExcessCompensationBasisPoints({
1537
+ path: mockPathway,
1538
+ assetIn: mockToken,
1539
+ assetOut: mockToken,
1540
+ bridgeKey: mockBridgeKey,
1541
+ feeType: FeeType.PERCENT,
1542
+ list: new Set(),
1543
+ assetOutAddress: mockToken.address,
1544
+ });
1545
+ expect(result).toBe(1_000n);
1546
+ });
1547
+
1548
+ it('should return 1_000n when assetIn is native', () => {
1549
+ const nativeToken = { ...mockToken, address: zeroAddress };
1550
+ const result = desiredExcessCompensationBasisPoints({
1551
+ path: mockPathway,
1552
+ assetIn: nativeToken,
1553
+ assetOut: mockToken,
1554
+ bridgeKey: mockBridgeKey,
1555
+ feeType: FeeType.FIXED,
1556
+ list: new Set(),
1557
+ assetOutAddress: mockToken.address,
1558
+ });
1559
+ expect(result).toBe(1_000n);
1560
+ });
1561
+
1562
+ it('should return 1_000n when assetOut is native', () => {
1563
+ const nativeToken = { ...mockToken, address: zeroAddress };
1564
+ const result = desiredExcessCompensationBasisPoints({
1565
+ path: mockPathway,
1566
+ assetIn: mockToken,
1567
+ assetOut: nativeToken,
1568
+ bridgeKey: mockBridgeKey,
1569
+ feeType: FeeType.FIXED,
1570
+ list: new Set(),
1571
+ assetOutAddress: nativeToken.address,
1572
+ });
1573
+ expect(result).toBe(1_000n);
1574
+ });
1575
+
1576
+ it('should return 5_000n when assetIn is in whitelist', () => {
1577
+ const list = new Set([getAddress(mockToken.address)]);
1578
+ const result = desiredExcessCompensationBasisPoints({
1579
+ path: mockPathway,
1580
+ assetIn: mockToken,
1581
+ assetOut: mockToken,
1582
+ bridgeKey: mockBridgeKey,
1583
+ feeType: FeeType.FIXED,
1584
+ list,
1585
+ assetOutAddress: '0x0000000000000000000000000000000000000001',
1586
+ });
1587
+ expect(result).toBe(5_000n);
1588
+ });
1589
+
1590
+ it('should return 5_000n when assetOutAddress is in whitelist', () => {
1591
+ const outAddress = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
1592
+ const list = new Set([getAddress(outAddress)]);
1593
+ const result = desiredExcessCompensationBasisPoints({
1594
+ path: mockPathway,
1595
+ assetIn: mockToken,
1596
+ assetOut: mockToken,
1597
+ bridgeKey: mockBridgeKey,
1598
+ feeType: FeeType.FIXED,
1599
+ list,
1600
+ assetOutAddress: outAddress,
1601
+ });
1602
+ expect(result).toBe(5_000n);
1603
+ });
1604
+
1605
+ it('should return 10_000n for non-whitelisted tokens', () => {
1606
+ const result = desiredExcessCompensationBasisPoints({
1607
+ path: mockPathway,
1608
+ assetIn: mockToken,
1609
+ assetOut: mockToken,
1610
+ bridgeKey: mockBridgeKey,
1611
+ feeType: FeeType.FIXED,
1612
+ list: new Set(),
1613
+ assetOutAddress: '0x0000000000000000000000000000000000000001',
1614
+ });
1615
+ expect(result).toBe(10_000n);
1616
+ });
1617
+
1618
+ it('should handle null path gracefully', () => {
1619
+ const result = desiredExcessCompensationBasisPoints({
1620
+ path: null,
1621
+ assetIn: mockToken,
1622
+ assetOut: mockToken,
1623
+ bridgeKey: mockBridgeKey,
1624
+ feeType: FeeType.FIXED,
1625
+ list: new Set(),
1626
+ assetOutAddress: mockToken.address,
1627
+ });
1628
+ expect(result).toBe(0n);
1629
+ });
1630
+
1631
+ it('should handle null assetOutAddress', () => {
1632
+ const result = desiredExcessCompensationBasisPoints({
1633
+ path: mockPathway,
1634
+ assetIn: mockToken,
1635
+ assetOut: mockToken,
1636
+ bridgeKey: mockBridgeKey,
1637
+ feeType: FeeType.FIXED,
1638
+ list: new Set(),
1639
+ assetOutAddress: null,
1640
+ });
1641
+ expect(result).toBe(10_000n);
1642
+ });
1643
+ });
1644
+
1645
+ describe('estimatedCost', () => {
1646
+ const baseParams = {
1647
+ shouldDeliver: true,
1648
+ amountAfterBridgeFee: oneEther,
1649
+ percentFee: oneEther / 100n, // 1%
1650
+ estimatedTokenNetworkCost: oneEther / 10n, // 0.1 tokens
1651
+ reasonablePercentOnTopOfGasFee: oneEther / 10n, // 10%
1652
+ feeType: FeeType.PERCENT,
1653
+ };
1654
+
1655
+ it('should return null when shouldDeliver is false', () => {
1656
+ const result = estimatedCost({
1657
+ ...baseParams,
1658
+ shouldDeliver: false,
1659
+ });
1660
+ expect(result).toBeNull();
1661
+ });
1662
+
1663
+ it('should calculate cost correctly for PERCENT fee type', () => {
1664
+ const result = estimatedCost(baseParams);
1665
+ const expected = (baseParams.amountAfterBridgeFee * baseParams.percentFee) / oneEther;
1666
+ expect(result).toBe(expected);
1667
+ });
1668
+
1669
+ it('should return null for PERCENT when amountAfterBridgeFee is null', () => {
1670
+ const result = estimatedCost({
1671
+ ...baseParams,
1672
+ amountAfterBridgeFee: null,
1673
+ });
1674
+ expect(result).toBeNull();
1675
+ });
1676
+
1677
+ it('should calculate cost correctly for GAS_TIP fee type', () => {
1678
+ const result = estimatedCost({
1679
+ ...baseParams,
1680
+ feeType: FeeType.GAS_TIP,
1681
+ });
1682
+ const expected =
1683
+ baseParams.estimatedTokenNetworkCost * (baseParams.reasonablePercentOnTopOfGasFee + oneEther);
1684
+ expect(result).toBe(expected);
1685
+ });
1686
+
1687
+ it('should return null for GAS_TIP when estimatedTokenNetworkCost is null', () => {
1688
+ const result = estimatedCost({
1689
+ ...baseParams,
1690
+ feeType: FeeType.GAS_TIP,
1691
+ estimatedTokenNetworkCost: null,
1692
+ });
1693
+ expect(result).toBeNull();
1694
+ });
1695
+
1696
+ it('should return null for GAS_TIP when reasonablePercentOnTopOfGasFee is null', () => {
1697
+ const result = estimatedCost({
1698
+ ...baseParams,
1699
+ feeType: FeeType.GAS_TIP,
1700
+ reasonablePercentOnTopOfGasFee: null,
1701
+ });
1702
+ expect(result).toBeNull();
1703
+ });
1704
+
1705
+ it('should handle zero percentFee for PERCENT type', () => {
1706
+ const result = estimatedCost({
1707
+ ...baseParams,
1708
+ percentFee: 0n,
1709
+ });
1710
+ expect(result).toBe(0n);
1711
+ });
1712
+
1713
+ it('should handle FIXED fee type (defaults to GAS_TIP calculation)', () => {
1714
+ const result = estimatedCost({
1715
+ ...baseParams,
1716
+ feeType: FeeType.FIXED,
1717
+ });
1718
+ // FIXED falls through to GAS_TIP calculation
1719
+ const expected =
1720
+ baseParams.estimatedTokenNetworkCost * (baseParams.reasonablePercentOnTopOfGasFee + oneEther);
1721
+ expect(result).toBe(expected);
1722
+ });
1723
+
1724
+ it('should handle null percentFee for PERCENT type', () => {
1725
+ const result = estimatedCost({
1726
+ ...baseParams,
1727
+ percentFee: null,
1728
+ });
1729
+ expect(result).toBe(0n); // Uses 0n as default
1730
+ });
1731
+ });
1732
+
1733
+ describe('reasonableFixedFee', () => {
1734
+ it('should calculate fee correctly', () => {
1735
+ const estimatedTokenNetworkCost = oneEther;
1736
+ const desiredExcessCompensationBasisPoints = 1_000n;
1737
+ const result = reasonableFixedFee({
1738
+ estimatedTokenNetworkCost,
1739
+ desiredExcessCompensationBasisPoints,
1740
+ });
1741
+ const expected =
1742
+ (estimatedTokenNetworkCost * (25_000n + desiredExcessCompensationBasisPoints)) / basisPoints;
1743
+ expect(result).toBe(expected);
1744
+ });
1745
+
1746
+ it('should return null when estimatedTokenNetworkCost is null', () => {
1747
+ const result = reasonableFixedFee({
1748
+ estimatedTokenNetworkCost: null,
1749
+ desiredExcessCompensationBasisPoints: 1_000n,
1750
+ });
1751
+ expect(result).toBeNull();
1752
+ });
1753
+
1754
+ it('should handle zero estimatedTokenNetworkCost', () => {
1755
+ const result = reasonableFixedFee({
1756
+ estimatedTokenNetworkCost: 0n,
1757
+ desiredExcessCompensationBasisPoints: 1_000n,
1758
+ });
1759
+ expect(result).toBeNull();
1760
+ });
1761
+
1762
+ it('should handle zero desiredExcessCompensationBasisPoints', () => {
1763
+ const estimatedTokenNetworkCost = oneEther;
1764
+ const result = reasonableFixedFee({
1765
+ estimatedTokenNetworkCost,
1766
+ desiredExcessCompensationBasisPoints: 0n,
1767
+ });
1768
+ const expected = (estimatedTokenNetworkCost * 25_000n) / basisPoints;
1769
+ expect(result).toBe(expected);
1770
+ });
1771
+
1772
+ it('should handle large desiredExcessCompensationBasisPoints', () => {
1773
+ const estimatedTokenNetworkCost = oneEther;
1774
+ const desiredExcessCompensationBasisPoints = 10_000n;
1775
+ const result = reasonableFixedFee({
1776
+ estimatedTokenNetworkCost,
1777
+ desiredExcessCompensationBasisPoints,
1778
+ });
1779
+ const expected = (estimatedTokenNetworkCost * 35_000n) / basisPoints;
1780
+ expect(result).toBe(expected);
1781
+ });
1782
+
1783
+ it('should maintain precision with small values', () => {
1784
+ const estimatedTokenNetworkCost = 100n;
1785
+ const desiredExcessCompensationBasisPoints = 500n;
1786
+ const result = reasonableFixedFee({
1787
+ estimatedTokenNetworkCost,
1788
+ desiredExcessCompensationBasisPoints,
1789
+ });
1790
+ expect(result).toBe((100n * 25_500n) / basisPoints);
1791
+ });
1792
+ });
1793
+
1794
+ describe('reasonablePercentFee', () => {
1795
+ const basisFeeTruncator = 10n ** 14n;
1796
+ const max = 1_000n * basisFeeTruncator;
1797
+ const min = 5n * basisFeeTruncator;
1798
+
1799
+ it('should return null when reasonableFixedFee is null', () => {
1800
+ const result = reasonablePercentFee({
1801
+ reasonableFixedFee: null,
1802
+ amountAfterBridgeFee: oneEther,
1803
+ });
1804
+ expect(result).toBeNull();
1805
+ });
1806
+
1807
+ it('should return null when amountAfterBridgeFee is null', () => {
1808
+ const result = reasonablePercentFee({
1809
+ reasonableFixedFee: oneEther,
1810
+ amountAfterBridgeFee: null,
1811
+ });
1812
+ expect(result).toBeNull();
1813
+ });
1814
+
1815
+ it('should return max (10%) when highFee < reasonableFixedFee', () => {
1816
+ const amountAfterBridgeFee = oneEther;
1817
+ const reasonableFixedFee = amountAfterBridgeFee / 10n + 1n;
1818
+ const result = reasonablePercentFee({
1819
+ reasonableFixedFee,
1820
+ amountAfterBridgeFee,
1821
+ });
1822
+ expect(result).toBe(max);
1823
+ });
1824
+
1825
+ it('should return min when calculated percent is below minimum', () => {
1826
+ const amountAfterBridgeFee = oneEther * 1000n;
1827
+ const reasonableFixedFee = oneEther / 100n; // Very small fee
1828
+ const result = reasonablePercentFee({
1829
+ reasonableFixedFee,
1830
+ amountAfterBridgeFee,
1831
+ });
1832
+ expect(result).toBe(min);
1833
+ });
1834
+
1835
+ it('should return max when calculated percent exceeds maximum', () => {
1836
+ const amountAfterBridgeFee = oneEther;
1837
+ const reasonableFixedFee = oneEther / 5n; // 20%
1838
+ const result = reasonablePercentFee({
1839
+ reasonableFixedFee,
1840
+ amountAfterBridgeFee,
1841
+ });
1842
+ expect(result).toBe(max);
1843
+ });
1844
+
1845
+ it('should return truncated percent when in valid range', () => {
1846
+ const amountAfterBridgeFee = oneEther;
1847
+ const reasonableFixedFee = oneEther / 100n; // 1%
1848
+ const result = reasonablePercentFee({
1849
+ reasonableFixedFee,
1850
+ amountAfterBridgeFee,
1851
+ });
1852
+ // 1% = 0.01 * 10^18 = 10^16
1853
+ // highResPercent = (10^16 * 10^18) / 10^18 = 10^16
1854
+ // basisPoint = 10^16 / 10^14 = 100
1855
+ // result = 100 * 10^14 = 10^16
1856
+ expect(result).toBe(100n * basisFeeTruncator);
1857
+ });
1858
+
1859
+ it('should handle truncation correctly', () => {
1860
+ const amountAfterBridgeFee = oneEther;
1861
+ const reasonableFixedFee = oneEther / 50n; // 2%
1862
+ const result = reasonablePercentFee({
1863
+ reasonableFixedFee,
1864
+ amountAfterBridgeFee,
1865
+ });
1866
+ // 2% = 0.02 * 10^18 = 2 * 10^16
1867
+ // highResPercent = (2 * 10^16 * 10^18) / 10^18 = 2 * 10^16
1868
+ // basisPoint = 2 * 10^16 / 10^14 = 200
1869
+ // result = 200 * 10^14 = 2 * 10^16
1870
+ expect(result).toBe(200n * basisFeeTruncator);
1871
+ });
1872
+
1873
+ it('should handle zero reasonableFixedFee', () => {
1874
+ const result = reasonablePercentFee({
1875
+ reasonableFixedFee: 0n,
1876
+ amountAfterBridgeFee: oneEther,
1877
+ });
1878
+ expect(result).toBeNull();
1879
+ });
1880
+
1881
+ it('should handle edge case near minimum boundary', () => {
1882
+ const amountAfterBridgeFee = oneEther * 200n;
1883
+ const reasonableFixedFee = oneEther; // 0.5%
1884
+ const result = reasonablePercentFee({
1885
+ reasonableFixedFee,
1886
+ amountAfterBridgeFee,
1887
+ });
1888
+ // highResPercent = (10^18 * 10^18) / (200 * 10^18) = 5 * 10^15
1889
+ // basisPoint = 5 * 10^15 / 10^14 = 50
1890
+ // result = 50 * 10^14 = 5 * 10^15
1891
+ expect(result).toBe(50n * basisFeeTruncator);
1892
+ });
1893
+ });
1894
+
1895
+ describe('availableCompensationMaximum', () => {
1896
+ const baseParams = {
1897
+ amountAfterBridgeFee: oneEther,
1898
+ percentFee: oneEther / 100n, // 1%
1899
+ feeType: FeeType.PERCENT,
1900
+ fixedFee: oneEther / 10n,
1901
+ limit: oneEther / 5n,
1902
+ };
1903
+
1904
+ it('should calculate correctly for PERCENT fee type', () => {
1905
+ const result = availableCompensationMaximum(baseParams);
1906
+ const expected = (baseParams.amountAfterBridgeFee * baseParams.percentFee) / oneEther;
1907
+ expect(result).toBe(expected);
1908
+ });
1909
+
1910
+ it('should return null for PERCENT when amountAfterBridgeFee is null', () => {
1911
+ const result = availableCompensationMaximum({
1912
+ ...baseParams,
1913
+ amountAfterBridgeFee: null,
1914
+ });
1915
+ expect(result).toBeNull();
1916
+ });
1917
+
1918
+ it('should return limit for GAS_TIP fee type', () => {
1919
+ const result = availableCompensationMaximum({
1920
+ ...baseParams,
1921
+ feeType: FeeType.GAS_TIP,
1922
+ });
1923
+ expect(result).toBe(baseParams.limit);
1924
+ });
1925
+
1926
+ it('should return fixedFee for FIXED fee type', () => {
1927
+ const result = availableCompensationMaximum({
1928
+ ...baseParams,
1929
+ feeType: FeeType.FIXED,
1930
+ });
1931
+ expect(result).toBe(baseParams.fixedFee);
1932
+ });
1933
+
1934
+ it('should return 0n for FIXED when fixedFee is null', () => {
1935
+ const result = availableCompensationMaximum({
1936
+ ...baseParams,
1937
+ feeType: FeeType.FIXED,
1938
+ fixedFee: null,
1939
+ });
1940
+ expect(result).toBe(0n);
1941
+ });
1942
+
1943
+ it('should handle zero percentFee for PERCENT type', () => {
1944
+ const result = availableCompensationMaximum({
1945
+ ...baseParams,
1946
+ percentFee: 0n,
1947
+ });
1948
+ expect(result).toBe(0n);
1949
+ });
1950
+
1951
+ it('should handle null percentFee for PERCENT type', () => {
1952
+ const result = availableCompensationMaximum({
1953
+ ...baseParams,
1954
+ percentFee: null,
1955
+ });
1956
+ expect(result).toBe(0n);
1957
+ });
1958
+
1959
+ it('should handle zero limit for GAS_TIP type', () => {
1960
+ const result = availableCompensationMaximum({
1961
+ ...baseParams,
1962
+ feeType: FeeType.GAS_TIP,
1963
+ limit: 0n,
1964
+ });
1965
+ expect(result).toBe(0n);
1966
+ });
1967
+ });
1968
+
1969
+ describe('isUndercompensated', () => {
1970
+ const baseParams = {
1971
+ estimatedTokenNetworkCost: oneEther / 10n, // 0.1 tokens
1972
+ feeType: FeeType.FIXED,
1973
+ limit: oneEther / 5n, // 0.2 tokens
1974
+ amountAfterBridgeFee: oneEther,
1975
+ desiredExcessCompensationBasisPoints: 1_000n, // 10%
1976
+ };
1977
+
1978
+ it('should return false when amountAfterBridgeFee is null', () => {
1979
+ const result = isUndercompensated({
1980
+ ...baseParams,
1981
+ amountAfterBridgeFee: null,
1982
+ });
1983
+ expect(result).toBe(false);
1984
+ });
1985
+
1986
+ it('should return false when amountAfterBridgeFee is 0', () => {
1987
+ const result = isUndercompensated({
1988
+ ...baseParams,
1989
+ amountAfterBridgeFee: 0n,
1990
+ });
1991
+ expect(result).toBe(false);
1992
+ });
1993
+
1994
+ it('should return false for GAS_TIP fee type', () => {
1995
+ const result = isUndercompensated({
1996
+ ...baseParams,
1997
+ feeType: FeeType.GAS_TIP,
1998
+ });
1999
+ expect(result).toBe(false);
2000
+ });
2001
+
2002
+ it('should return false when estimatedTokenNetworkCost is null', () => {
2003
+ const result = isUndercompensated({
2004
+ ...baseParams,
2005
+ estimatedTokenNetworkCost: null,
2006
+ });
2007
+ expect(result).toBe(false);
2008
+ });
2009
+
2010
+ it('should return true when desired compensation exceeds limit', () => {
2011
+ // estimatedTokenNetworkCost = 0.1 ETH
2012
+ // desiredCompensationRatio = 1.0 + (1000 / 10000) = 1.1
2013
+ // desiredCompensationForNetworkCost = 0.1 * 1.1 = 0.11 ETH
2014
+ // limit = 0.2 ETH, so 0.11 < 0.2 = false
2015
+ const result = isUndercompensated({
2016
+ ...baseParams,
2017
+ limit: oneEther / 100n, // 0.01 tokens (very low limit)
2018
+ });
2019
+ // Now 0.11 > 0.01 = true (undercompensated)
2020
+ expect(result).toBe(true);
2021
+ });
2022
+
2023
+ it('should return false when compensation is adequate', () => {
2024
+ const result = isUndercompensated(baseParams);
2025
+ // desiredCompensation = 0.1 * 1.1 = 0.11 ETH
2026
+ // limit = 0.2 ETH, so 0.11 < 0.2 = false (adequate)
2027
+ expect(result).toBe(false);
2028
+ });
2029
+
2030
+ it('should handle zero desiredExcessCompensationBasisPoints', () => {
2031
+ const result = isUndercompensated({
2032
+ ...baseParams,
2033
+ desiredExcessCompensationBasisPoints: 0n,
2034
+ });
2035
+ // desiredCompensationRatio = 1.0
2036
+ // desiredCompensation = 0.1 * 1.0 = 0.1 ETH
2037
+ // limit = 0.2 ETH, so false (adequate)
2038
+ expect(result).toBe(false);
2039
+ });
2040
+
2041
+ it('should handle large desiredExcessCompensationBasisPoints', () => {
2042
+ const result = isUndercompensated({
2043
+ ...baseParams,
2044
+ desiredExcessCompensationBasisPoints: 10_000n, // 100%
2045
+ limit: oneEther / 10n, // 0.1 tokens
2046
+ });
2047
+ // desiredCompensationRatio = 1.0 + 1.0 = 2.0
2048
+ // desiredCompensation = 0.1 * 2.0 = 0.2 ETH
2049
+ // limit = 0.1 ETH, so 0.2 > 0.1 = true (undercompensated)
2050
+ expect(result).toBe(true);
2051
+ });
2052
+
2053
+ it('should handle edge case at exact limit', () => {
2054
+ // Set limit to exactly match desired compensation
2055
+ const estimatedTokenNetworkCost = oneEther / 10n;
2056
+ const desiredExcessCompensationBasisPoints = 1_000n;
2057
+ const desiredCompensationRatio =
2058
+ oneEther + (desiredExcessCompensationBasisPoints * oneEther) / basisPoints;
2059
+ const limit = (estimatedTokenNetworkCost * desiredCompensationRatio) / oneEther;
2060
+
2061
+ const result = isUndercompensated({
2062
+ estimatedTokenNetworkCost,
2063
+ feeType: FeeType.FIXED,
2064
+ limit,
2065
+ amountAfterBridgeFee: oneEther,
2066
+ desiredExcessCompensationBasisPoints,
2067
+ });
2068
+ expect(result).toBe(false); // Exactly at limit, not under
2069
+ });
2070
+
2071
+ it('should handle PERCENT fee type', () => {
2072
+ const result = isUndercompensated({
2073
+ ...baseParams,
2074
+ feeType: FeeType.PERCENT,
2075
+ limit: oneEther / 100n,
2076
+ });
2077
+ // PERCENT fee type still does the calculation
2078
+ expect(result).toBe(true);
2079
+ });
2080
+ });
2081
+
2082
+ describe('bridgePathway', () => {
2083
+ it('should return pathway for valid bridge', () => {
2084
+ const result = bridgePathway({
2085
+ bridgeKey: ['pulsechain', Chains.PLS, Chains.ETH],
2086
+ isProd: true,
2087
+ assetIn: null,
2088
+ });
2089
+ // Should return a pathway (exact structure depends on config)
2090
+ expect(result).not.toBeNull();
2091
+ if (result) {
2092
+ expect(result).toHaveProperty('from');
2093
+ expect(result).toHaveProperty('to');
2094
+ expect(result).toHaveProperty('requiresDelivery');
2095
+ }
2096
+ });
2097
+
2098
+ it('should return pathway for valid bridge with assetIn', () => {
2099
+ const token: Token = {
2100
+ address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
2101
+ decimals: 6,
2102
+ name: 'USD Coin',
2103
+ symbol: 'USDC',
2104
+ chainId: parseInt(Chains.PLS, 16),
2105
+ };
2106
+ const result = bridgePathway({
2107
+ bridgeKey: ['pulsechain', Chains.PLS, Chains.ETH],
2108
+ isProd: true,
2109
+ assetIn: token,
2110
+ });
2111
+ expect(result).not.toBeNull();
2112
+ });
2113
+
2114
+ it('should respect isProd flag', () => {
2115
+ const resultProd = bridgePathway({
2116
+ bridgeKey: ['pulsechain', Chains.PLS, Chains.ETH],
2117
+ isProd: true,
2118
+ assetIn: null,
2119
+ });
2120
+ const resultDev = bridgePathway({
2121
+ bridgeKey: ['pulsechain', Chains.PLS, Chains.ETH],
2122
+ isProd: false,
2123
+ assetIn: null,
2124
+ });
2125
+ // Both should return pathways but they might differ
2126
+ expect(resultProd).not.toBeNull();
2127
+ expect(resultDev).not.toBeNull();
2128
+ });
2129
+
2130
+ it('should handle tokensex provider', () => {
2131
+ const result = bridgePathway({
2132
+ bridgeKey: ['tokensex', Chains.PLS, Chains.BNB],
2133
+ isProd: true,
2134
+ assetIn: null,
2135
+ });
2136
+ expect(result).not.toBeNull();
2137
+ if (result) {
2138
+ expect(result).toHaveProperty('from');
2139
+ expect(result).toHaveProperty('feeManager');
2140
+ }
2141
+ });
2142
+
2143
+ it('should pass assetIn address to pathway function', () => {
2144
+ const token: Token = {
2145
+ address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
2146
+ decimals: 6,
2147
+ name: 'Tether',
2148
+ symbol: 'USDT',
2149
+ chainId: parseInt(Chains.ETH, 16),
2150
+ };
2151
+ const result = bridgePathway({
2152
+ bridgeKey: ['pulsechain', Chains.PLS, Chains.ETH],
2153
+ isProd: true,
2154
+ assetIn: token,
2155
+ });
2156
+ expect(result).not.toBeNull();
2157
+ });
2158
+ });
2159
+
2160
+ describe('destinationDataParam', () => {
2161
+ const baseParams = {
2162
+ recipient: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' as Hex,
2163
+ bridgePathway: {
2164
+ from: '0x1' as Hex,
2165
+ to: '0x2' as Hex,
2166
+ requiresDelivery: false,
2167
+ feeManager: 'from' as const,
2168
+ nativeRouter: '0x3' as Hex,
2169
+ usesExtraParam: false,
2170
+ } as any,
2171
+ assetIn: {
2172
+ address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
2173
+ decimals: 6,
2174
+ name: 'USDC',
2175
+ symbol: 'USDC',
2176
+ chainId: 1,
2177
+ } as Token,
2178
+ destinationRouter: '0x4' as Hex,
2179
+ feeDirectorStructEncoded: null,
2180
+ assetLink: { toHome: '0x5', toForeign: '0x6' } as any,
2181
+ shouldDeliver: false,
2182
+ unwrap: false,
2183
+ };
2184
+
2185
+ it('should return null when recipient is null', () => {
2186
+ const result = destinationDataParam({
2187
+ ...baseParams,
2188
+ recipient: null,
2189
+ });
2190
+ expect(result).toBeNull();
2191
+ });
2192
+
2193
+ it('should return null when bridgePathway is null', () => {
2194
+ const result = destinationDataParam({
2195
+ ...baseParams,
2196
+ bridgePathway: null,
2197
+ });
2198
+ expect(result).toBeNull();
2199
+ });
2200
+
2201
+ it('should return null when assetIn is null', () => {
2202
+ const result = destinationDataParam({
2203
+ ...baseParams,
2204
+ assetIn: null,
2205
+ });
2206
+ expect(result).toBeNull();
2207
+ });
2208
+
2209
+ it('should return null when destinationRouter is null', () => {
2210
+ const result = destinationDataParam({
2211
+ ...baseParams,
2212
+ destinationRouter: null,
2213
+ });
2214
+ expect(result).toBeNull();
2215
+ });
2216
+
2217
+ it('should return null when requiresDelivery but no feeDirectorStructEncoded', () => {
2218
+ const result = destinationDataParam({
2219
+ ...baseParams,
2220
+ bridgePathway: {
2221
+ ...baseParams.bridgePathway,
2222
+ requiresDelivery: true,
2223
+ },
2224
+ feeDirectorStructEncoded: null,
2225
+ });
2226
+ expect(result).toBeNull();
2227
+ });
2228
+
2229
+ it('should return null when assetLink is null', () => {
2230
+ const result = destinationDataParam({
2231
+ ...baseParams,
2232
+ assetLink: null,
2233
+ });
2234
+ expect(result).toBeNull();
2235
+ });
2236
+
2237
+ it('should return recipient when not requiring delivery', () => {
2238
+ const result = destinationDataParam(baseParams);
2239
+ expect(result).toBe(baseParams.recipient);
2240
+ });
2241
+
2242
+ it('should return concatHex of destinationRouter and recipient when unwrap is true', () => {
2243
+ const result = destinationDataParam({
2244
+ ...baseParams,
2245
+ unwrap: true,
2246
+ });
2247
+ // concatHex mock joins the parts
2248
+ expect(result).toBe(`${baseParams.destinationRouter}${baseParams.recipient}`);
2249
+ });
2250
+
2251
+ it('should return recipient when requiresDelivery but shouldDeliver is false', () => {
2252
+ const result = destinationDataParam({
2253
+ ...baseParams,
2254
+ bridgePathway: {
2255
+ ...baseParams.bridgePathway,
2256
+ requiresDelivery: true,
2257
+ } as any,
2258
+ feeDirectorStructEncoded: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' as Hex,
2259
+ shouldDeliver: false,
2260
+ });
2261
+ expect(result).toBe(baseParams.recipient);
2262
+ });
2263
+
2264
+ it('should return concatHex when requiresDelivery and shouldDeliver with feeDirectorStructEncoded', () => {
2265
+ const feeStruct = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' as Hex;
2266
+ const result = destinationDataParam({
2267
+ ...baseParams,
2268
+ bridgePathway: {
2269
+ ...baseParams.bridgePathway,
2270
+ requiresDelivery: true,
2271
+ } as any,
2272
+ feeDirectorStructEncoded: feeStruct,
2273
+ shouldDeliver: true,
2274
+ });
2275
+ // concatHex mock joins the parts
2276
+ expect(result).toBe(`${baseParams.destinationRouter}${feeStruct}`);
2277
+ });
2278
+
2279
+ });
2280
+
2281
+ describe('feeDirectorStructEncoded', () => {
2282
+ const baseParams = {
2283
+ recipient: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' as Hex,
2284
+ feeType: FeeType.FIXED,
2285
+ priceCorrective: oneEther,
2286
+ gasTipFee: oneEther / 100n,
2287
+ percentFee: oneEther / 50n,
2288
+ feeTypeSettings: 1n,
2289
+ limit: oneEther,
2290
+ bridgePathway: {
2291
+ requiresDelivery: true,
2292
+ from: '0x1' as Hex,
2293
+ } as any,
2294
+ assetOut: {
2295
+ address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
2296
+ decimals: 6,
2297
+ name: 'USDC',
2298
+ symbol: 'USDC',
2299
+ chainId: 1,
2300
+ } as Token,
2301
+ };
2302
+
2303
+ it('should return null when assetOut is null', () => {
2304
+ const result = feeDirectorStructEncoded({
2305
+ ...baseParams,
2306
+ assetOut: null,
2307
+ });
2308
+ expect(result).toBeNull();
2309
+ });
2310
+
2311
+ it('should return null when bridgePathway is null', () => {
2312
+ const result = feeDirectorStructEncoded({
2313
+ ...baseParams,
2314
+ bridgePathway: null,
2315
+ });
2316
+ expect(result).toBeNull();
2317
+ });
2318
+
2319
+ it('should return null when pathway does not require delivery', () => {
2320
+ const result = feeDirectorStructEncoded({
2321
+ ...baseParams,
2322
+ bridgePathway: {
2323
+ requiresDelivery: false,
2324
+ } as any,
2325
+ });
2326
+ expect(result).toBeNull();
2327
+ });
2328
+
2329
+ it('should return null when priceCorrective is null', () => {
2330
+ const result = feeDirectorStructEncoded({
2331
+ ...baseParams,
2332
+ priceCorrective: null,
2333
+ });
2334
+ expect(result).toBeNull();
2335
+ });
2336
+
2337
+ it('should return null when priceCorrective is zero for GAS_TIP', () => {
2338
+ const result = feeDirectorStructEncoded({
2339
+ ...baseParams,
2340
+ feeType: FeeType.GAS_TIP,
2341
+ priceCorrective: 0n,
2342
+ });
2343
+ expect(result).toBeNull();
2344
+ });
2345
+
2346
+ it('should return null when recipient is null', () => {
2347
+ const result = feeDirectorStructEncoded({
2348
+ ...baseParams,
2349
+ recipient: null,
2350
+ });
2351
+ expect(result).toBeNull();
2352
+ });
2353
+
2354
+ it('should return null when recipient is invalid address', () => {
2355
+ const result = feeDirectorStructEncoded({
2356
+ ...baseParams,
2357
+ recipient: '0xinvalid' as Hex,
2358
+ });
2359
+ expect(result).toBeNull();
2360
+ });
2361
+ });
2362
+
2363
+ describe('transactionInputs', () => {
2364
+ const baseParams = {
2365
+ bridgePathway: {
2366
+ from: '0x1111111111111111111111111111111111111111' as Hex,
2367
+ to: '0x2222222222222222222222222222222222222222' as Hex,
2368
+ requiresDelivery: false,
2369
+ feeManager: 'from' as const,
2370
+ nativeRouter: '0x3333333333333333333333333333333333333333' as Hex,
2371
+ usesExtraParam: false,
2372
+ } as any,
2373
+ assetIn: {
2374
+ address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
2375
+ decimals: 6,
2376
+ name: 'USDC',
2377
+ symbol: 'USDC',
2378
+ chainId: 1,
2379
+ } as Token,
2380
+ recipient: '0x4444444444444444444444444444444444444444' as Hex,
2381
+ account: '0x5555555555555555555555555555555555555555' as Hex,
2382
+ assetLink: {
2383
+ toHome: '0x6666666666666666666666666666666666666666',
2384
+ toForeign: '0x7777777777777777777777777777777777777777',
2385
+ } as any,
2386
+ destinationDataParam: '0xDa7a' as Hex,
2387
+ amountToBridge: oneEther,
2388
+ feeDirectorStructEncoded: null,
2389
+ shouldDeliver: false,
2390
+ };
2391
+
2392
+ it('should return null when bridgePathway is null', () => {
2393
+ const result = transactionInputs({
2394
+ ...baseParams,
2395
+ bridgePathway: null,
2396
+ });
2397
+ expect(result).toBeNull();
2398
+ });
2399
+
2400
+ it('should return null when recipient is null', () => {
2401
+ const result = transactionInputs({
2402
+ ...baseParams,
2403
+ recipient: null,
2404
+ });
2405
+ expect(result).toBeNull();
2406
+ });
2407
+
2408
+ it('should return null when recipient is not a valid address', () => {
2409
+ const result = transactionInputs({
2410
+ ...baseParams,
2411
+ recipient: '0xinvalid' as Hex,
2412
+ });
2413
+ expect(result).toBeNull();
2414
+ });
2415
+
2416
+ it('should return null when recipient is zero address', () => {
2417
+ const result = transactionInputs({
2418
+ ...baseParams,
2419
+ recipient: zeroAddress,
2420
+ });
2421
+ expect(result).toBeNull();
2422
+ });
2423
+
2424
+ it('should return null when account is null', () => {
2425
+ const result = transactionInputs({
2426
+ ...baseParams,
2427
+ account: null,
2428
+ });
2429
+ expect(result).toBeNull();
2430
+ });
2431
+
2432
+ it('should return null when account is not a valid address', () => {
2433
+ const result = transactionInputs({
2434
+ ...baseParams,
2435
+ account: '0xinvalid' as Hex,
2436
+ });
2437
+ expect(result).toBeNull();
2438
+ });
2439
+
2440
+ it('should return null when account is zero address', () => {
2441
+ const result = transactionInputs({
2442
+ ...baseParams,
2443
+ account: zeroAddress,
2444
+ });
2445
+ expect(result).toBeNull();
2446
+ });
2447
+
2448
+ it('should return null when assetIn is null', () => {
2449
+ const result = transactionInputs({
2450
+ ...baseParams,
2451
+ assetIn: null,
2452
+ });
2453
+ expect(result).toBeNull();
2454
+ });
2455
+
2456
+ it('should return null when assetLink is null', () => {
2457
+ const result = transactionInputs({
2458
+ ...baseParams,
2459
+ assetLink: null,
2460
+ });
2461
+ expect(result).toBeNull();
2462
+ });
2463
+
2464
+ describe('bridged token (interactingWithBridgeToken = true)', () => {
2465
+ const bridgedTokenParams = {
2466
+ ...baseParams,
2467
+ assetIn: {
2468
+ address: '0x6666666666666666666666666666666666666666', // matches toHome from assetLink
2469
+ decimals: 18,
2470
+ name: 'Bridged Token',
2471
+ symbol: 'BTK',
2472
+ chainId: 1,
2473
+ } as Token,
2474
+ assetLink: {
2475
+ toHome: {
2476
+ home: '0x6666666666666666666666666666666666666666' as Hex,
2477
+ foreign: '0x9999999999999999999999999999999999999999' as Hex,
2478
+ },
2479
+ } as any,
2480
+ };
2481
+
2482
+ it('should return null when destinationDataParam is missing', () => {
2483
+ const result = transactionInputs({
2484
+ ...bridgedTokenParams,
2485
+ destinationDataParam: null,
2486
+ });
2487
+ expect(result).toBeNull();
2488
+ });
2489
+
2490
+ it('should encode transferAndCall without extra param', () => {
2491
+ const result = transactionInputs({
2492
+ ...bridgedTokenParams,
2493
+ bridgePathway: {
2494
+ ...baseParams.bridgePathway,
2495
+ usesExtraParam: false,
2496
+ } as any,
2497
+ });
2498
+ expect(result).toBeDefined();
2499
+ expect(result?.to).toBe(bridgedTokenParams.assetIn.address);
2500
+ expect(result?.value).toBe(0n);
2501
+ expect(result?.data).toBe('0xMOCKED_FUNCTION_DATA');
2502
+ });
2503
+
2504
+ it('should encode transferAndCall with extra param', () => {
2505
+ const result = transactionInputs({
2506
+ ...bridgedTokenParams,
2507
+ bridgePathway: {
2508
+ ...baseParams.bridgePathway,
2509
+ usesExtraParam: true,
2510
+ } as any,
2511
+ });
2512
+ expect(result).toBeDefined();
2513
+ expect(result?.to).toBe(bridgedTokenParams.assetIn.address);
2514
+ expect(result?.value).toBe(0n);
2515
+ expect(result?.data).toBe('0xMOCKED_FUNCTION_DATA');
2516
+ });
2517
+ });
2518
+
2519
+ describe('native token input (assetIn.address === zeroAddress)', () => {
2520
+ const nativeTokenParams = {
2521
+ ...baseParams,
2522
+ assetIn: {
2523
+ address: zeroAddress,
2524
+ decimals: 18,
2525
+ name: 'Ether',
2526
+ symbol: 'ETH',
2527
+ chainId: 1,
2528
+ } as Token,
2529
+ };
2530
+
2531
+ describe('with delivery (feeManager = from, shouldDeliver = true)', () => {
2532
+ const deliveryParams = {
2533
+ ...nativeTokenParams,
2534
+ shouldDeliver: true,
2535
+ feeDirectorStructEncoded: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' as Hex,
2536
+ bridgePathway: {
2537
+ ...baseParams.bridgePathway,
2538
+ feeManager: 'from' as const,
2539
+ destinationRouter: '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' as Hex,
2540
+ } as any,
2541
+ };
2542
+
2543
+ it('should return null when feeDirectorStructEncoded is missing', () => {
2544
+ const result = transactionInputs({
2545
+ ...deliveryParams,
2546
+ feeDirectorStructEncoded: null,
2547
+ });
2548
+ expect(result).toBeNull();
2549
+ });
2550
+
2551
+ it('should return null when destinationRouter is missing', () => {
2552
+ const result = transactionInputs({
2553
+ ...deliveryParams,
2554
+ bridgePathway: {
2555
+ ...deliveryParams.bridgePathway,
2556
+ destinationRouter: null,
2557
+ } as any,
2558
+ });
2559
+ expect(result).toBeNull();
2560
+ });
2561
+
2562
+ it('should encode relayTokensAndCall without extra param', () => {
2563
+ const result = transactionInputs({
2564
+ ...deliveryParams,
2565
+ bridgePathway: {
2566
+ ...deliveryParams.bridgePathway,
2567
+ usesExtraParam: false,
2568
+ } as any,
2569
+ });
2570
+ expect(result).toBeDefined();
2571
+ expect(result?.to).toBe(baseParams.bridgePathway.nativeRouter);
2572
+ expect(result?.value).toBe(oneEther);
2573
+ expect(result?.data).toBe('0xMOCKED_FUNCTION_DATA');
2574
+ });
2575
+
2576
+ it('should encode relayTokensAndCall with extra param', () => {
2577
+ const result = transactionInputs({
2578
+ ...deliveryParams,
2579
+ bridgePathway: {
2580
+ ...deliveryParams.bridgePathway,
2581
+ usesExtraParam: true,
2582
+ } as any,
2583
+ });
2584
+ expect(result).toBeDefined();
2585
+ expect(result?.to).toBe(baseParams.bridgePathway.nativeRouter);
2586
+ expect(result?.value).toBe(oneEther);
2587
+ expect(result?.data).toBe('0xMOCKED_FUNCTION_DATA');
2588
+ });
2589
+ });
2590
+
2591
+ describe('without delivery (foreign to home or no delivery)', () => {
2592
+ const noDeliveryParams = {
2593
+ ...nativeTokenParams,
2594
+ shouldDeliver: false,
2595
+ };
2596
+
2597
+ it('should encode wrapAndRelayTokens without extra param', () => {
2598
+ const result = transactionInputs({
2599
+ ...noDeliveryParams,
2600
+ bridgePathway: {
2601
+ ...baseParams.bridgePathway,
2602
+ usesExtraParam: false,
2603
+ } as any,
2604
+ });
2605
+ expect(result).toBeDefined();
2606
+ expect(result?.to).toBe(baseParams.bridgePathway.nativeRouter);
2607
+ expect(result?.value).toBe(oneEther);
2608
+ expect(result?.data).toBe('0xMOCKED_FUNCTION_DATA');
2609
+ });
2610
+
2611
+ it('should encode wrapAndRelayTokens with extra param', () => {
2612
+ const result = transactionInputs({
2613
+ ...noDeliveryParams,
2614
+ bridgePathway: {
2615
+ ...baseParams.bridgePathway,
2616
+ usesExtraParam: true,
2617
+ } as any,
2618
+ });
2619
+ expect(result).toBeDefined();
2620
+ expect(result?.to).toBe(baseParams.bridgePathway.nativeRouter);
2621
+ expect(result?.value).toBe(oneEther);
2622
+ expect(result?.data).toBe('0xMOCKED_FUNCTION_DATA');
2623
+ });
2624
+ });
2625
+ });
2626
+
2627
+ describe('ERC20 token input (tokens native to this side)', () => {
2628
+ const erc20Params = {
2629
+ ...baseParams,
2630
+ assetIn: {
2631
+ address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
2632
+ decimals: 6,
2633
+ name: 'USDC',
2634
+ symbol: 'USDC',
2635
+ chainId: 1,
2636
+ } as Token,
2637
+ // assetLink with addresses that don't match assetIn.address so interactingWithBridgeToken returns false
2638
+ assetLink: {
2639
+ toForeign: { home: '0x7777777777777777777777777777777777777777' as Hex, foreign: null },
2640
+ toHome: { home: null, foreign: '0x8888888888888888888888888888888888888888' as Hex },
2641
+ } as any,
2642
+ };
2643
+
2644
+ describe('with delivery (shouldDeliver = true, requiresDelivery = true)', () => {
2645
+ const deliveryParams = {
2646
+ ...erc20Params,
2647
+ shouldDeliver: true,
2648
+ feeDirectorStructEncoded: '0xcccccccccccccccccccccccccccccccccccccccc' as Hex,
2649
+ bridgePathway: {
2650
+ ...baseParams.bridgePathway,
2651
+ requiresDelivery: true,
2652
+ destinationRouter: '0xdddddddddddddddddddddddddddddddddddddddd' as Hex,
2653
+ } as any,
2654
+ };
2655
+
2656
+ it('should return null when feeDirectorStructEncoded is missing', () => {
2657
+ const result = transactionInputs({
2658
+ ...deliveryParams,
2659
+ feeDirectorStructEncoded: null,
2660
+ });
2661
+ expect(result).toBeNull();
2662
+ });
2663
+
2664
+ it('should return null when destinationRouter is missing', () => {
2665
+ const result = transactionInputs({
2666
+ ...deliveryParams,
2667
+ bridgePathway: {
2668
+ ...deliveryParams.bridgePathway,
2669
+ destinationRouter: null,
2670
+ } as any,
2671
+ });
2672
+ expect(result).toBeNull();
2673
+ });
2674
+
2675
+ it('should encode relayTokensAndCall without extra param', () => {
2676
+ const result = transactionInputs({
2677
+ ...deliveryParams,
2678
+ bridgePathway: {
2679
+ ...deliveryParams.bridgePathway,
2680
+ usesExtraParam: false,
2681
+ } as any,
2682
+ });
2683
+ expect(result).toBeDefined();
2684
+ expect(result?.to).toBe(baseParams.bridgePathway.from);
2685
+ expect(result?.value).toBe(0n);
2686
+ expect(result?.data).toBe('0xMOCKED_FUNCTION_DATA');
2687
+ });
2688
+
2689
+ it('should encode relayTokensAndCall with extra param', () => {
2690
+ const result = transactionInputs({
2691
+ ...deliveryParams,
2692
+ bridgePathway: {
2693
+ ...deliveryParams.bridgePathway,
2694
+ usesExtraParam: true,
2695
+ } as any,
2696
+ });
2697
+ expect(result).toBeDefined();
2698
+ expect(result?.to).toBe(baseParams.bridgePathway.from);
2699
+ expect(result?.value).toBe(0n);
2700
+ expect(result?.data).toBe('0xMOCKED_FUNCTION_DATA');
2701
+ });
2702
+ });
2703
+
2704
+ describe('without delivery (shouldDeliver = false or requiresDelivery = false)', () => {
2705
+ const noDeliveryParams = {
2706
+ ...erc20Params,
2707
+ shouldDeliver: false,
2708
+ bridgePathway: {
2709
+ ...baseParams.bridgePathway,
2710
+ requiresDelivery: false,
2711
+ } as any,
2712
+ };
2713
+
2714
+ it('should encode relayTokens without extra param', () => {
2715
+ const result = transactionInputs({
2716
+ ...noDeliveryParams,
2717
+ bridgePathway: {
2718
+ ...noDeliveryParams.bridgePathway,
2719
+ usesExtraParam: false,
2720
+ } as any,
2721
+ });
2722
+ expect(result).toBeDefined();
2723
+ expect(result?.to).toBe(baseParams.bridgePathway.from);
2724
+ expect(result?.value).toBe(0n);
2725
+ expect(result?.data).toBe('0xMOCKED_FUNCTION_DATA');
2726
+ });
2727
+
2728
+ it('should encode relayTokens with extra param', () => {
2729
+ const result = transactionInputs({
2730
+ ...noDeliveryParams,
2731
+ bridgePathway: {
2732
+ ...noDeliveryParams.bridgePathway,
2733
+ usesExtraParam: true,
2734
+ } as any,
2735
+ });
2736
+ expect(result).toBeDefined();
2737
+ expect(result?.to).toBe(baseParams.bridgePathway.from);
2738
+ expect(result?.value).toBe(0n);
2739
+ expect(result?.data).toBe('0xMOCKED_FUNCTION_DATA');
2740
+ });
2741
+
2742
+ it('should encode relayTokens when shouldDeliver is true but requiresDelivery is false', () => {
2743
+ const result = transactionInputs({
2744
+ ...noDeliveryParams,
2745
+ shouldDeliver: true, // Even with shouldDeliver true, requiresDelivery false takes precedence
2746
+ });
2747
+ expect(result).toBeDefined();
2748
+ expect(result?.to).toBe(baseParams.bridgePathway.from);
2749
+ expect(result?.value).toBe(0n);
2750
+ expect(result?.data).toBe('0xMOCKED_FUNCTION_DATA');
2751
+ });
2752
+ });
2753
+ });
2754
+ });