@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
@@ -1,18 +1,38 @@
1
1
  import chai, { expect } from 'chai';
2
2
  import chaiAsPromised from 'chai-as-promised';
3
- import { pino } from 'pino';
3
+ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { pathToFileURL } from 'node:url';
7
+ import { type Logger, pino } from 'pino';
4
8
  import Sinon from 'sinon';
5
9
 
6
10
  import { EthJsonRpcBlockParameterTag } from '@humanode-hyperlane/sdk';
11
+ import { ProtocolType, assert } from '@humanode-hyperlane/utils';
7
12
 
8
13
  import {
9
14
  DEFAULT_INTENT_TTL_MS,
10
15
  DEFAULT_MOVEMENT_STALENESS_MS,
11
16
  ExternalBridgeType,
12
17
  } from '../config/types.js';
13
- import type { ExplorerMessage } from '../utils/ExplorerClient.js';
18
+ import {
19
+ ID_A,
20
+ ID_B,
21
+ ID_C,
22
+ buildMessage,
23
+ } from '../test/pendingTransferMiddlewareMocks.js';
24
+ import {
25
+ ExplorerClient,
26
+ type ExplorerMessage,
27
+ } from '../utils/ExplorerClient.js';
14
28
 
15
- import { ActionTracker, type ActionTrackerConfig } from './ActionTracker.js';
29
+ import {
30
+ ActionTracker,
31
+ type ActionTrackerConfig,
32
+ TRANSFER_SYNC_ROW_LIMIT_MESSAGE,
33
+ } from './ActionTracker.js';
34
+ import { ExplorerRowLimitError } from './pendingTransferMiddlewares/PendingTransferMiddleware.js';
35
+ import { RelayerDenylistMiddleware } from './pendingTransferMiddlewares/RelayerDenylistMiddleware.js';
16
36
  import { InMemoryStore } from './store/InMemoryStore.js';
17
37
  import type { RebalanceAction, RebalanceIntent, Transfer } from './types.js';
18
38
 
@@ -349,6 +369,50 @@ describe('ActionTracker', () => {
349
369
  expect(transfers).to.have.lengthOf(1);
350
370
  });
351
371
 
372
+ interface SyncFailureCase {
373
+ name: string;
374
+ error: Error;
375
+ loggedAsError: boolean;
376
+ }
377
+ const syncFailures: SyncFailureCase[] = [
378
+ {
379
+ name: 'logs an Explorer row limit failure as an error and rethrows it',
380
+ error: new ExplorerRowLimitError('row limit', []),
381
+ loggedAsError: true,
382
+ },
383
+ {
384
+ name: 'rethrows other Explorer failures without an error log',
385
+ error: new Error('explorer down'),
386
+ loggedAsError: false,
387
+ },
388
+ ];
389
+ for (const c of syncFailures) {
390
+ it(c.name, async () => {
391
+ const logger: Logger = pino({ level: 'silent' });
392
+ const error = Sinon.spy(logger, 'error');
393
+ explorerClient.getInflightUserTransfers.rejects(c.error);
394
+ const tracker = new ActionTracker(
395
+ transferStore,
396
+ rebalanceIntentStore,
397
+ rebalanceActionStore,
398
+ explorerClient,
399
+ core,
400
+ config,
401
+ logger,
402
+ );
403
+
404
+ await expect(tracker.syncTransfers()).to.be.rejectedWith(c.error);
405
+
406
+ expect(
407
+ error.calledWith(
408
+ Sinon.match({ error: c.error.message }),
409
+ TRANSFER_SYNC_ROW_LIMIT_MESSAGE,
410
+ ),
411
+ ).to.equal(c.loggedAsError);
412
+ expect(error.callCount).to.equal(c.loggedAsError ? 1 : 0);
413
+ });
414
+ }
415
+
352
416
  it('should mark transfers as complete when delivered', async () => {
353
417
  // Pre-create transfer
354
418
  await transferStore.save({
@@ -372,6 +436,123 @@ describe('ActionTracker', () => {
372
436
  const transfer = await transferStore.get('0xmsg1');
373
437
  expect(transfer?.status).to.equal('complete');
374
438
  });
439
+
440
+ describe('with an ignored-transfer predicate', () => {
441
+ const trackedTransfer: Transfer = {
442
+ id: '0xmsg1',
443
+ status: 'in_progress',
444
+ messageId: '0xmsg1',
445
+ origin: 1,
446
+ destination: 2,
447
+ amount: 100n,
448
+ sender: '0xuser1',
449
+ recipient: '0xuser2',
450
+ createdAt: 1_000,
451
+ updatedAt: 1_000,
452
+ };
453
+
454
+ function createTracker(
455
+ isIgnoredTransfer: (messageId: string) => boolean,
456
+ logger: Logger = testLogger,
457
+ ): ActionTracker {
458
+ return new ActionTracker(
459
+ transferStore,
460
+ rebalanceIntentStore,
461
+ rebalanceActionStore,
462
+ explorerClient,
463
+ core,
464
+ {
465
+ routersByDomain: config.routersByDomain,
466
+ isIgnoredTransfer,
467
+ bridges: config.bridges,
468
+ rebalancerAddress: config.rebalancerAddress,
469
+ intentTTL: config.intentTTL,
470
+ },
471
+ logger,
472
+ );
473
+ }
474
+
475
+ it('keeps an ignored tracked transfer stored but excludes it from in-progress transfers without checking delivery', async () => {
476
+ await transferStore.save(trackedTransfer);
477
+ const tracker = createTracker((id) => id === '0xmsg1');
478
+
479
+ await tracker.syncTransfers();
480
+
481
+ const transfer = await tracker.getTransfer('0xmsg1');
482
+ expect(transfer?.status).to.equal('in_progress');
483
+ expect(await tracker.getInProgressTransfers()).to.deep.equal([]);
484
+ expect(mailboxStub.isDelivered.called).to.be.false;
485
+ });
486
+
487
+ it('reserves an ignored transfer again once it is no longer ignored, even when absent from the Explorer result', async () => {
488
+ await transferStore.save(trackedTransfer);
489
+ let ignored = true;
490
+ const tracker = createTracker((id) => ignored && id === '0xmsg1');
491
+ explorerClient.getInflightUserTransfers.resolves([]);
492
+
493
+ await tracker.syncTransfers();
494
+ expect(await tracker.getInProgressTransfers()).to.deep.equal([]);
495
+
496
+ ignored = false;
497
+ await tracker.syncTransfers();
498
+
499
+ const inProgress = await tracker.getInProgressTransfers();
500
+ expect(inProgress.map((t) => [t.id, t.amount])).to.deep.equal([
501
+ ['0xmsg1', 100n],
502
+ ]);
503
+ });
504
+
505
+ it('completes an ignored transfer on the first sync after it is no longer ignored once delivered', async () => {
506
+ await transferStore.save(trackedTransfer);
507
+ let ignored = true;
508
+ const tracker = createTracker((id) => ignored && id === '0xmsg1');
509
+ mailboxStub.isDelivered.resolves(true);
510
+
511
+ await tracker.syncTransfers();
512
+ expect(mailboxStub.isDelivered.called).to.be.false;
513
+ expect((await tracker.getTransfer('0xmsg1'))?.status).to.equal(
514
+ 'in_progress',
515
+ );
516
+
517
+ ignored = false;
518
+ await tracker.syncTransfers();
519
+
520
+ expect(mailboxStub.isDelivered.calledOnce).to.be.true;
521
+ expect((await tracker.getTransfer('0xmsg1'))?.status).to.equal(
522
+ 'complete',
523
+ );
524
+ });
525
+
526
+ it('keeps a tracked transfer the predicate does not ignore', async () => {
527
+ await transferStore.save(trackedTransfer);
528
+
529
+ await createTracker((id) => id === '0xother').syncTransfers();
530
+
531
+ const transfer = await transferStore.get('0xmsg1');
532
+
533
+ expect(transfer?.status).to.equal('in_progress');
534
+ expect(mailboxStub.isDelivered.calledOnce).to.be.true;
535
+ });
536
+
537
+ it('counts an ignored transfer on every sync while it stays ignored', async () => {
538
+ await transferStore.save(trackedTransfer);
539
+ const logger: Logger = pino({ level: 'silent' });
540
+ const info = Sinon.spy(logger, 'info');
541
+ const tracker = createTracker((id) => id === '0xmsg1', logger);
542
+
543
+ await tracker.syncTransfers();
544
+ await tracker.syncTransfers();
545
+
546
+ const syncedCounts = info
547
+ .getCalls()
548
+ .filter((call) => call.args.includes('Transfers synced'))
549
+ .map((call) => call.args[0]);
550
+ expect(syncedCounts).to.deep.equal([
551
+ { newTransfers: 0, completed: 0, ignored: 1, inProgress: 0 },
552
+ { newTransfers: 0, completed: 0, ignored: 1, inProgress: 0 },
553
+ ]);
554
+ });
555
+ });
375
556
  });
376
557
 
377
558
  describe('syncRebalanceIntents', () => {
@@ -565,6 +746,231 @@ describe('ActionTracker', () => {
565
746
  });
566
747
  });
567
748
 
749
+ describe('with a relayer denylist middleware', () => {
750
+ let dir: string;
751
+ let denylistPath: string;
752
+
753
+ beforeEach(() => {
754
+ dir = mkdtempSync(join(tmpdir(), 'action-tracker-denylist-'));
755
+ denylistPath = join(dir, 'denylist.json');
756
+ writeDenylist([]);
757
+ explorerClient.getInflightRebalanceActions.resolves([
758
+ buildMessage(ID_A),
759
+ buildMessage(ID_B),
760
+ buildMessage(ID_C),
761
+ ]);
762
+ });
763
+
764
+ afterEach(() => {
765
+ rmSync(dir, { recursive: true, force: true });
766
+ });
767
+
768
+ function writeDenylist(
769
+ messageIds: string[],
770
+ matches: Record<string, unknown>[] = [],
771
+ ): void {
772
+ writeFileSync(
773
+ denylistPath,
774
+ JSON.stringify({
775
+ rules: [
776
+ ...messageIds.map((messageId) => ({ messageId })),
777
+ ...matches,
778
+ ].map((match) => ({ reason: 'settled', addedBy: 'tester', match })),
779
+ }),
780
+ );
781
+ }
782
+
783
+ async function createDenylistTracker(): Promise<ActionTracker> {
784
+ const middleware = await RelayerDenylistMiddleware.create(
785
+ explorerClient,
786
+ { url: pathToFileURL(denylistPath).href, refreshIntervalMs: 0 },
787
+ testLogger,
788
+ );
789
+
790
+ return new ActionTracker(
791
+ transferStore,
792
+ rebalanceIntentStore,
793
+ rebalanceActionStore,
794
+ middleware,
795
+ core,
796
+ {
797
+ routersByDomain: config.routersByDomain,
798
+ isIgnoredTransfer: (id) => middleware.isIgnored(id),
799
+ isIgnoredRebalanceAction: (id) =>
800
+ middleware.isIgnoredRebalanceAction(id),
801
+ bridges: config.bridges,
802
+ rebalancerAddress: config.rebalancerAddress,
803
+ intentTTL: config.intentTTL,
804
+ },
805
+ testLogger,
806
+ );
807
+ }
808
+
809
+ async function activeIntentMessageIds(
810
+ tracker: ActionTracker,
811
+ ): Promise<string[]> {
812
+ const actions = await rebalanceActionStore.getAll();
813
+ const intents = await tracker.getActiveRebalanceIntents();
814
+
815
+ return intents.map((intent) => {
816
+ const action = actions.find((a) => a.intentId === intent.id);
817
+ assert(action?.messageId, `Missing action for intent ${intent.id}`);
818
+ return action.messageId;
819
+ });
820
+ }
821
+
822
+ it('does not recover denylisted rebalance messages at startup', async () => {
823
+ writeDenylist([ID_A, ID_B]);
824
+ const tracker = await createDenylistTracker();
825
+
826
+ await tracker.initialize();
827
+
828
+ const actions = await rebalanceActionStore.getAll();
829
+ expect(actions.map((a) => a.id)).to.deep.equal([ID_C]);
830
+ const intents = await rebalanceIntentStore.getAll();
831
+ expect(intents.map((i) => [i.status, i.amount])).to.deep.equal([
832
+ ['in_progress', 100n],
833
+ ]);
834
+ expect(await activeIntentMessageIds(tracker)).to.deep.equal([ID_C]);
835
+ });
836
+
837
+ it('stops counting a tracked rebalance message once it is denylisted without changing its status', async () => {
838
+ const tracker = await createDenylistTracker();
839
+ await tracker.initialize();
840
+ expect(await activeIntentMessageIds(tracker)).to.have.members([
841
+ ID_A,
842
+ ID_B,
843
+ ID_C,
844
+ ]);
845
+
846
+ writeDenylist([ID_A]);
847
+ mailboxStub.isDelivered.resetHistory();
848
+ await tracker.syncRebalanceActions();
849
+
850
+ expect(await activeIntentMessageIds(tracker)).to.have.members([
851
+ ID_B,
852
+ ID_C,
853
+ ]);
854
+ const ignoredAction = await rebalanceActionStore.get(ID_A);
855
+ assert(ignoredAction, 'Expected the ignored action to stay stored');
856
+ expect(ignoredAction.status).to.equal('in_progress');
857
+ expect(
858
+ (await rebalanceIntentStore.get(ignoredAction.intentId))?.status,
859
+ ).to.equal('in_progress');
860
+ expect(mailboxStub.isDelivered.calledWith(ID_A)).to.be.true;
861
+ });
862
+
863
+ it('completes a denylisted rebalance message the relayer still delivers', async () => {
864
+ const tracker = await createDenylistTracker();
865
+ await tracker.initialize();
866
+ writeDenylist([ID_A]);
867
+ mailboxStub.isDelivered.callsFake(async (id: string) => id === ID_A);
868
+
869
+ await tracker.syncRebalanceActions();
870
+
871
+ const action = await rebalanceActionStore.get(ID_A);
872
+ assert(action, 'Expected the delivered action to stay stored');
873
+ expect(action.status).to.equal('complete');
874
+ expect(
875
+ (await rebalanceIntentStore.get(action.intentId))?.status,
876
+ ).to.equal('complete');
877
+ });
878
+
879
+ it('still fails a denylisted rebalance message past the intent TTL', async () => {
880
+ await rebalanceIntentStore.save({
881
+ id: 'expired-intent',
882
+ status: 'in_progress',
883
+ origin: 42161,
884
+ destination: 1,
885
+ amount: 100n,
886
+ createdAt: 0,
887
+ updatedAt: 0,
888
+ });
889
+ await rebalanceActionStore.save({
890
+ id: ID_A,
891
+ type: 'rebalance_message',
892
+ status: 'in_progress',
893
+ intentId: 'expired-intent',
894
+ messageId: ID_A,
895
+ origin: 42161,
896
+ destination: 1,
897
+ amount: 100n,
898
+ createdAt: 0,
899
+ updatedAt: 0,
900
+ });
901
+ writeDenylist([ID_A]);
902
+ const tracker = await createDenylistTracker();
903
+ await tracker.syncRebalanceActions();
904
+ expect(await activeIntentMessageIds(tracker)).to.have.members([
905
+ ID_B,
906
+ ID_C,
907
+ ]);
908
+
909
+ await tracker.syncRebalanceIntents();
910
+
911
+ expect((await rebalanceActionStore.get(ID_A))?.status).to.equal('failed');
912
+ expect(
913
+ (await rebalanceIntentStore.get('expired-intent'))?.status,
914
+ ).to.equal('failed');
915
+ });
916
+
917
+ it('keeps ignoring a denylisted rebalance message pushed off the Explorer page', async () => {
918
+ const tracker = await createDenylistTracker();
919
+ await tracker.initialize();
920
+ writeDenylist([ID_A]);
921
+ await tracker.syncRebalanceActions();
922
+ expect(await activeIntentMessageIds(tracker)).to.not.include(ID_A);
923
+
924
+ const newerIds = Array.from(
925
+ { length: 100 },
926
+ (_, i) => `0x${(i + 1).toString(16).padStart(64, '0')}`,
927
+ );
928
+ explorerClient.getInflightRebalanceActions.resolves(
929
+ newerIds.map((id) => buildMessage(id)),
930
+ );
931
+ await tracker.syncRebalanceActions();
932
+
933
+ const countedIds = await activeIntentMessageIds(tracker);
934
+ expect(countedIds).to.not.include(ID_A);
935
+ expect(countedIds).to.include.members([ID_B, ID_C, ...newerIds]);
936
+
937
+ writeDenylist([]);
938
+ await tracker.syncRebalanceActions();
939
+
940
+ expect(await activeIntentMessageIds(tracker)).to.include(ID_A);
941
+ });
942
+
943
+ it('keeps recovering and counting rebalance messages a broad rule denies', async () => {
944
+ writeDenylist([], [{ destinationDomain: 1 }, { originDomain: 42161 }]);
945
+ const tracker = await createDenylistTracker();
946
+
947
+ await tracker.initialize();
948
+ await tracker.syncRebalanceActions();
949
+
950
+ expect(await activeIntentMessageIds(tracker)).to.have.members([
951
+ ID_A,
952
+ ID_B,
953
+ ID_C,
954
+ ]);
955
+ });
956
+
957
+ it('counts a rebalance message again once its rule is removed', async () => {
958
+ writeDenylist([ID_A, ID_B]);
959
+ const tracker = await createDenylistTracker();
960
+ await tracker.initialize();
961
+ expect(await activeIntentMessageIds(tracker)).to.deep.equal([ID_C]);
962
+
963
+ writeDenylist([]);
964
+ await tracker.syncRebalanceActions();
965
+
966
+ expect(await activeIntentMessageIds(tracker)).to.have.members([
967
+ ID_A,
968
+ ID_B,
969
+ ID_C,
970
+ ]);
971
+ });
972
+ });
973
+
568
974
  describe('syncInventoryMovementActions', () => {
569
975
  it('stores pending status on in-progress movement', async () => {
570
976
  await rebalanceActionStore.save({
@@ -746,6 +1152,125 @@ describe('ActionTracker', () => {
746
1152
  expect(result).to.have.lengthOf(1);
747
1153
  expect(result[0].id).to.equal('intent-2');
748
1154
  });
1155
+
1156
+ describe('with an ignored-rebalance-action predicate', () => {
1157
+ const ignoredIds = new Set(['0xignored1', '0xignored2']);
1158
+
1159
+ function createTracker(
1160
+ isIgnoredRebalanceAction?: (messageId: string) => boolean,
1161
+ ): ActionTracker {
1162
+ return new ActionTracker(
1163
+ transferStore,
1164
+ rebalanceIntentStore,
1165
+ rebalanceActionStore,
1166
+ explorerClient,
1167
+ core,
1168
+ {
1169
+ routersByDomain: config.routersByDomain,
1170
+ isIgnoredRebalanceAction,
1171
+ bridges: config.bridges,
1172
+ rebalancerAddress: config.rebalancerAddress,
1173
+ intentTTL: config.intentTTL,
1174
+ },
1175
+ testLogger,
1176
+ );
1177
+ }
1178
+
1179
+ async function saveIntent(
1180
+ id: string,
1181
+ executionMethod?: 'movable_collateral' | 'inventory',
1182
+ ): Promise<void> {
1183
+ await rebalanceIntentStore.save({
1184
+ id,
1185
+ status: 'in_progress',
1186
+ origin: 1,
1187
+ destination: 2,
1188
+ amount: 300n,
1189
+ executionMethod,
1190
+ createdAt: Date.now(),
1191
+ updatedAt: Date.now(),
1192
+ });
1193
+ }
1194
+
1195
+ async function saveAction(
1196
+ id: string,
1197
+ intentId: string,
1198
+ type: 'rebalance_message' | 'inventory_deposit',
1199
+ status: 'in_progress' | 'complete',
1200
+ ): Promise<void> {
1201
+ await rebalanceActionStore.save({
1202
+ id,
1203
+ type,
1204
+ status,
1205
+ intentId,
1206
+ messageId: id,
1207
+ origin: 1,
1208
+ destination: 2,
1209
+ amount: 100n,
1210
+ createdAt: Date.now(),
1211
+ updatedAt: Date.now(),
1212
+ });
1213
+ }
1214
+
1215
+ it('drops only intents whose in-progress messages are all ignored', async () => {
1216
+ await saveIntent('all-ignored', 'movable_collateral');
1217
+ await saveAction(
1218
+ '0xignored1',
1219
+ 'all-ignored',
1220
+ 'rebalance_message',
1221
+ 'in_progress',
1222
+ );
1223
+ await saveAction(
1224
+ '0xdone',
1225
+ 'all-ignored',
1226
+ 'rebalance_message',
1227
+ 'complete',
1228
+ );
1229
+ await saveIntent('mixed');
1230
+ await saveAction(
1231
+ '0xignored2',
1232
+ 'mixed',
1233
+ 'rebalance_message',
1234
+ 'in_progress',
1235
+ );
1236
+ await saveAction('0xkept', 'mixed', 'rebalance_message', 'in_progress');
1237
+ await saveIntent('no-actions', 'movable_collateral');
1238
+
1239
+ const result = await createTracker((id) =>
1240
+ ignoredIds.has(id),
1241
+ ).getActiveRebalanceIntents();
1242
+
1243
+ expect(result.map((i) => [i.id, i.amount])).to.have.deep.members([
1244
+ ['mixed', 300n],
1245
+ ['no-actions', 300n],
1246
+ ]);
1247
+ });
1248
+
1249
+ it('leaves inventory intents and their retry state untouched', async () => {
1250
+ await saveIntent('inventory', 'inventory');
1251
+ await saveAction(
1252
+ '0xignored1',
1253
+ 'inventory',
1254
+ 'inventory_deposit',
1255
+ 'in_progress',
1256
+ );
1257
+
1258
+ const withoutPredicate = createTracker();
1259
+ const withPredicate = createTracker((id) => ignoredIds.has(id));
1260
+
1261
+ expect(
1262
+ (await withPredicate.getActiveRebalanceIntents()).map((i) => i.id),
1263
+ ).to.deep.equal(['inventory']);
1264
+ expect(
1265
+ await withPredicate.getPartiallyFulfilledInventoryIntents(),
1266
+ ).to.deep.equal(
1267
+ await withoutPredicate.getPartiallyFulfilledInventoryIntents(),
1268
+ );
1269
+ expect(
1270
+ await withPredicate.getPartiallyFulfilledInventoryIntents(),
1271
+ ).to.have.lengthOf(1);
1272
+ });
1273
+ });
749
1274
  });
750
1275
 
751
1276
  describe('getPartiallyFulfilledInventoryIntents', () => {
@@ -807,6 +1332,53 @@ describe('ActionTracker', () => {
807
1332
  expect(partialIntents[0].remaining).to.equal(600000000000000000n); // 0.6 ETH remaining
808
1333
  });
809
1334
 
1335
+ it('ignores inventory_movement amounts when computing remaining', async () => {
1336
+ await rebalanceIntentStore.save({
1337
+ id: 'partial-intent-with-movement',
1338
+ status: 'in_progress',
1339
+ origin: 1,
1340
+ destination: 2,
1341
+ amount: 1000000000000000000n,
1342
+ executionMethod: 'inventory',
1343
+ createdAt: Date.now(),
1344
+ updatedAt: Date.now(),
1345
+ });
1346
+
1347
+ await rebalanceActionStore.save({
1348
+ id: 'action-deposit',
1349
+ type: 'inventory_deposit',
1350
+ status: 'complete',
1351
+ intentId: 'partial-intent-with-movement',
1352
+ origin: 1,
1353
+ destination: 2,
1354
+ amount: 400000000000000000n,
1355
+ createdAt: Date.now(),
1356
+ updatedAt: Date.now(),
1357
+ });
1358
+
1359
+ await rebalanceActionStore.save({
1360
+ id: 'action-movement',
1361
+ type: 'inventory_movement',
1362
+ status: 'complete',
1363
+ intentId: 'partial-intent-with-movement',
1364
+ origin: 3,
1365
+ destination: 2,
1366
+ amount: 9000000000000000000n,
1367
+ createdAt: Date.now(),
1368
+ updatedAt: Date.now(),
1369
+ });
1370
+
1371
+ const partialIntents =
1372
+ await tracker.getPartiallyFulfilledInventoryIntents();
1373
+
1374
+ expect(partialIntents).to.have.lengthOf(1);
1375
+ expect(partialIntents[0].intent.id).to.equal(
1376
+ 'partial-intent-with-movement',
1377
+ );
1378
+ expect(partialIntents[0].completedAmount).to.equal(400000000000000000n);
1379
+ expect(partialIntents[0].remaining).to.equal(600000000000000000n);
1380
+ });
1381
+
810
1382
  it('does not return non-inventory intents', async () => {
811
1383
  // Create a not_started intent without executionMethod: 'inventory'
812
1384
  await rebalanceIntentStore.save({
@@ -1484,6 +2056,66 @@ describe('ActionTracker', () => {
1484
2056
  expect(updatedAction?.status).to.equal('complete');
1485
2057
  });
1486
2058
 
2059
+ it('checks tracked delivery with the real Explorer client and no bridges', async () => {
2060
+ const intent: RebalanceIntent = {
2061
+ id: 'intent-1',
2062
+ status: 'in_progress',
2063
+ origin: 1,
2064
+ destination: 2,
2065
+ amount: 100n,
2066
+ createdAt: Date.now(),
2067
+ updatedAt: Date.now(),
2068
+ };
2069
+
2070
+ const action: RebalanceAction = {
2071
+ id: 'action-1',
2072
+ type: 'inventory_deposit',
2073
+ status: 'in_progress',
2074
+ intentId: 'intent-1',
2075
+ messageId: '0xmsg1',
2076
+ origin: 1,
2077
+ destination: 2,
2078
+ amount: 100n,
2079
+ createdAt: Date.now(),
2080
+ updatedAt: Date.now(),
2081
+ };
2082
+
2083
+ await rebalanceIntentStore.save(intent);
2084
+ await rebalanceActionStore.save(action);
2085
+
2086
+ config.bridges = [];
2087
+ const fetchStub = Sinon.stub(globalThis, 'fetch').rejects(
2088
+ new Error('unexpected HTTP'),
2089
+ );
2090
+ tracker = new ActionTracker(
2091
+ transferStore,
2092
+ rebalanceIntentStore,
2093
+ rebalanceActionStore,
2094
+ new ExplorerClient(
2095
+ 'https://explorer.test',
2096
+ () => ProtocolType.Ethereum,
2097
+ ),
2098
+ core,
2099
+ config,
2100
+ testLogger,
2101
+ );
2102
+ mailboxStub.isDelivered.resolves(true);
2103
+
2104
+ try {
2105
+ await tracker.syncRebalanceActions();
2106
+ expect(fetchStub.called).to.be.false;
2107
+ } finally {
2108
+ fetchStub.restore();
2109
+ }
2110
+
2111
+ expect(mailboxStub.isDelivered.calledOnce).to.be.true;
2112
+ const call = mailboxStub.isDelivered.firstCall;
2113
+ expect(call.args[0]).to.equal('0xmsg1');
2114
+
2115
+ const updatedAction = await rebalanceActionStore.get('action-1');
2116
+ expect(updatedAction?.status).to.equal('complete');
2117
+ });
2118
+
1487
2119
  it('should keep transfer in_progress when not delivered', async () => {
1488
2120
  await transferStore.save({
1489
2121
  id: '0xmsg1',