@humanode-hyperlane/rebalancer 0.0.0-stage → 27.2.2

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 (439) hide show
  1. package/LICENSE.md +193 -0
  2. package/README.md +297 -2
  3. package/dist/bridges/LiFiBridge.d.ts +93 -0
  4. package/dist/bridges/LiFiBridge.d.ts.map +1 -0
  5. package/dist/bridges/LiFiBridge.js +527 -0
  6. package/dist/bridges/LiFiBridge.js.map +1 -0
  7. package/dist/bridges/LiFiBridge.test.d.ts +2 -0
  8. package/dist/bridges/LiFiBridge.test.d.ts.map +1 -0
  9. package/dist/bridges/LiFiBridge.test.js +504 -0
  10. package/dist/bridges/LiFiBridge.test.js.map +1 -0
  11. package/dist/config/RebalancerConfig.d.ts +20 -0
  12. package/dist/config/RebalancerConfig.d.ts.map +1 -0
  13. package/dist/config/RebalancerConfig.js +35 -0
  14. package/dist/config/RebalancerConfig.js.map +1 -0
  15. package/dist/config/RebalancerConfig.test.d.ts +2 -0
  16. package/dist/config/RebalancerConfig.test.d.ts.map +1 -0
  17. package/dist/config/RebalancerConfig.test.js +798 -0
  18. package/dist/config/RebalancerConfig.test.js.map +1 -0
  19. package/dist/config/types.d.ts +3008 -0
  20. package/dist/config/types.d.ts.map +1 -0
  21. package/dist/config/types.js +451 -0
  22. package/dist/config/types.js.map +1 -0
  23. package/dist/core/InventoryRebalancer.d.ts +201 -0
  24. package/dist/core/InventoryRebalancer.d.ts.map +1 -0
  25. package/dist/core/InventoryRebalancer.js +994 -0
  26. package/dist/core/InventoryRebalancer.js.map +1 -0
  27. package/dist/core/InventoryRebalancer.test.d.ts +2 -0
  28. package/dist/core/InventoryRebalancer.test.d.ts.map +1 -0
  29. package/dist/core/InventoryRebalancer.test.js +1511 -0
  30. package/dist/core/InventoryRebalancer.test.js.map +1 -0
  31. package/dist/core/Rebalancer.d.ts +37 -0
  32. package/dist/core/Rebalancer.d.ts.map +1 -0
  33. package/dist/core/Rebalancer.js +433 -0
  34. package/dist/core/Rebalancer.js.map +1 -0
  35. package/dist/core/Rebalancer.test.d.ts +2 -0
  36. package/dist/core/Rebalancer.test.d.ts.map +1 -0
  37. package/dist/core/Rebalancer.test.js +424 -0
  38. package/dist/core/Rebalancer.test.js.map +1 -0
  39. package/dist/core/RebalancerOrchestrator.d.ts +56 -0
  40. package/dist/core/RebalancerOrchestrator.d.ts.map +1 -0
  41. package/dist/core/RebalancerOrchestrator.js +154 -0
  42. package/dist/core/RebalancerOrchestrator.js.map +1 -0
  43. package/dist/core/RebalancerOrchestrator.test.d.ts +2 -0
  44. package/dist/core/RebalancerOrchestrator.test.d.ts.map +1 -0
  45. package/dist/core/RebalancerOrchestrator.test.js +721 -0
  46. package/dist/core/RebalancerOrchestrator.test.js.map +1 -0
  47. package/dist/core/RebalancerService.d.ts +127 -0
  48. package/dist/core/RebalancerService.d.ts.map +1 -0
  49. package/dist/core/RebalancerService.js +270 -0
  50. package/dist/core/RebalancerService.js.map +1 -0
  51. package/dist/core/RebalancerService.test.d.ts +2 -0
  52. package/dist/core/RebalancerService.test.d.ts.map +1 -0
  53. package/dist/core/RebalancerService.test.js +777 -0
  54. package/dist/core/RebalancerService.test.js.map +1 -0
  55. package/dist/e2e/collateral-deficit.e2e-test.d.ts +2 -0
  56. package/dist/e2e/collateral-deficit.e2e-test.d.ts.map +1 -0
  57. package/dist/e2e/collateral-deficit.e2e-test.js +227 -0
  58. package/dist/e2e/collateral-deficit.e2e-test.js.map +1 -0
  59. package/dist/e2e/composite.e2e-test.d.ts +2 -0
  60. package/dist/e2e/composite.e2e-test.d.ts.map +1 -0
  61. package/dist/e2e/composite.e2e-test.js +666 -0
  62. package/dist/e2e/composite.e2e-test.js.map +1 -0
  63. package/dist/e2e/fixtures/routes.d.ts +89 -0
  64. package/dist/e2e/fixtures/routes.d.ts.map +1 -0
  65. package/dist/e2e/fixtures/routes.js +420 -0
  66. package/dist/e2e/fixtures/routes.js.map +1 -0
  67. package/dist/e2e/harness/BaseLocalDeploymentManager.d.ts +38 -0
  68. package/dist/e2e/harness/BaseLocalDeploymentManager.d.ts.map +1 -0
  69. package/dist/e2e/harness/BaseLocalDeploymentManager.js +144 -0
  70. package/dist/e2e/harness/BaseLocalDeploymentManager.js.map +1 -0
  71. package/dist/e2e/harness/BridgeSetup.d.ts +11 -0
  72. package/dist/e2e/harness/BridgeSetup.d.ts.map +1 -0
  73. package/dist/e2e/harness/BridgeSetup.js +50 -0
  74. package/dist/e2e/harness/BridgeSetup.js.map +1 -0
  75. package/dist/e2e/harness/Erc20InventoryLocalDeploymentManager.d.ts +13 -0
  76. package/dist/e2e/harness/Erc20InventoryLocalDeploymentManager.d.ts.map +1 -0
  77. package/dist/e2e/harness/Erc20InventoryLocalDeploymentManager.js +105 -0
  78. package/dist/e2e/harness/Erc20InventoryLocalDeploymentManager.js.map +1 -0
  79. package/dist/e2e/harness/Erc20LocalDeploymentManager.d.ts +11 -0
  80. package/dist/e2e/harness/Erc20LocalDeploymentManager.d.ts.map +1 -0
  81. package/dist/e2e/harness/Erc20LocalDeploymentManager.js +105 -0
  82. package/dist/e2e/harness/Erc20LocalDeploymentManager.js.map +1 -0
  83. package/dist/e2e/harness/ForkIndexer.d.ts +23 -0
  84. package/dist/e2e/harness/ForkIndexer.d.ts.map +1 -0
  85. package/dist/e2e/harness/ForkIndexer.js +127 -0
  86. package/dist/e2e/harness/ForkIndexer.js.map +1 -0
  87. package/dist/e2e/harness/ForkIndexer.test.d.ts +2 -0
  88. package/dist/e2e/harness/ForkIndexer.test.d.ts.map +1 -0
  89. package/dist/e2e/harness/ForkIndexer.test.js +257 -0
  90. package/dist/e2e/harness/ForkIndexer.test.js.map +1 -0
  91. package/dist/e2e/harness/MixedLocalDeploymentManager.d.ts +21 -0
  92. package/dist/e2e/harness/MixedLocalDeploymentManager.d.ts.map +1 -0
  93. package/dist/e2e/harness/MixedLocalDeploymentManager.js +115 -0
  94. package/dist/e2e/harness/MixedLocalDeploymentManager.js.map +1 -0
  95. package/dist/e2e/harness/MockExplorerClient.d.ts +23 -0
  96. package/dist/e2e/harness/MockExplorerClient.d.ts.map +1 -0
  97. package/dist/e2e/harness/MockExplorerClient.js +57 -0
  98. package/dist/e2e/harness/MockExplorerClient.js.map +1 -0
  99. package/dist/e2e/harness/MockExternalBridge.d.ts +32 -0
  100. package/dist/e2e/harness/MockExternalBridge.d.ts.map +1 -0
  101. package/dist/e2e/harness/MockExternalBridge.js +247 -0
  102. package/dist/e2e/harness/MockExternalBridge.js.map +1 -0
  103. package/dist/e2e/harness/NativeLocalDeploymentManager.d.ts +13 -0
  104. package/dist/e2e/harness/NativeLocalDeploymentManager.d.ts.map +1 -0
  105. package/dist/e2e/harness/NativeLocalDeploymentManager.js +91 -0
  106. package/dist/e2e/harness/NativeLocalDeploymentManager.js.map +1 -0
  107. package/dist/e2e/harness/SnapshotHelper.d.ts +13 -0
  108. package/dist/e2e/harness/SnapshotHelper.d.ts.map +1 -0
  109. package/dist/e2e/harness/SnapshotHelper.js +36 -0
  110. package/dist/e2e/harness/SnapshotHelper.js.map +1 -0
  111. package/dist/e2e/harness/TestHelpers.d.ts +25 -0
  112. package/dist/e2e/harness/TestHelpers.d.ts.map +1 -0
  113. package/dist/e2e/harness/TestHelpers.js +103 -0
  114. package/dist/e2e/harness/TestHelpers.js.map +1 -0
  115. package/dist/e2e/harness/TestRebalancer.d.ts +85 -0
  116. package/dist/e2e/harness/TestRebalancer.d.ts.map +1 -0
  117. package/dist/e2e/harness/TestRebalancer.js +427 -0
  118. package/dist/e2e/harness/TestRebalancer.js.map +1 -0
  119. package/dist/e2e/harness/TransferHelper.d.ts +34 -0
  120. package/dist/e2e/harness/TransferHelper.d.ts.map +1 -0
  121. package/dist/e2e/harness/TransferHelper.js +93 -0
  122. package/dist/e2e/harness/TransferHelper.js.map +1 -0
  123. package/dist/e2e/inventory-erc20-minAmount.e2e-test.d.ts +2 -0
  124. package/dist/e2e/inventory-erc20-minAmount.e2e-test.d.ts.map +1 -0
  125. package/dist/e2e/inventory-erc20-minAmount.e2e-test.js +411 -0
  126. package/dist/e2e/inventory-erc20-minAmount.e2e-test.js.map +1 -0
  127. package/dist/e2e/inventory-erc20-weighted.e2e-test.d.ts +2 -0
  128. package/dist/e2e/inventory-erc20-weighted.e2e-test.d.ts.map +1 -0
  129. package/dist/e2e/inventory-erc20-weighted.e2e-test.js +408 -0
  130. package/dist/e2e/inventory-erc20-weighted.e2e-test.js.map +1 -0
  131. package/dist/e2e/inventory-minAmount.e2e-test.d.ts +2 -0
  132. package/dist/e2e/inventory-minAmount.e2e-test.d.ts.map +1 -0
  133. package/dist/e2e/inventory-minAmount.e2e-test.js +411 -0
  134. package/dist/e2e/inventory-minAmount.e2e-test.js.map +1 -0
  135. package/dist/e2e/inventory-weighted.e2e-test.d.ts +2 -0
  136. package/dist/e2e/inventory-weighted.e2e-test.d.ts.map +1 -0
  137. package/dist/e2e/inventory-weighted.e2e-test.js +409 -0
  138. package/dist/e2e/inventory-weighted.e2e-test.js.map +1 -0
  139. package/dist/e2e/minAmount.e2e-test.d.ts +2 -0
  140. package/dist/e2e/minAmount.e2e-test.d.ts.map +1 -0
  141. package/dist/e2e/minAmount.e2e-test.js +203 -0
  142. package/dist/e2e/minAmount.e2e-test.js.map +1 -0
  143. package/dist/e2e/mixed-weighted.e2e-test.d.ts +2 -0
  144. package/dist/e2e/mixed-weighted.e2e-test.d.ts.map +1 -0
  145. package/dist/e2e/mixed-weighted.e2e-test.js +189 -0
  146. package/dist/e2e/mixed-weighted.e2e-test.js.map +1 -0
  147. package/dist/e2e/weighted.e2e-test.d.ts +2 -0
  148. package/dist/e2e/weighted.e2e-test.d.ts.map +1 -0
  149. package/dist/e2e/weighted.e2e-test.js +198 -0
  150. package/dist/e2e/weighted.e2e-test.js.map +1 -0
  151. package/dist/factories/RebalancerContextFactory.d.ts +89 -0
  152. package/dist/factories/RebalancerContextFactory.d.ts.map +1 -0
  153. package/dist/factories/RebalancerContextFactory.js +420 -0
  154. package/dist/factories/RebalancerContextFactory.js.map +1 -0
  155. package/dist/factories/RebalancerContextFactory.test.d.ts +2 -0
  156. package/dist/factories/RebalancerContextFactory.test.d.ts.map +1 -0
  157. package/dist/factories/RebalancerContextFactory.test.js +270 -0
  158. package/dist/factories/RebalancerContextFactory.test.js.map +1 -0
  159. package/dist/index.d.ts +37 -0
  160. package/dist/index.d.ts.map +1 -0
  161. package/dist/index.js +37 -0
  162. package/dist/index.js.map +1 -0
  163. package/dist/interfaces/IExternalBridge.d.ts +103 -0
  164. package/dist/interfaces/IExternalBridge.d.ts.map +1 -0
  165. package/dist/interfaces/IExternalBridge.js +2 -0
  166. package/dist/interfaces/IExternalBridge.js.map +1 -0
  167. package/dist/interfaces/IMetrics.d.ts +5 -0
  168. package/dist/interfaces/IMetrics.d.ts.map +1 -0
  169. package/dist/interfaces/IMetrics.js +2 -0
  170. package/dist/interfaces/IMetrics.js.map +1 -0
  171. package/dist/interfaces/IMonitor.d.ts +50 -0
  172. package/dist/interfaces/IMonitor.d.ts.map +1 -0
  173. package/dist/interfaces/IMonitor.js +14 -0
  174. package/dist/interfaces/IMonitor.js.map +1 -0
  175. package/dist/interfaces/IRebalancer.d.ts +31 -0
  176. package/dist/interfaces/IRebalancer.d.ts.map +1 -0
  177. package/dist/interfaces/IRebalancer.js +2 -0
  178. package/dist/interfaces/IRebalancer.js.map +1 -0
  179. package/dist/interfaces/IStrategy.d.ts +60 -0
  180. package/dist/interfaces/IStrategy.d.ts.map +1 -0
  181. package/dist/interfaces/IStrategy.js +13 -0
  182. package/dist/interfaces/IStrategy.js.map +1 -0
  183. package/dist/metrics/Metrics.d.ts +25 -0
  184. package/dist/metrics/Metrics.d.ts.map +1 -0
  185. package/dist/metrics/Metrics.js +164 -0
  186. package/dist/metrics/Metrics.js.map +1 -0
  187. package/dist/metrics/PriceGetter.d.ts +10 -0
  188. package/dist/metrics/PriceGetter.d.ts.map +1 -0
  189. package/dist/metrics/PriceGetter.js +41 -0
  190. package/dist/metrics/PriceGetter.js.map +1 -0
  191. package/dist/metrics/scripts/metrics.d.ts +28 -0
  192. package/dist/metrics/scripts/metrics.d.ts.map +1 -0
  193. package/dist/metrics/scripts/metrics.js +61 -0
  194. package/dist/metrics/scripts/metrics.js.map +1 -0
  195. package/dist/monitor/Monitor.d.ts +40 -0
  196. package/dist/monitor/Monitor.d.ts.map +1 -0
  197. package/dist/monitor/Monitor.js +211 -0
  198. package/dist/monitor/Monitor.js.map +1 -0
  199. package/dist/service.d.ts +3 -0
  200. package/dist/service.d.ts.map +1 -0
  201. package/dist/service.js +205 -0
  202. package/dist/service.js.map +1 -0
  203. package/dist/strategy/BaseStrategy.d.ts +86 -0
  204. package/dist/strategy/BaseStrategy.d.ts.map +1 -0
  205. package/dist/strategy/BaseStrategy.js +342 -0
  206. package/dist/strategy/BaseStrategy.js.map +1 -0
  207. package/dist/strategy/CollateralDeficitStrategy.d.ts +65 -0
  208. package/dist/strategy/CollateralDeficitStrategy.d.ts.map +1 -0
  209. package/dist/strategy/CollateralDeficitStrategy.js +253 -0
  210. package/dist/strategy/CollateralDeficitStrategy.js.map +1 -0
  211. package/dist/strategy/CollateralDeficitStrategy.test.d.ts +2 -0
  212. package/dist/strategy/CollateralDeficitStrategy.test.d.ts.map +1 -0
  213. package/dist/strategy/CollateralDeficitStrategy.test.js +497 -0
  214. package/dist/strategy/CollateralDeficitStrategy.test.js.map +1 -0
  215. package/dist/strategy/CompositeStrategy.d.ts +18 -0
  216. package/dist/strategy/CompositeStrategy.d.ts.map +1 -0
  217. package/dist/strategy/CompositeStrategy.js +63 -0
  218. package/dist/strategy/CompositeStrategy.js.map +1 -0
  219. package/dist/strategy/CompositeStrategy.test.d.ts +2 -0
  220. package/dist/strategy/CompositeStrategy.test.d.ts.map +1 -0
  221. package/dist/strategy/CompositeStrategy.test.js +278 -0
  222. package/dist/strategy/CompositeStrategy.test.js.map +1 -0
  223. package/dist/strategy/MinAmountStrategy.d.ts +35 -0
  224. package/dist/strategy/MinAmountStrategy.d.ts.map +1 -0
  225. package/dist/strategy/MinAmountStrategy.js +112 -0
  226. package/dist/strategy/MinAmountStrategy.js.map +1 -0
  227. package/dist/strategy/MinAmountStrategy.test.d.ts +2 -0
  228. package/dist/strategy/MinAmountStrategy.test.d.ts.map +1 -0
  229. package/dist/strategy/MinAmountStrategy.test.js +547 -0
  230. package/dist/strategy/MinAmountStrategy.test.js.map +1 -0
  231. package/dist/strategy/StrategyFactory.d.ts +27 -0
  232. package/dist/strategy/StrategyFactory.d.ts.map +1 -0
  233. package/dist/strategy/StrategyFactory.js +79 -0
  234. package/dist/strategy/StrategyFactory.js.map +1 -0
  235. package/dist/strategy/StrategyFactory.test.d.ts +2 -0
  236. package/dist/strategy/StrategyFactory.test.d.ts.map +1 -0
  237. package/dist/strategy/StrategyFactory.test.js +81 -0
  238. package/dist/strategy/StrategyFactory.test.js.map +1 -0
  239. package/dist/strategy/WeightedStrategy.d.ts +32 -0
  240. package/dist/strategy/WeightedStrategy.d.ts.map +1 -0
  241. package/dist/strategy/WeightedStrategy.js +73 -0
  242. package/dist/strategy/WeightedStrategy.js.map +1 -0
  243. package/dist/strategy/WeightedStrategy.test.d.ts +2 -0
  244. package/dist/strategy/WeightedStrategy.test.d.ts.map +1 -0
  245. package/dist/strategy/WeightedStrategy.test.js +403 -0
  246. package/dist/strategy/WeightedStrategy.test.js.map +1 -0
  247. package/dist/strategy/index.d.ts +7 -0
  248. package/dist/strategy/index.d.ts.map +1 -0
  249. package/dist/strategy/index.js +7 -0
  250. package/dist/strategy/index.js.map +1 -0
  251. package/dist/test/helpers.d.ts +99 -0
  252. package/dist/test/helpers.d.ts.map +1 -0
  253. package/dist/test/helpers.js +311 -0
  254. package/dist/test/helpers.js.map +1 -0
  255. package/dist/test/lifiMocks.d.ts +52 -0
  256. package/dist/test/lifiMocks.d.ts.map +1 -0
  257. package/dist/test/lifiMocks.js +160 -0
  258. package/dist/test/lifiMocks.js.map +1 -0
  259. package/dist/tracking/ActionTracker.d.ts +90 -0
  260. package/dist/tracking/ActionTracker.d.ts.map +1 -0
  261. package/dist/tracking/ActionTracker.js +678 -0
  262. package/dist/tracking/ActionTracker.js.map +1 -0
  263. package/dist/tracking/ActionTracker.test.d.ts +2 -0
  264. package/dist/tracking/ActionTracker.test.d.ts.map +1 -0
  265. package/dist/tracking/ActionTracker.test.js +1351 -0
  266. package/dist/tracking/ActionTracker.test.js.map +1 -0
  267. package/dist/tracking/IActionTracker.d.ts +157 -0
  268. package/dist/tracking/IActionTracker.d.ts.map +1 -0
  269. package/dist/tracking/IActionTracker.js +2 -0
  270. package/dist/tracking/IActionTracker.js.map +1 -0
  271. package/dist/tracking/InflightContextAdapter.d.ts +18 -0
  272. package/dist/tracking/InflightContextAdapter.d.ts.map +1 -0
  273. package/dist/tracking/InflightContextAdapter.js +52 -0
  274. package/dist/tracking/InflightContextAdapter.js.map +1 -0
  275. package/dist/tracking/InflightContextAdapter.test.d.ts +2 -0
  276. package/dist/tracking/InflightContextAdapter.test.d.ts.map +1 -0
  277. package/dist/tracking/InflightContextAdapter.test.js +175 -0
  278. package/dist/tracking/InflightContextAdapter.test.js.map +1 -0
  279. package/dist/tracking/store/IStore.d.ts +41 -0
  280. package/dist/tracking/store/IStore.d.ts.map +1 -0
  281. package/dist/tracking/store/IStore.js +2 -0
  282. package/dist/tracking/store/IStore.js.map +1 -0
  283. package/dist/tracking/store/InMemoryStore.d.ts +21 -0
  284. package/dist/tracking/store/InMemoryStore.d.ts.map +1 -0
  285. package/dist/tracking/store/InMemoryStore.js +40 -0
  286. package/dist/tracking/store/InMemoryStore.js.map +1 -0
  287. package/dist/tracking/store/InMemoryStore.test.d.ts +2 -0
  288. package/dist/tracking/store/InMemoryStore.test.d.ts.map +1 -0
  289. package/dist/tracking/store/InMemoryStore.test.js +290 -0
  290. package/dist/tracking/store/InMemoryStore.test.js.map +1 -0
  291. package/dist/tracking/store/index.d.ts +3 -0
  292. package/dist/tracking/store/index.d.ts.map +1 -0
  293. package/dist/tracking/store/index.js +2 -0
  294. package/dist/tracking/store/index.js.map +1 -0
  295. package/dist/tracking/types.d.ts +77 -0
  296. package/dist/tracking/types.d.ts.map +1 -0
  297. package/dist/tracking/types.js +2 -0
  298. package/dist/tracking/types.js.map +1 -0
  299. package/dist/utils/ExplorerClient.d.ts +68 -0
  300. package/dist/utils/ExplorerClient.d.ts.map +1 -0
  301. package/dist/utils/ExplorerClient.js +324 -0
  302. package/dist/utils/ExplorerClient.js.map +1 -0
  303. package/dist/utils/ExplorerClient.test.d.ts +2 -0
  304. package/dist/utils/ExplorerClient.test.d.ts.map +1 -0
  305. package/dist/utils/ExplorerClient.test.js +261 -0
  306. package/dist/utils/ExplorerClient.test.js.map +1 -0
  307. package/dist/utils/balanceUtils.d.ts +12 -0
  308. package/dist/utils/balanceUtils.d.ts.map +1 -0
  309. package/dist/utils/balanceUtils.js +40 -0
  310. package/dist/utils/balanceUtils.js.map +1 -0
  311. package/dist/utils/balanceUtils.test.d.ts +2 -0
  312. package/dist/utils/balanceUtils.test.d.ts.map +1 -0
  313. package/dist/utils/balanceUtils.test.js +55 -0
  314. package/dist/utils/balanceUtils.test.js.map +1 -0
  315. package/dist/utils/blockTag.d.ts +14 -0
  316. package/dist/utils/blockTag.d.ts.map +1 -0
  317. package/dist/utils/blockTag.js +46 -0
  318. package/dist/utils/blockTag.js.map +1 -0
  319. package/dist/utils/bridgeUtils.d.ts +40 -0
  320. package/dist/utils/bridgeUtils.d.ts.map +1 -0
  321. package/dist/utils/bridgeUtils.js +54 -0
  322. package/dist/utils/bridgeUtils.js.map +1 -0
  323. package/dist/utils/bridgeUtils.test.d.ts +2 -0
  324. package/dist/utils/bridgeUtils.test.d.ts.map +1 -0
  325. package/dist/utils/bridgeUtils.test.js +163 -0
  326. package/dist/utils/bridgeUtils.test.js.map +1 -0
  327. package/dist/utils/errors.d.ts +4 -0
  328. package/dist/utils/errors.d.ts.map +1 -0
  329. package/dist/utils/errors.js +6 -0
  330. package/dist/utils/errors.js.map +1 -0
  331. package/dist/utils/gasEstimation.d.ts +65 -0
  332. package/dist/utils/gasEstimation.d.ts.map +1 -0
  333. package/dist/utils/gasEstimation.js +206 -0
  334. package/dist/utils/gasEstimation.js.map +1 -0
  335. package/dist/utils/solanaKeyParser.d.ts +10 -0
  336. package/dist/utils/solanaKeyParser.d.ts.map +1 -0
  337. package/dist/utils/solanaKeyParser.js +87 -0
  338. package/dist/utils/solanaKeyParser.js.map +1 -0
  339. package/dist/utils/solanaKeyParser.test.d.ts +2 -0
  340. package/dist/utils/solanaKeyParser.test.d.ts.map +1 -0
  341. package/dist/utils/solanaKeyParser.test.js +71 -0
  342. package/dist/utils/solanaKeyParser.test.js.map +1 -0
  343. package/dist/utils/tokenUtils.d.ts +38 -0
  344. package/dist/utils/tokenUtils.d.ts.map +1 -0
  345. package/dist/utils/tokenUtils.js +65 -0
  346. package/dist/utils/tokenUtils.js.map +1 -0
  347. package/dist/utils/transactionUtils.d.ts +22 -0
  348. package/dist/utils/transactionUtils.d.ts.map +1 -0
  349. package/dist/utils/transactionUtils.js +26 -0
  350. package/dist/utils/transactionUtils.js.map +1 -0
  351. package/package.json +87 -3
  352. package/src/bridges/LiFiBridge.test.ts +615 -0
  353. package/src/bridges/LiFiBridge.ts +731 -0
  354. package/src/config/RebalancerConfig.test.ts +921 -0
  355. package/src/config/RebalancerConfig.ts +63 -0
  356. package/src/config/types.ts +613 -0
  357. package/src/core/InventoryRebalancer.test.ts +1858 -0
  358. package/src/core/InventoryRebalancer.ts +1455 -0
  359. package/src/core/Rebalancer.test.ts +686 -0
  360. package/src/core/Rebalancer.ts +686 -0
  361. package/src/core/RebalancerOrchestrator.test.ts +871 -0
  362. package/src/core/RebalancerOrchestrator.ts +273 -0
  363. package/src/core/RebalancerService.test.ts +1094 -0
  364. package/src/core/RebalancerService.ts +412 -0
  365. package/src/e2e/E2E_TEST_CONVENTIONS.md +222 -0
  366. package/src/e2e/collateral-deficit.e2e-test.ts +403 -0
  367. package/src/e2e/composite.e2e-test.ts +1008 -0
  368. package/src/e2e/fixtures/routes.ts +532 -0
  369. package/src/e2e/harness/BaseLocalDeploymentManager.ts +246 -0
  370. package/src/e2e/harness/BridgeSetup.ts +95 -0
  371. package/src/e2e/harness/Erc20InventoryLocalDeploymentManager.ts +183 -0
  372. package/src/e2e/harness/Erc20LocalDeploymentManager.ts +190 -0
  373. package/src/e2e/harness/ForkIndexer.test.ts +448 -0
  374. package/src/e2e/harness/ForkIndexer.ts +183 -0
  375. package/src/e2e/harness/MixedLocalDeploymentManager.ts +196 -0
  376. package/src/e2e/harness/MockExplorerClient.ts +94 -0
  377. package/src/e2e/harness/MockExternalBridge.ts +411 -0
  378. package/src/e2e/harness/NativeLocalDeploymentManager.ts +138 -0
  379. package/src/e2e/harness/SnapshotHelper.ts +56 -0
  380. package/src/e2e/harness/TestHelpers.ts +169 -0
  381. package/src/e2e/harness/TestRebalancer.ts +758 -0
  382. package/src/e2e/harness/TransferHelper.ts +188 -0
  383. package/src/e2e/inventory-erc20-minAmount.e2e-test.ts +735 -0
  384. package/src/e2e/inventory-erc20-weighted.e2e-test.ts +733 -0
  385. package/src/e2e/inventory-minAmount.e2e-test.ts +734 -0
  386. package/src/e2e/inventory-weighted.e2e-test.ts +734 -0
  387. package/src/e2e/minAmount.e2e-test.ts +313 -0
  388. package/src/e2e/mixed-weighted.e2e-test.ts +267 -0
  389. package/src/e2e/weighted.e2e-test.ts +304 -0
  390. package/src/factories/RebalancerContextFactory.test.ts +394 -0
  391. package/src/factories/RebalancerContextFactory.ts +771 -0
  392. package/src/index.ts +125 -0
  393. package/src/interfaces/IExternalBridge.ts +117 -0
  394. package/src/interfaces/IMetrics.ts +5 -0
  395. package/src/interfaces/IMonitor.ts +71 -0
  396. package/src/interfaces/IRebalancer.ts +57 -0
  397. package/src/interfaces/IStrategy.ts +81 -0
  398. package/src/metrics/Metrics.ts +335 -0
  399. package/src/metrics/PriceGetter.ts +74 -0
  400. package/src/metrics/scripts/metrics.ts +139 -0
  401. package/src/monitor/Monitor.ts +324 -0
  402. package/src/service.ts +285 -0
  403. package/src/strategy/BaseStrategy.ts +553 -0
  404. package/src/strategy/CollateralDeficitStrategy.test.ts +728 -0
  405. package/src/strategy/CollateralDeficitStrategy.ts +421 -0
  406. package/src/strategy/CompositeStrategy.test.ts +418 -0
  407. package/src/strategy/CompositeStrategy.ts +102 -0
  408. package/src/strategy/MinAmountStrategy.test.ts +732 -0
  409. package/src/strategy/MinAmountStrategy.ts +200 -0
  410. package/src/strategy/StrategyFactory.test.ts +108 -0
  411. package/src/strategy/StrategyFactory.ts +157 -0
  412. package/src/strategy/WeightedStrategy.test.ts +529 -0
  413. package/src/strategy/WeightedStrategy.ts +126 -0
  414. package/src/strategy/index.ts +6 -0
  415. package/src/test/helpers.ts +477 -0
  416. package/src/test/lifiMocks.ts +211 -0
  417. package/src/tracking/ActionTracker.test.ts +1625 -0
  418. package/src/tracking/ActionTracker.ts +1019 -0
  419. package/src/tracking/IActionTracker.ts +213 -0
  420. package/src/tracking/InflightContextAdapter.test.ts +206 -0
  421. package/src/tracking/InflightContextAdapter.ts +75 -0
  422. package/src/tracking/store/IStore.ts +48 -0
  423. package/src/tracking/store/InMemoryStore.test.ts +338 -0
  424. package/src/tracking/store/InMemoryStore.ts +59 -0
  425. package/src/tracking/store/index.ts +2 -0
  426. package/src/tracking/types.ts +118 -0
  427. package/src/utils/ExplorerClient.test.ts +386 -0
  428. package/src/utils/ExplorerClient.ts +439 -0
  429. package/src/utils/balanceUtils.test.ts +79 -0
  430. package/src/utils/balanceUtils.ts +65 -0
  431. package/src/utils/blockTag.ts +68 -0
  432. package/src/utils/bridgeUtils.test.ts +193 -0
  433. package/src/utils/bridgeUtils.ts +101 -0
  434. package/src/utils/errors.ts +5 -0
  435. package/src/utils/gasEstimation.ts +306 -0
  436. package/src/utils/solanaKeyParser.test.ts +103 -0
  437. package/src/utils/solanaKeyParser.ts +117 -0
  438. package/src/utils/tokenUtils.ts +89 -0
  439. package/src/utils/transactionUtils.ts +41 -0
@@ -0,0 +1,1351 @@
1
+ import chai, { expect } from 'chai';
2
+ import chaiAsPromised from 'chai-as-promised';
3
+ import { pino } from 'pino';
4
+ import Sinon from 'sinon';
5
+ import { EthJsonRpcBlockParameterTag } from '@humanode-hyperlane/sdk';
6
+ import { DEFAULT_INTENT_TTL_MS, DEFAULT_MOVEMENT_STALENESS_MS, ExternalBridgeType, } from '../config/types.js';
7
+ import { ActionTracker } from './ActionTracker.js';
8
+ import { InMemoryStore } from './store/InMemoryStore.js';
9
+ chai.use(chaiAsPromised);
10
+ const testLogger = pino({ level: 'silent' });
11
+ describe('ActionTracker', () => {
12
+ let transferStore;
13
+ let rebalanceIntentStore;
14
+ let rebalanceActionStore;
15
+ let explorerClient;
16
+ let core;
17
+ let config;
18
+ let tracker;
19
+ let mailboxStub;
20
+ beforeEach(() => {
21
+ transferStore = new InMemoryStore();
22
+ rebalanceIntentStore = new InMemoryStore();
23
+ rebalanceActionStore = new InMemoryStore();
24
+ // Create stub for ExplorerClient methods with default return values
25
+ const explorerGetInflightUserTransfers = Sinon.stub().resolves([]);
26
+ const explorerGetInflightRebalanceActions = Sinon.stub().resolves([]);
27
+ explorerClient = {
28
+ getInflightUserTransfers: explorerGetInflightUserTransfers,
29
+ getInflightRebalanceActions: explorerGetInflightRebalanceActions,
30
+ };
31
+ // Create stub for adapter (used by MultiProtocolCore)
32
+ mailboxStub = {
33
+ isDelivered: Sinon.stub().resolves(false),
34
+ };
35
+ // Create stub for MultiProtocolCore
36
+ const multiProviderGetChainName = Sinon.stub().callsFake((domain) => `chain${domain}`);
37
+ const adapterStub = Sinon.stub().returns(mailboxStub);
38
+ core = {
39
+ adapter: adapterStub,
40
+ multiProvider: {
41
+ getChainName: multiProviderGetChainName,
42
+ },
43
+ };
44
+ config = {
45
+ routersByDomain: {
46
+ 1: '0xrouter1',
47
+ 2: '0xrouter2',
48
+ 3: '0xrouter3',
49
+ },
50
+ bridges: ['0xbridge1', '0xbridge2'],
51
+ rebalancerAddress: '0xrebalancer',
52
+ intentTTL: DEFAULT_INTENT_TTL_MS,
53
+ };
54
+ tracker = new ActionTracker(transferStore, rebalanceIntentStore, rebalanceActionStore, explorerClient, core, config, testLogger);
55
+ });
56
+ describe('initialize', () => {
57
+ it('should query for inflight rebalance messages and create synthetic entities', async () => {
58
+ const inflightMessages = [
59
+ {
60
+ msg_id: '0xmsg1',
61
+ origin_domain_id: 1,
62
+ destination_domain_id: 2,
63
+ sender: '0xrouter1',
64
+ recipient: '0xrouter2',
65
+ origin_tx_hash: '0xtx1',
66
+ origin_tx_sender: '0xrebalancer',
67
+ origin_tx_recipient: '0xrouter1',
68
+ is_delivered: false,
69
+ message_body: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064',
70
+ send_occurred_at: null,
71
+ },
72
+ ];
73
+ explorerClient.getInflightRebalanceActions.resolves(inflightMessages);
74
+ explorerClient.getInflightUserTransfers.resolves([]);
75
+ // Ensure mailbox returns false so action stays in_progress
76
+ mailboxStub.isDelivered.resolves(false);
77
+ await tracker.initialize();
78
+ // Verify ExplorerClient was called twice:
79
+ // 1. During startup recovery in initialize()
80
+ // 2. During syncRebalanceActions() called from initialize()
81
+ expect(explorerClient.getInflightRebalanceActions.callCount).to.equal(2);
82
+ // Verify synthetic intent and action were created
83
+ const intents = await rebalanceIntentStore.getAll();
84
+ expect(intents).to.have.lengthOf(1);
85
+ expect(intents[0].status).to.equal('in_progress');
86
+ expect(intents[0].amount).to.equal(100n);
87
+ const actions = await rebalanceActionStore.getAll();
88
+ expect(actions).to.have.lengthOf(1);
89
+ expect(actions[0].id).to.equal('0xmsg1');
90
+ expect(actions[0].status).to.equal('in_progress');
91
+ expect(actions[0].messageId).to.equal('0xmsg1');
92
+ });
93
+ it('should use send_occurred_at for createdAt when available', async () => {
94
+ const inflightMessages = [
95
+ {
96
+ msg_id: '0xmsg1',
97
+ origin_domain_id: 1,
98
+ destination_domain_id: 2,
99
+ sender: '0xrouter1',
100
+ recipient: '0xrouter2',
101
+ origin_tx_hash: '0xtx1',
102
+ origin_tx_sender: '0xrebalancer',
103
+ origin_tx_recipient: '0xrouter1',
104
+ is_delivered: false,
105
+ message_body: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064',
106
+ send_occurred_at: '2024-01-15T12:30:45',
107
+ },
108
+ ];
109
+ explorerClient.getInflightRebalanceActions.resolves(inflightMessages);
110
+ explorerClient.getInflightUserTransfers.resolves([]);
111
+ mailboxStub.isDelivered.resolves(false);
112
+ await tracker.initialize();
113
+ const intents = await rebalanceIntentStore.getAll();
114
+ expect(intents).to.have.lengthOf(1);
115
+ // Hasura timestamps are UTC without 'Z'; recoverAction appends 'Z' before parsing
116
+ const expectedMs = new Date('2024-01-15T12:30:45Z').getTime();
117
+ expect(intents[0].createdAt).to.equal(expectedMs);
118
+ const actions = await rebalanceActionStore.getAll();
119
+ expect(actions[0].createdAt).to.equal(expectedMs);
120
+ });
121
+ it('should fall back to Date.now() when send_occurred_at is null', async () => {
122
+ const before = Date.now();
123
+ const inflightMessages = [
124
+ {
125
+ msg_id: '0xmsg1',
126
+ origin_domain_id: 1,
127
+ destination_domain_id: 2,
128
+ sender: '0xrouter1',
129
+ recipient: '0xrouter2',
130
+ origin_tx_hash: '0xtx1',
131
+ origin_tx_sender: '0xrebalancer',
132
+ origin_tx_recipient: '0xrouter1',
133
+ is_delivered: false,
134
+ message_body: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064',
135
+ send_occurred_at: null,
136
+ },
137
+ ];
138
+ explorerClient.getInflightRebalanceActions.resolves(inflightMessages);
139
+ explorerClient.getInflightUserTransfers.resolves([]);
140
+ mailboxStub.isDelivered.resolves(false);
141
+ await tracker.initialize();
142
+ const after = Date.now();
143
+ const intents = await rebalanceIntentStore.getAll();
144
+ expect(intents[0].createdAt).to.be.at.least(before);
145
+ expect(intents[0].createdAt).to.be.at.most(after);
146
+ });
147
+ it('should skip action with invalid send_occurred_at timestamp', async () => {
148
+ const inflightMessages = [
149
+ {
150
+ msg_id: '0xmsg1',
151
+ origin_domain_id: 1,
152
+ destination_domain_id: 2,
153
+ sender: '0xrouter1',
154
+ recipient: '0xrouter2',
155
+ origin_tx_hash: '0xtx1',
156
+ origin_tx_sender: '0xrebalancer',
157
+ origin_tx_recipient: '0xrouter1',
158
+ is_delivered: false,
159
+ message_body: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064',
160
+ send_occurred_at: 'garbage',
161
+ },
162
+ ];
163
+ explorerClient.getInflightRebalanceActions.resolves(inflightMessages);
164
+ explorerClient.getInflightUserTransfers.resolves([]);
165
+ mailboxStub.isDelivered.resolves(false);
166
+ await tracker.initialize();
167
+ // Invalid timestamp is caught by recoverAction's catch block, so the action is skipped
168
+ const intents = await rebalanceIntentStore.getAll();
169
+ expect(intents).to.have.lengthOf(0);
170
+ const actions = await rebalanceActionStore.getAll();
171
+ expect(actions).to.have.lengthOf(0);
172
+ });
173
+ it('should skip creating action if it already exists', async () => {
174
+ const inflightMessages = [
175
+ {
176
+ msg_id: '0xmsg1',
177
+ origin_domain_id: 1,
178
+ destination_domain_id: 2,
179
+ sender: '0xrouter1',
180
+ recipient: '0xrouter2',
181
+ origin_tx_hash: '0xtx1',
182
+ origin_tx_sender: '0xrebalancer',
183
+ origin_tx_recipient: '0xrouter1',
184
+ is_delivered: false,
185
+ message_body: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064',
186
+ send_occurred_at: null,
187
+ },
188
+ ];
189
+ // Pre-create action
190
+ await rebalanceActionStore.save({
191
+ id: '0xmsg1',
192
+ type: 'rebalance_message',
193
+ status: 'in_progress',
194
+ intentId: 'existing-intent',
195
+ messageId: '0xmsg1',
196
+ origin: 1,
197
+ destination: 2,
198
+ amount: 100n,
199
+ createdAt: Date.now(),
200
+ updatedAt: Date.now(),
201
+ });
202
+ explorerClient.getInflightRebalanceActions.resolves(inflightMessages);
203
+ explorerClient.getInflightUserTransfers.resolves([]);
204
+ await tracker.initialize();
205
+ // Verify no additional action was created
206
+ const actions = await rebalanceActionStore.getAll();
207
+ expect(actions).to.have.lengthOf(1);
208
+ // Verify no intent was created either
209
+ const intents = await rebalanceIntentStore.getAll();
210
+ expect(intents).to.have.lengthOf(0);
211
+ });
212
+ });
213
+ describe('syncTransfers', () => {
214
+ it('should create new transfers from Explorer messages', async () => {
215
+ const inflightMessages = [
216
+ {
217
+ msg_id: '0xmsg1',
218
+ origin_domain_id: 1,
219
+ destination_domain_id: 2,
220
+ sender: '0xuser1',
221
+ recipient: '0xuser2',
222
+ origin_tx_hash: '0xtx1',
223
+ origin_tx_sender: '0xuser1',
224
+ origin_tx_recipient: '0xrouter1',
225
+ is_delivered: false,
226
+ message_body: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064',
227
+ send_occurred_at: null,
228
+ },
229
+ ];
230
+ explorerClient.getInflightUserTransfers.resolves(inflightMessages);
231
+ await tracker.syncTransfers();
232
+ const transfers = await transferStore.getAll();
233
+ expect(transfers).to.have.lengthOf(1);
234
+ expect(transfers[0].id).to.equal('0xmsg1');
235
+ expect(transfers[0].status).to.equal('in_progress');
236
+ expect(transfers[0].sender).to.equal('0xuser1');
237
+ expect(transfers[0].amount).to.equal(100n);
238
+ });
239
+ it('should not duplicate transfers that already exist', async () => {
240
+ // Pre-create transfer
241
+ await transferStore.save({
242
+ id: '0xmsg1',
243
+ status: 'in_progress',
244
+ messageId: '0xmsg1',
245
+ origin: 1,
246
+ destination: 2,
247
+ amount: 100n,
248
+ sender: '0xuser1',
249
+ recipient: '0xuser2',
250
+ createdAt: Date.now(),
251
+ updatedAt: Date.now(),
252
+ });
253
+ const inflightMessages = [
254
+ {
255
+ msg_id: '0xmsg1',
256
+ origin_domain_id: 1,
257
+ destination_domain_id: 2,
258
+ sender: '0xuser1',
259
+ recipient: '0xuser2',
260
+ origin_tx_hash: '0xtx1',
261
+ origin_tx_sender: '0xuser1',
262
+ origin_tx_recipient: '0xrouter1',
263
+ is_delivered: false,
264
+ message_body: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064',
265
+ send_occurred_at: null,
266
+ },
267
+ ];
268
+ explorerClient.getInflightUserTransfers.resolves(inflightMessages);
269
+ await tracker.syncTransfers();
270
+ const transfers = await transferStore.getAll();
271
+ expect(transfers).to.have.lengthOf(1);
272
+ });
273
+ it('should mark transfers as complete when delivered', async () => {
274
+ // Pre-create transfer
275
+ await transferStore.save({
276
+ id: '0xmsg1',
277
+ status: 'in_progress',
278
+ messageId: '0xmsg1',
279
+ origin: 1,
280
+ destination: 2,
281
+ amount: 100n,
282
+ sender: '0xuser1',
283
+ recipient: '0xuser2',
284
+ createdAt: Date.now(),
285
+ updatedAt: Date.now(),
286
+ });
287
+ explorerClient.getInflightUserTransfers.resolves([]);
288
+ mailboxStub.isDelivered.resolves(true);
289
+ await tracker.syncTransfers();
290
+ const transfer = await transferStore.get('0xmsg1');
291
+ expect(transfer?.status).to.equal('complete');
292
+ });
293
+ });
294
+ describe('syncRebalanceIntents', () => {
295
+ it('should mark intents as complete when fully fulfilled', async () => {
296
+ // Intent derives completion from action states, so we need a complete action
297
+ const intent = {
298
+ id: 'intent-1',
299
+ status: 'in_progress',
300
+ origin: 1,
301
+ destination: 2,
302
+ amount: 100n,
303
+ createdAt: Date.now(),
304
+ updatedAt: Date.now(),
305
+ };
306
+ const action = {
307
+ id: 'action-1',
308
+ type: 'rebalance_message',
309
+ status: 'complete',
310
+ intentId: 'intent-1',
311
+ messageId: '0xmsg1',
312
+ origin: 1,
313
+ destination: 2,
314
+ amount: 100n,
315
+ createdAt: Date.now(),
316
+ updatedAt: Date.now(),
317
+ };
318
+ await rebalanceIntentStore.save(intent);
319
+ await rebalanceActionStore.save(action);
320
+ await tracker.syncRebalanceIntents();
321
+ const updated = await rebalanceIntentStore.get('intent-1');
322
+ expect(updated?.status).to.equal('complete');
323
+ });
324
+ it('should not mark intents as complete if not fully fulfilled', async () => {
325
+ // Intent with only partial completion via actions
326
+ const intent = {
327
+ id: 'intent-1',
328
+ status: 'in_progress',
329
+ origin: 1,
330
+ destination: 2,
331
+ amount: 100n,
332
+ createdAt: Date.now(),
333
+ updatedAt: Date.now(),
334
+ };
335
+ const action = {
336
+ id: 'action-1',
337
+ type: 'rebalance_message',
338
+ status: 'complete',
339
+ intentId: 'intent-1',
340
+ messageId: '0xmsg1',
341
+ origin: 1,
342
+ destination: 2,
343
+ amount: 50n, // Only partial
344
+ createdAt: Date.now(),
345
+ updatedAt: Date.now(),
346
+ };
347
+ await rebalanceIntentStore.save(intent);
348
+ await rebalanceActionStore.save(action);
349
+ await tracker.syncRebalanceIntents();
350
+ const updated = await rebalanceIntentStore.get('intent-1');
351
+ expect(updated?.status).to.equal('in_progress');
352
+ });
353
+ it('should mark unfulfilled intents as failed when TTL exceeded', async () => {
354
+ const intent = {
355
+ id: 'intent-1',
356
+ status: 'in_progress',
357
+ origin: 1,
358
+ destination: 2,
359
+ amount: 100n,
360
+ createdAt: Date.now() - DEFAULT_INTENT_TTL_MS - 1,
361
+ updatedAt: Date.now(),
362
+ };
363
+ const action = {
364
+ id: 'action-1',
365
+ type: 'rebalance_message',
366
+ status: 'in_progress',
367
+ intentId: 'intent-1',
368
+ messageId: '0xmsg1',
369
+ origin: 1,
370
+ destination: 2,
371
+ amount: 50n,
372
+ createdAt: Date.now(),
373
+ updatedAt: Date.now(),
374
+ };
375
+ await rebalanceIntentStore.save(intent);
376
+ await rebalanceActionStore.save(action);
377
+ await tracker.syncRebalanceIntents();
378
+ const updatedIntent = await rebalanceIntentStore.get('intent-1');
379
+ expect(updatedIntent?.status).to.equal('failed');
380
+ const updatedAction = await rebalanceActionStore.get('action-1');
381
+ expect(updatedAction?.status).to.equal('failed');
382
+ });
383
+ it('should not expire intents within TTL', async () => {
384
+ const intent = {
385
+ id: 'intent-1',
386
+ status: 'in_progress',
387
+ origin: 1,
388
+ destination: 2,
389
+ amount: 100n,
390
+ createdAt: Date.now() - DEFAULT_INTENT_TTL_MS + 60_000,
391
+ updatedAt: Date.now(),
392
+ };
393
+ await rebalanceIntentStore.save(intent);
394
+ await tracker.syncRebalanceIntents();
395
+ const updated = await rebalanceIntentStore.get('intent-1');
396
+ expect(updated?.status).to.equal('in_progress');
397
+ });
398
+ });
399
+ describe('syncRebalanceActions', () => {
400
+ it('should mark actions as complete when delivered and update parent intent', async () => {
401
+ const intent = {
402
+ id: 'intent-1',
403
+ status: 'in_progress',
404
+ origin: 1,
405
+ destination: 2,
406
+ amount: 100n,
407
+ createdAt: Date.now(),
408
+ updatedAt: Date.now(),
409
+ };
410
+ const action = {
411
+ id: 'action-1',
412
+ type: 'rebalance_message',
413
+ status: 'in_progress',
414
+ intentId: 'intent-1',
415
+ messageId: '0xmsg1',
416
+ origin: 1,
417
+ destination: 2,
418
+ amount: 100n,
419
+ createdAt: Date.now(),
420
+ updatedAt: Date.now(),
421
+ };
422
+ await rebalanceIntentStore.save(intent);
423
+ await rebalanceActionStore.save(action);
424
+ mailboxStub.isDelivered.resolves(true);
425
+ await tracker.syncRebalanceActions();
426
+ // Action should be complete
427
+ const updatedAction = await rebalanceActionStore.get('action-1');
428
+ expect(updatedAction?.status).to.equal('complete');
429
+ // Intent should be complete (derived from completed action amounts)
430
+ const updatedIntent = await rebalanceIntentStore.get('intent-1');
431
+ expect(updatedIntent?.status).to.equal('complete');
432
+ });
433
+ it('should not mark actions as complete if not delivered', async () => {
434
+ const action = {
435
+ id: 'action-1',
436
+ type: 'rebalance_message',
437
+ status: 'in_progress',
438
+ intentId: 'intent-1',
439
+ messageId: '0xmsg1',
440
+ origin: 1,
441
+ destination: 2,
442
+ amount: 100n,
443
+ createdAt: Date.now(),
444
+ updatedAt: Date.now(),
445
+ };
446
+ await rebalanceActionStore.save(action);
447
+ mailboxStub.isDelivered.resolves(false);
448
+ await tracker.syncRebalanceActions();
449
+ const updatedAction = await rebalanceActionStore.get('action-1');
450
+ expect(updatedAction?.status).to.equal('in_progress');
451
+ });
452
+ });
453
+ describe('syncInventoryMovementActions', () => {
454
+ it('stores pending status on in-progress movement', async () => {
455
+ await rebalanceActionStore.save({
456
+ id: 'action-pending',
457
+ type: 'inventory_movement',
458
+ status: 'in_progress',
459
+ intentId: 'intent-1',
460
+ origin: 1,
461
+ destination: 2,
462
+ amount: 100n,
463
+ txHash: '0xtx-pending',
464
+ externalBridgeId: ExternalBridgeType.LiFi,
465
+ createdAt: Date.now() - DEFAULT_MOVEMENT_STALENESS_MS - 1,
466
+ updatedAt: Date.now() - DEFAULT_MOVEMENT_STALENESS_MS - 1,
467
+ });
468
+ const getStatus = Sinon.stub().resolves({ status: 'pending' });
469
+ await tracker.syncInventoryMovementActions({
470
+ lifi: { getStatus },
471
+ });
472
+ const action = await rebalanceActionStore.get('action-pending');
473
+ expect(action?.status).to.equal('in_progress');
474
+ expect(action?.lastBridgeStatus).to.equal('pending');
475
+ });
476
+ it('stores not_found status on in-progress movement', async () => {
477
+ await rebalanceActionStore.save({
478
+ id: 'action-not-found',
479
+ type: 'inventory_movement',
480
+ status: 'in_progress',
481
+ intentId: 'intent-1',
482
+ origin: 1,
483
+ destination: 2,
484
+ amount: 100n,
485
+ txHash: '0xtx-not-found',
486
+ externalBridgeId: ExternalBridgeType.LiFi,
487
+ createdAt: Date.now() - DEFAULT_MOVEMENT_STALENESS_MS - 1,
488
+ updatedAt: Date.now() - DEFAULT_MOVEMENT_STALENESS_MS - 1,
489
+ });
490
+ const getStatus = Sinon.stub().resolves({ status: 'not_found' });
491
+ await tracker.syncInventoryMovementActions({
492
+ lifi: { getStatus },
493
+ });
494
+ const action = await rebalanceActionStore.get('action-not-found');
495
+ expect(action?.status).to.equal('in_progress');
496
+ expect(action?.lastBridgeStatus).to.equal('not_found');
497
+ expect(action?.nonPendingSince).to.be.a('number');
498
+ });
499
+ it('clears nonPendingSince when status returns to pending', async () => {
500
+ await rebalanceActionStore.save({
501
+ id: 'action-back-to-pending',
502
+ type: 'inventory_movement',
503
+ status: 'in_progress',
504
+ intentId: 'intent-1',
505
+ origin: 1,
506
+ destination: 2,
507
+ amount: 100n,
508
+ txHash: '0xtx-back-pending',
509
+ externalBridgeId: ExternalBridgeType.LiFi,
510
+ lastBridgeStatus: 'not_found',
511
+ nonPendingSince: Date.now() - 60_000,
512
+ createdAt: Date.now() - DEFAULT_MOVEMENT_STALENESS_MS - 1,
513
+ updatedAt: Date.now(),
514
+ });
515
+ const getStatus = Sinon.stub().resolves({ status: 'pending' });
516
+ await tracker.syncInventoryMovementActions({
517
+ lifi: { getStatus },
518
+ });
519
+ const action = await rebalanceActionStore.get('action-back-to-pending');
520
+ expect(action?.lastBridgeStatus).to.equal('pending');
521
+ expect(action?.nonPendingSince).to.be.undefined;
522
+ });
523
+ it('preserves existing nonPendingSince on repeated not_found polls', async () => {
524
+ const originalNonPendingSince = Date.now() - 120_000;
525
+ await rebalanceActionStore.save({
526
+ id: 'action-repeated-not-found',
527
+ type: 'inventory_movement',
528
+ status: 'in_progress',
529
+ intentId: 'intent-1',
530
+ origin: 1,
531
+ destination: 2,
532
+ amount: 100n,
533
+ txHash: '0xtx-repeated',
534
+ externalBridgeId: ExternalBridgeType.LiFi,
535
+ lastBridgeStatus: 'not_found',
536
+ nonPendingSince: originalNonPendingSince,
537
+ createdAt: Date.now() - DEFAULT_MOVEMENT_STALENESS_MS - 1,
538
+ updatedAt: Date.now(),
539
+ });
540
+ const getStatus = Sinon.stub().resolves({ status: 'not_found' });
541
+ await tracker.syncInventoryMovementActions({
542
+ lifi: { getStatus },
543
+ });
544
+ const action = await rebalanceActionStore.get('action-repeated-not-found');
545
+ expect(action?.nonPendingSince).to.equal(originalNonPendingSince);
546
+ });
547
+ });
548
+ describe('getInProgressTransfers', () => {
549
+ it('should return only in_progress transfers', async () => {
550
+ await transferStore.save({
551
+ id: 'transfer-1',
552
+ status: 'in_progress',
553
+ messageId: '0xmsg1',
554
+ origin: 1,
555
+ destination: 2,
556
+ amount: 100n,
557
+ sender: '0xsender1',
558
+ recipient: '0xrecipient1',
559
+ createdAt: Date.now(),
560
+ updatedAt: Date.now(),
561
+ });
562
+ await transferStore.save({
563
+ id: 'transfer-2',
564
+ status: 'complete',
565
+ messageId: '0xmsg2',
566
+ origin: 2,
567
+ destination: 3,
568
+ amount: 200n,
569
+ sender: '0xsender2',
570
+ recipient: '0xrecipient2',
571
+ createdAt: Date.now(),
572
+ updatedAt: Date.now(),
573
+ });
574
+ const result = await tracker.getInProgressTransfers();
575
+ expect(result).to.have.lengthOf(1);
576
+ expect(result[0].id).to.equal('transfer-1');
577
+ });
578
+ });
579
+ describe('getActiveRebalanceIntents', () => {
580
+ it('should return only in_progress intents (origin tx confirmed)', async () => {
581
+ await rebalanceIntentStore.save({
582
+ id: 'intent-1',
583
+ status: 'not_started',
584
+ origin: 1,
585
+ destination: 2,
586
+ amount: 100n,
587
+ createdAt: Date.now(),
588
+ updatedAt: Date.now(),
589
+ });
590
+ await rebalanceIntentStore.save({
591
+ id: 'intent-2',
592
+ status: 'in_progress',
593
+ origin: 2,
594
+ destination: 3,
595
+ amount: 200n,
596
+ createdAt: Date.now(),
597
+ updatedAt: Date.now(),
598
+ });
599
+ await rebalanceIntentStore.save({
600
+ id: 'intent-3',
601
+ status: 'complete',
602
+ origin: 3,
603
+ destination: 1,
604
+ amount: 300n,
605
+ createdAt: Date.now(),
606
+ updatedAt: Date.now(),
607
+ });
608
+ // Only in_progress intents are returned - their origin tx is confirmed
609
+ // so simulation only needs to add to destination (origin already deducted on-chain)
610
+ const result = await tracker.getActiveRebalanceIntents();
611
+ expect(result).to.have.lengthOf(1);
612
+ expect(result[0].id).to.equal('intent-2');
613
+ });
614
+ });
615
+ describe('getPartiallyFulfilledInventoryIntents', () => {
616
+ it('returns not_started inventory intents', async () => {
617
+ // Create a not_started inventory intent (simulates failed execution before any action created)
618
+ await rebalanceIntentStore.save({
619
+ id: 'stuck-intent',
620
+ status: 'not_started',
621
+ origin: 1,
622
+ destination: 2,
623
+ amount: 1000000000000000000n, // 1 ETH
624
+ executionMethod: 'inventory',
625
+ createdAt: Date.now(),
626
+ updatedAt: Date.now(),
627
+ });
628
+ // Should be returned even though status is 'not_started'
629
+ const partialIntents = await tracker.getPartiallyFulfilledInventoryIntents();
630
+ expect(partialIntents).to.have.lengthOf(1);
631
+ expect(partialIntents[0].intent.id).to.equal('stuck-intent');
632
+ expect(partialIntents[0].completedAmount).to.equal(0n);
633
+ expect(partialIntents[0].remaining).to.equal(1000000000000000000n);
634
+ });
635
+ it('returns in_progress inventory intents with partial completion', async () => {
636
+ // Create an in_progress inventory intent with a completed action
637
+ await rebalanceIntentStore.save({
638
+ id: 'partial-intent',
639
+ status: 'in_progress',
640
+ origin: 1,
641
+ destination: 2,
642
+ amount: 1000000000000000000n, // 1 ETH
643
+ executionMethod: 'inventory',
644
+ createdAt: Date.now(),
645
+ updatedAt: Date.now(),
646
+ });
647
+ // Create a completed inventory_deposit action for partial amount
648
+ await rebalanceActionStore.save({
649
+ id: 'action-1',
650
+ type: 'inventory_deposit',
651
+ status: 'complete',
652
+ intentId: 'partial-intent',
653
+ origin: 1,
654
+ destination: 2,
655
+ amount: 400000000000000000n, // 0.4 ETH completed
656
+ createdAt: Date.now(),
657
+ updatedAt: Date.now(),
658
+ });
659
+ const partialIntents = await tracker.getPartiallyFulfilledInventoryIntents();
660
+ expect(partialIntents).to.have.lengthOf(1);
661
+ expect(partialIntents[0].intent.id).to.equal('partial-intent');
662
+ expect(partialIntents[0].completedAmount).to.equal(400000000000000000n);
663
+ expect(partialIntents[0].remaining).to.equal(600000000000000000n); // 0.6 ETH remaining
664
+ });
665
+ it('does not return non-inventory intents', async () => {
666
+ // Create a not_started intent without executionMethod: 'inventory'
667
+ await rebalanceIntentStore.save({
668
+ id: 'non-inventory-intent',
669
+ status: 'not_started',
670
+ origin: 1,
671
+ destination: 2,
672
+ amount: 1000000000000000000n,
673
+ // executionMethod is undefined - not an inventory intent
674
+ createdAt: Date.now(),
675
+ updatedAt: Date.now(),
676
+ });
677
+ const partialIntents = await tracker.getPartiallyFulfilledInventoryIntents();
678
+ expect(partialIntents).to.have.lengthOf(0);
679
+ });
680
+ it('returns intent with in-flight deposit and sets hasInflightDeposit flag', async () => {
681
+ // Setup: in_progress inventory intent (amount: 1 ETH = 1_000_000_000_000_000_000n)
682
+ await rebalanceIntentStore.save({
683
+ id: 'intent-with-inflight',
684
+ status: 'in_progress',
685
+ origin: 1,
686
+ destination: 2,
687
+ amount: 1000000000000000000n, // 1 ETH
688
+ executionMethod: 'inventory',
689
+ createdAt: Date.now(),
690
+ updatedAt: Date.now(),
691
+ });
692
+ // Complete inventory_deposit action (amount: 400_000_000_000_000_000n = 0.4 ETH)
693
+ await rebalanceActionStore.save({
694
+ id: 'action-complete',
695
+ type: 'inventory_deposit',
696
+ status: 'complete',
697
+ intentId: 'intent-with-inflight',
698
+ origin: 1,
699
+ destination: 2,
700
+ amount: 400000000000000000n, // 0.4 ETH completed
701
+ createdAt: Date.now(),
702
+ updatedAt: Date.now(),
703
+ });
704
+ // In_progress inventory_deposit action (amount: 300_000_000_000_000_000n = 0.3 ETH)
705
+ await rebalanceActionStore.save({
706
+ id: 'action-inflight',
707
+ type: 'inventory_deposit',
708
+ status: 'in_progress',
709
+ intentId: 'intent-with-inflight',
710
+ origin: 1,
711
+ destination: 2,
712
+ amount: 300000000000000000n, // 0.3 ETH in-flight
713
+ createdAt: Date.now(),
714
+ updatedAt: Date.now(),
715
+ });
716
+ const partialIntents = await tracker.getPartiallyFulfilledInventoryIntents();
717
+ expect(partialIntents).to.have.lengthOf(1);
718
+ expect(partialIntents[0].hasInflightDeposit).to.be.true;
719
+ expect(partialIntents[0].completedAmount).to.equal(400000000000000000n);
720
+ expect(partialIntents[0].remaining).to.equal(300000000000000000n); // 1.0 - 0.4 - 0.3
721
+ });
722
+ it('returns intent without in-flight deposit with hasInflightDeposit false', async () => {
723
+ // Setup: in_progress inventory intent (amount: 1 ETH)
724
+ await rebalanceIntentStore.save({
725
+ id: 'intent-no-inflight',
726
+ status: 'in_progress',
727
+ origin: 1,
728
+ destination: 2,
729
+ amount: 1000000000000000000n, // 1 ETH
730
+ executionMethod: 'inventory',
731
+ createdAt: Date.now(),
732
+ updatedAt: Date.now(),
733
+ });
734
+ // Complete inventory_deposit action (amount: 0.4 ETH)
735
+ await rebalanceActionStore.save({
736
+ id: 'action-complete-only',
737
+ type: 'inventory_deposit',
738
+ status: 'complete',
739
+ intentId: 'intent-no-inflight',
740
+ origin: 1,
741
+ destination: 2,
742
+ amount: 400000000000000000n, // 0.4 ETH completed
743
+ createdAt: Date.now(),
744
+ updatedAt: Date.now(),
745
+ });
746
+ // NO in_progress inventory_deposit actions
747
+ const partialIntents = await tracker.getPartiallyFulfilledInventoryIntents();
748
+ expect(partialIntents).to.have.lengthOf(1);
749
+ expect(partialIntents[0].hasInflightDeposit).to.be.false;
750
+ expect(partialIntents[0].remaining).to.equal(600000000000000000n);
751
+ });
752
+ it('returns intent when remaining is 0n but has in-flight deposit', async () => {
753
+ // intent.amount = 1 ETH, completedAmount = 0.7 ETH, inflightAmount = 0.3 ETH → remaining = 0n
754
+ await rebalanceIntentStore.save({
755
+ id: 'intent-zero-remaining',
756
+ status: 'in_progress',
757
+ origin: 1,
758
+ destination: 2,
759
+ amount: 1000000000000000000n, // 1 ETH
760
+ executionMethod: 'inventory',
761
+ createdAt: Date.now(),
762
+ updatedAt: Date.now(),
763
+ });
764
+ // 0.7 ETH already completed
765
+ await rebalanceActionStore.save({
766
+ id: 'action-complete-part',
767
+ type: 'inventory_deposit',
768
+ status: 'complete',
769
+ intentId: 'intent-zero-remaining',
770
+ origin: 1,
771
+ destination: 2,
772
+ amount: 700000000000000000n, // 0.7 ETH
773
+ createdAt: Date.now(),
774
+ updatedAt: Date.now(),
775
+ });
776
+ // 0.3 ETH in-flight — exactly fills the remaining gap
777
+ await rebalanceActionStore.save({
778
+ id: 'action-inflight-rest',
779
+ type: 'inventory_deposit',
780
+ status: 'in_progress',
781
+ intentId: 'intent-zero-remaining',
782
+ origin: 1,
783
+ destination: 2,
784
+ amount: 300000000000000000n, // 0.3 ETH in-flight
785
+ createdAt: Date.now(),
786
+ updatedAt: Date.now(),
787
+ });
788
+ const partialIntents = await tracker.getPartiallyFulfilledInventoryIntents();
789
+ expect(partialIntents).to.have.lengthOf(1);
790
+ expect(partialIntents[0].remaining).to.equal(0n);
791
+ expect(partialIntents[0].hasInflightDeposit).to.be.true;
792
+ expect(partialIntents[0].completedAmount).to.equal(700000000000000000n);
793
+ });
794
+ it('does not return fully completed intent with no in-flight deposits', async () => {
795
+ // intent.amount = 1 ETH, completedAmount = 1 ETH, inflightAmount = 0 → remaining = 0n, no inflight
796
+ await rebalanceIntentStore.save({
797
+ id: 'intent-fully-done',
798
+ status: 'in_progress',
799
+ origin: 1,
800
+ destination: 2,
801
+ amount: 1000000000000000000n, // 1 ETH
802
+ executionMethod: 'inventory',
803
+ createdAt: Date.now(),
804
+ updatedAt: Date.now(),
805
+ });
806
+ // Full amount completed
807
+ await rebalanceActionStore.save({
808
+ id: 'action-all-done',
809
+ type: 'inventory_deposit',
810
+ status: 'complete',
811
+ intentId: 'intent-fully-done',
812
+ origin: 1,
813
+ destination: 2,
814
+ amount: 1000000000000000000n, // 1 ETH — full amount
815
+ createdAt: Date.now(),
816
+ updatedAt: Date.now(),
817
+ });
818
+ const partialIntents = await tracker.getPartiallyFulfilledInventoryIntents();
819
+ // remaining = 0n AND inflightAmount = 0n → should NOT be returned
820
+ expect(partialIntents).to.have.lengthOf(0);
821
+ });
822
+ it('skips intent with recent in-flight inventory_movement', async () => {
823
+ await rebalanceIntentStore.save({
824
+ id: 'intent-recent-movement',
825
+ status: 'in_progress',
826
+ origin: 1,
827
+ destination: 2,
828
+ amount: 1000000000000000000n,
829
+ executionMethod: 'inventory',
830
+ createdAt: Date.now(),
831
+ updatedAt: Date.now(),
832
+ });
833
+ // Recent movement (created just now)
834
+ await rebalanceActionStore.save({
835
+ id: 'movement-recent',
836
+ type: 'inventory_movement',
837
+ status: 'in_progress',
838
+ intentId: 'intent-recent-movement',
839
+ origin: 1,
840
+ destination: 2,
841
+ amount: 1000000000000000000n,
842
+ createdAt: Date.now(),
843
+ updatedAt: Date.now(),
844
+ });
845
+ const partialIntents = await tracker.getPartiallyFulfilledInventoryIntents();
846
+ expect(partialIntents).to.have.lengthOf(0);
847
+ });
848
+ it('fails stale movement and returns intent', async () => {
849
+ await rebalanceIntentStore.save({
850
+ id: 'intent-stale-movement',
851
+ status: 'in_progress',
852
+ origin: 1,
853
+ destination: 2,
854
+ amount: 1000000000000000000n,
855
+ executionMethod: 'inventory',
856
+ createdAt: Date.now(),
857
+ updatedAt: Date.now(),
858
+ });
859
+ // Stale movement (non-pending for > 30 min)
860
+ await rebalanceActionStore.save({
861
+ id: 'movement-stale',
862
+ type: 'inventory_movement',
863
+ status: 'in_progress',
864
+ lastBridgeStatus: 'not_found',
865
+ nonPendingSince: Date.now() - DEFAULT_MOVEMENT_STALENESS_MS - 1,
866
+ intentId: 'intent-stale-movement',
867
+ origin: 1,
868
+ destination: 2,
869
+ amount: 1000000000000000000n,
870
+ createdAt: Date.now() - DEFAULT_MOVEMENT_STALENESS_MS - 1,
871
+ updatedAt: Date.now(),
872
+ });
873
+ const partialIntents = await tracker.getPartiallyFulfilledInventoryIntents();
874
+ expect(partialIntents).to.have.lengthOf(1);
875
+ expect(partialIntents[0].intent.id).to.equal('intent-stale-movement');
876
+ // Verify the stale movement was failed
877
+ const failedAction = await rebalanceActionStore.get('movement-stale');
878
+ expect(failedAction?.status).to.equal('failed');
879
+ });
880
+ it('fails stale movement with undefined lastBridgeStatus (pre-deploy data)', async () => {
881
+ await rebalanceIntentStore.save({
882
+ id: 'intent-undefined-status',
883
+ status: 'in_progress',
884
+ origin: 1,
885
+ destination: 2,
886
+ amount: 1000000000000000000n,
887
+ executionMethod: 'inventory',
888
+ createdAt: Date.now(),
889
+ updatedAt: Date.now(),
890
+ });
891
+ // Stale movement without lastBridgeStatus (simulates pre-deploy data)
892
+ await rebalanceActionStore.save({
893
+ id: 'movement-undefined-status',
894
+ type: 'inventory_movement',
895
+ status: 'in_progress',
896
+ intentId: 'intent-undefined-status',
897
+ origin: 1,
898
+ destination: 2,
899
+ amount: 1000000000000000000n,
900
+ createdAt: Date.now() - DEFAULT_MOVEMENT_STALENESS_MS - 1,
901
+ updatedAt: Date.now() - DEFAULT_MOVEMENT_STALENESS_MS - 1,
902
+ });
903
+ const partialIntents = await tracker.getPartiallyFulfilledInventoryIntents();
904
+ expect(partialIntents).to.have.lengthOf(1);
905
+ const action = await rebalanceActionStore.get('movement-undefined-status');
906
+ expect(action?.status).to.equal('failed');
907
+ });
908
+ it('does not fail long-running pending movement', async () => {
909
+ await rebalanceIntentStore.save({
910
+ id: 'intent-pending-movement',
911
+ status: 'in_progress',
912
+ origin: 1,
913
+ destination: 2,
914
+ amount: 1000000000000000000n,
915
+ executionMethod: 'inventory',
916
+ createdAt: Date.now(),
917
+ updatedAt: Date.now(),
918
+ });
919
+ await rebalanceActionStore.save({
920
+ id: 'movement-pending-old',
921
+ type: 'inventory_movement',
922
+ status: 'in_progress',
923
+ lastBridgeStatus: 'pending',
924
+ intentId: 'intent-pending-movement',
925
+ origin: 1,
926
+ destination: 2,
927
+ amount: 1000000000000000000n,
928
+ createdAt: Date.now() - DEFAULT_MOVEMENT_STALENESS_MS - 1,
929
+ updatedAt: Date.now() - DEFAULT_MOVEMENT_STALENESS_MS - 1,
930
+ });
931
+ const partialIntents = await tracker.getPartiallyFulfilledInventoryIntents();
932
+ expect(partialIntents).to.have.lengthOf(0);
933
+ const action = await rebalanceActionStore.get('movement-pending-old');
934
+ expect(action?.status).to.equal('in_progress');
935
+ });
936
+ it('does not fail old movement that recently became non-pending', async () => {
937
+ await rebalanceIntentStore.save({
938
+ id: 'intent-recent-not-found',
939
+ status: 'in_progress',
940
+ origin: 1,
941
+ destination: 2,
942
+ amount: 1000000000000000000n,
943
+ executionMethod: 'inventory',
944
+ createdAt: Date.now(),
945
+ updatedAt: Date.now(),
946
+ });
947
+ // Old by createdAt but only recently transitioned to not_found
948
+ await rebalanceActionStore.save({
949
+ id: 'movement-recent-not-found',
950
+ type: 'inventory_movement',
951
+ status: 'in_progress',
952
+ lastBridgeStatus: 'not_found',
953
+ nonPendingSince: Date.now() - 60_000, // only 1 min ago
954
+ intentId: 'intent-recent-not-found',
955
+ origin: 1,
956
+ destination: 2,
957
+ amount: 1000000000000000000n,
958
+ createdAt: Date.now() - DEFAULT_MOVEMENT_STALENESS_MS - 1,
959
+ updatedAt: Date.now(),
960
+ });
961
+ const partialIntents = await tracker.getPartiallyFulfilledInventoryIntents();
962
+ expect(partialIntents).to.have.lengthOf(0);
963
+ const action = await rebalanceActionStore.get('movement-recent-not-found');
964
+ expect(action?.status).to.equal('in_progress');
965
+ });
966
+ it('handles mix of recent and stale movements', async () => {
967
+ await rebalanceIntentStore.save({
968
+ id: 'intent-mixed-movements',
969
+ status: 'in_progress',
970
+ origin: 1,
971
+ destination: 2,
972
+ amount: 1000000000000000000n,
973
+ executionMethod: 'inventory',
974
+ createdAt: Date.now(),
975
+ updatedAt: Date.now(),
976
+ });
977
+ // One stale movement (non-pending for > 30 min)
978
+ await rebalanceActionStore.save({
979
+ id: 'movement-old',
980
+ type: 'inventory_movement',
981
+ status: 'in_progress',
982
+ lastBridgeStatus: 'not_found',
983
+ nonPendingSince: Date.now() - DEFAULT_MOVEMENT_STALENESS_MS - 1,
984
+ intentId: 'intent-mixed-movements',
985
+ origin: 1,
986
+ destination: 2,
987
+ amount: 500000000000000000n,
988
+ createdAt: Date.now() - DEFAULT_MOVEMENT_STALENESS_MS - 1,
989
+ updatedAt: Date.now(),
990
+ });
991
+ // One recent movement
992
+ await rebalanceActionStore.save({
993
+ id: 'movement-new',
994
+ type: 'inventory_movement',
995
+ status: 'in_progress',
996
+ lastBridgeStatus: 'pending',
997
+ intentId: 'intent-mixed-movements',
998
+ origin: 1,
999
+ destination: 2,
1000
+ amount: 500000000000000000n,
1001
+ createdAt: Date.now(),
1002
+ updatedAt: Date.now(),
1003
+ });
1004
+ // Should skip because there's still a recent movement
1005
+ const partialIntents = await tracker.getPartiallyFulfilledInventoryIntents();
1006
+ expect(partialIntents).to.have.lengthOf(0);
1007
+ const staleAction = await rebalanceActionStore.get('movement-old');
1008
+ expect(staleAction?.status).to.equal('in_progress');
1009
+ });
1010
+ });
1011
+ describe('createRebalanceIntent', () => {
1012
+ it('should create a new intent with status not_started', async () => {
1013
+ const result = await tracker.createRebalanceIntent({
1014
+ origin: 1,
1015
+ destination: 2,
1016
+ amount: 100n,
1017
+ priority: 1,
1018
+ strategyType: 'MinAmountStrategy',
1019
+ });
1020
+ expect(result.status).to.equal('not_started');
1021
+ expect(result.origin).to.equal(1);
1022
+ expect(result.destination).to.equal(2);
1023
+ expect(result.amount).to.equal(100n);
1024
+ expect(result.priority).to.equal(1);
1025
+ expect(result.strategyType).to.equal('MinAmountStrategy');
1026
+ const stored = await rebalanceIntentStore.get(result.id);
1027
+ expect(stored).to.deep.equal(result);
1028
+ });
1029
+ });
1030
+ describe('createRebalanceAction', () => {
1031
+ it('should create action and transition intent from not_started to in_progress', async () => {
1032
+ const intent = {
1033
+ id: 'intent-1',
1034
+ status: 'not_started',
1035
+ origin: 1,
1036
+ destination: 2,
1037
+ amount: 100n,
1038
+ createdAt: Date.now(),
1039
+ updatedAt: Date.now(),
1040
+ };
1041
+ await rebalanceIntentStore.save(intent);
1042
+ const result = await tracker.createRebalanceAction({
1043
+ type: 'rebalance_message',
1044
+ intentId: 'intent-1',
1045
+ origin: 1,
1046
+ destination: 2,
1047
+ amount: 100n,
1048
+ messageId: '0xmsg1',
1049
+ txHash: '0xtx1',
1050
+ });
1051
+ expect(result.status).to.equal('in_progress');
1052
+ expect(result.intentId).to.equal('intent-1');
1053
+ expect(result.messageId).to.equal('0xmsg1');
1054
+ const updatedIntent = await rebalanceIntentStore.get('intent-1');
1055
+ expect(updatedIntent?.status).to.equal('in_progress');
1056
+ });
1057
+ it('should not transition intent if already in_progress', async () => {
1058
+ const intent = {
1059
+ id: 'intent-1',
1060
+ status: 'in_progress',
1061
+ origin: 1,
1062
+ destination: 2,
1063
+ amount: 100n,
1064
+ createdAt: Date.now(),
1065
+ updatedAt: Date.now(),
1066
+ };
1067
+ await rebalanceIntentStore.save(intent);
1068
+ await tracker.createRebalanceAction({
1069
+ type: 'rebalance_message',
1070
+ intentId: 'intent-1',
1071
+ origin: 1,
1072
+ destination: 2,
1073
+ amount: 50n,
1074
+ messageId: '0xmsg2',
1075
+ txHash: '0xtx2',
1076
+ });
1077
+ const updatedIntent = await rebalanceIntentStore.get('intent-1');
1078
+ expect(updatedIntent?.status).to.equal('in_progress');
1079
+ });
1080
+ });
1081
+ describe('completeRebalanceAction', () => {
1082
+ it('should mark action as complete and mark parent intent complete if fully fulfilled', async () => {
1083
+ const intent = {
1084
+ id: 'intent-1',
1085
+ status: 'in_progress',
1086
+ origin: 1,
1087
+ destination: 2,
1088
+ amount: 100n,
1089
+ createdAt: Date.now(),
1090
+ updatedAt: Date.now(),
1091
+ };
1092
+ const action = {
1093
+ id: 'action-1',
1094
+ type: 'rebalance_message',
1095
+ status: 'in_progress',
1096
+ intentId: 'intent-1',
1097
+ messageId: '0xmsg1',
1098
+ origin: 1,
1099
+ destination: 2,
1100
+ amount: 100n,
1101
+ createdAt: Date.now(),
1102
+ updatedAt: Date.now(),
1103
+ };
1104
+ await rebalanceIntentStore.save(intent);
1105
+ await rebalanceActionStore.save(action);
1106
+ await tracker.completeRebalanceAction('action-1');
1107
+ const updatedAction = await rebalanceActionStore.get('action-1');
1108
+ expect(updatedAction?.status).to.equal('complete');
1109
+ // Intent should be complete (derived from completed action amounts)
1110
+ const updatedIntent = await rebalanceIntentStore.get('intent-1');
1111
+ expect(updatedIntent?.status).to.equal('complete');
1112
+ });
1113
+ it('should throw error when action not found', async () => {
1114
+ await expect(tracker.completeRebalanceAction('non-existent')).to.be.rejectedWith('RebalanceAction non-existent not found');
1115
+ });
1116
+ });
1117
+ describe('cancelRebalanceIntent', () => {
1118
+ it('should mark intent as cancelled', async () => {
1119
+ const intent = {
1120
+ id: 'intent-1',
1121
+ status: 'not_started',
1122
+ origin: 1,
1123
+ destination: 2,
1124
+ amount: 100n,
1125
+ createdAt: Date.now(),
1126
+ updatedAt: Date.now(),
1127
+ };
1128
+ await rebalanceIntentStore.save(intent);
1129
+ await tracker.cancelRebalanceIntent('intent-1');
1130
+ const updated = await rebalanceIntentStore.get('intent-1');
1131
+ expect(updated?.status).to.equal('cancelled');
1132
+ });
1133
+ });
1134
+ describe('failRebalanceAction', () => {
1135
+ it('should mark action as failed', async () => {
1136
+ const action = {
1137
+ id: 'action-1',
1138
+ type: 'rebalance_message',
1139
+ status: 'in_progress',
1140
+ intentId: 'intent-1',
1141
+ messageId: '0xmsg1',
1142
+ origin: 1,
1143
+ destination: 2,
1144
+ amount: 100n,
1145
+ createdAt: Date.now(),
1146
+ updatedAt: Date.now(),
1147
+ };
1148
+ await rebalanceActionStore.save(action);
1149
+ await tracker.failRebalanceAction('action-1');
1150
+ const updated = await rebalanceActionStore.get('action-1');
1151
+ expect(updated?.status).to.equal('failed');
1152
+ });
1153
+ });
1154
+ describe('Explorer query parameters', () => {
1155
+ it('should pass routersByDomain to getInflightRebalanceActions for warp route filtering', async () => {
1156
+ explorerClient.getInflightRebalanceActions.resolves([]);
1157
+ explorerClient.getInflightUserTransfers.resolves([]);
1158
+ await tracker.initialize();
1159
+ const call = explorerClient.getInflightRebalanceActions.firstCall;
1160
+ expect(call).to.not.be.null;
1161
+ const params = call.args[0];
1162
+ expect(params.routersByDomain).to.deep.equal(config.routersByDomain);
1163
+ expect(params.bridges).to.deep.equal(config.bridges);
1164
+ expect(params.rebalancerAddress).to.equal(config.rebalancerAddress);
1165
+ });
1166
+ it('should pass routersByDomain to getInflightUserTransfers for warp route filtering', async () => {
1167
+ explorerClient.getInflightRebalanceActions.resolves([]);
1168
+ explorerClient.getInflightUserTransfers.resolves([]);
1169
+ await tracker.initialize();
1170
+ const call = explorerClient.getInflightUserTransfers.firstCall;
1171
+ expect(call).to.not.be.null;
1172
+ const params = call.args[0];
1173
+ expect(params.routersByDomain).to.deep.equal(config.routersByDomain);
1174
+ expect(params.excludeTxSenders).to.deep.equal([config.rebalancerAddress]);
1175
+ });
1176
+ });
1177
+ describe('delivery check synchronization', () => {
1178
+ it('should check delivery status in syncTransfers using adapter', async () => {
1179
+ await transferStore.save({
1180
+ id: '0xmsg1',
1181
+ status: 'in_progress',
1182
+ messageId: '0xmsg1',
1183
+ origin: 1,
1184
+ destination: 2,
1185
+ amount: 100n,
1186
+ sender: '0xuser1',
1187
+ recipient: '0xuser2',
1188
+ createdAt: Date.now(),
1189
+ updatedAt: Date.now(),
1190
+ });
1191
+ explorerClient.getInflightUserTransfers.resolves([]);
1192
+ mailboxStub.isDelivered.resolves(true);
1193
+ await tracker.syncTransfers();
1194
+ expect(mailboxStub.isDelivered.calledOnce).to.be.true;
1195
+ const call = mailboxStub.isDelivered.firstCall;
1196
+ expect(call.args[0]).to.equal('0xmsg1');
1197
+ const transfer = await transferStore.get('0xmsg1');
1198
+ expect(transfer?.status).to.equal('complete');
1199
+ });
1200
+ it('should check delivery status in syncRebalanceActions using adapter', async () => {
1201
+ const intent = {
1202
+ id: 'intent-1',
1203
+ status: 'in_progress',
1204
+ origin: 1,
1205
+ destination: 2,
1206
+ amount: 100n,
1207
+ createdAt: Date.now(),
1208
+ updatedAt: Date.now(),
1209
+ };
1210
+ const action = {
1211
+ id: 'action-1',
1212
+ type: 'rebalance_message',
1213
+ status: 'in_progress',
1214
+ intentId: 'intent-1',
1215
+ messageId: '0xmsg1',
1216
+ origin: 1,
1217
+ destination: 2,
1218
+ amount: 100n,
1219
+ createdAt: Date.now(),
1220
+ updatedAt: Date.now(),
1221
+ };
1222
+ await rebalanceIntentStore.save(intent);
1223
+ await rebalanceActionStore.save(action);
1224
+ explorerClient.getInflightRebalanceActions.resolves([]);
1225
+ mailboxStub.isDelivered.resolves(true);
1226
+ await tracker.syncRebalanceActions();
1227
+ expect(mailboxStub.isDelivered.calledOnce).to.be.true;
1228
+ const call = mailboxStub.isDelivered.firstCall;
1229
+ expect(call.args[0]).to.equal('0xmsg1');
1230
+ const updatedAction = await rebalanceActionStore.get('action-1');
1231
+ expect(updatedAction?.status).to.equal('complete');
1232
+ });
1233
+ it('should keep transfer in_progress when not delivered', async () => {
1234
+ await transferStore.save({
1235
+ id: '0xmsg1',
1236
+ status: 'in_progress',
1237
+ messageId: '0xmsg1',
1238
+ origin: 1,
1239
+ destination: 2,
1240
+ amount: 100n,
1241
+ sender: '0xuser1',
1242
+ recipient: '0xuser2',
1243
+ createdAt: Date.now(),
1244
+ updatedAt: Date.now(),
1245
+ });
1246
+ explorerClient.getInflightUserTransfers.resolves([]);
1247
+ mailboxStub.isDelivered.resolves(false);
1248
+ await tracker.syncTransfers();
1249
+ expect(mailboxStub.isDelivered.calledOnce).to.be.true;
1250
+ const transfer = await transferStore.get('0xmsg1');
1251
+ expect(transfer?.status).to.equal('in_progress');
1252
+ });
1253
+ it('should check delivery for multiple destinations', async () => {
1254
+ await transferStore.save({
1255
+ id: '0xmsg1',
1256
+ status: 'in_progress',
1257
+ messageId: '0xmsg1',
1258
+ origin: 1,
1259
+ destination: 3,
1260
+ amount: 100n,
1261
+ sender: '0xuser1',
1262
+ recipient: '0xuser2',
1263
+ createdAt: Date.now(),
1264
+ updatedAt: Date.now(),
1265
+ });
1266
+ explorerClient.getInflightUserTransfers.resolves([]);
1267
+ mailboxStub.isDelivered.resolves(false);
1268
+ await tracker.syncTransfers();
1269
+ expect(mailboxStub.isDelivered.calledOnce).to.be.true;
1270
+ });
1271
+ it('should pass blockTag when checking delivery in syncTransfers', async () => {
1272
+ await transferStore.save({
1273
+ id: '0xmsg1',
1274
+ status: 'in_progress',
1275
+ messageId: '0xmsg1',
1276
+ origin: 1,
1277
+ destination: 2,
1278
+ amount: 100n,
1279
+ sender: '0xuser1',
1280
+ recipient: '0xuser2',
1281
+ createdAt: Date.now(),
1282
+ updatedAt: Date.now(),
1283
+ });
1284
+ explorerClient.getInflightUserTransfers.resolves([]);
1285
+ mailboxStub.isDelivered.resolves(true);
1286
+ const confirmedBlockTags = {
1287
+ chain2: EthJsonRpcBlockParameterTag.Finalized,
1288
+ };
1289
+ await tracker.syncTransfers(confirmedBlockTags);
1290
+ expect(mailboxStub.isDelivered.calledOnce).to.be.true;
1291
+ const call = mailboxStub.isDelivered.firstCall;
1292
+ expect(call.args[0]).to.equal('0xmsg1');
1293
+ expect(call.args[1]).to.equal(EthJsonRpcBlockParameterTag.Finalized);
1294
+ });
1295
+ it('should pass blockTag when checking delivery in syncRebalanceActions', async () => {
1296
+ const intent = {
1297
+ id: 'intent-1',
1298
+ status: 'in_progress',
1299
+ origin: 1,
1300
+ destination: 2,
1301
+ amount: 100n,
1302
+ createdAt: Date.now(),
1303
+ updatedAt: Date.now(),
1304
+ };
1305
+ const action = {
1306
+ id: 'action-1',
1307
+ status: 'in_progress',
1308
+ type: 'rebalance_message',
1309
+ intentId: 'intent-1',
1310
+ messageId: '0xmsg1',
1311
+ origin: 1,
1312
+ destination: 2,
1313
+ amount: 100n,
1314
+ createdAt: Date.now(),
1315
+ updatedAt: Date.now(),
1316
+ };
1317
+ await rebalanceIntentStore.save(intent);
1318
+ await rebalanceActionStore.save(action);
1319
+ explorerClient.getInflightRebalanceActions.resolves([]);
1320
+ mailboxStub.isDelivered.resolves(true);
1321
+ const confirmedBlockTags = { chain2: 12345 };
1322
+ await tracker.syncRebalanceActions(confirmedBlockTags);
1323
+ expect(mailboxStub.isDelivered.calledOnce).to.be.true;
1324
+ const call = mailboxStub.isDelivered.firstCall;
1325
+ expect(call.args[0]).to.equal('0xmsg1');
1326
+ expect(call.args[1]).to.equal(12345);
1327
+ });
1328
+ it('should pass undefined blockTag when confirmedBlockTags not provided', async () => {
1329
+ await transferStore.save({
1330
+ id: '0xmsg1',
1331
+ status: 'in_progress',
1332
+ messageId: '0xmsg1',
1333
+ origin: 1,
1334
+ destination: 2,
1335
+ amount: 100n,
1336
+ sender: '0xuser1',
1337
+ recipient: '0xuser2',
1338
+ createdAt: Date.now(),
1339
+ updatedAt: Date.now(),
1340
+ });
1341
+ explorerClient.getInflightUserTransfers.resolves([]);
1342
+ mailboxStub.isDelivered.resolves(false);
1343
+ await tracker.syncTransfers(); // No confirmedBlockTags
1344
+ expect(mailboxStub.isDelivered.calledOnce).to.be.true;
1345
+ const call = mailboxStub.isDelivered.firstCall;
1346
+ expect(call.args[0]).to.equal('0xmsg1');
1347
+ expect(call.args[1]).to.be.undefined;
1348
+ });
1349
+ });
1350
+ });
1351
+ //# sourceMappingURL=ActionTracker.test.js.map