@humanode-hyperlane/rebalancer 27.2.2-humanode.1 → 1029.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (295) hide show
  1. package/dist/bridges/LiFiBridge.d.ts +3 -3
  2. package/dist/bridges/LiFiBridge.d.ts.map +1 -1
  3. package/dist/bridges/LiFiBridge.js +66 -55
  4. package/dist/bridges/LiFiBridge.js.map +1 -1
  5. package/dist/bridges/LiFiBridge.test.js +213 -0
  6. package/dist/bridges/LiFiBridge.test.js.map +1 -1
  7. package/dist/config/RebalancerConfig.d.ts +4 -3
  8. package/dist/config/RebalancerConfig.d.ts.map +1 -1
  9. package/dist/config/RebalancerConfig.js +7 -5
  10. package/dist/config/RebalancerConfig.js.map +1 -1
  11. package/dist/config/RebalancerConfig.test.js +278 -2
  12. package/dist/config/RebalancerConfig.test.js.map +1 -1
  13. package/dist/config/types.d.ts +707 -2664
  14. package/dist/config/types.d.ts.map +1 -1
  15. package/dist/config/types.js +62 -7
  16. package/dist/config/types.js.map +1 -1
  17. package/dist/core/InventoryRebalancer.d.ts +13 -19
  18. package/dist/core/InventoryRebalancer.d.ts.map +1 -1
  19. package/dist/core/InventoryRebalancer.js +404 -276
  20. package/dist/core/InventoryRebalancer.js.map +1 -1
  21. package/dist/core/InventoryRebalancer.test.js +814 -25
  22. package/dist/core/InventoryRebalancer.test.js.map +1 -1
  23. package/dist/core/Rebalancer.d.ts.map +1 -1
  24. package/dist/core/Rebalancer.js +12 -6
  25. package/dist/core/Rebalancer.js.map +1 -1
  26. package/dist/core/Rebalancer.test.js +51 -0
  27. package/dist/core/Rebalancer.test.js.map +1 -1
  28. package/dist/core/RebalancerOrchestrator.d.ts.map +1 -1
  29. package/dist/core/RebalancerOrchestrator.test.js +0 -1
  30. package/dist/core/RebalancerOrchestrator.test.js.map +1 -1
  31. package/dist/core/RebalancerService.d.ts.map +1 -1
  32. package/dist/core/RebalancerService.js +3 -2
  33. package/dist/core/RebalancerService.js.map +1 -1
  34. package/dist/core/RebalancerService.test.js +24 -0
  35. package/dist/core/RebalancerService.test.js.map +1 -1
  36. package/dist/e2e/fixtures/routes.d.ts +3 -3
  37. package/dist/e2e/fixtures/routes.d.ts.map +1 -1
  38. package/dist/e2e/harness/BaseLocalDeploymentManager.d.ts.map +1 -1
  39. package/dist/e2e/harness/BaseLocalDeploymentManager.js +3 -2
  40. package/dist/e2e/harness/BaseLocalDeploymentManager.js.map +1 -1
  41. package/dist/e2e/harness/Erc20InventoryLocalDeploymentManager.d.ts.map +1 -1
  42. package/dist/e2e/harness/Erc20InventoryLocalDeploymentManager.js.map +1 -1
  43. package/dist/e2e/harness/Erc20LocalDeploymentManager.d.ts.map +1 -1
  44. package/dist/e2e/harness/ForkIndexer.d.ts.map +1 -1
  45. package/dist/e2e/harness/ForkIndexer.js.map +1 -1
  46. package/dist/e2e/harness/LocalProvider.d.ts +3 -0
  47. package/dist/e2e/harness/LocalProvider.d.ts.map +1 -0
  48. package/dist/e2e/harness/LocalProvider.js +9 -0
  49. package/dist/e2e/harness/LocalProvider.js.map +1 -0
  50. package/dist/e2e/harness/LocalProvider.test.d.ts +2 -0
  51. package/dist/e2e/harness/LocalProvider.test.d.ts.map +1 -0
  52. package/dist/e2e/harness/LocalProvider.test.js +14 -0
  53. package/dist/e2e/harness/LocalProvider.test.js.map +1 -0
  54. package/dist/e2e/harness/MixedLocalDeploymentManager.d.ts.map +1 -1
  55. package/dist/e2e/harness/MixedLocalDeploymentManager.js.map +1 -1
  56. package/dist/e2e/harness/MockExplorerClient.d.ts.map +1 -1
  57. package/dist/e2e/harness/MockExplorerClient.js.map +1 -1
  58. package/dist/e2e/harness/MockExternalBridge.d.ts.map +1 -1
  59. package/dist/e2e/harness/MockExternalBridge.js.map +1 -1
  60. package/dist/e2e/harness/NativeLocalDeploymentManager.d.ts.map +1 -1
  61. package/dist/e2e/harness/NativeLocalDeploymentManager.js +2 -1
  62. package/dist/e2e/harness/NativeLocalDeploymentManager.js.map +1 -1
  63. package/dist/e2e/harness/SnapshotHelper.d.ts.map +1 -1
  64. package/dist/e2e/harness/SnapshotHelper.js +3 -5
  65. package/dist/e2e/harness/SnapshotHelper.js.map +1 -1
  66. package/dist/e2e/harness/TestHelpers.js +1 -2
  67. package/dist/e2e/harness/TestHelpers.js.map +1 -1
  68. package/dist/e2e/harness/TestRebalancer.d.ts +5 -1
  69. package/dist/e2e/harness/TestRebalancer.d.ts.map +1 -1
  70. package/dist/e2e/harness/TestRebalancer.js +14 -4
  71. package/dist/e2e/harness/TestRebalancer.js.map +1 -1
  72. package/dist/e2e/relayer-denylist.e2e-test.d.ts +2 -0
  73. package/dist/e2e/relayer-denylist.e2e-test.d.ts.map +1 -0
  74. package/dist/e2e/relayer-denylist.e2e-test.js +388 -0
  75. package/dist/e2e/relayer-denylist.e2e-test.js.map +1 -0
  76. package/dist/factories/RebalancerContextFactory.d.ts.map +1 -1
  77. package/dist/factories/RebalancerContextFactory.js +61 -31
  78. package/dist/factories/RebalancerContextFactory.js.map +1 -1
  79. package/dist/factories/RebalancerContextFactory.test.js +336 -5
  80. package/dist/factories/RebalancerContextFactory.test.js.map +1 -1
  81. package/dist/index.d.ts +2 -2
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +1 -1
  84. package/dist/index.js.map +1 -1
  85. package/dist/interfaces/IRebalancer.d.ts +4 -2
  86. package/dist/interfaces/IRebalancer.d.ts.map +1 -1
  87. package/dist/metrics/Metrics.d.ts.map +1 -1
  88. package/dist/metrics/Metrics.js +1 -1
  89. package/dist/metrics/Metrics.js.map +1 -1
  90. package/dist/metrics/PriceGetter.d.ts +1 -0
  91. package/dist/metrics/PriceGetter.d.ts.map +1 -1
  92. package/dist/metrics/PriceGetter.js +14 -4
  93. package/dist/metrics/PriceGetter.js.map +1 -1
  94. package/dist/metrics/PriceGetter.test.d.ts +2 -0
  95. package/dist/metrics/PriceGetter.test.d.ts.map +1 -0
  96. package/dist/metrics/PriceGetter.test.js +84 -0
  97. package/dist/metrics/PriceGetter.test.js.map +1 -0
  98. package/dist/metrics/scripts/metrics.d.ts +4 -4
  99. package/dist/monitor/Monitor.d.ts +1 -0
  100. package/dist/monitor/Monitor.d.ts.map +1 -1
  101. package/dist/monitor/Monitor.js +55 -46
  102. package/dist/monitor/Monitor.js.map +1 -1
  103. package/dist/monitor/Monitor.test.d.ts +2 -0
  104. package/dist/monitor/Monitor.test.d.ts.map +1 -0
  105. package/dist/monitor/Monitor.test.js +346 -0
  106. package/dist/monitor/Monitor.test.js.map +1 -0
  107. package/dist/service.d.ts +1 -1
  108. package/dist/service.d.ts.map +1 -1
  109. package/dist/service.js +9 -5
  110. package/dist/service.js.map +1 -1
  111. package/dist/strategy/BaseStrategy.d.ts.map +1 -1
  112. package/dist/strategy/BaseStrategy.js +13 -11
  113. package/dist/strategy/BaseStrategy.js.map +1 -1
  114. package/dist/strategy/CollateralDeficitStrategy.d.ts.map +1 -1
  115. package/dist/strategy/CollateralDeficitStrategy.js +2 -2
  116. package/dist/strategy/CollateralDeficitStrategy.js.map +1 -1
  117. package/dist/strategy/CompositeStrategy.d.ts.map +1 -1
  118. package/dist/strategy/CompositeStrategy.js.map +1 -1
  119. package/dist/strategy/CompositeStrategy.test.js.map +1 -1
  120. package/dist/strategy/MinAmountStrategy.d.ts +1 -0
  121. package/dist/strategy/MinAmountStrategy.d.ts.map +1 -1
  122. package/dist/strategy/MinAmountStrategy.js +12 -8
  123. package/dist/strategy/MinAmountStrategy.js.map +1 -1
  124. package/dist/strategy/MinAmountStrategy.test.js +189 -2
  125. package/dist/strategy/MinAmountStrategy.test.js.map +1 -1
  126. package/dist/strategy/StrategyFactory.d.ts.map +1 -1
  127. package/dist/strategy/StrategyFactory.js.map +1 -1
  128. package/dist/strategy/WeightedStrategy.d.ts.map +1 -1
  129. package/dist/test/helpers.d.ts +11 -3
  130. package/dist/test/helpers.d.ts.map +1 -1
  131. package/dist/test/helpers.js +9 -11
  132. package/dist/test/helpers.js.map +1 -1
  133. package/dist/test/lifiMocks.d.ts +6 -6
  134. package/dist/test/lifiMocks.d.ts.map +1 -1
  135. package/dist/test/lifiMocks.js +5 -2
  136. package/dist/test/lifiMocks.js.map +1 -1
  137. package/dist/test/pendingTransferMiddlewareMocks.d.ts +17 -0
  138. package/dist/test/pendingTransferMiddlewareMocks.d.ts.map +1 -0
  139. package/dist/test/pendingTransferMiddlewareMocks.js +43 -0
  140. package/dist/test/pendingTransferMiddlewareMocks.js.map +1 -0
  141. package/dist/tracking/ActionTracker.d.ts +4 -0
  142. package/dist/tracking/ActionTracker.d.ts.map +1 -1
  143. package/dist/tracking/ActionTracker.js +76 -9
  144. package/dist/tracking/ActionTracker.js.map +1 -1
  145. package/dist/tracking/ActionTracker.test.js +418 -1
  146. package/dist/tracking/ActionTracker.test.js.map +1 -1
  147. package/dist/tracking/IActionTracker.d.ts +4 -2
  148. package/dist/tracking/IActionTracker.d.ts.map +1 -1
  149. package/dist/tracking/InflightContextAdapter.d.ts.map +1 -1
  150. package/dist/tracking/InflightContextAdapter.js.map +1 -1
  151. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.d.ts +64 -0
  152. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.d.ts.map +1 -0
  153. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.js +474 -0
  154. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.js.map +1 -0
  155. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.test.d.ts +2 -0
  156. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.test.d.ts.map +1 -0
  157. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.test.js +1168 -0
  158. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.test.js.map +1 -0
  159. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.d.ts +61 -0
  160. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.d.ts.map +1 -0
  161. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.js +160 -0
  162. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.js.map +1 -0
  163. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.test.d.ts +2 -0
  164. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.test.d.ts.map +1 -0
  165. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.test.js +369 -0
  166. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.test.js.map +1 -0
  167. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.d.ts +60 -0
  168. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.d.ts.map +1 -0
  169. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.js +288 -0
  170. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.js.map +1 -0
  171. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.test.d.ts +2 -0
  172. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.test.d.ts.map +1 -0
  173. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.test.js +606 -0
  174. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.test.js.map +1 -0
  175. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.d.ts +25 -0
  176. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.d.ts.map +1 -0
  177. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.js +41 -0
  178. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.js.map +1 -0
  179. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.test.d.ts +2 -0
  180. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.test.d.ts.map +1 -0
  181. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.test.js +141 -0
  182. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.test.js.map +1 -0
  183. package/dist/tracking/pendingTransferMiddlewares/matchBody.d.ts +12 -0
  184. package/dist/tracking/pendingTransferMiddlewares/matchBody.d.ts.map +1 -0
  185. package/dist/tracking/pendingTransferMiddlewares/matchBody.js +29 -0
  186. package/dist/tracking/pendingTransferMiddlewares/matchBody.js.map +1 -0
  187. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.d.ts +59 -0
  188. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.d.ts.map +1 -0
  189. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.js +661 -0
  190. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.js.map +1 -0
  191. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.test.d.ts +2 -0
  192. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.test.d.ts.map +1 -0
  193. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.test.js +1093 -0
  194. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.test.js.map +1 -0
  195. package/dist/tracking/pendingTransferMiddlewares/types.d.ts +2 -0
  196. package/dist/tracking/pendingTransferMiddlewares/types.d.ts.map +1 -0
  197. package/dist/tracking/pendingTransferMiddlewares/types.js +4 -0
  198. package/dist/tracking/pendingTransferMiddlewares/types.js.map +1 -0
  199. package/dist/tracking/store/InMemoryStore.d.ts.map +1 -1
  200. package/dist/utils/ExplorerClient.d.ts +1 -0
  201. package/dist/utils/ExplorerClient.d.ts.map +1 -1
  202. package/dist/utils/ExplorerClient.js +8 -3
  203. package/dist/utils/ExplorerClient.js.map +1 -1
  204. package/dist/utils/ExplorerClient.test.js +25 -0
  205. package/dist/utils/ExplorerClient.test.js.map +1 -1
  206. package/dist/utils/balanceUtils.d.ts +7 -1
  207. package/dist/utils/balanceUtils.d.ts.map +1 -1
  208. package/dist/utils/balanceUtils.js +39 -1
  209. package/dist/utils/balanceUtils.js.map +1 -1
  210. package/dist/utils/balanceUtils.test.js +55 -1
  211. package/dist/utils/balanceUtils.test.js.map +1 -1
  212. package/dist/utils/blockTag.d.ts +5 -4
  213. package/dist/utils/blockTag.d.ts.map +1 -1
  214. package/dist/utils/blockTag.js +26 -10
  215. package/dist/utils/blockTag.js.map +1 -1
  216. package/dist/utils/blockTag.test.d.ts +2 -0
  217. package/dist/utils/blockTag.test.d.ts.map +1 -0
  218. package/dist/utils/blockTag.test.js +132 -0
  219. package/dist/utils/blockTag.test.js.map +1 -0
  220. package/dist/utils/bridgeUtils.js.map +1 -1
  221. package/dist/utils/errors.d.ts.map +1 -1
  222. package/dist/utils/gasEstimation.d.ts +2 -1
  223. package/dist/utils/gasEstimation.d.ts.map +1 -1
  224. package/dist/utils/gasEstimation.js +16 -13
  225. package/dist/utils/gasEstimation.js.map +1 -1
  226. package/dist/utils/gasEstimation.test.d.ts +2 -0
  227. package/dist/utils/gasEstimation.test.d.ts.map +1 -0
  228. package/dist/utils/gasEstimation.test.js +104 -0
  229. package/dist/utils/gasEstimation.test.js.map +1 -0
  230. package/dist/utils/tokenUtils.d.ts.map +1 -1
  231. package/dist/utils/tokenUtils.js +5 -2
  232. package/dist/utils/tokenUtils.js.map +1 -1
  233. package/package.json +23 -22
  234. package/src/bridges/LiFiBridge.test.ts +270 -0
  235. package/src/bridges/LiFiBridge.ts +90 -71
  236. package/src/config/RebalancerConfig.test.ts +315 -1
  237. package/src/config/RebalancerConfig.ts +6 -3
  238. package/src/config/types.ts +98 -8
  239. package/src/core/InventoryRebalancer.test.ts +1092 -38
  240. package/src/core/InventoryRebalancer.ts +584 -351
  241. package/src/core/Rebalancer.test.ts +84 -0
  242. package/src/core/Rebalancer.ts +22 -6
  243. package/src/core/RebalancerOrchestrator.test.ts +0 -1
  244. package/src/core/RebalancerService.test.ts +35 -0
  245. package/src/core/RebalancerService.ts +3 -2
  246. package/src/e2e/harness/BaseLocalDeploymentManager.ts +3 -1
  247. package/src/e2e/harness/LocalProvider.test.ts +16 -0
  248. package/src/e2e/harness/LocalProvider.ts +10 -0
  249. package/src/e2e/harness/NativeLocalDeploymentManager.ts +2 -1
  250. package/src/e2e/harness/SnapshotHelper.ts +2 -5
  251. package/src/e2e/harness/TestHelpers.ts +3 -3
  252. package/src/e2e/harness/TestRebalancer.ts +21 -1
  253. package/src/e2e/relayer-denylist.e2e-test.ts +558 -0
  254. package/src/factories/RebalancerContextFactory.test.ts +489 -8
  255. package/src/factories/RebalancerContextFactory.ts +86 -36
  256. package/src/index.ts +2 -0
  257. package/src/interfaces/IRebalancer.ts +4 -1
  258. package/src/metrics/Metrics.ts +1 -0
  259. package/src/metrics/PriceGetter.test.ts +113 -0
  260. package/src/metrics/PriceGetter.ts +18 -3
  261. package/src/monitor/Monitor.test.ts +408 -0
  262. package/src/monitor/Monitor.ts +123 -97
  263. package/src/service.ts +14 -8
  264. package/src/strategy/BaseStrategy.ts +18 -15
  265. package/src/strategy/CollateralDeficitStrategy.ts +4 -3
  266. package/src/strategy/MinAmountStrategy.test.ts +238 -2
  267. package/src/strategy/MinAmountStrategy.ts +29 -17
  268. package/src/test/helpers.ts +13 -12
  269. package/src/test/lifiMocks.ts +5 -2
  270. package/src/test/pendingTransferMiddlewareMocks.ts +65 -0
  271. package/src/tracking/ActionTracker.test.ts +635 -3
  272. package/src/tracking/ActionTracker.ts +92 -12
  273. package/src/tracking/IActionTracker.ts +4 -2
  274. package/src/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.test.ts +1693 -0
  275. package/src/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.ts +784 -0
  276. package/src/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.test.ts +554 -0
  277. package/src/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.ts +233 -0
  278. package/src/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.test.ts +894 -0
  279. package/src/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.ts +404 -0
  280. package/src/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.test.ts +205 -0
  281. package/src/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.ts +89 -0
  282. package/src/tracking/pendingTransferMiddlewares/fixtures/mainnet-denylist.json +3556 -0
  283. package/src/tracking/pendingTransferMiddlewares/matchBody.ts +43 -0
  284. package/src/tracking/pendingTransferMiddlewares/relayerDenylistRules.test.ts +1220 -0
  285. package/src/tracking/pendingTransferMiddlewares/relayerDenylistRules.ts +934 -0
  286. package/src/tracking/pendingTransferMiddlewares/types.ts +3 -0
  287. package/src/utils/ExplorerClient.test.ts +42 -0
  288. package/src/utils/ExplorerClient.ts +14 -3
  289. package/src/utils/balanceUtils.test.ts +87 -1
  290. package/src/utils/balanceUtils.ts +73 -2
  291. package/src/utils/blockTag.test.ts +173 -0
  292. package/src/utils/blockTag.ts +35 -15
  293. package/src/utils/gasEstimation.test.ts +192 -0
  294. package/src/utils/gasEstimation.ts +17 -11
  295. package/src/utils/tokenUtils.ts +5 -2
@@ -0,0 +1,404 @@
1
+ import { open } from 'node:fs/promises';
2
+ import { fileURLToPath } from 'node:url';
3
+ import type { Logger } from 'pino';
4
+
5
+ import { assert, sleep, timeout } from '@humanode-hyperlane/utils';
6
+
7
+ import { PendingTransferMiddlewareType } from '../../config/types.js';
8
+ import type {
9
+ ExplorerMessage,
10
+ IExplorerClient,
11
+ RebalanceActionQueryParams,
12
+ } from '../../utils/ExplorerClient.js';
13
+
14
+ import { PendingTransferMiddleware } from './PendingTransferMiddleware.js';
15
+
16
+ import {
17
+ DenylistLoadError,
18
+ DenylistLoadFailure,
19
+ type DenylistRules,
20
+ findDenyReason,
21
+ findMessageIdDenyReason,
22
+ parseDenylistFile,
23
+ } from './relayerDenylistRules.js';
24
+ import { normalizeMessageId } from './types.js';
25
+
26
+ export const FETCH_TIMEOUT_MS = 10_000;
27
+
28
+ /** Startup fetch attempts and the delay between them, as in the relayer. */
29
+ const INITIAL_LOAD_ATTEMPTS = 3;
30
+ export const INITIAL_LOAD_RETRY_DELAY_MS = 2_000;
31
+
32
+ /** The relayer's limit on a denylist file, in bytes. */
33
+ export const MAX_DENYLIST_FILE_BYTES = 4 * 1024 * 1024;
34
+
35
+ export const RELAYER_DENYLIST_REFRESH_FAILED_MESSAGE =
36
+ 'Relayer denylist refresh failed, keeping last good rules';
37
+
38
+ export const RELAYER_DENYLIST_INITIAL_LOAD_RETRY_MESSAGE =
39
+ 'Failed to fetch relayer denylist at startup, retrying';
40
+
41
+ export const RELAYER_DENYLIST_CHECK_FAILED_MESSAGE =
42
+ 'Failed to check pending transfer against relayer denylist, keeping it';
43
+
44
+ export interface RelayerDenylistMiddlewareOptions {
45
+ url: string;
46
+ refreshIntervalMs: number;
47
+ }
48
+
49
+ interface DenylistSource {
50
+ url: URL;
51
+ redactedUrl: string;
52
+ refreshIntervalMs: number;
53
+ }
54
+
55
+ /** Drops credentials, query and fragment, which may carry a token. */
56
+ export function redactUrl(url: string): string {
57
+ const parsed = new URL(url);
58
+
59
+ return parsed.protocol === 'file:'
60
+ ? `file://${parsed.pathname}`
61
+ : `${parsed.protocol}//${parsed.host}${parsed.pathname}`;
62
+ }
63
+
64
+ /** Removes the URL and its secret-bearing parts from an error message. */
65
+ function scrubUrl(message: string, url: URL): string {
66
+ let scrubbed = message.split(url.href).join(redactUrl(url.href));
67
+ for (const secret of [url.username, url.password, url.search]) {
68
+ if (secret) scrubbed = scrubbed.split(secret).join('[redacted]');
69
+ }
70
+
71
+ return scrubbed;
72
+ }
73
+
74
+ function fileTooLarge(bytes: number): Error {
75
+ return new Error(
76
+ `Denylist file is at least ${bytes} bytes; the limit is ${MAX_DENYLIST_FILE_BYTES}`,
77
+ );
78
+ }
79
+
80
+ /**
81
+ * Decodes like the relayer's serde_json parser: invalid UTF-8 is an invalid
82
+ * file, and a leading BOM is kept so `JSON.parse` rejects it.
83
+ */
84
+ function decodeUtf8(bytes: Uint8Array): string {
85
+ try {
86
+ return new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(
87
+ bytes,
88
+ );
89
+ } catch (error: unknown) {
90
+ if (!(error instanceof TypeError)) throw error;
91
+ throw new DenylistLoadError(
92
+ DenylistLoadFailure.InvalidFile,
93
+ 'Invalid denylist JSON: not valid UTF-8',
94
+ );
95
+ }
96
+ }
97
+
98
+ /** Reads at most one byte past the relayer's limit, rejecting a larger file. */
99
+ async function readFileCapped(path: string): Promise<Uint8Array> {
100
+ const file = await open(path);
101
+ try {
102
+ const buffer = Buffer.alloc(MAX_DENYLIST_FILE_BYTES + 1);
103
+ let bytes = 0;
104
+ while (bytes < buffer.length) {
105
+ const { bytesRead } = await file.read(
106
+ buffer,
107
+ bytes,
108
+ buffer.length - bytes,
109
+ );
110
+ if (bytesRead === 0) break;
111
+ bytes += bytesRead;
112
+ }
113
+ if (bytes > MAX_DENYLIST_FILE_BYTES) throw fileTooLarge(bytes);
114
+
115
+ return buffer.subarray(0, bytes);
116
+ } finally {
117
+ await file.close();
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Reads at most the relayer's limit, rejecting a larger body before buffering
123
+ * it. Cancels the body once `signal` aborts, as a fetch wrapper may not.
124
+ */
125
+ async function readBodyCapped(
126
+ response: Response,
127
+ signal: AbortSignal,
128
+ ): Promise<Uint8Array> {
129
+ const length = Number(response.headers.get('content-length'));
130
+ if (length > MAX_DENYLIST_FILE_BYTES) throw fileTooLarge(length);
131
+
132
+ const chunks: Uint8Array[] = [];
133
+ let bytes = 0;
134
+ const reader = response.body?.getReader();
135
+ // A rejected cancel means the stream already errored, which ends the read too.
136
+ const cancel = (): void => {
137
+ void reader?.cancel().catch(() => {});
138
+ };
139
+ if (signal.aborted) cancel();
140
+ else signal.addEventListener('abort', cancel, { once: true });
141
+ while (reader) {
142
+ const { done, value } = await reader.read();
143
+ if (done) break;
144
+
145
+ bytes += value.byteLength;
146
+ if (bytes > MAX_DENYLIST_FILE_BYTES) {
147
+ await reader.cancel();
148
+ throw fileTooLarge(bytes);
149
+ }
150
+ chunks.push(value);
151
+ }
152
+
153
+ return Buffer.concat(chunks);
154
+ }
155
+
156
+ async function fetchBodyCapped(
157
+ url: URL,
158
+ signal: AbortSignal,
159
+ ): Promise<Uint8Array> {
160
+ const response = await fetch(url, { signal });
161
+ if (!response.ok) {
162
+ throw new Error(`HTTP status ${response.status}`);
163
+ }
164
+
165
+ return readBodyCapped(response, signal);
166
+ }
167
+
168
+ async function readDenylistBytes(url: URL): Promise<Uint8Array> {
169
+ switch (url.protocol) {
170
+ case 'file:':
171
+ return readFileCapped(fileURLToPath(url));
172
+ case 'http:':
173
+ case 'https:': {
174
+ // The timer settles the load even if the fetch ignores the abort.
175
+ const controller = new AbortController();
176
+ try {
177
+ return await timeout(
178
+ fetchBodyCapped(url, controller.signal),
179
+ FETCH_TIMEOUT_MS,
180
+ `Denylist fetch timed out after ${FETCH_TIMEOUT_MS} ms`,
181
+ );
182
+ } finally {
183
+ controller.abort();
184
+ }
185
+ }
186
+ default:
187
+ throw new Error(`Unsupported denylist URL protocol ${url.protocol}`);
188
+ }
189
+ }
190
+
191
+ export async function fetchDenylistFile(url: URL): Promise<string> {
192
+ let bytes: Uint8Array;
193
+ try {
194
+ bytes = await readDenylistBytes(url);
195
+ } catch (error: unknown) {
196
+ throw new DenylistLoadError(
197
+ DenylistLoadFailure.FetchFailed,
198
+ scrubUrl(error instanceof Error ? error.message : String(error), url),
199
+ );
200
+ }
201
+
202
+ return decodeUtf8(bytes);
203
+ }
204
+
205
+ async function loadRules(
206
+ source: DenylistSource,
207
+ logger: Logger,
208
+ ): Promise<DenylistRules> {
209
+ const rules = parseDenylistFile(await fetchDenylistFile(source.url));
210
+
211
+ logger.debug(
212
+ {
213
+ url: source.redactedUrl,
214
+ matchRules: rules.matchRules.length,
215
+ addressRules: rules.addressRules.length,
216
+ },
217
+ 'Loaded relayer denylist',
218
+ );
219
+ return rules;
220
+ }
221
+
222
+ function loadFailure(error: unknown): DenylistLoadFailure {
223
+ return error instanceof DenylistLoadError
224
+ ? error.failure
225
+ : DenylistLoadFailure.FetchFailed;
226
+ }
227
+
228
+ /** Retries fetch failures only: an invalid file stays invalid. */
229
+ async function loadInitialRules(
230
+ source: DenylistSource,
231
+ logger: Logger,
232
+ ): Promise<DenylistRules> {
233
+ for (let attempt = 1; ; attempt++) {
234
+ try {
235
+ return await loadRules(source, logger);
236
+ } catch (error: unknown) {
237
+ if (
238
+ attempt >= INITIAL_LOAD_ATTEMPTS ||
239
+ loadFailure(error) !== DenylistLoadFailure.FetchFailed
240
+ ) {
241
+ throw error;
242
+ }
243
+
244
+ logger.warn(
245
+ {
246
+ url: source.redactedUrl,
247
+ attempt,
248
+ error: error instanceof Error ? error.message : String(error),
249
+ },
250
+ RELAYER_DENYLIST_INITIAL_LOAD_RETRY_MESSAGE,
251
+ );
252
+ await sleep(INITIAL_LOAD_RETRY_DELAY_MS);
253
+ }
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Ignores messages the relayer's runtime denylist blocks. `create()` loads the
259
+ * file; it is re-fetched once `refreshIntervalMs` has passed, and a failed
260
+ * refresh keeps the last good rules.
261
+ *
262
+ * `address` rules are checked against the Explorer's `origin_tx_sender`, the
263
+ * origin transaction's fee payer, where the relayer checks every origin
264
+ * transaction signer. A denied depositor whose transaction another account
265
+ * paid for (e.g. a sponsored Sealevel transfer) is therefore not ignored and
266
+ * stays reserved.
267
+ *
268
+ * The relayer also skips its signer check, so it may deliver a message this
269
+ * middleware ignores for its `origin_tx_sender` alone, when no dispatch tx
270
+ * hash is stored (Cosmos block-level events, an old DB backlog, a zero hash),
271
+ * the origin tx is not found (pruned or reorged out), the sender is zero
272
+ * (Radix, Aleo) or the lookup keeps failing for about 8 minutes
273
+ * (rust/main/agents/relayer/src/msg/pending_message.rs and
274
+ * runtime_denylist.rs).
275
+ *
276
+ * Only `match` rules that set nothing but `messageId` apply to the
277
+ * rebalancer's own rebalance messages, which then stop counting as incoming
278
+ * collateral. Deny a rebalance message only once its funds settled another
279
+ * way; otherwise the rebalancer sends a replacement.
280
+ */
281
+ export class RelayerDenylistMiddleware extends PendingTransferMiddleware {
282
+ private lastAttemptAt: number;
283
+ private refreshing?: Promise<void>;
284
+
285
+ private constructor(
286
+ inner: IExplorerClient,
287
+ private readonly source: DenylistSource,
288
+ private rules: DenylistRules,
289
+ private loadedAt: number,
290
+ ) {
291
+ super(inner, PendingTransferMiddlewareType.RelayerDenylist);
292
+
293
+ this.lastAttemptAt = loadedAt;
294
+ }
295
+
296
+ /**
297
+ * Loads the denylist, retrying fetch failures. Throws if it still cannot be
298
+ * fetched or is invalid.
299
+ */
300
+ static async create(
301
+ inner: IExplorerClient,
302
+ options: RelayerDenylistMiddlewareOptions,
303
+ logger: Logger,
304
+ ): Promise<RelayerDenylistMiddleware> {
305
+ const url = new URL(options.url);
306
+ const redactedUrl = redactUrl(options.url);
307
+ assert(
308
+ url.username === '' && url.password === '',
309
+ `Relayer denylist url ${redactedUrl} must not contain credentials`,
310
+ );
311
+
312
+ const source: DenylistSource = {
313
+ url,
314
+ redactedUrl,
315
+ refreshIntervalMs: options.refreshIntervalMs,
316
+ };
317
+
318
+ let rules: DenylistRules;
319
+ try {
320
+ rules = await loadInitialRules(source, logger);
321
+ } catch (error: unknown) {
322
+ const message = error instanceof Error ? error.message : String(error);
323
+ throw new Error(
324
+ `Failed to load relayer denylist from ${redactedUrl} (${loadFailure(error)}): ${message}`,
325
+ );
326
+ }
327
+
328
+ return new RelayerDenylistMiddleware(inner, source, rules, Date.now());
329
+ }
330
+
331
+ protected async findIgnored(
332
+ messages: ExplorerMessage[],
333
+ logger: Logger,
334
+ ): Promise<Map<string, string>> {
335
+ await this.refreshIfStale(logger);
336
+
337
+ const now = Date.now();
338
+ const ignored = new Map<string, string>();
339
+ for (const msg of messages) {
340
+ try {
341
+ const reason = findDenyReason(this.rules, msg, now);
342
+ if (reason !== undefined) {
343
+ ignored.set(normalizeMessageId(msg.msg_id), reason);
344
+ }
345
+ } catch (error: unknown) {
346
+ logger.warn(
347
+ {
348
+ msgId: msg.msg_id,
349
+ error: error instanceof Error ? error.message : String(error),
350
+ },
351
+ RELAYER_DENYLIST_CHECK_FAILED_MESSAGE,
352
+ );
353
+ }
354
+ }
355
+
356
+ return ignored;
357
+ }
358
+
359
+ override isIgnoredRebalanceAction(messageId: string): boolean {
360
+ return (
361
+ findMessageIdDenyReason(this.rules, messageId, Date.now()) !==
362
+ undefined || super.isIgnoredRebalanceAction(messageId)
363
+ );
364
+ }
365
+
366
+ // Keeps the rules `isIgnoredRebalanceAction` reads fresh; rows pass through.
367
+ override async getInflightRebalanceActions(
368
+ params: RebalanceActionQueryParams,
369
+ logger: Logger,
370
+ ): Promise<ExplorerMessage[]> {
371
+ await this.refreshIfStale(logger);
372
+ return super.getInflightRebalanceActions(params, logger);
373
+ }
374
+
375
+ private refreshIfStale(logger: Logger): Promise<void> {
376
+ if (this.refreshing) return this.refreshing;
377
+ if (Date.now() - this.lastAttemptAt < this.source.refreshIntervalMs) {
378
+ return Promise.resolve();
379
+ }
380
+
381
+ this.lastAttemptAt = Date.now();
382
+ this.refreshing = this.reload(logger).finally(() => {
383
+ this.refreshing = undefined;
384
+ });
385
+ return this.refreshing;
386
+ }
387
+
388
+ private async reload(logger: Logger): Promise<void> {
389
+ try {
390
+ this.rules = await loadRules(this.source, logger);
391
+ this.loadedAt = Date.now();
392
+ } catch (error: unknown) {
393
+ logger.warn(
394
+ {
395
+ url: this.source.redactedUrl,
396
+ failure: loadFailure(error),
397
+ cachedRulesAgeSecs: Math.floor((Date.now() - this.loadedAt) / 1000),
398
+ error: error instanceof Error ? error.message : String(error),
399
+ },
400
+ RELAYER_DENYLIST_REFRESH_FAILED_MESSAGE,
401
+ );
402
+ }
403
+ }
404
+ }
@@ -0,0 +1,205 @@
1
+ import { expect } from 'chai';
2
+ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { pathToFileURL } from 'node:url';
6
+ import { type Logger, pino } from 'pino';
7
+ import Sinon from 'sinon';
8
+
9
+ import { MultiProvider } from '@humanode-hyperlane/sdk';
10
+ import { ProtocolType, assert } from '@humanode-hyperlane/utils';
11
+
12
+ import {
13
+ type PendingTransferMiddlewareConfig,
14
+ PendingTransferMiddlewareType,
15
+ } from '../../config/types.js';
16
+ import {
17
+ ID_A,
18
+ ID_B,
19
+ ID_C,
20
+ PARAMS,
21
+ REBALANCE_PARAMS,
22
+ StubExplorerClient,
23
+ buildMessage,
24
+ } from '../../test/pendingTransferMiddlewareMocks.js';
25
+
26
+ import { INITIAL_LOAD_RETRY_DELAY_MS } from './RelayerDenylistMiddleware.js';
27
+ import {
28
+ type PendingTransferMiddlewareDeps,
29
+ buildPendingTransferMiddlewares,
30
+ } from './buildPendingTransferMiddlewares.js';
31
+
32
+ describe('buildPendingTransferMiddlewares', () => {
33
+ const logger = pino({ level: 'silent' });
34
+ let deps: PendingTransferMiddlewareDeps;
35
+
36
+ beforeEach(() => {
37
+ const multiProvider = Sinon.createStubInstance(MultiProvider);
38
+ multiProvider.getProtocol.returns(ProtocolType.Ethereum);
39
+ multiProvider.getProvider.throws(new Error('No provider in this test'));
40
+
41
+ deps = {
42
+ routersByDomain: { 1: '0x67899664446ccfadf75e0d3f5668be262acd1069' },
43
+ mailboxByDomain: { 1: '0xc005dc82818d67af737725bd4bf75435d065d239' },
44
+ multiProvider,
45
+ logger,
46
+ };
47
+ });
48
+
49
+ it('returns the inner client unchanged when no middlewares are configured', async () => {
50
+ const inner = new StubExplorerClient([]);
51
+
52
+ const result = await buildPendingTransferMiddlewares(inner, [], deps);
53
+
54
+ expect(result.client).to.equal(inner);
55
+ expect(result.isIgnoredTransfer).to.equal(undefined);
56
+ expect(result.isIgnoredRebalanceAction).to.equal(undefined);
57
+ });
58
+
59
+ it('builds layers in config order and unions their ignored ids', async () => {
60
+ const dir = mkdtempSync(join(tmpdir(), 'pending-transfer-middlewares-'));
61
+ try {
62
+ const writeDenylist = (name: string, ids: string[]): string => {
63
+ const path = join(dir, `${name}.json`);
64
+ writeFileSync(
65
+ path,
66
+ JSON.stringify({
67
+ rules: ids.map((messageId) => ({
68
+ reason: name,
69
+ addedBy: 'tester',
70
+ match: { messageId },
71
+ })),
72
+ }),
73
+ );
74
+
75
+ return pathToFileURL(path).href;
76
+ };
77
+ const configs: PendingTransferMiddlewareConfig[] = [
78
+ {
79
+ type: PendingTransferMiddlewareType.RelayerDenylist,
80
+ url: writeDenylist('first', [ID_A, ID_B]),
81
+ refreshIntervalSecs: 60,
82
+ },
83
+ {
84
+ type: PendingTransferMiddlewareType.RelayerDenylist,
85
+ url: writeDenylist('second', [ID_A]),
86
+ refreshIntervalSecs: 60,
87
+ },
88
+ ];
89
+ const inner = new StubExplorerClient([
90
+ buildMessage(ID_A),
91
+ buildMessage(ID_B),
92
+ buildMessage(ID_C),
93
+ ]);
94
+ const queryLogger: Logger = pino({ level: 'silent' });
95
+ const info = Sinon.spy(queryLogger, 'info');
96
+
97
+ inner.rebalanceActions = [buildMessage(ID_B), buildMessage(ID_C)];
98
+
99
+ const { client, isIgnoredTransfer, isIgnoredRebalanceAction } =
100
+ await buildPendingTransferMiddlewares(inner, configs, deps);
101
+ const result = await client.getInflightUserTransfers(PARAMS, queryLogger);
102
+ const rebalanceActions = await client.getInflightRebalanceActions(
103
+ REBALANCE_PARAMS,
104
+ queryLogger,
105
+ );
106
+
107
+ expect(result.map((msg) => msg.msg_id)).to.deep.equal([ID_C]);
108
+
109
+ // Item 0 is innermost, so it sees ID_A first and records its reason.
110
+ const ignoredLogs = info
111
+ .getCalls()
112
+ .filter((call) => call.args.includes('Ignoring pending transfer'));
113
+ expect(ignoredLogs).to.have.lengthOf(2);
114
+ for (const id of [ID_A, ID_B]) {
115
+ expect(
116
+ info.calledWith(
117
+ Sinon.match({ msgId: id, reason: 'first (added by tester)' }),
118
+ 'Ignoring pending transfer',
119
+ ),
120
+ ).to.be.true;
121
+ }
122
+
123
+ assert(isIgnoredTransfer, 'Expected an ignored-transfer predicate');
124
+ expect(isIgnoredTransfer(ID_A)).to.be.true;
125
+ expect(isIgnoredTransfer(ID_B)).to.be.true;
126
+ expect(isIgnoredTransfer(ID_C)).to.be.false;
127
+
128
+ expect(rebalanceActions.map((msg) => msg.msg_id)).to.deep.equal([
129
+ ID_B,
130
+ ID_C,
131
+ ]);
132
+ assert(
133
+ isIgnoredRebalanceAction,
134
+ 'Expected an ignored-rebalance-action predicate',
135
+ );
136
+ expect(isIgnoredRebalanceAction(ID_B)).to.be.true;
137
+ expect(isIgnoredRebalanceAction(ID_C)).to.be.false;
138
+ } finally {
139
+ rmSync(dir, { recursive: true, force: true });
140
+ }
141
+ });
142
+
143
+ it('fails when the BlacklistIsm trees cannot be resolved at startup', async () => {
144
+ const inner = new StubExplorerClient([]);
145
+ const configs: PendingTransferMiddlewareConfig[] = [
146
+ {
147
+ type: PendingTransferMiddlewareType.BlacklistIsm,
148
+ refreshIntervalSecs: 600,
149
+ },
150
+ ];
151
+
152
+ const error = await buildPendingTransferMiddlewares(
153
+ inner,
154
+ configs,
155
+ deps,
156
+ ).then(
157
+ () => expect.fail('Expected buildPendingTransferMiddlewares to throw'),
158
+ (err: unknown) => err,
159
+ );
160
+
161
+ expect(error).to.be.instanceOf(Error);
162
+ if (error instanceof Error) {
163
+ expect(error.message).to.equal(
164
+ 'Failed to resolve the ISM of router 0x67899664446ccfadf75e0d3f5668be262acd1069 on domain 1: No provider in this test',
165
+ );
166
+ }
167
+ });
168
+
169
+ it('fails when a relayer denylist cannot be loaded at startup', async () => {
170
+ const inner = new StubExplorerClient([]);
171
+ const configs: PendingTransferMiddlewareConfig[] = [
172
+ {
173
+ type: PendingTransferMiddlewareType.RelayerDenylist,
174
+ url: 'https://example.com/denylist.json',
175
+ refreshIntervalSecs: 60,
176
+ },
177
+ ];
178
+ // Startup retries a failed fetch after a delay; a stubbed fetch keeps
179
+ // the faked delays off real I/O.
180
+ const clock = Sinon.useFakeTimers({
181
+ toFake: ['setTimeout', 'clearTimeout'],
182
+ });
183
+ const fetchStub = Sinon.stub(globalThis, 'fetch').rejects(
184
+ new Error('getaddrinfo ENOTFOUND example.com'),
185
+ );
186
+ try {
187
+ const result = buildPendingTransferMiddlewares(inner, configs, deps).then(
188
+ () => expect.fail('Expected buildPendingTransferMiddlewares to throw'),
189
+ (err: unknown) => err,
190
+ );
191
+ await clock.tickAsync(2 * INITIAL_LOAD_RETRY_DELAY_MS);
192
+ const error = await result;
193
+
194
+ expect(error).to.be.instanceOf(Error);
195
+ if (error instanceof Error) {
196
+ expect(error.message).to.equal(
197
+ 'Failed to load relayer denylist from https://example.com/denylist.json (fetch_failed): getaddrinfo ENOTFOUND example.com',
198
+ );
199
+ }
200
+ } finally {
201
+ fetchStub.restore();
202
+ clock.restore();
203
+ }
204
+ });
205
+ });
@@ -0,0 +1,89 @@
1
+ import type { Logger } from 'pino';
2
+
3
+ import type { MultiProvider } from '@humanode-hyperlane/sdk';
4
+
5
+ import {
6
+ type PendingTransferMiddlewareConfig,
7
+ PendingTransferMiddlewareType,
8
+ } from '../../config/types.js';
9
+ import type { IExplorerClient } from '../../utils/ExplorerClient.js';
10
+
11
+ import { BlacklistIsmMiddleware } from './BlacklistIsmMiddleware.js';
12
+ import type { PendingTransferMiddleware } from './PendingTransferMiddleware.js';
13
+ import { RelayerDenylistMiddleware } from './RelayerDenylistMiddleware.js';
14
+
15
+ export interface PendingTransferMiddlewareDeps {
16
+ routersByDomain: Record<number, string>;
17
+ mailboxByDomain: Record<number, string>;
18
+ multiProvider: MultiProvider;
19
+ logger: Logger;
20
+ }
21
+
22
+ export interface PendingTransferMiddlewares {
23
+ client: IExplorerClient;
24
+ /** Set only when at least one middleware is configured. */
25
+ isIgnoredTransfer?: (messageId: string) => boolean;
26
+ /** Set only when at least one middleware is configured. */
27
+ isIgnoredRebalanceAction?: (messageId: string) => boolean;
28
+ }
29
+
30
+ function createMiddleware(
31
+ config: PendingTransferMiddlewareConfig,
32
+ inner: IExplorerClient,
33
+ deps: PendingTransferMiddlewareDeps,
34
+ ): Promise<PendingTransferMiddleware> {
35
+ switch (config.type) {
36
+ case PendingTransferMiddlewareType.RelayerDenylist:
37
+ return RelayerDenylistMiddleware.create(
38
+ inner,
39
+ {
40
+ url: config.url,
41
+ refreshIntervalMs: config.refreshIntervalSecs * 1_000,
42
+ },
43
+ deps.logger,
44
+ );
45
+ case PendingTransferMiddlewareType.BlacklistIsm:
46
+ return BlacklistIsmMiddleware.create(
47
+ inner,
48
+ {
49
+ refreshIntervalMs: config.refreshIntervalSecs * 1_000,
50
+ routersByDomain: deps.routersByDomain,
51
+ mailboxByDomain: deps.mailboxByDomain,
52
+ multiProvider: deps.multiProvider,
53
+ },
54
+ deps.logger,
55
+ );
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Wraps `inner` with one middleware per configured item. Item 0 is the
61
+ * innermost middleware, so it sees the raw Explorer result first and each
62
+ * later item sees only what the earlier ones kept. With no items, `inner` is
63
+ * returned unchanged.
64
+ */
65
+ export async function buildPendingTransferMiddlewares(
66
+ inner: IExplorerClient,
67
+ configs: PendingTransferMiddlewareConfig[],
68
+ deps: PendingTransferMiddlewareDeps,
69
+ ): Promise<PendingTransferMiddlewares> {
70
+ let outermost: PendingTransferMiddleware | undefined;
71
+ for (const config of configs) {
72
+ outermost = await createMiddleware(config, outermost ?? inner, deps);
73
+ }
74
+
75
+ if (!outermost) return { client: inner };
76
+
77
+ deps.logger.info(
78
+ { middlewares: configs.map((config) => config.type) },
79
+ 'Pending transfer middlewares enabled',
80
+ );
81
+
82
+ const client = outermost;
83
+ return {
84
+ client,
85
+ isIgnoredTransfer: (messageId) => client.isIgnored(messageId),
86
+ isIgnoredRebalanceAction: (messageId) =>
87
+ client.isIgnoredRebalanceAction(messageId),
88
+ };
89
+ }