@humanode-hyperlane/rebalancer 27.2.2 → 1029.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 (295) 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 +66 -55
  4. package/dist/bridges/LiFiBridge.js.map +1 -1
  5. package/dist/bridges/LiFiBridge.test.js +213 -0
  6. package/dist/bridges/LiFiBridge.test.js.map +1 -1
  7. package/dist/config/RebalancerConfig.d.ts +4 -3
  8. package/dist/config/RebalancerConfig.d.ts.map +1 -1
  9. package/dist/config/RebalancerConfig.js +7 -5
  10. package/dist/config/RebalancerConfig.js.map +1 -1
  11. package/dist/config/RebalancerConfig.test.js +278 -2
  12. package/dist/config/RebalancerConfig.test.js.map +1 -1
  13. package/dist/config/types.d.ts +707 -2664
  14. package/dist/config/types.d.ts.map +1 -1
  15. package/dist/config/types.js +62 -7
  16. package/dist/config/types.js.map +1 -1
  17. package/dist/core/InventoryRebalancer.d.ts +13 -19
  18. package/dist/core/InventoryRebalancer.d.ts.map +1 -1
  19. package/dist/core/InventoryRebalancer.js +404 -276
  20. package/dist/core/InventoryRebalancer.js.map +1 -1
  21. package/dist/core/InventoryRebalancer.test.js +814 -25
  22. package/dist/core/InventoryRebalancer.test.js.map +1 -1
  23. package/dist/core/Rebalancer.d.ts.map +1 -1
  24. package/dist/core/Rebalancer.js +12 -6
  25. package/dist/core/Rebalancer.js.map +1 -1
  26. package/dist/core/Rebalancer.test.js +51 -0
  27. package/dist/core/Rebalancer.test.js.map +1 -1
  28. package/dist/core/RebalancerOrchestrator.d.ts.map +1 -1
  29. package/dist/core/RebalancerOrchestrator.test.js +0 -1
  30. package/dist/core/RebalancerOrchestrator.test.js.map +1 -1
  31. package/dist/core/RebalancerService.d.ts.map +1 -1
  32. package/dist/core/RebalancerService.js +3 -2
  33. package/dist/core/RebalancerService.js.map +1 -1
  34. package/dist/core/RebalancerService.test.js +24 -0
  35. package/dist/core/RebalancerService.test.js.map +1 -1
  36. package/dist/e2e/fixtures/routes.d.ts +3 -3
  37. package/dist/e2e/fixtures/routes.d.ts.map +1 -1
  38. package/dist/e2e/harness/BaseLocalDeploymentManager.d.ts.map +1 -1
  39. package/dist/e2e/harness/BaseLocalDeploymentManager.js +3 -2
  40. package/dist/e2e/harness/BaseLocalDeploymentManager.js.map +1 -1
  41. package/dist/e2e/harness/Erc20InventoryLocalDeploymentManager.d.ts.map +1 -1
  42. package/dist/e2e/harness/Erc20InventoryLocalDeploymentManager.js.map +1 -1
  43. package/dist/e2e/harness/Erc20LocalDeploymentManager.d.ts.map +1 -1
  44. package/dist/e2e/harness/ForkIndexer.d.ts.map +1 -1
  45. package/dist/e2e/harness/ForkIndexer.js.map +1 -1
  46. package/dist/e2e/harness/LocalProvider.d.ts +3 -0
  47. package/dist/e2e/harness/LocalProvider.d.ts.map +1 -0
  48. package/dist/e2e/harness/LocalProvider.js +9 -0
  49. package/dist/e2e/harness/LocalProvider.js.map +1 -0
  50. package/dist/e2e/harness/LocalProvider.test.d.ts +2 -0
  51. package/dist/e2e/harness/LocalProvider.test.d.ts.map +1 -0
  52. package/dist/e2e/harness/LocalProvider.test.js +14 -0
  53. package/dist/e2e/harness/LocalProvider.test.js.map +1 -0
  54. package/dist/e2e/harness/MixedLocalDeploymentManager.d.ts.map +1 -1
  55. package/dist/e2e/harness/MixedLocalDeploymentManager.js.map +1 -1
  56. package/dist/e2e/harness/MockExplorerClient.d.ts.map +1 -1
  57. package/dist/e2e/harness/MockExplorerClient.js.map +1 -1
  58. package/dist/e2e/harness/MockExternalBridge.d.ts.map +1 -1
  59. package/dist/e2e/harness/MockExternalBridge.js.map +1 -1
  60. package/dist/e2e/harness/NativeLocalDeploymentManager.d.ts.map +1 -1
  61. package/dist/e2e/harness/NativeLocalDeploymentManager.js +2 -1
  62. package/dist/e2e/harness/NativeLocalDeploymentManager.js.map +1 -1
  63. package/dist/e2e/harness/SnapshotHelper.d.ts.map +1 -1
  64. package/dist/e2e/harness/SnapshotHelper.js +3 -5
  65. package/dist/e2e/harness/SnapshotHelper.js.map +1 -1
  66. package/dist/e2e/harness/TestHelpers.js +1 -2
  67. package/dist/e2e/harness/TestHelpers.js.map +1 -1
  68. package/dist/e2e/harness/TestRebalancer.d.ts +5 -1
  69. package/dist/e2e/harness/TestRebalancer.d.ts.map +1 -1
  70. package/dist/e2e/harness/TestRebalancer.js +14 -4
  71. package/dist/e2e/harness/TestRebalancer.js.map +1 -1
  72. package/dist/e2e/relayer-denylist.e2e-test.d.ts +2 -0
  73. package/dist/e2e/relayer-denylist.e2e-test.d.ts.map +1 -0
  74. package/dist/e2e/relayer-denylist.e2e-test.js +388 -0
  75. package/dist/e2e/relayer-denylist.e2e-test.js.map +1 -0
  76. package/dist/factories/RebalancerContextFactory.d.ts.map +1 -1
  77. package/dist/factories/RebalancerContextFactory.js +61 -31
  78. package/dist/factories/RebalancerContextFactory.js.map +1 -1
  79. package/dist/factories/RebalancerContextFactory.test.js +336 -5
  80. package/dist/factories/RebalancerContextFactory.test.js.map +1 -1
  81. package/dist/index.d.ts +2 -2
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +1 -1
  84. package/dist/index.js.map +1 -1
  85. package/dist/interfaces/IRebalancer.d.ts +4 -2
  86. package/dist/interfaces/IRebalancer.d.ts.map +1 -1
  87. package/dist/metrics/Metrics.d.ts.map +1 -1
  88. package/dist/metrics/Metrics.js +1 -1
  89. package/dist/metrics/Metrics.js.map +1 -1
  90. package/dist/metrics/PriceGetter.d.ts +1 -0
  91. package/dist/metrics/PriceGetter.d.ts.map +1 -1
  92. package/dist/metrics/PriceGetter.js +14 -4
  93. package/dist/metrics/PriceGetter.js.map +1 -1
  94. package/dist/metrics/PriceGetter.test.d.ts +2 -0
  95. package/dist/metrics/PriceGetter.test.d.ts.map +1 -0
  96. package/dist/metrics/PriceGetter.test.js +84 -0
  97. package/dist/metrics/PriceGetter.test.js.map +1 -0
  98. package/dist/metrics/scripts/metrics.d.ts +4 -4
  99. package/dist/monitor/Monitor.d.ts +1 -0
  100. package/dist/monitor/Monitor.d.ts.map +1 -1
  101. package/dist/monitor/Monitor.js +55 -46
  102. package/dist/monitor/Monitor.js.map +1 -1
  103. package/dist/monitor/Monitor.test.d.ts +2 -0
  104. package/dist/monitor/Monitor.test.d.ts.map +1 -0
  105. package/dist/monitor/Monitor.test.js +346 -0
  106. package/dist/monitor/Monitor.test.js.map +1 -0
  107. package/dist/service.d.ts +1 -1
  108. package/dist/service.d.ts.map +1 -1
  109. package/dist/service.js +9 -5
  110. package/dist/service.js.map +1 -1
  111. package/dist/strategy/BaseStrategy.d.ts.map +1 -1
  112. package/dist/strategy/BaseStrategy.js +13 -11
  113. package/dist/strategy/BaseStrategy.js.map +1 -1
  114. package/dist/strategy/CollateralDeficitStrategy.d.ts.map +1 -1
  115. package/dist/strategy/CollateralDeficitStrategy.js +2 -2
  116. package/dist/strategy/CollateralDeficitStrategy.js.map +1 -1
  117. package/dist/strategy/CompositeStrategy.d.ts.map +1 -1
  118. package/dist/strategy/CompositeStrategy.js.map +1 -1
  119. package/dist/strategy/CompositeStrategy.test.js.map +1 -1
  120. package/dist/strategy/MinAmountStrategy.d.ts +1 -0
  121. package/dist/strategy/MinAmountStrategy.d.ts.map +1 -1
  122. package/dist/strategy/MinAmountStrategy.js +12 -8
  123. package/dist/strategy/MinAmountStrategy.js.map +1 -1
  124. package/dist/strategy/MinAmountStrategy.test.js +189 -2
  125. package/dist/strategy/MinAmountStrategy.test.js.map +1 -1
  126. package/dist/strategy/StrategyFactory.d.ts.map +1 -1
  127. package/dist/strategy/StrategyFactory.js.map +1 -1
  128. package/dist/strategy/WeightedStrategy.d.ts.map +1 -1
  129. package/dist/test/helpers.d.ts +11 -3
  130. package/dist/test/helpers.d.ts.map +1 -1
  131. package/dist/test/helpers.js +9 -11
  132. package/dist/test/helpers.js.map +1 -1
  133. package/dist/test/lifiMocks.d.ts +6 -6
  134. package/dist/test/lifiMocks.d.ts.map +1 -1
  135. package/dist/test/lifiMocks.js +5 -2
  136. package/dist/test/lifiMocks.js.map +1 -1
  137. package/dist/test/pendingTransferMiddlewareMocks.d.ts +17 -0
  138. package/dist/test/pendingTransferMiddlewareMocks.d.ts.map +1 -0
  139. package/dist/test/pendingTransferMiddlewareMocks.js +43 -0
  140. package/dist/test/pendingTransferMiddlewareMocks.js.map +1 -0
  141. package/dist/tracking/ActionTracker.d.ts +4 -0
  142. package/dist/tracking/ActionTracker.d.ts.map +1 -1
  143. package/dist/tracking/ActionTracker.js +76 -9
  144. package/dist/tracking/ActionTracker.js.map +1 -1
  145. package/dist/tracking/ActionTracker.test.js +418 -1
  146. package/dist/tracking/ActionTracker.test.js.map +1 -1
  147. package/dist/tracking/IActionTracker.d.ts +4 -2
  148. package/dist/tracking/IActionTracker.d.ts.map +1 -1
  149. package/dist/tracking/InflightContextAdapter.d.ts.map +1 -1
  150. package/dist/tracking/InflightContextAdapter.js.map +1 -1
  151. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.d.ts +64 -0
  152. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.d.ts.map +1 -0
  153. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.js +474 -0
  154. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.js.map +1 -0
  155. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.test.d.ts +2 -0
  156. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.test.d.ts.map +1 -0
  157. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.test.js +1168 -0
  158. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.test.js.map +1 -0
  159. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.d.ts +61 -0
  160. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.d.ts.map +1 -0
  161. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.js +160 -0
  162. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.js.map +1 -0
  163. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.test.d.ts +2 -0
  164. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.test.d.ts.map +1 -0
  165. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.test.js +369 -0
  166. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.test.js.map +1 -0
  167. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.d.ts +60 -0
  168. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.d.ts.map +1 -0
  169. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.js +288 -0
  170. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.js.map +1 -0
  171. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.test.d.ts +2 -0
  172. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.test.d.ts.map +1 -0
  173. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.test.js +606 -0
  174. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.test.js.map +1 -0
  175. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.d.ts +25 -0
  176. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.d.ts.map +1 -0
  177. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.js +41 -0
  178. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.js.map +1 -0
  179. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.test.d.ts +2 -0
  180. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.test.d.ts.map +1 -0
  181. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.test.js +141 -0
  182. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.test.js.map +1 -0
  183. package/dist/tracking/pendingTransferMiddlewares/matchBody.d.ts +12 -0
  184. package/dist/tracking/pendingTransferMiddlewares/matchBody.d.ts.map +1 -0
  185. package/dist/tracking/pendingTransferMiddlewares/matchBody.js +29 -0
  186. package/dist/tracking/pendingTransferMiddlewares/matchBody.js.map +1 -0
  187. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.d.ts +59 -0
  188. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.d.ts.map +1 -0
  189. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.js +661 -0
  190. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.js.map +1 -0
  191. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.test.d.ts +2 -0
  192. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.test.d.ts.map +1 -0
  193. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.test.js +1093 -0
  194. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.test.js.map +1 -0
  195. package/dist/tracking/pendingTransferMiddlewares/types.d.ts +2 -0
  196. package/dist/tracking/pendingTransferMiddlewares/types.d.ts.map +1 -0
  197. package/dist/tracking/pendingTransferMiddlewares/types.js +4 -0
  198. package/dist/tracking/pendingTransferMiddlewares/types.js.map +1 -0
  199. package/dist/tracking/store/InMemoryStore.d.ts.map +1 -1
  200. package/dist/utils/ExplorerClient.d.ts +1 -0
  201. package/dist/utils/ExplorerClient.d.ts.map +1 -1
  202. package/dist/utils/ExplorerClient.js +8 -3
  203. package/dist/utils/ExplorerClient.js.map +1 -1
  204. package/dist/utils/ExplorerClient.test.js +25 -0
  205. package/dist/utils/ExplorerClient.test.js.map +1 -1
  206. package/dist/utils/balanceUtils.d.ts +7 -1
  207. package/dist/utils/balanceUtils.d.ts.map +1 -1
  208. package/dist/utils/balanceUtils.js +39 -1
  209. package/dist/utils/balanceUtils.js.map +1 -1
  210. package/dist/utils/balanceUtils.test.js +55 -1
  211. package/dist/utils/balanceUtils.test.js.map +1 -1
  212. package/dist/utils/blockTag.d.ts +5 -4
  213. package/dist/utils/blockTag.d.ts.map +1 -1
  214. package/dist/utils/blockTag.js +26 -10
  215. package/dist/utils/blockTag.js.map +1 -1
  216. package/dist/utils/blockTag.test.d.ts +2 -0
  217. package/dist/utils/blockTag.test.d.ts.map +1 -0
  218. package/dist/utils/blockTag.test.js +132 -0
  219. package/dist/utils/blockTag.test.js.map +1 -0
  220. package/dist/utils/bridgeUtils.js.map +1 -1
  221. package/dist/utils/errors.d.ts.map +1 -1
  222. package/dist/utils/gasEstimation.d.ts +2 -1
  223. package/dist/utils/gasEstimation.d.ts.map +1 -1
  224. package/dist/utils/gasEstimation.js +16 -13
  225. package/dist/utils/gasEstimation.js.map +1 -1
  226. package/dist/utils/gasEstimation.test.d.ts +2 -0
  227. package/dist/utils/gasEstimation.test.d.ts.map +1 -0
  228. package/dist/utils/gasEstimation.test.js +104 -0
  229. package/dist/utils/gasEstimation.test.js.map +1 -0
  230. package/dist/utils/tokenUtils.d.ts.map +1 -1
  231. package/dist/utils/tokenUtils.js +5 -2
  232. package/dist/utils/tokenUtils.js.map +1 -1
  233. package/package.json +23 -22
  234. package/src/bridges/LiFiBridge.test.ts +270 -0
  235. package/src/bridges/LiFiBridge.ts +90 -71
  236. package/src/config/RebalancerConfig.test.ts +315 -1
  237. package/src/config/RebalancerConfig.ts +6 -3
  238. package/src/config/types.ts +98 -8
  239. package/src/core/InventoryRebalancer.test.ts +1092 -38
  240. package/src/core/InventoryRebalancer.ts +584 -351
  241. package/src/core/Rebalancer.test.ts +84 -0
  242. package/src/core/Rebalancer.ts +22 -6
  243. package/src/core/RebalancerOrchestrator.test.ts +0 -1
  244. package/src/core/RebalancerService.test.ts +35 -0
  245. package/src/core/RebalancerService.ts +3 -2
  246. package/src/e2e/harness/BaseLocalDeploymentManager.ts +3 -1
  247. package/src/e2e/harness/LocalProvider.test.ts +16 -0
  248. package/src/e2e/harness/LocalProvider.ts +10 -0
  249. package/src/e2e/harness/NativeLocalDeploymentManager.ts +2 -1
  250. package/src/e2e/harness/SnapshotHelper.ts +2 -5
  251. package/src/e2e/harness/TestHelpers.ts +3 -3
  252. package/src/e2e/harness/TestRebalancer.ts +21 -1
  253. package/src/e2e/relayer-denylist.e2e-test.ts +558 -0
  254. package/src/factories/RebalancerContextFactory.test.ts +489 -8
  255. package/src/factories/RebalancerContextFactory.ts +86 -36
  256. package/src/index.ts +2 -0
  257. package/src/interfaces/IRebalancer.ts +4 -1
  258. package/src/metrics/Metrics.ts +1 -0
  259. package/src/metrics/PriceGetter.test.ts +113 -0
  260. package/src/metrics/PriceGetter.ts +18 -3
  261. package/src/monitor/Monitor.test.ts +408 -0
  262. package/src/monitor/Monitor.ts +123 -97
  263. package/src/service.ts +14 -8
  264. package/src/strategy/BaseStrategy.ts +18 -15
  265. package/src/strategy/CollateralDeficitStrategy.ts +4 -3
  266. package/src/strategy/MinAmountStrategy.test.ts +238 -2
  267. package/src/strategy/MinAmountStrategy.ts +29 -17
  268. package/src/test/helpers.ts +13 -12
  269. package/src/test/lifiMocks.ts +5 -2
  270. package/src/test/pendingTransferMiddlewareMocks.ts +65 -0
  271. package/src/tracking/ActionTracker.test.ts +635 -3
  272. package/src/tracking/ActionTracker.ts +92 -12
  273. package/src/tracking/IActionTracker.ts +4 -2
  274. package/src/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.test.ts +1693 -0
  275. package/src/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.ts +784 -0
  276. package/src/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.test.ts +554 -0
  277. package/src/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.ts +233 -0
  278. package/src/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.test.ts +894 -0
  279. package/src/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.ts +404 -0
  280. package/src/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.test.ts +205 -0
  281. package/src/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.ts +89 -0
  282. package/src/tracking/pendingTransferMiddlewares/fixtures/mainnet-denylist.json +3556 -0
  283. package/src/tracking/pendingTransferMiddlewares/matchBody.ts +43 -0
  284. package/src/tracking/pendingTransferMiddlewares/relayerDenylistRules.test.ts +1220 -0
  285. package/src/tracking/pendingTransferMiddlewares/relayerDenylistRules.ts +934 -0
  286. package/src/tracking/pendingTransferMiddlewares/types.ts +3 -0
  287. package/src/utils/ExplorerClient.test.ts +42 -0
  288. package/src/utils/ExplorerClient.ts +14 -3
  289. package/src/utils/balanceUtils.test.ts +87 -1
  290. package/src/utils/balanceUtils.ts +73 -2
  291. package/src/utils/blockTag.test.ts +173 -0
  292. package/src/utils/blockTag.ts +35 -15
  293. package/src/utils/gasEstimation.test.ts +192 -0
  294. package/src/utils/gasEstimation.ts +17 -11
  295. package/src/utils/tokenUtils.ts +5 -2
@@ -12,6 +12,7 @@ import {
12
12
  DEFAULT_INTENT_TTL_MS,
13
13
  ExecutionType,
14
14
  ExternalBridgeType,
15
+ PendingTransferMiddlewareType,
15
16
  type RebalancerConfigFileInput,
16
17
  RebalancerMinAmountType,
17
18
  RebalancerStrategyOptions,
@@ -104,6 +105,7 @@ describe('RebalancerConfig', () => {
104
105
  intentTTL: DEFAULT_INTENT_TTL_MS,
105
106
  inventorySigners: undefined,
106
107
  externalBridges: undefined,
108
+ pendingTransferMiddlewares: undefined,
107
109
  });
108
110
  });
109
111
 
@@ -124,7 +126,18 @@ describe('RebalancerConfig', () => {
124
126
  writeYamlOrJson(TEST_CONFIG_PATH, data);
125
127
 
126
128
  expect(() => RebalancerConfig.load(TEST_CONFIG_PATH)).to.throw(
127
- 'Validation error: Required at "warpRouteId"',
129
+ 'Invalid input: expected string, received undefined',
130
+ );
131
+ });
132
+
133
+ it('reports an invalid bridge before running cross-field rules', () => {
134
+ const strategy = getStrategyArray(data)[0];
135
+ strategy.chains.chain1.bridge = 'bridge';
136
+ data.strategy = strategy;
137
+ writeYamlOrJson(TEST_CONFIG_PATH, data);
138
+
139
+ expect(() => RebalancerConfig.load(TEST_CONFIG_PATH)).to.throw(
140
+ 'Invalid string: must match pattern',
128
141
  );
129
142
  });
130
143
 
@@ -572,6 +585,141 @@ describe('RebalancerConfig', () => {
572
585
  });
573
586
  });
574
587
 
588
+ describe('Tron inventorySigners validation', () => {
589
+ const TEST_CONFIG_PATH_TRON = join(tmpdir(), 'rebalancer-tron-test.yaml');
590
+
591
+ afterEach(() => {
592
+ rmSync(TEST_CONFIG_PATH_TRON, { force: true });
593
+ });
594
+
595
+ it('should reject Tron T-prefix base58 address in inventorySigners', () => {
596
+ const data: RebalancerConfigFileInput = {
597
+ warpRouteId: 'test-tron-route',
598
+ strategy: [
599
+ {
600
+ rebalanceStrategy: RebalancerStrategyOptions.Weighted,
601
+ chains: {
602
+ tron: {
603
+ weighted: { weight: 50, tolerance: 5 },
604
+ executionType: ExecutionType.Inventory,
605
+ externalBridge: ExternalBridgeType.LiFi,
606
+ },
607
+ ethereum: {
608
+ weighted: { weight: 50, tolerance: 5 },
609
+ executionType: ExecutionType.Inventory,
610
+ externalBridge: ExternalBridgeType.LiFi,
611
+ },
612
+ },
613
+ },
614
+ ],
615
+ inventorySigners: {
616
+ tron: {
617
+ address: 'TJRabPrwbZy45sbavfcjinPJC18kjpRTv8',
618
+ key: '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
619
+ },
620
+ ethereum: {
621
+ address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
622
+ key: '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
623
+ },
624
+ },
625
+ externalBridges: {
626
+ lifi: {
627
+ integrator: 'test-app',
628
+ },
629
+ },
630
+ };
631
+
632
+ writeYamlOrJson(TEST_CONFIG_PATH_TRON, data);
633
+ expect(() => RebalancerConfig.load(TEST_CONFIG_PATH_TRON)).to.throw(
634
+ /must be a valid 0x hex address/,
635
+ );
636
+ });
637
+
638
+ it('should accept valid 0x hex address for Tron inventorySigners', () => {
639
+ const data: RebalancerConfigFileInput = {
640
+ warpRouteId: 'test-tron-route',
641
+ strategy: [
642
+ {
643
+ rebalanceStrategy: RebalancerStrategyOptions.Weighted,
644
+ chains: {
645
+ tron: {
646
+ weighted: { weight: 50, tolerance: 5 },
647
+ executionType: ExecutionType.Inventory,
648
+ externalBridge: ExternalBridgeType.LiFi,
649
+ },
650
+ ethereum: {
651
+ weighted: { weight: 50, tolerance: 5 },
652
+ executionType: ExecutionType.Inventory,
653
+ externalBridge: ExternalBridgeType.LiFi,
654
+ },
655
+ },
656
+ },
657
+ ],
658
+ inventorySigners: {
659
+ tron: {
660
+ address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
661
+ key: '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
662
+ },
663
+ ethereum: {
664
+ address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
665
+ key: '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
666
+ },
667
+ },
668
+ externalBridges: {
669
+ lifi: {
670
+ integrator: 'test-app',
671
+ },
672
+ },
673
+ };
674
+
675
+ writeYamlOrJson(TEST_CONFIG_PATH_TRON, data);
676
+ expect(() => RebalancerConfig.load(TEST_CONFIG_PATH_TRON)).to.not.throw();
677
+ });
678
+
679
+ it('should reject invalid Tron address in inventorySigners', () => {
680
+ const data: RebalancerConfigFileInput = {
681
+ warpRouteId: 'test-tron-route',
682
+ strategy: [
683
+ {
684
+ rebalanceStrategy: RebalancerStrategyOptions.Weighted,
685
+ chains: {
686
+ tron: {
687
+ weighted: { weight: 50, tolerance: 5 },
688
+ executionType: ExecutionType.Inventory,
689
+ externalBridge: ExternalBridgeType.LiFi,
690
+ },
691
+ ethereum: {
692
+ weighted: { weight: 50, tolerance: 5 },
693
+ executionType: ExecutionType.Inventory,
694
+ externalBridge: ExternalBridgeType.LiFi,
695
+ },
696
+ },
697
+ },
698
+ ],
699
+ inventorySigners: {
700
+ tron: {
701
+ address: 'INVALID_TRON_ADDRESS',
702
+ key: '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
703
+ },
704
+ ethereum: {
705
+ address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
706
+ key: '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
707
+ },
708
+ },
709
+ externalBridges: {
710
+ lifi: {
711
+ integrator: 'test-app',
712
+ },
713
+ },
714
+ };
715
+
716
+ writeYamlOrJson(TEST_CONFIG_PATH_TRON, data);
717
+ expect(() => RebalancerConfig.load(TEST_CONFIG_PATH_TRON)).to.throw(
718
+ /must be a valid 0x hex address/,
719
+ );
720
+ });
721
+ });
722
+
575
723
  describe('per-chain bridge configuration', () => {
576
724
  const TEST_CONFIG_PATH_BRIDGE = join(tmpdir(), 'rebalancer-bridge-test.yaml');
577
725
 
@@ -919,3 +1067,169 @@ describe('getAllBridges', () => {
919
1067
  expect(result).to.deep.equal([BRIDGE_A]);
920
1068
  });
921
1069
  });
1070
+
1071
+ describe('pendingTransferMiddlewares configuration', () => {
1072
+ const TEST_CONFIG_PATH_MIDDLEWARES = join(
1073
+ tmpdir(),
1074
+ 'rebalancer-pending-transfer-middlewares-test.yaml',
1075
+ );
1076
+
1077
+ function writeConfig(pendingTransferMiddlewares: unknown): void {
1078
+ writeYamlOrJson(TEST_CONFIG_PATH_MIDDLEWARES, {
1079
+ warpRouteId: 'USDC/radix',
1080
+ strategy: [
1081
+ {
1082
+ rebalanceStrategy: RebalancerStrategyOptions.Weighted,
1083
+ chains: {
1084
+ ethereum: {
1085
+ weighted: { weight: 50, tolerance: 10 },
1086
+ bridge: ethers.constants.AddressZero,
1087
+ bridgeLockTime: 1800,
1088
+ },
1089
+ arbitrum: {
1090
+ weighted: { weight: 50, tolerance: 10 },
1091
+ bridge: ethers.constants.AddressZero,
1092
+ bridgeLockTime: 1800,
1093
+ },
1094
+ },
1095
+ },
1096
+ ],
1097
+ pendingTransferMiddlewares,
1098
+ });
1099
+ }
1100
+
1101
+ afterEach(() => {
1102
+ rmSync(TEST_CONFIG_PATH_MIDDLEWARES, { force: true });
1103
+ });
1104
+
1105
+ it('leaves middlewares unset when the field is absent', () => {
1106
+ writeConfig(undefined);
1107
+
1108
+ expect(
1109
+ RebalancerConfig.load(TEST_CONFIG_PATH_MIDDLEWARES)
1110
+ .pendingTransferMiddlewares,
1111
+ ).to.equal(undefined);
1112
+ });
1113
+
1114
+ it('loads middlewares in order, with defaults and duplicate types', () => {
1115
+ writeConfig([
1116
+ {
1117
+ type: 'relayerDenylist',
1118
+ url: 'https://raw.githubusercontent.com/hyperlane-xyz/hyperlane-registry/main/deployments/relayer/mainnet-denylist.json',
1119
+ },
1120
+ { type: 'blacklistIsm', refreshIntervalSecs: 300 },
1121
+ {
1122
+ type: 'relayerDenylist',
1123
+ url: 'file:///etc/rebalancer/denylist.json',
1124
+ refreshIntervalSecs: 30,
1125
+ },
1126
+ { type: 'blacklistIsm' },
1127
+ ]);
1128
+
1129
+ expect(
1130
+ RebalancerConfig.load(TEST_CONFIG_PATH_MIDDLEWARES)
1131
+ .pendingTransferMiddlewares,
1132
+ ).to.deep.equal([
1133
+ {
1134
+ type: PendingTransferMiddlewareType.RelayerDenylist,
1135
+ url: 'https://raw.githubusercontent.com/hyperlane-xyz/hyperlane-registry/main/deployments/relayer/mainnet-denylist.json',
1136
+ refreshIntervalSecs: 60,
1137
+ },
1138
+ {
1139
+ type: PendingTransferMiddlewareType.BlacklistIsm,
1140
+ refreshIntervalSecs: 300,
1141
+ },
1142
+ {
1143
+ type: PendingTransferMiddlewareType.RelayerDenylist,
1144
+ url: 'file:///etc/rebalancer/denylist.json',
1145
+ refreshIntervalSecs: 30,
1146
+ },
1147
+ {
1148
+ type: PendingTransferMiddlewareType.BlacklistIsm,
1149
+ refreshIntervalSecs: 600,
1150
+ },
1151
+ ]);
1152
+ });
1153
+
1154
+ interface InvalidMiddlewareCase {
1155
+ name: string;
1156
+ middlewares: unknown;
1157
+ error: RegExp;
1158
+ }
1159
+ const invalidCases: InvalidMiddlewareCase[] = [
1160
+ {
1161
+ name: 'an unknown type',
1162
+ middlewares: [{ type: 'ageCap' }],
1163
+ error: /pendingTransferMiddlewares/,
1164
+ },
1165
+ {
1166
+ name: 'a denylist without url',
1167
+ middlewares: [{ type: 'relayerDenylist' }],
1168
+ error: /url/,
1169
+ },
1170
+ {
1171
+ name: 'a denylist url with an unsupported protocol',
1172
+ middlewares: [
1173
+ { type: 'relayerDenylist', url: 'ftp://example.com/x.json' },
1174
+ ],
1175
+ error: /url must be an https:\/\/, http:\/\/ or file:\/\/ URL/,
1176
+ },
1177
+ {
1178
+ name: 'a denylist url with credentials',
1179
+ middlewares: [
1180
+ {
1181
+ type: 'relayerDenylist',
1182
+ url: 'https://user:hunter2@example.com/x.json',
1183
+ },
1184
+ ],
1185
+ error: /url must not contain credentials/,
1186
+ },
1187
+ {
1188
+ name: 'a non-positive refresh interval',
1189
+ middlewares: [
1190
+ {
1191
+ type: 'relayerDenylist',
1192
+ url: 'https://example.com/x.json',
1193
+ refreshIntervalSecs: 0,
1194
+ },
1195
+ ],
1196
+ error: /refreshIntervalSecs/,
1197
+ },
1198
+ {
1199
+ name: 'a non-integer blacklistIsm refresh interval',
1200
+ middlewares: [{ type: 'blacklistIsm', refreshIntervalSecs: 1.5 }],
1201
+ error: /refreshIntervalSecs/,
1202
+ },
1203
+ {
1204
+ name: 'a misspelled denylist key',
1205
+ middlewares: [
1206
+ {
1207
+ type: 'relayerDenylist',
1208
+ url: 'https://example.com/x.json',
1209
+ refreshIntervalSec: 30,
1210
+ },
1211
+ ],
1212
+ error: /Unrecognized key: "refreshIntervalSec"/,
1213
+ },
1214
+ {
1215
+ name: 'a misspelled blacklistIsm key',
1216
+ middlewares: [{ type: 'blacklistIsm', refreshIntervalSec: 300 }],
1217
+ error: /Unrecognized key: "refreshIntervalSec"/,
1218
+ },
1219
+ {
1220
+ name: 'a non-array value',
1221
+ middlewares: { blacklistIsm: {} },
1222
+ error: /pendingTransferMiddlewares/,
1223
+ },
1224
+ ];
1225
+ for (const c of invalidCases) {
1226
+ it(`rejects ${c.name}`, () => {
1227
+ writeConfig(c.middlewares);
1228
+
1229
+ expect(() => RebalancerConfig.load(TEST_CONFIG_PATH_MIDDLEWARES))
1230
+ .to.throw(c.error)
1231
+ .with.property('message')
1232
+ .that.does.not.include('hunter2');
1233
+ });
1234
+ }
1235
+ });
@@ -1,5 +1,4 @@
1
- import type { z } from 'zod';
2
- import { fromZodError } from 'zod-validation-error';
1
+ import { z } from 'zod';
3
2
 
4
3
  import type { ProtocolType } from '@humanode-hyperlane/utils';
5
4
  import { readYamlOrJson } from '@humanode-hyperlane/utils/fs';
@@ -7,6 +6,7 @@ import { readYamlOrJson } from '@humanode-hyperlane/utils/fs';
7
6
  import type { InventorySignerConfig } from '../core/InventoryRebalancer.js';
8
7
  import {
9
8
  type ExternalBridgesConfigSchema,
9
+ type PendingTransferMiddlewareConfig,
10
10
  type RebalancerConfigFileInput,
11
11
  RebalancerConfigSchema,
12
12
  type StrategyConfig,
@@ -24,6 +24,7 @@ export class RebalancerConfig {
24
24
  Record<ProtocolType, InventorySignerConfig>
25
25
  >,
26
26
  public readonly externalBridges?: ExternalBridgesConfig,
27
+ public readonly pendingTransferMiddlewares?: PendingTransferMiddlewareConfig[],
27
28
  ) {}
28
29
 
29
30
  /**
@@ -36,7 +37,7 @@ export class RebalancerConfig {
36
37
  const validationResult = RebalancerConfigSchema.safeParse(config);
37
38
 
38
39
  if (!validationResult.success) {
39
- throw new Error(fromZodError(validationResult.error).message);
40
+ throw new Error(z.prettifyError(validationResult.error));
40
41
  }
41
42
 
42
43
  const {
@@ -45,6 +46,7 @@ export class RebalancerConfig {
45
46
  intentTTL,
46
47
  inventorySigners,
47
48
  externalBridges,
49
+ pendingTransferMiddlewares,
48
50
  } = validationResult.data;
49
51
 
50
52
  const chainNames = getStrategyChainNames(strategy);
@@ -58,6 +60,7 @@ export class RebalancerConfig {
58
60
  intentTTL,
59
61
  inventorySigners,
60
62
  externalBridges,
63
+ pendingTransferMiddlewares,
61
64
  );
62
65
  }
63
66
  }
@@ -1,6 +1,8 @@
1
1
  import { z } from 'zod';
2
2
  import { ProtocolType, isAddressEvm } from '@humanode-hyperlane/utils';
3
3
 
4
+ const SEALEVEL_ADDRESS_REGEX = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
5
+
4
6
  export enum RebalancerStrategyOptions {
5
7
  Weighted = 'weighted',
6
8
  MinAmount = 'minAmount',
@@ -41,7 +43,7 @@ export enum ExternalBridgeType {
41
43
  export const RebalancerMinAmountConfigSchema = z.object({
42
44
  min: z.string().or(z.number()),
43
45
  target: z.string().or(z.number()),
44
- type: z.nativeEnum(RebalancerMinAmountType),
46
+ type: z.enum(RebalancerMinAmountType),
45
47
  });
46
48
 
47
49
  const RebalancerBridgeConfigSchema = z.object({
@@ -49,8 +51,8 @@ const RebalancerBridgeConfigSchema = z.object({
49
51
  .string()
50
52
  .regex(/0x[a-fA-F0-9]{40}/)
51
53
  .optional(),
52
- executionType: z.nativeEnum(ExecutionType).optional(),
53
- externalBridge: z.nativeEnum(ExternalBridgeType).optional(),
54
+ executionType: z.enum(ExecutionType).optional(),
55
+ externalBridge: z.enum(ExternalBridgeType).optional(),
54
56
  bridgeMinAcceptedAmount: z.string().or(z.number()).optional(),
55
57
  bridgeLockTime: z
56
58
  .number()
@@ -62,7 +64,10 @@ const RebalancerBridgeConfigSchema = z.object({
62
64
  export const RebalancerBaseChainConfigSchema =
63
65
  RebalancerBridgeConfigSchema.extend({
64
66
  override: z
65
- .record(z.string(), RebalancerBridgeConfigSchema.partial().passthrough())
67
+ .record(
68
+ z.string(),
69
+ z.looseObject(RebalancerBridgeConfigSchema.partial().shape),
70
+ )
66
71
  .optional(),
67
72
  });
68
73
 
@@ -130,13 +135,82 @@ export const ExternalBridgesConfigSchema = z.object({
130
135
  lifi: LiFiBridgeConfigSchema.optional(),
131
136
  });
132
137
 
138
+ export const PendingTransferMiddlewareType = {
139
+ RelayerDenylist: 'relayerDenylist',
140
+ BlacklistIsm: 'blacklistIsm',
141
+ } as const;
142
+
143
+ export type PendingTransferMiddlewareType =
144
+ (typeof PendingTransferMiddlewareType)[keyof typeof PendingTransferMiddlewareType];
145
+
146
+ const DEFAULT_RELAYER_DENYLIST_REFRESH_INTERVAL_IN_SECONDS = 60;
147
+ const DEFAULT_BLACKLIST_ISM_REFRESH_INTERVAL_IN_SECONDS = 600;
148
+
149
+ const RELAYER_DENYLIST_URL_PROTOCOLS = ['https:', 'http:', 'file:'];
150
+
151
+ const RelayerDenylistMiddlewareConfigSchema = z
152
+ .strictObject({
153
+ type: z.literal(PendingTransferMiddlewareType.RelayerDenylist),
154
+ url: z
155
+ .string()
156
+ .refine(
157
+ (url) =>
158
+ URL.canParse(url) &&
159
+ RELAYER_DENYLIST_URL_PROTOCOLS.includes(new URL(url).protocol),
160
+ 'url must be an https://, http:// or file:// URL',
161
+ )
162
+ .refine(
163
+ (url) =>
164
+ !URL.canParse(url) ||
165
+ (new URL(url).username === '' && new URL(url).password === ''),
166
+ 'url must not contain credentials',
167
+ )
168
+ .describe(
169
+ 'https://, http:// or file:// URL of the relayer denylist JSON.',
170
+ ),
171
+ refreshIntervalSecs: z
172
+ .number()
173
+ .int()
174
+ .positive()
175
+ .default(DEFAULT_RELAYER_DENYLIST_REFRESH_INTERVAL_IN_SECONDS)
176
+ .describe('Seconds between denylist re-fetches.'),
177
+ })
178
+ .describe(
179
+ 'Ignores messages the relayer runtime denylist blocks. Startup retries a failed fetch twice and fails if every attempt fails or the file is invalid. Only messageId-only rules apply to rebalance messages: deny one only after its funds settled another way, since a denied rebalance stops counting as incoming collateral until its rule is removed or expires.',
180
+ );
181
+
182
+ const BlacklistIsmMiddlewareConfigSchema = z
183
+ .strictObject({
184
+ type: z.literal(PendingTransferMiddlewareType.BlacklistIsm),
185
+ refreshIntervalSecs: z
186
+ .number()
187
+ .int()
188
+ .positive()
189
+ .default(DEFAULT_BLACKLIST_ISM_REFRESH_INTERVAL_IN_SECONDS)
190
+ .describe(
191
+ 'Minimum seconds between refreshes, which re-resolve the router ISMs and re-read their BlacklistIsm ids.',
192
+ ),
193
+ })
194
+ .describe(
195
+ 'Ignores messages a BlacklistIsm in the destination router ISM rejects. Router ISMs are re-resolved on each refresh, and a destination whose refresh fails keeps its previous BlacklistIsms and ids; startup fails if no route pair has a BlacklistIsm that can block.',
196
+ );
197
+
198
+ const PendingTransferMiddlewareConfigSchema = z.discriminatedUnion('type', [
199
+ RelayerDenylistMiddlewareConfigSchema,
200
+ BlacklistIsmMiddlewareConfigSchema,
201
+ ]);
202
+
203
+ export type PendingTransferMiddlewareConfig = z.infer<
204
+ typeof PendingTransferMiddlewareConfigSchema
205
+ >;
206
+
133
207
  export const RebalancerConfigSchema = z
134
208
  .object({
135
209
  warpRouteId: z.string(),
136
210
  strategy: RebalancerStrategySchema,
137
211
  inventorySigners: z
138
- .record(
139
- z.nativeEnum(ProtocolType),
212
+ .partialRecord(
213
+ z.enum(ProtocolType),
140
214
  z.union([
141
215
  z.object({ address: z.string(), key: z.string().optional() }),
142
216
  z.string().transform((address) => ({ address })),
@@ -144,6 +218,12 @@ export const RebalancerConfigSchema = z
144
218
  )
145
219
  .optional(),
146
220
  externalBridges: ExternalBridgesConfigSchema.optional(),
221
+ pendingTransferMiddlewares: z
222
+ .array(PendingTransferMiddlewareConfigSchema)
223
+ .optional()
224
+ .describe(
225
+ 'Ordered middlewares applied to pending user transfers read from the Explorer; item 0 runs first on the raw result. Omit to disable.',
226
+ ),
147
227
  intentTTL: z
148
228
  .number()
149
229
  .positive()
@@ -153,7 +233,7 @@ export const RebalancerConfigSchema = z
153
233
  )
154
234
  .transform((val) => val * 1_000),
155
235
  })
156
- .superRefine((config, ctx) => {
236
+ .transform((config, ctx) => {
157
237
  // CollateralDeficitStrategy must be first in composite if it is used
158
238
  if (config.strategy.length > 1) {
159
239
  const hasCollateralDeficit = config.strategy.some(
@@ -354,13 +434,21 @@ export const RebalancerConfigSchema = z
354
434
  });
355
435
  }
356
436
  } else if (protocol === ProtocolType.Sealevel) {
357
- if (!/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(signerConfig.address)) {
437
+ if (!SEALEVEL_ADDRESS_REGEX.test(signerConfig.address)) {
358
438
  ctx.addIssue({
359
439
  code: z.ZodIssueCode.custom,
360
440
  message: `inventorySigners.${protocol} must be a valid Solana base58 address (32-44 chars), got: ${signerConfig.address}`,
361
441
  path: ['inventorySigners', protocol],
362
442
  });
363
443
  }
444
+ } else if (protocol === ProtocolType.Tron) {
445
+ if (!isAddressEvm(signerConfig.address)) {
446
+ ctx.addIssue({
447
+ code: z.ZodIssueCode.custom,
448
+ message: `inventorySigners.${protocol} must be a valid 0x hex address, got: ${signerConfig.address}`,
449
+ path: ['inventorySigners', protocol],
450
+ });
451
+ }
364
452
  }
365
453
  // Other protocols: accept any non-empty string (future-proof)
366
454
  }
@@ -428,6 +516,8 @@ export const RebalancerConfigSchema = z
428
516
  }
429
517
  }
430
518
  }
519
+
520
+ return config;
431
521
  });
432
522
 
433
523
  // Define separate types for each strategy config