@hyperlane-xyz/rebalancer 2.0.0 → 3.1.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.
Files changed (213) hide show
  1. package/dist/bridges/LiFiBridge.d.ts +67 -0
  2. package/dist/bridges/LiFiBridge.d.ts.map +1 -0
  3. package/dist/bridges/LiFiBridge.js +386 -0
  4. package/dist/bridges/LiFiBridge.js.map +1 -0
  5. package/dist/config/RebalancerConfig.d.ts +8 -2
  6. package/dist/config/RebalancerConfig.d.ts.map +1 -1
  7. package/dist/config/RebalancerConfig.js +9 -4
  8. package/dist/config/RebalancerConfig.js.map +1 -1
  9. package/dist/config/RebalancerConfig.test.js +135 -1
  10. package/dist/config/RebalancerConfig.test.js.map +1 -1
  11. package/dist/config/types.d.ts +1023 -304
  12. package/dist/config/types.d.ts.map +1 -1
  13. package/dist/config/types.js +113 -10
  14. package/dist/config/types.js.map +1 -1
  15. package/dist/core/InventoryRebalancer.d.ts +190 -0
  16. package/dist/core/InventoryRebalancer.d.ts.map +1 -0
  17. package/dist/core/InventoryRebalancer.js +892 -0
  18. package/dist/core/InventoryRebalancer.js.map +1 -0
  19. package/dist/core/InventoryRebalancer.test.d.ts +2 -0
  20. package/dist/core/InventoryRebalancer.test.d.ts.map +1 -0
  21. package/dist/core/InventoryRebalancer.test.js +1382 -0
  22. package/dist/core/InventoryRebalancer.test.js.map +1 -0
  23. package/dist/core/Rebalancer.d.ts +11 -4
  24. package/dist/core/Rebalancer.d.ts.map +1 -1
  25. package/dist/core/Rebalancer.js +92 -9
  26. package/dist/core/Rebalancer.js.map +1 -1
  27. package/dist/core/Rebalancer.test.js +82 -49
  28. package/dist/core/Rebalancer.test.js.map +1 -1
  29. package/dist/core/RebalancerOrchestrator.d.ts +30 -9
  30. package/dist/core/RebalancerOrchestrator.d.ts.map +1 -1
  31. package/dist/core/RebalancerOrchestrator.js +79 -71
  32. package/dist/core/RebalancerOrchestrator.js.map +1 -1
  33. package/dist/core/RebalancerOrchestrator.test.d.ts +2 -0
  34. package/dist/core/RebalancerOrchestrator.test.d.ts.map +1 -0
  35. package/dist/core/RebalancerOrchestrator.test.js +719 -0
  36. package/dist/core/RebalancerOrchestrator.test.js.map +1 -0
  37. package/dist/core/RebalancerService.d.ts +7 -3
  38. package/dist/core/RebalancerService.d.ts.map +1 -1
  39. package/dist/core/RebalancerService.js +44 -24
  40. package/dist/core/RebalancerService.js.map +1 -1
  41. package/dist/core/RebalancerService.test.js +74 -110
  42. package/dist/core/RebalancerService.test.js.map +1 -1
  43. package/dist/e2e/collateral-deficit.e2e-test.js +1 -3
  44. package/dist/e2e/collateral-deficit.e2e-test.js.map +1 -1
  45. package/dist/e2e/composite.e2e-test.js.map +1 -1
  46. package/dist/e2e/harness/BridgeSetup.d.ts +6 -0
  47. package/dist/e2e/harness/BridgeSetup.d.ts.map +1 -1
  48. package/dist/e2e/harness/BridgeSetup.js +10 -1
  49. package/dist/e2e/harness/BridgeSetup.js.map +1 -1
  50. package/dist/e2e/harness/ForkIndexer.d.ts.map +1 -1
  51. package/dist/e2e/harness/ForkIndexer.js +1 -0
  52. package/dist/e2e/harness/ForkIndexer.js.map +1 -1
  53. package/dist/e2e/harness/TestHelpers.d.ts.map +1 -1
  54. package/dist/e2e/harness/TestHelpers.js +1 -4
  55. package/dist/e2e/harness/TestHelpers.js.map +1 -1
  56. package/dist/e2e/harness/TestRebalancer.d.ts +1 -1
  57. package/dist/e2e/harness/TestRebalancer.d.ts.map +1 -1
  58. package/dist/e2e/harness/TestRebalancer.js +9 -9
  59. package/dist/e2e/harness/TestRebalancer.js.map +1 -1
  60. package/dist/e2e/minAmount.e2e-test.js +0 -1
  61. package/dist/e2e/minAmount.e2e-test.js.map +1 -1
  62. package/dist/e2e/weighted.e2e-test.js +0 -1
  63. package/dist/e2e/weighted.e2e-test.js.map +1 -1
  64. package/dist/factories/RebalancerContextFactory.d.ts +48 -6
  65. package/dist/factories/RebalancerContextFactory.d.ts.map +1 -1
  66. package/dist/factories/RebalancerContextFactory.js +171 -17
  67. package/dist/factories/RebalancerContextFactory.js.map +1 -1
  68. package/dist/index.d.ts +6 -6
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +2 -2
  71. package/dist/index.js.map +1 -1
  72. package/dist/interfaces/IExternalBridge.d.ts +101 -0
  73. package/dist/interfaces/IExternalBridge.d.ts.map +1 -0
  74. package/dist/interfaces/IExternalBridge.js +2 -0
  75. package/dist/interfaces/IExternalBridge.js.map +1 -0
  76. package/dist/interfaces/IMonitor.d.ts +1 -0
  77. package/dist/interfaces/IMonitor.d.ts.map +1 -1
  78. package/dist/interfaces/IRebalancer.d.ts +25 -25
  79. package/dist/interfaces/IRebalancer.d.ts.map +1 -1
  80. package/dist/interfaces/IStrategy.d.ts +36 -3
  81. package/dist/interfaces/IStrategy.d.ts.map +1 -1
  82. package/dist/interfaces/IStrategy.js +12 -1
  83. package/dist/interfaces/IStrategy.js.map +1 -1
  84. package/dist/metrics/PriceGetter.js +1 -1
  85. package/dist/metrics/PriceGetter.js.map +1 -1
  86. package/dist/metrics/scripts/metrics.d.ts +3 -3
  87. package/dist/monitor/Monitor.d.ts +12 -2
  88. package/dist/monitor/Monitor.d.ts.map +1 -1
  89. package/dist/monitor/Monitor.js +46 -1
  90. package/dist/monitor/Monitor.js.map +1 -1
  91. package/dist/service.js +40 -17
  92. package/dist/service.js.map +1 -1
  93. package/dist/strategy/BaseStrategy.d.ts +12 -6
  94. package/dist/strategy/BaseStrategy.d.ts.map +1 -1
  95. package/dist/strategy/BaseStrategy.js +56 -21
  96. package/dist/strategy/BaseStrategy.js.map +1 -1
  97. package/dist/strategy/CollateralDeficitStrategy.d.ts +1 -1
  98. package/dist/strategy/CollateralDeficitStrategy.d.ts.map +1 -1
  99. package/dist/strategy/CollateralDeficitStrategy.js +19 -11
  100. package/dist/strategy/CollateralDeficitStrategy.js.map +1 -1
  101. package/dist/strategy/CollateralDeficitStrategy.test.js +135 -2
  102. package/dist/strategy/CollateralDeficitStrategy.test.js.map +1 -1
  103. package/dist/strategy/CompositeStrategy.test.js +13 -0
  104. package/dist/strategy/CompositeStrategy.test.js.map +1 -1
  105. package/dist/strategy/MinAmountStrategy.test.js +4 -0
  106. package/dist/strategy/MinAmountStrategy.test.js.map +1 -1
  107. package/dist/strategy/StrategyFactory.d.ts +2 -1
  108. package/dist/strategy/StrategyFactory.d.ts.map +1 -1
  109. package/dist/strategy/StrategyFactory.js +24 -8
  110. package/dist/strategy/StrategyFactory.js.map +1 -1
  111. package/dist/strategy/WeightedStrategy.test.js +6 -0
  112. package/dist/strategy/WeightedStrategy.test.js.map +1 -1
  113. package/dist/test/helpers.d.ts +8 -7
  114. package/dist/test/helpers.d.ts.map +1 -1
  115. package/dist/test/helpers.js +23 -5
  116. package/dist/test/helpers.js.map +1 -1
  117. package/dist/test/lifiMocks.d.ts +51 -0
  118. package/dist/test/lifiMocks.d.ts.map +1 -0
  119. package/dist/test/lifiMocks.js +130 -0
  120. package/dist/test/lifiMocks.js.map +1 -0
  121. package/dist/tracking/ActionTracker.d.ts +34 -1
  122. package/dist/tracking/ActionTracker.d.ts.map +1 -1
  123. package/dist/tracking/ActionTracker.js +233 -26
  124. package/dist/tracking/ActionTracker.js.map +1 -1
  125. package/dist/tracking/ActionTracker.test.js +380 -19
  126. package/dist/tracking/ActionTracker.test.js.map +1 -1
  127. package/dist/tracking/IActionTracker.d.ts +48 -3
  128. package/dist/tracking/IActionTracker.d.ts.map +1 -1
  129. package/dist/tracking/InflightContextAdapter.d.ts.map +1 -1
  130. package/dist/tracking/InflightContextAdapter.js +24 -7
  131. package/dist/tracking/InflightContextAdapter.js.map +1 -1
  132. package/dist/tracking/InflightContextAdapter.test.js +7 -4
  133. package/dist/tracking/InflightContextAdapter.test.js.map +1 -1
  134. package/dist/tracking/types.d.ts +33 -2
  135. package/dist/tracking/types.d.ts.map +1 -1
  136. package/dist/utils/ExplorerClient.d.ts +3 -1
  137. package/dist/utils/ExplorerClient.d.ts.map +1 -1
  138. package/dist/utils/ExplorerClient.js +16 -8
  139. package/dist/utils/ExplorerClient.js.map +1 -1
  140. package/dist/utils/bridgeUtils.d.ts +27 -4
  141. package/dist/utils/bridgeUtils.d.ts.map +1 -1
  142. package/dist/utils/bridgeUtils.js +38 -0
  143. package/dist/utils/bridgeUtils.js.map +1 -1
  144. package/dist/utils/bridgeUtils.test.js +9 -0
  145. package/dist/utils/bridgeUtils.test.js.map +1 -1
  146. package/dist/utils/gasEstimation.d.ts +65 -0
  147. package/dist/utils/gasEstimation.d.ts.map +1 -0
  148. package/dist/utils/gasEstimation.js +176 -0
  149. package/dist/utils/gasEstimation.js.map +1 -0
  150. package/dist/utils/tokenUtils.d.ts +9 -1
  151. package/dist/utils/tokenUtils.d.ts.map +1 -1
  152. package/dist/utils/tokenUtils.js +11 -0
  153. package/dist/utils/tokenUtils.js.map +1 -1
  154. package/package.json +9 -7
  155. package/src/bridges/LiFiBridge.ts +538 -0
  156. package/src/config/RebalancerConfig.test.ts +162 -0
  157. package/src/config/RebalancerConfig.ts +21 -3
  158. package/src/config/types.ts +147 -10
  159. package/src/core/InventoryRebalancer.test.ts +1721 -0
  160. package/src/core/InventoryRebalancer.ts +1265 -0
  161. package/src/core/Rebalancer.test.ts +84 -30
  162. package/src/core/Rebalancer.ts +144 -23
  163. package/src/core/RebalancerOrchestrator.test.ts +869 -0
  164. package/src/core/RebalancerOrchestrator.ts +146 -95
  165. package/src/core/RebalancerService.test.ts +86 -124
  166. package/src/core/RebalancerService.ts +67 -33
  167. package/src/e2e/collateral-deficit.e2e-test.ts +2 -4
  168. package/src/e2e/composite.e2e-test.ts +5 -5
  169. package/src/e2e/harness/BridgeSetup.ts +28 -1
  170. package/src/e2e/harness/ForkIndexer.ts +1 -0
  171. package/src/e2e/harness/TestHelpers.ts +1 -4
  172. package/src/e2e/harness/TestRebalancer.ts +10 -7
  173. package/src/e2e/minAmount.e2e-test.ts +1 -2
  174. package/src/e2e/weighted.e2e-test.ts +1 -2
  175. package/src/factories/RebalancerContextFactory.ts +294 -24
  176. package/src/index.ts +22 -5
  177. package/src/interfaces/IExternalBridge.ts +115 -0
  178. package/src/interfaces/IMonitor.ts +1 -0
  179. package/src/interfaces/IRebalancer.ts +45 -29
  180. package/src/interfaces/IStrategy.ts +50 -3
  181. package/src/metrics/PriceGetter.ts +1 -1
  182. package/src/monitor/Monitor.ts +81 -2
  183. package/src/service.ts +59 -18
  184. package/src/strategy/BaseStrategy.ts +77 -24
  185. package/src/strategy/CollateralDeficitStrategy.test.ts +181 -4
  186. package/src/strategy/CollateralDeficitStrategy.ts +42 -15
  187. package/src/strategy/CompositeStrategy.test.ts +13 -0
  188. package/src/strategy/MinAmountStrategy.test.ts +4 -0
  189. package/src/strategy/StrategyFactory.ts +33 -6
  190. package/src/strategy/WeightedStrategy.test.ts +6 -0
  191. package/src/test/helpers.ts +39 -14
  192. package/src/test/lifiMocks.ts +174 -0
  193. package/src/tracking/ActionTracker.test.ts +443 -19
  194. package/src/tracking/ActionTracker.ts +339 -28
  195. package/src/tracking/IActionTracker.ts +59 -3
  196. package/src/tracking/InflightContextAdapter.test.ts +7 -4
  197. package/src/tracking/InflightContextAdapter.ts +42 -9
  198. package/src/tracking/types.ts +45 -2
  199. package/src/utils/ExplorerClient.ts +27 -10
  200. package/src/utils/bridgeUtils.test.ts +9 -0
  201. package/src/utils/bridgeUtils.ts +75 -6
  202. package/src/utils/gasEstimation.ts +272 -0
  203. package/src/utils/tokenUtils.ts +12 -0
  204. package/dist/tracking/index.d.ts +0 -7
  205. package/dist/tracking/index.d.ts.map +0 -1
  206. package/dist/tracking/index.js +0 -6
  207. package/dist/tracking/index.js.map +0 -1
  208. package/dist/utils/index.d.ts +0 -5
  209. package/dist/utils/index.d.ts.map +0 -1
  210. package/dist/utils/index.js +0 -5
  211. package/dist/utils/index.js.map +0 -1
  212. package/src/tracking/index.ts +0 -36
  213. package/src/utils/index.ts +0 -4
@@ -233,7 +233,7 @@ describe('CompositeStrategy E2E', function () {
233
233
  hyperlaneCore,
234
234
  {
235
235
  dispatchTx: rebalanceTxReceipt!,
236
- messageId: action.messageId,
236
+ messageId: action.messageId!,
237
237
  origin: originChain,
238
238
  destination: destChain,
239
239
  },
@@ -432,7 +432,7 @@ describe('CompositeStrategy E2E', function () {
432
432
  hyperlaneCore,
433
433
  {
434
434
  dispatchTx: rebalanceTxReceipt!,
435
- messageId: action.messageId,
435
+ messageId: action.messageId!,
436
436
  origin: originChain,
437
437
  destination: destChain,
438
438
  },
@@ -651,7 +651,7 @@ describe('CompositeStrategy E2E', function () {
651
651
  hyperlaneCore,
652
652
  {
653
653
  dispatchTx: rebalanceTxReceipt!,
654
- messageId: action.messageId,
654
+ messageId: action.messageId!,
655
655
  origin: originChain,
656
656
  destination: destChain,
657
657
  },
@@ -794,7 +794,7 @@ describe('CompositeStrategy E2E', function () {
794
794
  );
795
795
  const relayResult = await tryRelayMessage(multiProvider, hyperlaneCore, {
796
796
  dispatchTx: rebalanceTxReceipt,
797
- messageId: inflightToBase.messageId,
797
+ messageId: inflightToBase.messageId!,
798
798
  origin: 'anvil1',
799
799
  destination: 'anvil3',
800
800
  });
@@ -959,7 +959,7 @@ describe('CompositeStrategy E2E', function () {
959
959
 
960
960
  const relayResult = await tryRelayMessage(multiProvider, hyperlaneCore, {
961
961
  dispatchTx: rebalanceTxReceipt,
962
- messageId: action.messageId,
962
+ messageId: action.messageId!,
963
963
  origin: originChain,
964
964
  destination: destChain,
965
965
  });
@@ -1,6 +1,9 @@
1
1
  import { BigNumber, ethers, providers } from 'ethers';
2
2
 
3
- import { ERC20__factory } from '@hyperlane-xyz/core';
3
+ import {
4
+ ERC20__factory,
5
+ MovableCollateralRouter__factory,
6
+ } from '@hyperlane-xyz/core';
4
7
 
5
8
  export async function setupCollateralBalances(
6
9
  providers: Map<string, providers.JsonRpcProvider>,
@@ -65,3 +68,27 @@ export async function getAllCollateralBalances(
65
68
  }
66
69
  return balances;
67
70
  }
71
+
72
+ export interface AllowedBridgeConfig {
73
+ monitoredRouterAddress: string;
74
+ bridgeAddress: string;
75
+ destinationDomain: number;
76
+ }
77
+
78
+ export async function configureAllowedBridges(
79
+ provider: providers.JsonRpcProvider,
80
+ configs: AllowedBridgeConfig[],
81
+ deployerKey: string,
82
+ ): Promise<void> {
83
+ if (configs.length === 0) return;
84
+
85
+ const deployer = new ethers.Wallet(deployerKey, provider);
86
+ const router = MovableCollateralRouter__factory.connect(
87
+ configs[0].monitoredRouterAddress,
88
+ deployer,
89
+ );
90
+
91
+ for (const config of configs) {
92
+ await router.addBridge(config.destinationDomain, config.bridgeAddress);
93
+ }
94
+ }
@@ -120,6 +120,7 @@ export class ForkIndexer {
120
120
  origin_tx_recipient: event.args.sender,
121
121
  is_delivered: false,
122
122
  message_body: parsed.body,
123
+ send_occurred_at: null,
123
124
  };
124
125
 
125
126
  if (
@@ -22,9 +22,6 @@ export async function getFirstMonitorEvent(
22
22
  reject(error);
23
23
  });
24
24
 
25
- monitor.start().catch((err: unknown) => {
26
- clearTimeout(timeout);
27
- reject(err instanceof Error ? err : new Error(String(err)));
28
- });
25
+ void monitor.start();
29
26
  });
30
27
  }
@@ -10,6 +10,7 @@ import { addressToBytes32 } from '@hyperlane-xyz/utils';
10
10
 
11
11
  import { RebalancerConfig } from '../../config/RebalancerConfig.js';
12
12
  import {
13
+ DEFAULT_INTENT_TTL_MS,
13
14
  type StrategyConfig,
14
15
  getStrategyChainNames,
15
16
  } from '../../config/types.js';
@@ -21,7 +22,7 @@ import { RebalancerContextFactory } from '../../factories/RebalancerContextFacto
21
22
  import type { ConfirmedBlockTags } from '../../interfaces/IMonitor.js';
22
23
  import type { IStrategy } from '../../interfaces/IStrategy.js';
23
24
  import type { Monitor } from '../../monitor/Monitor.js';
24
- import type { IActionTracker } from '../../tracking/index.js';
25
+ import type { IActionTracker } from '../../tracking/IActionTracker.js';
25
26
  import type { ExplorerMessage } from '../../utils/ExplorerClient.js';
26
27
  import {
27
28
  ANVIL_TEST_PRIVATE_KEY,
@@ -203,6 +204,7 @@ export class TestRebalancerBuilder {
203
204
  const rebalancerConfig = new RebalancerConfig(
204
205
  MONITORED_ROUTE_ID,
205
206
  this.strategyConfig,
207
+ DEFAULT_INTENT_TTL_MS,
206
208
  );
207
209
 
208
210
  const registry = this.deploymentManager.getRegistry();
@@ -212,6 +214,7 @@ export class TestRebalancerBuilder {
212
214
  const contextFactory = await RebalancerContextFactory.create(
213
215
  rebalancerConfig,
214
216
  workingMultiProvider,
217
+ undefined,
215
218
  mpp,
216
219
  registry,
217
220
  this.logger,
@@ -225,18 +228,17 @@ export class TestRebalancerBuilder {
225
228
  await tracker.initialize();
226
229
  this.logger.info('ActionTracker initialized with mock explorer');
227
230
 
228
- // In execute mode, create an actual Rebalancer to enable intent creation and execution
229
- const rebalancer =
231
+ // In execute mode, create actual Rebalancers to enable intent creation and execution
232
+ const rebalancers =
230
233
  this.executionMode === 'execute'
231
- ? contextFactory.createRebalancer()
232
- : undefined;
234
+ ? (await contextFactory.createRebalancers(tracker)).rebalancers
235
+ : [];
233
236
 
234
237
  const orchestratorDeps: RebalancerOrchestratorDeps = {
235
238
  strategy,
236
- rebalancer,
239
+ rebalancers,
237
240
  actionTracker: tracker,
238
241
  inflightContextAdapter: adapter,
239
- multiProvider: workingMultiProvider,
240
242
  rebalancerConfig,
241
243
  logger: this.logger,
242
244
  };
@@ -325,6 +327,7 @@ export class TestRebalancerBuilder {
325
327
  origin_tx_recipient: deployedAddresses.monitoredRoute[params.from],
326
328
  is_delivered: false,
327
329
  message_body: encodeWarpRouteMessageBody(warpRecipient, params.amount),
330
+ send_occurred_at: null,
328
331
  };
329
332
 
330
333
  userTransfers.push(mockTransfer);
@@ -212,7 +212,7 @@ describe('MinAmountStrategy E2E', function () {
212
212
  hyperlaneCore,
213
213
  {
214
214
  dispatchTx: rebalanceTxReceipt,
215
- messageId: actionToArbitrum.messageId,
215
+ messageId: actionToArbitrum.messageId!,
216
216
  origin: 'anvil1',
217
217
  destination: 'anvil2',
218
218
  },
@@ -236,7 +236,6 @@ describe('MinAmountStrategy E2E', function () {
236
236
  activeIntents[0].id,
237
237
  );
238
238
  expect(completedIntent!.status).to.equal('complete');
239
- expect(completedIntent!.fulfilledAmount).to.equal(70000000n);
240
239
  });
241
240
 
242
241
  it('should handle stuck transfer and propose routes to destination', async function () {
@@ -194,7 +194,7 @@ describe('WeightedStrategy E2E', function () {
194
194
  hyperlaneCore,
195
195
  {
196
196
  dispatchTx: rebalanceTxReceipt,
197
- messageId: actionToBase.messageId,
197
+ messageId: actionToBase.messageId!,
198
198
  origin: 'anvil1',
199
199
  destination: 'anvil3',
200
200
  },
@@ -218,7 +218,6 @@ describe('WeightedStrategy E2E', function () {
218
218
  activeIntents[0].id,
219
219
  );
220
220
  expect(completedIntent!.status).to.equal('complete');
221
- expect(completedIntent!.fulfilledAmount).to.equal(1000000000n);
222
221
 
223
222
  // Assert: No more in-progress actions
224
223
  const remainingActions = await context.tracker.getInProgressActions();
@@ -11,35 +11,47 @@ import {
11
11
  WarpCore,
12
12
  type WarpCoreConfig,
13
13
  } from '@hyperlane-xyz/sdk';
14
- import { objMap } from '@hyperlane-xyz/utils';
14
+ import { objMap, toWei } from '@hyperlane-xyz/utils';
15
15
 
16
+ import { LiFiBridge } from '../bridges/LiFiBridge.js';
16
17
  import { type RebalancerConfig } from '../config/RebalancerConfig.js';
17
- import { getAllBridges, getStrategyChainNames } from '../config/types.js';
18
+ import {
19
+ ExecutionType,
20
+ ExternalBridgeType,
21
+ getAllBridges,
22
+ getStrategyChainConfig,
23
+ getStrategyChainNames,
24
+ } from '../config/types.js';
25
+ import { InventoryRebalancer } from '../core/InventoryRebalancer.js';
18
26
  import { Rebalancer } from '../core/Rebalancer.js';
27
+ import { RebalancerOrchestrator } from '../core/RebalancerOrchestrator.js';
28
+ import type { ExternalBridgeRegistry } from '../interfaces/IExternalBridge.js';
19
29
  import type { IRebalancer } from '../interfaces/IRebalancer.js';
20
30
  import type { IStrategy } from '../interfaces/IStrategy.js';
21
31
  import { Metrics } from '../metrics/Metrics.js';
22
32
  import { PriceGetter } from '../metrics/PriceGetter.js';
23
- import { Monitor } from '../monitor/Monitor.js';
33
+ import { type InventoryMonitorConfig, Monitor } from '../monitor/Monitor.js';
24
34
  import { StrategyFactory } from '../strategy/StrategyFactory.js';
25
35
  import {
26
36
  ActionTracker,
27
37
  type ActionTrackerConfig,
28
- type IActionTracker,
29
- InMemoryStore,
30
- InflightContextAdapter,
31
- type RebalanceAction,
32
- type RebalanceActionStatus,
33
- type RebalanceIntent,
34
- type RebalanceIntentStatus,
35
- type Transfer,
36
- type TransferStatus,
37
- } from '../tracking/index.js';
38
+ } from '../tracking/ActionTracker.js';
39
+ import type { IActionTracker } from '../tracking/IActionTracker.js';
40
+ import { InflightContextAdapter } from '../tracking/InflightContextAdapter.js';
41
+ import { InMemoryStore } from '../tracking/store/index.js';
42
+ import type {
43
+ RebalanceAction,
44
+ RebalanceActionStatus,
45
+ RebalanceIntent,
46
+ RebalanceIntentStatus,
47
+ Transfer,
48
+ TransferStatus,
49
+ } from '../tracking/types.js';
38
50
  import {
39
51
  ExplorerClient,
40
52
  type IExplorerClient,
41
53
  } from '../utils/ExplorerClient.js';
42
- import { isCollateralizedTokenEligibleForRebalancing } from '../utils/index.js';
54
+ import { isCollateralizedTokenEligibleForRebalancing } from '../utils/tokenUtils.js';
43
55
 
44
56
  const DEFAULT_EXPLORER_URL =
45
57
  process.env.EXPLORER_API_URL || 'https://explorer4.hasura.app/v1/graphql';
@@ -49,7 +61,8 @@ export class RebalancerContextFactory {
49
61
  * @param config - The rebalancer config
50
62
  * @param warpCore - An instance of `WarpCore` configured for the specified `warpRouteId`.
51
63
  * @param tokensByChainName - A map of chain->token to ease the lookup of token by chain
52
- * @param multiProvider - MultiProvider instance
64
+ * @param multiProvider - MultiProvider instance (for movable collateral operations)
65
+ * @param inventoryMultiProvider - MultiProvider instance for inventory operations (optional)
53
66
  * @param multiProtocolProvider - MultiProtocolProvider instance (with mailbox metadata)
54
67
  * @param registry - IRegistry instance
55
68
  * @param logger - Logger instance
@@ -59,14 +72,24 @@ export class RebalancerContextFactory {
59
72
  private readonly warpCore: WarpCore,
60
73
  private readonly tokensByChainName: ChainMap<Token>,
61
74
  private readonly multiProvider: MultiProvider,
75
+ private readonly inventoryMultiProvider: MultiProvider | undefined,
62
76
  private readonly multiProtocolProvider: MultiProtocolProvider,
63
77
  private readonly registry: IRegistry,
64
78
  private readonly logger: Logger,
65
79
  ) {}
66
80
 
81
+ /**
82
+ * @param config - The rebalancer config
83
+ * @param multiProvider - MultiProvider instance (for movable collateral operations)
84
+ * @param inventoryMultiProvider - MultiProvider instance for inventory operations (optional)
85
+ * @param multiProtocolProvider - MultiProtocolProvider instance (optional, created from multiProvider if not provided)
86
+ * @param registry - IRegistry instance
87
+ * @param logger - Logger instance
88
+ */
67
89
  public static async create(
68
90
  config: RebalancerConfig,
69
91
  multiProvider: MultiProvider,
92
+ inventoryMultiProvider: MultiProvider | undefined,
70
93
  multiProtocolProvider: MultiProtocolProvider | undefined,
71
94
  registry: IRegistry,
72
95
  logger: Logger,
@@ -78,18 +101,15 @@ export class RebalancerContextFactory {
78
101
  },
79
102
  'Creating RebalancerContextFactory',
80
103
  );
104
+
105
+ // TODO: should we pull addressed for chains we care about, i.e those in the warp config
81
106
  const addresses = await registry.getAddresses();
82
107
 
83
108
  // The Sealevel warp adapters require the Mailbox address, so we
84
109
  // get mailboxes for all chains and merge them with the chain metadata.
85
110
  const mailboxes = objMap(addresses, (_, { mailbox }) => ({ mailbox }));
86
111
 
87
- // Create MultiProtocolProvider (convert from MultiProvider if not provided)
88
- const mpp =
89
- multiProtocolProvider ??
90
- MultiProtocolProvider.fromMultiProvider(multiProvider);
91
- const extendedMultiProtocolProvider = mpp.extendChainMetadata(mailboxes);
92
-
112
+ // Fetch warp route config FIRST to get chain list
93
113
  const warpCoreConfig =
94
114
  warpCoreConfigOverride ??
95
115
  (await registry.getWarpRoute(config.warpRouteId));
@@ -98,6 +118,22 @@ export class RebalancerContextFactory {
98
118
  `Warp route config for ${config.warpRouteId} not found in registry`,
99
119
  );
100
120
  }
121
+
122
+ // Force-initialize providers for all warp route chains
123
+ // This ensures fromMultiProvider() snapshots actual provider instances
124
+ const warpChains = [
125
+ ...new Set(warpCoreConfig.tokens.map((t: any) => t.chainName)),
126
+ ];
127
+ for (const chain of warpChains) {
128
+ multiProvider.getProvider(chain);
129
+ }
130
+
131
+ // Create MultiProtocolProvider (convert from MultiProvider if not provided)
132
+ const mpp =
133
+ multiProtocolProvider ??
134
+ MultiProtocolProvider.fromMultiProvider(multiProvider);
135
+ const extendedMultiProtocolProvider = mpp.extendChainMetadata(mailboxes);
136
+
101
137
  const warpCore = WarpCore.FromConfig(
102
138
  extendedMultiProtocolProvider,
103
139
  warpCoreConfig,
@@ -117,6 +153,7 @@ export class RebalancerContextFactory {
117
153
  warpCore,
118
154
  tokensByChainName,
119
155
  multiProvider,
156
+ inventoryMultiProvider,
120
157
  extendedMultiProtocolProvider,
121
158
  registry,
122
159
  logger,
@@ -154,7 +191,10 @@ export class RebalancerContextFactory {
154
191
  );
155
192
  }
156
193
 
157
- public createMonitor(checkFrequency: number): Monitor {
194
+ public createMonitor(
195
+ checkFrequency: number,
196
+ inventoryConfig?: InventoryMonitorConfig,
197
+ ): Monitor {
158
198
  this.logger.debug(
159
199
  {
160
200
  warpRouteId: this.config.warpRouteId,
@@ -162,7 +202,12 @@ export class RebalancerContextFactory {
162
202
  },
163
203
  'Creating Monitor',
164
204
  );
165
- return new Monitor(checkFrequency, this.warpCore, this.logger);
205
+ return new Monitor(
206
+ checkFrequency,
207
+ this.warpCore,
208
+ this.logger,
209
+ inventoryConfig,
210
+ );
166
211
  }
167
212
 
168
213
  public async createStrategy(metrics?: Metrics): Promise<IStrategy> {
@@ -177,16 +222,48 @@ export class RebalancerContextFactory {
177
222
  },
178
223
  'Creating Strategy',
179
224
  );
225
+
226
+ // Build minAmountsByChain from chain configs
227
+ const chainNames = getStrategyChainNames(this.config.strategyConfig);
228
+ const minAmountsByChain: ChainMap<bigint> = {};
229
+
230
+ for (const chainName of chainNames) {
231
+ const chainConfig = getStrategyChainConfig(
232
+ this.config.strategyConfig,
233
+ chainName,
234
+ );
235
+ if (chainConfig?.bridgeMinAcceptedAmount) {
236
+ const token = this.tokensByChainName[chainName];
237
+ const decimals = token?.decimals ?? 18;
238
+ minAmountsByChain[chainName] = BigInt(
239
+ toWei(chainConfig.bridgeMinAcceptedAmount, decimals),
240
+ );
241
+ }
242
+ }
243
+
244
+ this.logger.debug(
245
+ {
246
+ minAmountsByChain: Object.fromEntries(
247
+ Object.entries(minAmountsByChain).map(([k, v]) => [k, v.toString()]),
248
+ ),
249
+ },
250
+ 'Built minimum amounts by chain for strategy',
251
+ );
252
+
180
253
  return StrategyFactory.createStrategy(
181
254
  this.config.strategyConfig,
182
255
  this.tokensByChainName,
183
256
  await this.getInitialTotalCollateral(),
184
257
  this.logger,
185
258
  metrics,
259
+ minAmountsByChain,
186
260
  );
187
261
  }
188
262
 
189
- public createRebalancer(metrics?: Metrics): IRebalancer {
263
+ private createMovableCollateralRebalancer(
264
+ actionTracker: IActionTracker,
265
+ metrics?: Metrics,
266
+ ): IRebalancer {
190
267
  this.logger.debug(
191
268
  { warpRouteId: this.config.warpRouteId },
192
269
  'Creating Rebalancer',
@@ -197,6 +274,7 @@ export class RebalancerContextFactory {
197
274
  this.multiProvider.metadata,
198
275
  this.tokensByChainName,
199
276
  this.multiProvider,
277
+ actionTracker,
200
278
  this.logger,
201
279
  metrics,
202
280
  );
@@ -272,6 +350,8 @@ export class RebalancerContextFactory {
272
350
  routersByDomain,
273
351
  bridges,
274
352
  rebalancerAddress,
353
+ inventorySignerAddress: this.config.inventorySigner,
354
+ intentTTL: this.config.intentTTL,
275
355
  };
276
356
 
277
357
  // 6. Create ActionTracker
@@ -301,6 +381,196 @@ export class RebalancerContextFactory {
301
381
  return { tracker, adapter };
302
382
  }
303
383
 
384
+ /**
385
+ * Creates inventory components for inventory-based rebalancing.
386
+ * Returns null if inventory config is not available.
387
+ *
388
+ * @param actionTracker - ActionTracker instance for tracking inventory actions
389
+ */
390
+ private async createInventoryRebalancerAndConfig(
391
+ actionTracker: IActionTracker,
392
+ ): Promise<{
393
+ inventoryRebalancer: IRebalancer;
394
+ externalBridgeRegistry: Partial<ExternalBridgeRegistry>;
395
+ inventoryConfig: InventoryMonitorConfig;
396
+ } | null> {
397
+ const { inventorySigner, externalBridges } = this.config;
398
+
399
+ if (!inventorySigner) {
400
+ this.logger.debug(
401
+ 'Inventory config not available, skipping inventory components creation',
402
+ );
403
+ return null;
404
+ }
405
+
406
+ this.logger.debug(
407
+ { warpRouteId: this.config.warpRouteId, inventorySigner },
408
+ 'Creating inventory components',
409
+ );
410
+
411
+ const inventoryChains = getStrategyChainNames(
412
+ this.config.strategyConfig,
413
+ ).filter((chainName) => {
414
+ const chainConfig = getStrategyChainConfig(
415
+ this.config.strategyConfig,
416
+ chainName,
417
+ );
418
+ return chainConfig?.executionType === ExecutionType.Inventory;
419
+ });
420
+
421
+ if (inventoryChains.length === 0) {
422
+ this.logger.debug('No inventory chains configured');
423
+ return null;
424
+ }
425
+
426
+ // Build registry dynamically from ExternalBridgeType enum
427
+ const externalBridgeRegistry: Partial<ExternalBridgeRegistry> = {};
428
+
429
+ for (const bridgeType of Object.values(ExternalBridgeType)) {
430
+ switch (bridgeType) {
431
+ case ExternalBridgeType.LiFi: {
432
+ const lifiConfig = externalBridges?.lifi;
433
+ if (lifiConfig?.integrator) {
434
+ externalBridgeRegistry[ExternalBridgeType.LiFi] = new LiFiBridge(
435
+ {
436
+ integrator: lifiConfig.integrator,
437
+ defaultSlippage: lifiConfig.defaultSlippage,
438
+ chainMetadata: this.multiProvider.metadata,
439
+ },
440
+ this.logger,
441
+ );
442
+ }
443
+ break;
444
+ }
445
+ default: {
446
+ // Exhaustive check - TypeScript will error if new enum value added
447
+ const _exhaustive: never = bridgeType;
448
+ throw new Error(`Unknown bridge type: ${_exhaustive}`);
449
+ }
450
+ }
451
+ }
452
+
453
+ if (Object.keys(externalBridgeRegistry).length === 0) {
454
+ this.logger.debug(
455
+ 'No external bridges configured, skipping inventory components',
456
+ );
457
+ return null;
458
+ }
459
+
460
+ // 3. Build inventory config
461
+ const inventoryConfig: InventoryMonitorConfig = {
462
+ inventoryAddress: inventorySigner,
463
+ chains: inventoryChains,
464
+ };
465
+
466
+ // 4. Create InventoryRebalancer
467
+ // Use inventoryMultiProvider for inventory operations if available, otherwise fall back to multiProvider
468
+ const inventoryRebalancer = new InventoryRebalancer(
469
+ {
470
+ inventorySigner,
471
+ inventoryMultiProvider: this.inventoryMultiProvider,
472
+ inventoryChains,
473
+ },
474
+ actionTracker,
475
+ externalBridgeRegistry,
476
+ this.warpCore,
477
+ this.multiProvider,
478
+ this.logger,
479
+ );
480
+
481
+ this.logger.info(
482
+ {
483
+ inventoryChains,
484
+ inventorySigner,
485
+ },
486
+ 'Inventory components created successfully',
487
+ );
488
+
489
+ return { inventoryRebalancer, externalBridgeRegistry, inventoryConfig };
490
+ }
491
+
492
+ /**
493
+ * Creates all rebalancers based on config execution types.
494
+ * Returns an array of rebalancers (movableCollateral and/or inventory)
495
+ * along with metadata needed for monitor and orchestrator.
496
+ */
497
+ public async createRebalancers(
498
+ actionTracker: IActionTracker,
499
+ metrics?: Metrics,
500
+ ): Promise<{
501
+ rebalancers: IRebalancer[];
502
+ externalBridgeRegistry: Partial<ExternalBridgeRegistry>;
503
+ inventoryConfig?: InventoryMonitorConfig;
504
+ }> {
505
+ const rebalancers: IRebalancer[] = [];
506
+ let externalBridgeRegistry: Partial<ExternalBridgeRegistry> = {};
507
+ let inventoryConfig: InventoryMonitorConfig | undefined;
508
+
509
+ // Check if any chains use movableCollateral execution type
510
+ const hasMovableCollateral = this.hasMovableCollateralChains();
511
+ if (hasMovableCollateral) {
512
+ const rebalancer = this.createMovableCollateralRebalancer(
513
+ actionTracker,
514
+ metrics,
515
+ );
516
+ rebalancers.push(rebalancer);
517
+ }
518
+
519
+ // Check if any chains use inventory execution type
520
+ const inventoryComponents =
521
+ await this.createInventoryRebalancerAndConfig(actionTracker);
522
+ if (inventoryComponents) {
523
+ rebalancers.push(inventoryComponents.inventoryRebalancer);
524
+ externalBridgeRegistry = inventoryComponents.externalBridgeRegistry;
525
+ inventoryConfig = inventoryComponents.inventoryConfig;
526
+ }
527
+
528
+ return { rebalancers, externalBridgeRegistry, inventoryConfig };
529
+ }
530
+
531
+ /**
532
+ * Creates a RebalancerOrchestrator with all required dependencies.
533
+ */
534
+ public createOrchestrator(options: {
535
+ strategy: IStrategy;
536
+ actionTracker: IActionTracker;
537
+ inflightContextAdapter: InflightContextAdapter;
538
+ rebalancers: IRebalancer[];
539
+ externalBridgeRegistry: Partial<ExternalBridgeRegistry>;
540
+ metrics?: Metrics;
541
+ }): RebalancerOrchestrator {
542
+ this.logger.debug(
543
+ { warpRouteId: this.config.warpRouteId },
544
+ 'Creating RebalancerOrchestrator',
545
+ );
546
+
547
+ return new RebalancerOrchestrator({
548
+ strategy: options.strategy,
549
+ actionTracker: options.actionTracker,
550
+ inflightContextAdapter: options.inflightContextAdapter,
551
+ rebalancerConfig: this.config,
552
+ logger: this.logger,
553
+ rebalancers: options.rebalancers,
554
+ externalBridgeRegistry: options.externalBridgeRegistry,
555
+ metrics: options.metrics,
556
+ });
557
+ }
558
+
559
+ private hasMovableCollateralChains(): boolean {
560
+ return getStrategyChainNames(this.config.strategyConfig).some(
561
+ (chainName) => {
562
+ const chainConfig = getStrategyChainConfig(
563
+ this.config.strategyConfig,
564
+ chainName,
565
+ );
566
+ return (
567
+ chainConfig?.executionType === ExecutionType.MovableCollateral ||
568
+ chainConfig?.executionType === undefined
569
+ ); // Default is movableCollateral
570
+ },
571
+ );
572
+ }
573
+
304
574
  private async getInitialTotalCollateral(): Promise<bigint> {
305
575
  let initialTotalCollateral = 0n;
306
576