@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
@@ -0,0 +1,3 @@
1
+ export function normalizeMessageId(messageId: string): string {
2
+ return messageId.toLowerCase();
3
+ }
@@ -160,6 +160,48 @@ describe('ExplorerClient', () => {
160
160
  });
161
161
 
162
162
  describe('getInflightRebalanceActions address encoding', () => {
163
+ it('skips empty bridge discovery and queries freshly for supplied bridges', async () => {
164
+ const client = new ExplorerClient(
165
+ 'https://explorer.test',
166
+ () => ProtocolType.Ethereum,
167
+ );
168
+ const bridges: string[] = [];
169
+ const params = {
170
+ bridges,
171
+ routersByDomain: { 1: '0x1234567890abcdef1234567890abcdef12345678' },
172
+ rebalancerAddress: '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef',
173
+ };
174
+ fetchStub.rejects(new Error('offline'));
175
+ expect(
176
+ await client.getInflightRebalanceActions(params, testLogger),
177
+ ).to.deep.equal([]);
178
+ expect(fetchStub.called).to.be.false;
179
+ bridges.push('0x1234567890abcdef1234567890abcdef12345678');
180
+ await expect(
181
+ client.getInflightRebalanceActions(params, testLogger),
182
+ ).to.be.rejectedWith('offline');
183
+ expect(fetchStub.calledOnce).to.be.true;
184
+ });
185
+
186
+ it('preserves HTTP failures for nonempty bridge discovery', async () => {
187
+ const client = new ExplorerClient(
188
+ 'https://explorer.test',
189
+ () => ProtocolType.Ethereum,
190
+ );
191
+ fetchStub.resolves(new Response('unavailable', { status: 503 }));
192
+ await expect(
193
+ client.getInflightRebalanceActions(
194
+ {
195
+ bridges: ['0x1234567890abcdef1234567890abcdef12345678'],
196
+ routersByDomain: {},
197
+ rebalancerAddress: '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef',
198
+ },
199
+ testLogger,
200
+ ),
201
+ ).to.be.rejectedWith('Explorer query failed: 503');
202
+ expect(fetchStub.calledOnce).to.be.true;
203
+ });
204
+
163
205
  it('encodes mixed EVM+Solana routersByDomain in originTxRecipients', async () => {
164
206
  const evmAddr = '0x5a0e13290ec57f5e9031d01d03c6a40029cc24ea';
165
207
  const solAddr = 'E5rVV8zXwtc4TKGypCJvSBaYbgxa4XaYg5MS6N9QGdeo';
@@ -6,6 +6,10 @@ import {
6
6
  ProtocolType,
7
7
  } from '@humanode-hyperlane/utils';
8
8
 
9
+ const HEX_PREFIX_REGEX = /^0x/i;
10
+
11
+ export const DEFAULT_INFLIGHT_USER_TRANSFER_LIMIT = 100;
12
+
9
13
  export type InflightRebalanceQueryParams = {
10
14
  bridges: string[];
11
15
  routersByDomain: Record<number, string>; // Domain ID → router address (derive routers and domains from this)
@@ -71,9 +75,9 @@ export class ExplorerClient implements IExplorerClient {
71
75
  domain !== undefined ? this.getProtocol(domain) : undefined;
72
76
  if (protocol && !isEVMLike(protocol)) {
73
77
  const hex = addressToByteHexString(addr, protocol);
74
- return hex.replace(/^0x/i, '\\x').toLowerCase();
78
+ return hex.replace(HEX_PREFIX_REGEX, '\\x').toLowerCase();
75
79
  }
76
- return addr.replace(/^0x/i, '\\x').toLowerCase();
80
+ return addr.replace(HEX_PREFIX_REGEX, '\\x').toLowerCase();
77
81
  }
78
82
 
79
83
  /**
@@ -230,7 +234,11 @@ export class ExplorerClient implements IExplorerClient {
230
234
  params: UserTransferQueryParams,
231
235
  logger: Logger,
232
236
  ): Promise<ExplorerMessage[]> {
233
- const { routersByDomain, excludeTxSenders, limit = 100 } = params;
237
+ const {
238
+ routersByDomain,
239
+ excludeTxSenders,
240
+ limit = DEFAULT_INFLIGHT_USER_TRANSFER_LIMIT,
241
+ } = params;
234
242
 
235
243
  // Derive routers and domains from routersByDomain
236
244
  const routerEntries = Object.entries(routersByDomain);
@@ -337,6 +345,9 @@ export class ExplorerClient implements IExplorerClient {
337
345
  limit = 100,
338
346
  } = params;
339
347
 
348
+ // Both sender and recipient must belong to bridges; an empty set cannot match.
349
+ if (bridges.length === 0) return [];
350
+
340
351
  // Derive routers and domains from routersByDomain
341
352
  const routerEntries = Object.entries(routersByDomain);
342
353
  const domains = Object.keys(routersByDomain).map(Number);
@@ -10,7 +10,15 @@ import {
10
10
 
11
11
  import { type MonitorEvent } from '../interfaces/IMonitor.js';
12
12
 
13
- import { getRawBalances } from './balanceUtils.js';
13
+ import {
14
+ alignLocalToCanonical,
15
+ denormalizeToLocal,
16
+ getRawBalances,
17
+ isIdentityScale,
18
+ getTokenScale,
19
+ normalizeConfiguredAmount,
20
+ normalizeToCanonical,
21
+ } from './balanceUtils.js';
14
22
 
15
23
  const testLogger = pino({ level: 'silent' });
16
24
 
@@ -49,6 +57,16 @@ describe('getRawBalances', () => {
49
57
  });
50
58
  });
51
59
 
60
+ it('should normalize bridged supply to canonical units when token has scale', () => {
61
+ token.scale = { numerator: 1, denominator: 1_000_000_000_000 };
62
+ tokenBridgedSupply = 1_000_000_000_000_000_000n;
63
+ event.tokensInfo[0].bridgedSupply = tokenBridgedSupply;
64
+
65
+ expect(getRawBalances(chains, event, testLogger)).to.deep.equal({
66
+ mainnet: 1_000_000n,
67
+ });
68
+ });
69
+
52
70
  it('should return the bridged supply for the token (EvmHypNative)', () => {
53
71
  token.standard = TokenStandard.EvmHypNative;
54
72
 
@@ -77,3 +95,71 @@ describe('getRawBalances', () => {
77
95
  );
78
96
  });
79
97
  });
98
+
99
+ describe('scale helpers', () => {
100
+ const token = {
101
+ decimals: 18,
102
+ scale: { numerator: 1, denominator: 1_000_000_000_000 },
103
+ } as unknown as Token;
104
+
105
+ it('normalizes local amount to canonical amount', () => {
106
+ expect(normalizeToCanonical(1_000_000_000_000_000_000n, token)).to.equal(
107
+ 1_000_000n,
108
+ );
109
+ });
110
+
111
+ it('denormalizes canonical amount to local amount', () => {
112
+ expect(denormalizeToLocal(1_000_000n, token)).to.equal(
113
+ 1_000_000_000_000_000_000n,
114
+ );
115
+ });
116
+
117
+ it('aligns local amount to exact canonical progress', () => {
118
+ expect(alignLocalToCanonical(999_999_999_999n, token)).to.deep.equal({
119
+ localAmount: 0n,
120
+ messageAmount: 0n,
121
+ });
122
+ expect(
123
+ alignLocalToCanonical(1_000_000_000_000_500_000n, token),
124
+ ).to.deep.equal({
125
+ localAmount: 1_000_000_000_000_000_000n,
126
+ messageAmount: 1_000_000n,
127
+ });
128
+ });
129
+
130
+ it('returns identity scale when scale is undefined', () => {
131
+ expect(getTokenScale({} as Token)).to.deep.equal({
132
+ numerator: 1n,
133
+ denominator: 1n,
134
+ });
135
+ });
136
+
137
+ it('treats equivalent ratios as identity scale', () => {
138
+ expect(isIdentityScale({} as Token)).to.be.true;
139
+ expect(
140
+ isIdentityScale({
141
+ scale: { numerator: 1, denominator: 1 },
142
+ } as Token),
143
+ ).to.be.true;
144
+ expect(
145
+ isIdentityScale({
146
+ scale: { numerator: 5, denominator: 5 },
147
+ } as Token),
148
+ ).to.be.true;
149
+ expect(
150
+ isIdentityScale({
151
+ scale: { numerator: 10, denominator: 20 },
152
+ } as Token),
153
+ ).to.be.false;
154
+ });
155
+
156
+ it('normalizes configured amount using token decimals and scale', () => {
157
+ expect(normalizeConfiguredAmount('1', token)).to.equal(1_000_000n);
158
+ });
159
+
160
+ it('normalizes sub-unit local dust to zero canonical amount', () => {
161
+ expect(normalizeConfiguredAmount('0.000000000000000001', token)).to.equal(
162
+ 0n,
163
+ );
164
+ });
165
+ });
@@ -1,12 +1,68 @@
1
1
  import { type Logger } from 'pino';
2
2
 
3
- import { type ChainName } from '@humanode-hyperlane/sdk';
3
+ import {
4
+ type IToken,
5
+ alignLocalAmountToMessage,
6
+ messageAmountFromLocal,
7
+ minLocalAmountForMessage,
8
+ normalizeScale,
9
+ scalesEqual,
10
+ type ChainName,
11
+ type NormalizedScale,
12
+ type ScaleAlignment,
13
+ type Token,
14
+ } from '@humanode-hyperlane/sdk';
15
+ import { toWei } from '@humanode-hyperlane/utils';
4
16
 
5
17
  import { type MonitorEvent } from '../interfaces/IMonitor.js';
6
18
  import { type RawBalances } from '../interfaces/IStrategy.js';
7
19
 
8
20
  import { isCollateralizedTokenEligibleForRebalancing } from './tokenUtils.js';
9
21
 
22
+ export function getTokenScale(token: Token): NormalizedScale {
23
+ return normalizeScale(token.scale);
24
+ }
25
+
26
+ export function isIdentityScale(token: Token): boolean {
27
+ return scalesEqual(token.scale, undefined);
28
+ }
29
+
30
+ export function normalizeToCanonical(
31
+ localAmount: bigint,
32
+ tokenOrScale: Token | IToken | NormalizedScale,
33
+ ): bigint {
34
+ return messageAmountFromLocal(localAmount, getScaleInput(tokenOrScale));
35
+ }
36
+
37
+ export function denormalizeToLocal(
38
+ canonicalAmount: bigint,
39
+ tokenOrScale: Token | IToken | NormalizedScale,
40
+ ): bigint {
41
+ // Use ceil semantics so the returned local amount always produces at least
42
+ // the requested canonical progress once the token router floors on outbound.
43
+ return minLocalAmountForMessage(canonicalAmount, getScaleInput(tokenOrScale));
44
+ }
45
+
46
+ export function alignLocalToCanonical(
47
+ localAmount: bigint,
48
+ tokenOrScale: Token | IToken | NormalizedScale,
49
+ ): ScaleAlignment {
50
+ return alignLocalAmountToMessage(localAmount, getScaleInput(tokenOrScale));
51
+ }
52
+
53
+ export function normalizeConfiguredAmount(
54
+ amount: string | number,
55
+ token: Token,
56
+ ): bigint {
57
+ return normalizeToCanonical(BigInt(toWei(amount, token.decimals)), token);
58
+ }
59
+
60
+ function getScaleInput(tokenOrScale: Token | IToken | NormalizedScale) {
61
+ return 'numerator' in tokenOrScale && 'denominator' in tokenOrScale
62
+ ? tokenOrScale
63
+ : tokenOrScale.scale;
64
+ }
65
+
10
66
  /**
11
67
  * Returns the raw balances required by the strategies from the monitor event
12
68
  * @param chains - The chains that should be included in the raw balances (e.g. the chains in the rebalancer config)
@@ -58,7 +114,22 @@ export function getRawBalances(
58
114
  );
59
115
  }
60
116
 
61
- balances[token.chainName] = bridgedSupply;
117
+ const normalizedBalance = normalizeToCanonical(bridgedSupply, token);
118
+ balances[token.chainName] = normalizedBalance;
119
+
120
+ if (!isIdentityScale(token)) {
121
+ logger.debug(
122
+ {
123
+ context: getRawBalances.name,
124
+ chain: token.chainName,
125
+ tokenSymbol: token.symbol,
126
+ bridgedSupply: bridgedSupply.toString(),
127
+ normalizedBalance: normalizedBalance.toString(),
128
+ scale: getTokenScale(token),
129
+ },
130
+ 'Normalized bridged supply to canonical units',
131
+ );
132
+ }
62
133
  }
63
134
 
64
135
  return balances;
@@ -0,0 +1,173 @@
1
+ import chai, { expect } from 'chai';
2
+ import chaiAsPromised from 'chai-as-promised';
3
+ import { providers } from 'ethers';
4
+ import { pino } from 'pino';
5
+ import Sinon from 'sinon';
6
+
7
+ import {
8
+ type ChainMetadata,
9
+ EthJsonRpcBlockParameterTag,
10
+ MultiProtocolProvider,
11
+ } from '@humanode-hyperlane/sdk';
12
+ import { ProtocolType } from '@humanode-hyperlane/utils';
13
+
14
+ import { getConfirmedBlockTag } from './blockTag.js';
15
+
16
+ chai.use(chaiAsPromised);
17
+
18
+ const tronMetadata: ChainMetadata = {
19
+ protocol: ProtocolType.Tron,
20
+ name: 'tron',
21
+ chainId: 728126428,
22
+ domainId: 728126428,
23
+ rpcUrls: [{ http: 'http://unused' }],
24
+ blocks: { reorgPeriod: 20, confirmations: 1 },
25
+ };
26
+
27
+ const logger = pino({ level: 'silent' });
28
+
29
+ describe('getConfirmedBlockTag', () => {
30
+ let mpp: Sinon.SinonStubbedInstance<MultiProtocolProvider>;
31
+ let provider: Sinon.SinonStubbedInstance<providers.JsonRpcProvider>;
32
+
33
+ beforeEach(() => {
34
+ mpp = Sinon.createStubInstance(MultiProtocolProvider);
35
+ provider = Sinon.createStubInstance(providers.JsonRpcProvider);
36
+ mpp.getChainMetadata.returns(tronMetadata);
37
+ mpp.getEthersV5Provider.returns(provider);
38
+ provider.send.resolves('0x64');
39
+ });
40
+
41
+ afterEach(() => {
42
+ Sinon.restore();
43
+ });
44
+
45
+ it('returns a confirmed block number for a numeric Tron reorg period', async () => {
46
+ expect(await getConfirmedBlockTag(mpp, 'tron')).to.equal(80);
47
+ });
48
+
49
+ it('clamps a confirmed block number to zero', async () => {
50
+ provider.send.resolves('0x0a');
51
+ expect(await getConfirmedBlockTag(mpp, 'tron')).to.equal(0);
52
+ });
53
+
54
+ it('uses the default numeric reorg period for Tron', async () => {
55
+ mpp.getChainMetadata.returns({ ...tronMetadata, blocks: undefined });
56
+ expect(await getConfirmedBlockTag(mpp, 'tron')).to.equal(68);
57
+ });
58
+
59
+ it('returns undefined for a non-EVM-like chain without querying blocks', async () => {
60
+ mpp.getChainMetadata.returns({
61
+ ...tronMetadata,
62
+ protocol: ProtocolType.Sealevel,
63
+ name: 'solana',
64
+ });
65
+
66
+ expect(await getConfirmedBlockTag(mpp, 'solana')).to.equal(undefined);
67
+ expect(mpp.getEthersV5Provider.notCalled).to.equal(true);
68
+ });
69
+
70
+ it('preserves the optional Tron tag for unsupported named reorg periods', async () => {
71
+ const warn = Sinon.stub(logger, 'warn');
72
+ mpp.getChainMetadata.returns({
73
+ ...tronMetadata,
74
+ blocks: {
75
+ reorgPeriod: EthJsonRpcBlockParameterTag.Finalized,
76
+ confirmations: 1,
77
+ },
78
+ });
79
+
80
+ expect(await getConfirmedBlockTag(mpp, 'tron', logger)).to.equal(undefined);
81
+ expect(mpp.getEthersV5Provider.notCalled).to.equal(true);
82
+ expect(warn.calledOnce).to.equal(true);
83
+ expect(warn.firstCall.args[1]).to.include(
84
+ 'Tron does not support named block tags',
85
+ );
86
+ });
87
+
88
+ it('propagates failed numeric Tron block reads', async () => {
89
+ const failure = new Error('block unavailable');
90
+ const warn = Sinon.stub(logger, 'warn');
91
+ provider.send.rejects(failure);
92
+
93
+ await expect(getConfirmedBlockTag(mpp, 'tron', logger)).to.be.rejectedWith(
94
+ failure,
95
+ );
96
+ expect(warn.calledOnce).to.equal(true);
97
+ expect(warn.firstCall.args[1]).to.equal('Failed to get confirmed block');
98
+ });
99
+
100
+ it('propagates provider lookup failures in numeric Tron mode', async () => {
101
+ const failure = new Error('provider unavailable');
102
+ mpp.getEthersV5Provider.throws(failure);
103
+
104
+ await expect(getConfirmedBlockTag(mpp, 'tron')).to.be.rejectedWith(failure);
105
+ });
106
+
107
+ for (const invalidResponse of [
108
+ undefined,
109
+ null,
110
+ 100,
111
+ '100',
112
+ '0x',
113
+ '0xgg',
114
+ '0x10000000000000000',
115
+ `0x${'f'.repeat(300)}`,
116
+ ]) {
117
+ it(`rejects an invalid Tron block response (${typeof invalidResponse}): ${String(invalidResponse).slice(0, 32)}`, async () => {
118
+ provider.send.resolves(invalidResponse);
119
+ await expect(getConfirmedBlockTag(mpp, 'tron')).to.be.rejectedWith(
120
+ 'Invalid',
121
+ );
122
+ });
123
+ }
124
+
125
+ it('rejects an invalid numeric block from a non-JSON-RPC provider', async () => {
126
+ const fallbackProvider = Sinon.createStubInstance(providers.BaseProvider);
127
+ fallbackProvider.getBlockNumber.resolves(Number.NaN);
128
+ mpp.getEthersV5Provider.returns(fallbackProvider);
129
+
130
+ await expect(getConfirmedBlockTag(mpp, 'tron')).to.be.rejectedWith(
131
+ 'Invalid block number for tron: NaN',
132
+ );
133
+ });
134
+
135
+ it('preserves undefined on an Ethereum lookup failure', async () => {
136
+ mpp.getChainMetadata.returns({
137
+ ...tronMetadata,
138
+ protocol: ProtocolType.Ethereum,
139
+ name: 'ethereum',
140
+ });
141
+ provider.send.rejects(new Error('block unavailable'));
142
+
143
+ expect(await getConfirmedBlockTag(mpp, 'ethereum')).to.equal(undefined);
144
+ });
145
+
146
+ it('preserves Ethereum named block tags', async () => {
147
+ mpp.getChainMetadata.returns({
148
+ ...tronMetadata,
149
+ protocol: ProtocolType.Ethereum,
150
+ name: 'ethereum',
151
+ blocks: {
152
+ reorgPeriod: EthJsonRpcBlockParameterTag.Finalized,
153
+ confirmations: 1,
154
+ },
155
+ });
156
+
157
+ expect(await getConfirmedBlockTag(mpp, 'ethereum')).to.equal(
158
+ EthJsonRpcBlockParameterTag.Finalized,
159
+ );
160
+ expect(mpp.getEthersV5Provider.notCalled).to.equal(true);
161
+ });
162
+
163
+ it('preserves undefined on metadata lookup failures', async () => {
164
+ const failure = new Error('metadata unavailable');
165
+ mpp.getChainMetadata.throws(failure);
166
+
167
+ const warn = Sinon.stub(logger, 'warn');
168
+ expect(await getConfirmedBlockTag(mpp, 'tron', logger)).to.equal(undefined);
169
+ expect(warn.calledOnce).to.equal(true);
170
+ expect(warn.firstCall.args[1]).to.equal('Failed to get confirmed block');
171
+ expect(mpp.getEthersV5Provider.notCalled).to.equal(true);
172
+ });
173
+ });
@@ -4,65 +4,85 @@ import { providers } from 'ethers';
4
4
 
5
5
  import {
6
6
  EthJsonRpcBlockParameterTag,
7
- MultiProtocolProvider,
7
+ type MultiProtocolProvider,
8
8
  } from '@humanode-hyperlane/sdk';
9
- import { ProtocolType } from '@humanode-hyperlane/utils';
9
+ import { isEVMLike, ProtocolType } from '@humanode-hyperlane/utils';
10
10
  import type { ConfirmedBlockTag } from '../interfaces/IMonitor.js';
11
11
 
12
+ const HEX_BLOCK_NUMBER_REGEX = /^0x[0-9a-fA-F]+$/;
13
+
12
14
  /**
13
15
  * Get the confirmed block tag for a chain, accounting for reorg period.
14
16
  * Returns a block number that is safe from reorgs, or a named tag like 'finalized'.
15
17
  *
16
- * @param multiProvider - MultiProtocolProvider instance
18
+ * @param multiProvider - Provider registry with chain metadata and ethers access
17
19
  * @param chainName - Name of the chain
18
20
  * @param logger - Optional logger for warnings
19
- * @returns Confirmed block tag (number, named tag, or undefined on error)
21
+ * @returns Confirmed block tag, or undefined for optional tags, metadata errors or failed Ethereum lookups.
22
+ * @throws If a numeric Tron block lookup fails.
20
23
  */
21
24
  export async function getConfirmedBlockTag(
22
- multiProvider: MultiProtocolProvider,
25
+ multiProvider: Pick<
26
+ MultiProtocolProvider,
27
+ 'getChainMetadata' | 'getEthersV5Provider'
28
+ >,
23
29
  chainName: string,
24
30
  logger?: Logger,
25
31
  ): Promise<ConfirmedBlockTag> {
32
+ let numericTronLookup = false;
26
33
  try {
27
34
  const metadata = multiProvider.getChainMetadata(chainName);
28
35
 
29
- // Only EVM chains support block tag queries
30
- if (metadata.protocol !== ProtocolType.Ethereum) {
36
+ // Only EVM-like chains support block tag queries (e.g., Ethereum, Tron).
37
+ // Tron uses TronJsonRpcProvider which supports eth_blockNumber
38
+ if (!isEVMLike(metadata.protocol)) {
31
39
  return undefined;
32
40
  }
33
41
 
34
42
  const reorgPeriod = metadata.blocks?.reorgPeriod ?? 32;
35
43
  if (typeof reorgPeriod === 'string') {
44
+ if (metadata.protocol === ProtocolType.Tron) {
45
+ logger?.warn(
46
+ { chain: chainName, reorgPeriod },
47
+ 'Tron does not support named block tags — ignoring string reorgPeriod, using latest block',
48
+ );
49
+ return undefined;
50
+ }
36
51
  return reorgPeriod as EthJsonRpcBlockParameterTag;
37
52
  }
38
53
 
54
+ numericTronLookup = metadata.protocol === ProtocolType.Tron;
39
55
  const provider = multiProvider.getEthersV5Provider(chainName);
40
56
  let latestBlock: number;
41
57
  if (provider instanceof providers.JsonRpcProvider) {
42
58
  const latestBlockHex = await provider.send('eth_blockNumber', []);
43
59
  if (
44
60
  typeof latestBlockHex !== 'string' ||
45
- !/^0x[0-9a-fA-F]+$/.test(latestBlockHex)
61
+ !HEX_BLOCK_NUMBER_REGEX.test(latestBlockHex)
46
62
  ) {
47
63
  throw new Error(
48
64
  `Invalid eth_blockNumber response for ${chainName}: ${latestBlockHex}`,
49
65
  );
50
66
  }
51
67
  latestBlock = parseInt(latestBlockHex, 16);
52
- if (!Number.isFinite(latestBlock)) {
53
- throw new Error(
54
- `Parsed block number is not finite for ${chainName}: ${latestBlockHex}`,
55
- );
56
- }
57
68
  } else {
58
69
  latestBlock = await provider.getBlockNumber();
59
70
  }
71
+ if (!Number.isSafeInteger(latestBlock) || latestBlock < 0) {
72
+ throw new Error(`Invalid block number for ${chainName}: ${latestBlock}`);
73
+ }
60
74
  return Math.max(0, latestBlock - reorgPeriod);
61
75
  } catch (error) {
62
76
  logger?.warn(
63
- { chain: chainName, error: (error as Error).message },
64
- 'Failed to get confirmed block, using latest',
77
+ {
78
+ chain: chainName,
79
+ error: error instanceof Error ? error.message : String(error),
80
+ },
81
+ 'Failed to get confirmed block',
65
82
  );
83
+ // For Tron, undefined is intentional only in named-tag mode. A failed
84
+ // numeric lookup must not silently query supply at latest instead.
85
+ if (numericTronLookup) throw error;
66
86
  return undefined;
67
87
  }
68
88
  }