@hyperlane-xyz/rebalancer 27.2.14 → 27.3.1

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.
Files changed (54) hide show
  1. package/dist/bridges/LiFiBridge.d.ts +3 -3
  2. package/dist/bridges/LiFiBridge.d.ts.map +1 -1
  3. package/dist/bridges/LiFiBridge.js +59 -51
  4. package/dist/bridges/LiFiBridge.js.map +1 -1
  5. package/dist/bridges/LiFiBridge.test.js +176 -0
  6. package/dist/bridges/LiFiBridge.test.js.map +1 -1
  7. package/dist/config/RebalancerConfig.test.js +123 -0
  8. package/dist/config/RebalancerConfig.test.js.map +1 -1
  9. package/dist/config/types.d.ts.map +1 -1
  10. package/dist/config/types.js +9 -0
  11. package/dist/config/types.js.map +1 -1
  12. package/dist/core/InventoryRebalancer.d.ts.map +1 -1
  13. package/dist/core/InventoryRebalancer.js +3 -2
  14. package/dist/core/InventoryRebalancer.js.map +1 -1
  15. package/dist/core/InventoryRebalancer.test.js +108 -1
  16. package/dist/core/InventoryRebalancer.test.js.map +1 -1
  17. package/dist/factories/RebalancerContextFactory.d.ts.map +1 -1
  18. package/dist/factories/RebalancerContextFactory.js +34 -24
  19. package/dist/factories/RebalancerContextFactory.js.map +1 -1
  20. package/dist/factories/RebalancerContextFactory.test.js +84 -1
  21. package/dist/factories/RebalancerContextFactory.test.js.map +1 -1
  22. package/dist/service.js +7 -4
  23. package/dist/service.js.map +1 -1
  24. package/dist/utils/blockTag.d.ts.map +1 -1
  25. package/dist/utils/blockTag.js +8 -3
  26. package/dist/utils/blockTag.js.map +1 -1
  27. package/dist/utils/blockTag.test.d.ts +2 -0
  28. package/dist/utils/blockTag.test.d.ts.map +1 -0
  29. package/dist/utils/blockTag.test.js +57 -0
  30. package/dist/utils/blockTag.test.js.map +1 -0
  31. package/dist/utils/gasEstimation.js +4 -4
  32. package/dist/utils/gasEstimation.js.map +1 -1
  33. package/dist/utils/gasEstimation.test.d.ts +2 -0
  34. package/dist/utils/gasEstimation.test.d.ts.map +1 -0
  35. package/dist/utils/gasEstimation.test.js +63 -0
  36. package/dist/utils/gasEstimation.test.js.map +1 -0
  37. package/dist/utils/tokenUtils.d.ts.map +1 -1
  38. package/dist/utils/tokenUtils.js +5 -2
  39. package/dist/utils/tokenUtils.js.map +1 -1
  40. package/package.json +7 -7
  41. package/src/bridges/LiFiBridge.test.ts +227 -0
  42. package/src/bridges/LiFiBridge.ts +82 -67
  43. package/src/config/RebalancerConfig.test.ts +135 -0
  44. package/src/config/types.ts +8 -0
  45. package/src/core/InventoryRebalancer.test.ts +160 -0
  46. package/src/core/InventoryRebalancer.ts +9 -3
  47. package/src/factories/RebalancerContextFactory.test.ts +116 -1
  48. package/src/factories/RebalancerContextFactory.ts +38 -28
  49. package/src/service.ts +11 -8
  50. package/src/utils/blockTag.test.ts +70 -0
  51. package/src/utils/blockTag.ts +11 -3
  52. package/src/utils/gasEstimation.test.ts +99 -0
  53. package/src/utils/gasEstimation.ts +4 -4
  54. package/src/utils/tokenUtils.ts +5 -2
@@ -0,0 +1,70 @@
1
+ import { expect } from 'chai';
2
+ import { providers } from 'ethers';
3
+ import Sinon from 'sinon';
4
+
5
+ import { MultiProtocolProvider } from '@hyperlane-xyz/sdk';
6
+ import { ProtocolType } from '@hyperlane-xyz/utils';
7
+
8
+ import { getConfirmedBlockTag } from './blockTag.js';
9
+
10
+ describe('getConfirmedBlockTag', () => {
11
+ let mpp: Sinon.SinonStubbedInstance<MultiProtocolProvider>;
12
+
13
+ beforeEach(() => {
14
+ mpp = Sinon.createStubInstance(MultiProtocolProvider);
15
+ });
16
+
17
+ afterEach(() => {
18
+ Sinon.restore();
19
+ });
20
+
21
+ it('returns a confirmed block number for Tron chain (EVM-like)', async () => {
22
+ mpp.getChainMetadata.returns({
23
+ protocol: ProtocolType.Tron,
24
+ name: 'tron',
25
+ chainId: 728126428,
26
+ blocks: { reorgPeriod: 20 },
27
+ } as any);
28
+
29
+ const mockProvider = {
30
+ send: Sinon.stub().resolves('0x64'), // 100 in hex
31
+ getBlockNumber: Sinon.stub().resolves(100),
32
+ };
33
+ // Make instanceof check pass
34
+ Object.setPrototypeOf(mockProvider, providers.JsonRpcProvider.prototype);
35
+ mpp.getEthersV5Provider.returns(mockProvider as any);
36
+
37
+ const result = await getConfirmedBlockTag(mpp, 'tron');
38
+ // 100 - 20 = 80
39
+ expect(result).to.equal(80);
40
+ });
41
+
42
+ it('returns undefined for Sealevel chain (non-EVM-like)', async () => {
43
+ mpp.getChainMetadata.returns({
44
+ protocol: ProtocolType.Sealevel,
45
+ name: 'solana',
46
+ chainId: 1399811149,
47
+ } as any);
48
+
49
+ const result = await getConfirmedBlockTag(mpp, 'solana');
50
+ expect(result).to.be.undefined;
51
+ });
52
+
53
+ it('returns undefined for Tron chain with string reorgPeriod (named block tags not supported)', async () => {
54
+ const logger = { warn: Sinon.stub() };
55
+ mpp.getChainMetadata.returns({
56
+ protocol: ProtocolType.Tron,
57
+ name: 'tron',
58
+ chainId: 728126428,
59
+ blocks: { reorgPeriod: 'finalized' },
60
+ } as any);
61
+
62
+ const result = await getConfirmedBlockTag(mpp, 'tron', logger as any);
63
+ expect(result).to.be.undefined;
64
+ expect(logger.warn.calledOnce).to.be.true;
65
+ const warnCall = logger.warn.getCall(0);
66
+ expect(warnCall.args[1]).to.include(
67
+ 'Tron does not support named block tags',
68
+ );
69
+ });
70
+ });
@@ -6,7 +6,7 @@ import {
6
6
  EthJsonRpcBlockParameterTag,
7
7
  type MultiProtocolProvider,
8
8
  } from '@hyperlane-xyz/sdk';
9
- import { ProtocolType } from '@hyperlane-xyz/utils';
9
+ import { isEVMLike, ProtocolType } from '@hyperlane-xyz/utils';
10
10
  import type { ConfirmedBlockTag } from '../interfaces/IMonitor.js';
11
11
 
12
12
  /**
@@ -29,13 +29,21 @@ export async function getConfirmedBlockTag(
29
29
  try {
30
30
  const metadata = multiProvider.getChainMetadata(chainName);
31
31
 
32
- // Only EVM chains support block tag queries
33
- if (metadata.protocol !== ProtocolType.Ethereum) {
32
+ // Only EVM-like chains support block tag queries (e.g., Ethereum, Tron).
33
+ // Tron uses TronJsonRpcProvider which supports eth_blockNumber
34
+ if (!isEVMLike(metadata.protocol)) {
34
35
  return undefined;
35
36
  }
36
37
 
37
38
  const reorgPeriod = metadata.blocks?.reorgPeriod ?? 32;
38
39
  if (typeof reorgPeriod === 'string') {
40
+ if (metadata.protocol === ProtocolType.Tron) {
41
+ logger?.warn(
42
+ { chain: chainName, reorgPeriod },
43
+ 'Tron does not support named block tags — ignoring string reorgPeriod, using latest block',
44
+ );
45
+ return undefined;
46
+ }
39
47
  return reorgPeriod as EthJsonRpcBlockParameterTag;
40
48
  }
41
49
 
@@ -0,0 +1,99 @@
1
+ import { expect } from 'chai';
2
+ import { BigNumber } from 'ethers';
3
+ import { pino } from 'pino';
4
+ import Sinon from 'sinon';
5
+
6
+ import type { ChainName, MultiProvider, Token } from '@hyperlane-xyz/sdk';
7
+ import { TokenStandard } from '@hyperlane-xyz/sdk';
8
+ import { ProtocolType } from '@hyperlane-xyz/utils';
9
+
10
+ import { calculateTransferCosts } from './gasEstimation.js';
11
+
12
+ const testLogger = pino({ level: 'silent' });
13
+
14
+ describe('calculateTransferCosts — Tron vs Sealevel protocol path', () => {
15
+ afterEach(() => {
16
+ Sinon.restore();
17
+ });
18
+
19
+ function createMockDeps(protocol: ProtocolType) {
20
+ const mockAdapter = {
21
+ quoteTransferRemoteGas: Sinon.stub().resolves({
22
+ igpQuote: { amount: 1000n },
23
+ tokenFeeQuote: { amount: 0n, addressOrDenom: '' },
24
+ }),
25
+ populateTransferRemoteTx: Sinon.stub().resolves({
26
+ to: '0xRouter',
27
+ data: '0x',
28
+ value: 1000n,
29
+ }),
30
+ };
31
+
32
+ const mockToken = {
33
+ standard: TokenStandard.EvmHypNative, // Native so we reach the isEVMLike check
34
+ getHypAdapter: Sinon.stub().returns(mockAdapter),
35
+ } as unknown as Token;
36
+
37
+ const multiProvider = {
38
+ getDomainId: Sinon.stub().returns(42161),
39
+ getProtocol: Sinon.stub().returns(protocol),
40
+ getProvider: Sinon.stub().returns({
41
+ estimateGas: Sinon.stub().resolves(BigNumber.from(200000)),
42
+ getFeeData: Sinon.stub().resolves({
43
+ maxFeePerGas: BigNumber.from(10_000_000_000n),
44
+ gasPrice: BigNumber.from(10_000_000_000n),
45
+ }),
46
+ }),
47
+ } as unknown as MultiProvider;
48
+
49
+ const getTokenForChain = Sinon.stub().returns(mockToken);
50
+ const isNativeTokenStandard = Sinon.stub().returns(true);
51
+
52
+ return { multiProvider, getTokenForChain, isNativeTokenStandard };
53
+ }
54
+
55
+ it('Tron origin (EVM-like) produces non-zero gasCost for native tokens', async () => {
56
+ const { multiProvider, getTokenForChain, isNativeTokenStandard } =
57
+ createMockDeps(ProtocolType.Tron);
58
+
59
+ const result = await calculateTransferCosts(
60
+ 'tron' as ChainName,
61
+ 'arbitrum' as ChainName,
62
+ 10000000000000000000n, // 10 ETH available
63
+ 1000000000000000000n, // 1 ETH requested
64
+ multiProvider,
65
+ {} as any, // warpCoreMultiProvider
66
+ getTokenForChain,
67
+ '0xInventorySigner',
68
+ isNativeTokenStandard,
69
+ testLogger,
70
+ );
71
+
72
+ // Tron is EVM-like — gas estimation runs, producing gasCost > 0
73
+ expect(result.gasCost > 0n).to.be.true;
74
+ expect(result.igpCost).to.equal(1000n);
75
+ expect(result.maxTransferable > 0n).to.be.true;
76
+ });
77
+
78
+ it('Sealevel origin (non-EVM) returns gasCost = 0 for native tokens', async () => {
79
+ const { multiProvider, getTokenForChain, isNativeTokenStandard } =
80
+ createMockDeps(ProtocolType.Sealevel);
81
+
82
+ const result = await calculateTransferCosts(
83
+ 'solana' as ChainName,
84
+ 'arbitrum' as ChainName,
85
+ 200000000000n,
86
+ 100000000000n,
87
+ multiProvider,
88
+ {} as any,
89
+ getTokenForChain,
90
+ '0xInventorySigner',
91
+ isNativeTokenStandard,
92
+ testLogger,
93
+ );
94
+
95
+ // Sealevel is non-EVM — gasCost is 0 (skips gas estimation)
96
+ expect(result.gasCost).to.equal(0n);
97
+ expect(result.igpCost).to.equal(1000n);
98
+ });
99
+ });
@@ -11,8 +11,8 @@ import {
11
11
  } from '@hyperlane-xyz/sdk';
12
12
  import {
13
13
  addBufferToGasLimit,
14
+ isEVMLike,
14
15
  isZeroishAddress,
15
- ProtocolType,
16
16
  } from '@hyperlane-xyz/utils';
17
17
 
18
18
  /**
@@ -211,13 +211,13 @@ export async function calculateTransferCosts(
211
211
  ? (gasQuote.tokenFeeQuote?.amount ?? 0n)
212
212
  : 0n;
213
213
 
214
- // Skip gas estimation for non-EVM chains (e.g., Solana).
214
+ // Skip gas estimation for non-EVM-like chains (e.g., Solana).
215
215
  // Non-EVM chains have negligible base fees (~5000 lamports ~$0.0001 on Solana)
216
216
  // compared to EVM gas costs ($0.50-$50/tx). The IGP (Interchain Gas Paymaster)
217
217
  // reservation dominates the cost, not chain-specific gas. Thus gasCost=0 is a
218
- // safe approximation for non-EVM origin chains.
218
+ // safe approximation for non-EVM-like origin chains.
219
219
  const originProtocol = multiProvider.getProtocol(originChain);
220
- if (originProtocol !== ProtocolType.Ethereum) {
220
+ if (!isEVMLike(originProtocol)) {
221
221
  const totalCost = igpCost + tokenFeeCost;
222
222
  let maxTransferable: bigint;
223
223
  if (availableInventory <= totalCost) {
@@ -13,15 +13,18 @@ const REBALANCEABLE_TOKEN_COLLATERALIZED_STANDARDS = new Set<TokenStandard>([
13
13
  TokenStandard.EvmHypNative,
14
14
  TokenStandard.SealevelHypCollateral,
15
15
  TokenStandard.SealevelHypNative,
16
+ TokenStandard.TronHypCollateral,
17
+ TokenStandard.TronHypNative,
16
18
  ]);
17
19
 
18
- // SDK-backed native token standard check scoped to EVM and Sealevel only (2-protocol scope).
20
+ // SDK-backed native token standard check scoped to EVM, Sealevel, and Tron (3-protocol scope).
19
21
  // NOTE: We intentionally do NOT use the full PROTOCOL_TO_HYP_NATIVE_STANDARD map (all 7 protocols)
20
- // because the rebalancer only supports EVM and Sealevel native token bridging.
22
+ // because the rebalancer only supports EVM, Sealevel, and Tron native token bridging.
21
23
  // Expanding this would change gas reservation behavior for other protocols.
22
24
  const REBALANCER_NATIVE_STANDARDS = new Set([
23
25
  PROTOCOL_TO_HYP_NATIVE_STANDARD[ProtocolType.Ethereum],
24
26
  PROTOCOL_TO_HYP_NATIVE_STANDARD[ProtocolType.Sealevel],
27
+ PROTOCOL_TO_HYP_NATIVE_STANDARD[ProtocolType.Tron],
25
28
  ]);
26
29
 
27
30
  /**