@hyperlane-xyz/rebalancer 0.1.2 → 1.0.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 (223) hide show
  1. package/README.md +134 -14
  2. package/dist/config/RebalancerConfig.d.ts +2 -2
  3. package/dist/config/RebalancerConfig.d.ts.map +1 -1
  4. package/dist/config/RebalancerConfig.js +4 -3
  5. package/dist/config/RebalancerConfig.js.map +1 -1
  6. package/dist/config/RebalancerConfig.test.js +434 -163
  7. package/dist/config/RebalancerConfig.test.js.map +1 -1
  8. package/dist/config/types.d.ts +1650 -290
  9. package/dist/config/types.d.ts.map +1 -1
  10. package/dist/config/types.js +124 -46
  11. package/dist/config/types.js.map +1 -1
  12. package/dist/core/Rebalancer.d.ts +14 -7
  13. package/dist/core/Rebalancer.d.ts.map +1 -1
  14. package/dist/core/Rebalancer.js +168 -99
  15. package/dist/core/Rebalancer.js.map +1 -1
  16. package/dist/core/Rebalancer.test.d.ts +2 -0
  17. package/dist/core/Rebalancer.test.d.ts.map +1 -0
  18. package/dist/core/Rebalancer.test.js +391 -0
  19. package/dist/core/Rebalancer.test.js.map +1 -0
  20. package/dist/core/RebalancerService.d.ts +16 -2
  21. package/dist/core/RebalancerService.d.ts.map +1 -1
  22. package/dist/core/RebalancerService.js +164 -21
  23. package/dist/core/RebalancerService.js.map +1 -1
  24. package/dist/core/RebalancerService.test.d.ts +2 -0
  25. package/dist/core/RebalancerService.test.d.ts.map +1 -0
  26. package/dist/core/RebalancerService.test.js +809 -0
  27. package/dist/core/RebalancerService.test.js.map +1 -0
  28. package/dist/factories/RebalancerContextFactory.d.ts +11 -0
  29. package/dist/factories/RebalancerContextFactory.d.ts.map +1 -1
  30. package/dist/factories/RebalancerContextFactory.js +60 -13
  31. package/dist/factories/RebalancerContextFactory.js.map +1 -1
  32. package/dist/index.d.ts +6 -6
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +4 -3
  35. package/dist/index.js.map +1 -1
  36. package/dist/interfaces/IMonitor.d.ts +6 -8
  37. package/dist/interfaces/IMonitor.d.ts.map +1 -1
  38. package/dist/interfaces/IMonitor.js.map +1 -1
  39. package/dist/interfaces/IRebalancer.d.ts +20 -4
  40. package/dist/interfaces/IRebalancer.d.ts.map +1 -1
  41. package/dist/interfaces/IStrategy.d.ts +18 -2
  42. package/dist/interfaces/IStrategy.d.ts.map +1 -1
  43. package/dist/metrics/Metrics.d.ts +4 -2
  44. package/dist/metrics/Metrics.d.ts.map +1 -1
  45. package/dist/metrics/Metrics.js +21 -1
  46. package/dist/metrics/Metrics.js.map +1 -1
  47. package/dist/metrics/scripts/metrics.d.ts +2 -0
  48. package/dist/metrics/scripts/metrics.d.ts.map +1 -1
  49. package/dist/metrics/scripts/metrics.js +12 -0
  50. package/dist/metrics/scripts/metrics.js.map +1 -1
  51. package/dist/monitor/Monitor.d.ts +8 -3
  52. package/dist/monitor/Monitor.d.ts.map +1 -1
  53. package/dist/monitor/Monitor.js +75 -15
  54. package/dist/monitor/Monitor.js.map +1 -1
  55. package/dist/strategy/BaseStrategy.d.ts +51 -5
  56. package/dist/strategy/BaseStrategy.d.ts.map +1 -1
  57. package/dist/strategy/BaseStrategy.js +199 -19
  58. package/dist/strategy/BaseStrategy.js.map +1 -1
  59. package/dist/strategy/CollateralDeficitStrategy.d.ts +65 -0
  60. package/dist/strategy/CollateralDeficitStrategy.d.ts.map +1 -0
  61. package/dist/strategy/CollateralDeficitStrategy.js +245 -0
  62. package/dist/strategy/CollateralDeficitStrategy.js.map +1 -0
  63. package/dist/strategy/CollateralDeficitStrategy.test.d.ts +2 -0
  64. package/dist/strategy/CollateralDeficitStrategy.test.d.ts.map +1 -0
  65. package/dist/strategy/CollateralDeficitStrategy.test.js +364 -0
  66. package/dist/strategy/CollateralDeficitStrategy.test.js.map +1 -0
  67. package/dist/strategy/CompositeStrategy.d.ts +18 -0
  68. package/dist/strategy/CompositeStrategy.d.ts.map +1 -0
  69. package/dist/strategy/CompositeStrategy.js +63 -0
  70. package/dist/strategy/CompositeStrategy.js.map +1 -0
  71. package/dist/strategy/CompositeStrategy.test.d.ts +2 -0
  72. package/dist/strategy/CompositeStrategy.test.d.ts.map +1 -0
  73. package/dist/strategy/CompositeStrategy.test.js +265 -0
  74. package/dist/strategy/CompositeStrategy.test.js.map +1 -0
  75. package/dist/strategy/MinAmountStrategy.d.ts +12 -5
  76. package/dist/strategy/MinAmountStrategy.d.ts.map +1 -1
  77. package/dist/strategy/MinAmountStrategy.js +23 -14
  78. package/dist/strategy/MinAmountStrategy.js.map +1 -1
  79. package/dist/strategy/MinAmountStrategy.test.js +88 -20
  80. package/dist/strategy/MinAmountStrategy.test.js.map +1 -1
  81. package/dist/strategy/StrategyFactory.d.ts +15 -6
  82. package/dist/strategy/StrategyFactory.d.ts.map +1 -1
  83. package/dist/strategy/StrategyFactory.js +48 -10
  84. package/dist/strategy/StrategyFactory.js.map +1 -1
  85. package/dist/strategy/StrategyFactory.test.js +2 -2
  86. package/dist/strategy/StrategyFactory.test.js.map +1 -1
  87. package/dist/strategy/WeightedStrategy.d.ts +13 -4
  88. package/dist/strategy/WeightedStrategy.d.ts.map +1 -1
  89. package/dist/strategy/WeightedStrategy.js +18 -6
  90. package/dist/strategy/WeightedStrategy.js.map +1 -1
  91. package/dist/strategy/WeightedStrategy.test.js +108 -18
  92. package/dist/strategy/WeightedStrategy.test.js.map +1 -1
  93. package/dist/strategy/index.d.ts +2 -0
  94. package/dist/strategy/index.d.ts.map +1 -1
  95. package/dist/strategy/index.js +2 -0
  96. package/dist/strategy/index.js.map +1 -1
  97. package/dist/test/helpers.d.ts +93 -3
  98. package/dist/test/helpers.d.ts.map +1 -1
  99. package/dist/test/helpers.js +267 -10
  100. package/dist/test/helpers.js.map +1 -1
  101. package/dist/tracking/ActionTracker.d.ts +49 -0
  102. package/dist/tracking/ActionTracker.d.ts.map +1 -0
  103. package/dist/tracking/ActionTracker.js +422 -0
  104. package/dist/tracking/ActionTracker.js.map +1 -0
  105. package/dist/tracking/ActionTracker.test.d.ts +2 -0
  106. package/dist/tracking/ActionTracker.test.d.ts.map +1 -0
  107. package/dist/tracking/ActionTracker.test.js +637 -0
  108. package/dist/tracking/ActionTracker.test.js.map +1 -0
  109. package/dist/tracking/IActionTracker.d.ts +101 -0
  110. package/dist/tracking/IActionTracker.d.ts.map +1 -0
  111. package/dist/tracking/IActionTracker.js +2 -0
  112. package/dist/tracking/IActionTracker.js.map +1 -0
  113. package/dist/tracking/InflightContextAdapter.d.ts +18 -0
  114. package/dist/tracking/InflightContextAdapter.d.ts.map +1 -0
  115. package/dist/tracking/InflightContextAdapter.js +35 -0
  116. package/dist/tracking/InflightContextAdapter.js.map +1 -0
  117. package/dist/tracking/InflightContextAdapter.test.d.ts +2 -0
  118. package/dist/tracking/InflightContextAdapter.test.d.ts.map +1 -0
  119. package/dist/tracking/InflightContextAdapter.test.js +172 -0
  120. package/dist/tracking/InflightContextAdapter.test.js.map +1 -0
  121. package/dist/tracking/index.d.ts +7 -0
  122. package/dist/tracking/index.d.ts.map +1 -0
  123. package/dist/tracking/index.js +6 -0
  124. package/dist/tracking/index.js.map +1 -0
  125. package/dist/tracking/store/IStore.d.ts +41 -0
  126. package/dist/tracking/store/IStore.d.ts.map +1 -0
  127. package/dist/tracking/store/IStore.js +2 -0
  128. package/dist/tracking/store/IStore.js.map +1 -0
  129. package/dist/tracking/store/InMemoryStore.d.ts +21 -0
  130. package/dist/tracking/store/InMemoryStore.d.ts.map +1 -0
  131. package/dist/tracking/store/InMemoryStore.js +40 -0
  132. package/dist/tracking/store/InMemoryStore.js.map +1 -0
  133. package/dist/tracking/store/InMemoryStore.test.d.ts +2 -0
  134. package/dist/tracking/store/InMemoryStore.test.d.ts.map +1 -0
  135. package/dist/tracking/store/InMemoryStore.test.js +290 -0
  136. package/dist/tracking/store/InMemoryStore.test.js.map +1 -0
  137. package/dist/tracking/store/index.d.ts +3 -0
  138. package/dist/tracking/store/index.d.ts.map +1 -0
  139. package/dist/tracking/store/index.js +2 -0
  140. package/dist/tracking/store/index.js.map +1 -0
  141. package/dist/tracking/types.d.ts +43 -0
  142. package/dist/tracking/types.d.ts.map +1 -0
  143. package/dist/tracking/types.js +2 -0
  144. package/dist/tracking/types.js.map +1 -0
  145. package/dist/utils/ExplorerClient.d.ts +39 -1
  146. package/dist/utils/ExplorerClient.d.ts.map +1 -1
  147. package/dist/utils/ExplorerClient.js +205 -2
  148. package/dist/utils/ExplorerClient.js.map +1 -1
  149. package/dist/utils/balanceUtils.js +2 -2
  150. package/dist/utils/balanceUtils.js.map +1 -1
  151. package/dist/utils/balanceUtils.test.js +1 -0
  152. package/dist/utils/balanceUtils.test.js.map +1 -1
  153. package/dist/utils/bridgeUtils.d.ts +1 -3
  154. package/dist/utils/bridgeUtils.d.ts.map +1 -1
  155. package/dist/utils/bridgeUtils.js +1 -5
  156. package/dist/utils/bridgeUtils.js.map +1 -1
  157. package/dist/utils/bridgeUtils.test.js +3 -14
  158. package/dist/utils/bridgeUtils.test.js.map +1 -1
  159. package/package.json +11 -9
  160. package/src/config/RebalancerConfig.test.ts +459 -163
  161. package/src/config/RebalancerConfig.ts +5 -3
  162. package/src/config/types.ts +159 -52
  163. package/src/core/Rebalancer.test.ts +632 -0
  164. package/src/core/Rebalancer.ts +247 -157
  165. package/src/core/RebalancerService.test.ts +1144 -0
  166. package/src/core/RebalancerService.ts +245 -23
  167. package/src/factories/RebalancerContextFactory.ts +115 -14
  168. package/src/index.ts +16 -4
  169. package/src/interfaces/IMonitor.ts +15 -8
  170. package/src/interfaces/IRebalancer.ts +22 -4
  171. package/src/interfaces/IStrategy.ts +23 -2
  172. package/src/metrics/Metrics.ts +26 -5
  173. package/src/metrics/scripts/metrics.ts +14 -0
  174. package/src/monitor/Monitor.ts +109 -22
  175. package/src/strategy/BaseStrategy.ts +316 -26
  176. package/src/strategy/CollateralDeficitStrategy.test.ts +551 -0
  177. package/src/strategy/CollateralDeficitStrategy.ts +390 -0
  178. package/src/strategy/CompositeStrategy.test.ts +405 -0
  179. package/src/strategy/CompositeStrategy.ts +102 -0
  180. package/src/strategy/MinAmountStrategy.test.ts +189 -88
  181. package/src/strategy/MinAmountStrategy.ts +44 -13
  182. package/src/strategy/StrategyFactory.test.ts +2 -2
  183. package/src/strategy/StrategyFactory.ts +91 -8
  184. package/src/strategy/WeightedStrategy.test.ts +187 -72
  185. package/src/strategy/WeightedStrategy.ts +41 -7
  186. package/src/strategy/index.ts +2 -0
  187. package/src/test/helpers.ts +418 -14
  188. package/src/tracking/ActionTracker.test.ts +783 -0
  189. package/src/tracking/ActionTracker.ts +647 -0
  190. package/src/tracking/IActionTracker.ts +140 -0
  191. package/src/tracking/InflightContextAdapter.test.ts +203 -0
  192. package/src/tracking/InflightContextAdapter.ts +42 -0
  193. package/src/tracking/index.ts +36 -0
  194. package/src/tracking/store/IStore.ts +48 -0
  195. package/src/tracking/store/InMemoryStore.test.ts +338 -0
  196. package/src/tracking/store/InMemoryStore.ts +58 -0
  197. package/src/tracking/store/index.ts +2 -0
  198. package/src/tracking/types.ts +74 -0
  199. package/src/utils/ExplorerClient.ts +266 -3
  200. package/src/utils/balanceUtils.test.ts +1 -0
  201. package/src/utils/balanceUtils.ts +2 -2
  202. package/src/utils/bridgeUtils.test.ts +3 -15
  203. package/src/utils/bridgeUtils.ts +0 -10
  204. package/dist/core/WithInflightGuard.d.ts +0 -20
  205. package/dist/core/WithInflightGuard.d.ts.map +0 -1
  206. package/dist/core/WithInflightGuard.js +0 -47
  207. package/dist/core/WithInflightGuard.js.map +0 -1
  208. package/dist/core/WithInflightGuard.test.d.ts +0 -2
  209. package/dist/core/WithInflightGuard.test.d.ts.map +0 -1
  210. package/dist/core/WithInflightGuard.test.js +0 -64
  211. package/dist/core/WithInflightGuard.test.js.map +0 -1
  212. package/dist/core/WithSemaphore.d.ts +0 -22
  213. package/dist/core/WithSemaphore.d.ts.map +0 -1
  214. package/dist/core/WithSemaphore.js +0 -67
  215. package/dist/core/WithSemaphore.js.map +0 -1
  216. package/dist/core/WithSemaphore.test.d.ts +0 -2
  217. package/dist/core/WithSemaphore.test.d.ts.map +0 -1
  218. package/dist/core/WithSemaphore.test.js +0 -83
  219. package/dist/core/WithSemaphore.test.js.map +0 -1
  220. package/src/core/WithInflightGuard.test.ts +0 -131
  221. package/src/core/WithInflightGuard.ts +0 -67
  222. package/src/core/WithSemaphore.test.ts +0 -111
  223. package/src/core/WithSemaphore.ts +0 -92
@@ -0,0 +1,338 @@
1
+ import chai, { expect } from 'chai';
2
+ import chaiAsPromised from 'chai-as-promised';
3
+
4
+ import type { Transfer } from '../types.js';
5
+
6
+ import { InMemoryStore } from './InMemoryStore.js';
7
+
8
+ chai.use(chaiAsPromised);
9
+
10
+ describe('InMemoryStore', () => {
11
+ let store: InMemoryStore<Transfer, 'in_progress' | 'complete'>;
12
+
13
+ beforeEach(() => {
14
+ store = new InMemoryStore<Transfer, 'in_progress' | 'complete'>();
15
+ });
16
+
17
+ describe('save', () => {
18
+ it('should save a new entity', async () => {
19
+ const transfer: Transfer = {
20
+ id: 'transfer-1',
21
+ status: 'in_progress',
22
+ messageId: 'msg-1',
23
+ origin: 1,
24
+ destination: 2,
25
+ amount: 100n,
26
+ sender: '0xsender',
27
+ recipient: '0xrecipient',
28
+ createdAt: Date.now(),
29
+ updatedAt: Date.now(),
30
+ };
31
+
32
+ await store.save(transfer);
33
+
34
+ const retrieved = await store.get('transfer-1');
35
+ expect(retrieved).to.deep.equal(transfer);
36
+ });
37
+
38
+ it('should overwrite an existing entity with the same id', async () => {
39
+ const transfer1: Transfer = {
40
+ id: 'transfer-1',
41
+ status: 'in_progress',
42
+ messageId: 'msg-1',
43
+ origin: 1,
44
+ destination: 2,
45
+ amount: 100n,
46
+ sender: '0xsender',
47
+ recipient: '0xrecipient',
48
+ createdAt: Date.now(),
49
+ updatedAt: Date.now(),
50
+ };
51
+
52
+ const transfer2: Transfer = {
53
+ ...transfer1,
54
+ status: 'complete',
55
+ updatedAt: Date.now() + 1000,
56
+ };
57
+
58
+ await store.save(transfer1);
59
+ await store.save(transfer2);
60
+
61
+ const retrieved = await store.get('transfer-1');
62
+ expect(retrieved?.status).to.equal('complete');
63
+ });
64
+ });
65
+
66
+ describe('get', () => {
67
+ it('should return undefined for non-existent entity', async () => {
68
+ const result = await store.get('non-existent');
69
+ expect(result).to.be.undefined;
70
+ });
71
+
72
+ it('should retrieve an existing entity', async () => {
73
+ const transfer: Transfer = {
74
+ id: 'transfer-1',
75
+ status: 'in_progress',
76
+ messageId: 'msg-1',
77
+ origin: 1,
78
+ destination: 2,
79
+ amount: 100n,
80
+ sender: '0xsender',
81
+ recipient: '0xrecipient',
82
+ createdAt: Date.now(),
83
+ updatedAt: Date.now(),
84
+ };
85
+
86
+ await store.save(transfer);
87
+ const retrieved = await store.get('transfer-1');
88
+
89
+ expect(retrieved).to.deep.equal(transfer);
90
+ });
91
+ });
92
+
93
+ describe('getAll', () => {
94
+ it('should return empty array when no entities exist', async () => {
95
+ const result = await store.getAll();
96
+ expect(result).to.be.an('array').that.is.empty;
97
+ });
98
+
99
+ it('should return all entities', async () => {
100
+ const transfer1: Transfer = {
101
+ id: 'transfer-1',
102
+ status: 'in_progress',
103
+ messageId: 'msg-1',
104
+ origin: 1,
105
+ destination: 2,
106
+ amount: 100n,
107
+ sender: '0xsender1',
108
+ recipient: '0xrecipient1',
109
+ createdAt: Date.now(),
110
+ updatedAt: Date.now(),
111
+ };
112
+
113
+ const transfer2: Transfer = {
114
+ id: 'transfer-2',
115
+ status: 'complete',
116
+ messageId: 'msg-2',
117
+ origin: 2,
118
+ destination: 3,
119
+ amount: 200n,
120
+ sender: '0xsender2',
121
+ recipient: '0xrecipient2',
122
+ createdAt: Date.now(),
123
+ updatedAt: Date.now(),
124
+ };
125
+
126
+ await store.save(transfer1);
127
+ await store.save(transfer2);
128
+
129
+ const result = await store.getAll();
130
+ expect(result).to.have.lengthOf(2);
131
+ expect(result).to.deep.include(transfer1);
132
+ expect(result).to.deep.include(transfer2);
133
+ });
134
+ });
135
+
136
+ describe('update', () => {
137
+ it('should throw error when updating non-existent entity', async () => {
138
+ await expect(
139
+ store.update('non-existent', { status: 'complete' }),
140
+ ).to.be.rejectedWith('Entity non-existent not found');
141
+ });
142
+
143
+ it('should update existing entity', async () => {
144
+ const transfer: Transfer = {
145
+ id: 'transfer-1',
146
+ status: 'in_progress',
147
+ messageId: 'msg-1',
148
+ origin: 1,
149
+ destination: 2,
150
+ amount: 100n,
151
+ sender: '0xsender',
152
+ recipient: '0xrecipient',
153
+ createdAt: Date.now(),
154
+ updatedAt: Date.now(),
155
+ };
156
+
157
+ await store.save(transfer);
158
+ await store.update('transfer-1', { status: 'complete' });
159
+
160
+ const updated = await store.get('transfer-1');
161
+ expect(updated?.status).to.equal('complete');
162
+ expect(updated?.updatedAt).to.be.at.least(transfer.updatedAt);
163
+ });
164
+
165
+ it('should preserve non-updated fields', async () => {
166
+ const transfer: Transfer = {
167
+ id: 'transfer-1',
168
+ status: 'in_progress',
169
+ messageId: 'msg-1',
170
+ origin: 1,
171
+ destination: 2,
172
+ amount: 100n,
173
+ sender: '0xsender',
174
+ recipient: '0xrecipient',
175
+ createdAt: Date.now(),
176
+ updatedAt: Date.now(),
177
+ };
178
+
179
+ await store.save(transfer);
180
+ await store.update('transfer-1', { status: 'complete' });
181
+
182
+ const updated = await store.get('transfer-1');
183
+ expect(updated?.messageId).to.equal('msg-1');
184
+ expect(updated?.origin).to.equal(1);
185
+ expect(updated?.destination).to.equal(2);
186
+ expect(updated?.amount).to.equal(100n);
187
+ });
188
+ });
189
+
190
+ describe('delete', () => {
191
+ it('should delete existing entity', async () => {
192
+ const transfer: Transfer = {
193
+ id: 'transfer-1',
194
+ status: 'in_progress',
195
+ messageId: 'msg-1',
196
+ origin: 1,
197
+ destination: 2,
198
+ amount: 100n,
199
+ sender: '0xsender',
200
+ recipient: '0xrecipient',
201
+ createdAt: Date.now(),
202
+ updatedAt: Date.now(),
203
+ };
204
+
205
+ await store.save(transfer);
206
+ await store.delete('transfer-1');
207
+
208
+ const retrieved = await store.get('transfer-1');
209
+ expect(retrieved).to.be.undefined;
210
+ });
211
+
212
+ it('should not throw when deleting non-existent entity', async () => {
213
+ await expect(store.delete('non-existent')).to.not.be.rejected;
214
+ });
215
+ });
216
+
217
+ describe('getByStatus', () => {
218
+ it('should return empty array when no entities match status', async () => {
219
+ const result = await store.getByStatus('complete');
220
+ expect(result).to.be.an('array').that.is.empty;
221
+ });
222
+
223
+ it('should return only entities with matching status', async () => {
224
+ const transfer1: Transfer = {
225
+ id: 'transfer-1',
226
+ status: 'in_progress',
227
+ messageId: 'msg-1',
228
+ origin: 1,
229
+ destination: 2,
230
+ amount: 100n,
231
+ sender: '0xsender1',
232
+ recipient: '0xrecipient1',
233
+ createdAt: Date.now(),
234
+ updatedAt: Date.now(),
235
+ };
236
+
237
+ const transfer2: Transfer = {
238
+ id: 'transfer-2',
239
+ status: 'complete',
240
+ messageId: 'msg-2',
241
+ origin: 2,
242
+ destination: 3,
243
+ amount: 200n,
244
+ sender: '0xsender2',
245
+ recipient: '0xrecipient2',
246
+ createdAt: Date.now(),
247
+ updatedAt: Date.now(),
248
+ };
249
+
250
+ const transfer3: Transfer = {
251
+ id: 'transfer-3',
252
+ status: 'in_progress',
253
+ messageId: 'msg-3',
254
+ origin: 3,
255
+ destination: 1,
256
+ amount: 300n,
257
+ sender: '0xsender3',
258
+ recipient: '0xrecipient3',
259
+ createdAt: Date.now(),
260
+ updatedAt: Date.now(),
261
+ };
262
+
263
+ await store.save(transfer1);
264
+ await store.save(transfer2);
265
+ await store.save(transfer3);
266
+
267
+ const inProgress = await store.getByStatus('in_progress');
268
+ expect(inProgress).to.have.lengthOf(2);
269
+ expect(inProgress).to.deep.include(transfer1);
270
+ expect(inProgress).to.deep.include(transfer3);
271
+
272
+ const complete = await store.getByStatus('complete');
273
+ expect(complete).to.have.lengthOf(1);
274
+ expect(complete).to.deep.include(transfer2);
275
+ });
276
+ });
277
+
278
+ describe('getByDestination', () => {
279
+ it('should return empty array when no entities match destination', async () => {
280
+ const result = await store.getByDestination(999);
281
+ expect(result).to.be.an('array').that.is.empty;
282
+ });
283
+
284
+ it('should return only entities with matching destination', async () => {
285
+ const transfer1: Transfer = {
286
+ id: 'transfer-1',
287
+ status: 'in_progress',
288
+ messageId: 'msg-1',
289
+ origin: 1,
290
+ destination: 2,
291
+ amount: 100n,
292
+ sender: '0xsender1',
293
+ recipient: '0xrecipient1',
294
+ createdAt: Date.now(),
295
+ updatedAt: Date.now(),
296
+ };
297
+
298
+ const transfer2: Transfer = {
299
+ id: 'transfer-2',
300
+ status: 'complete',
301
+ messageId: 'msg-2',
302
+ origin: 2,
303
+ destination: 3,
304
+ amount: 200n,
305
+ sender: '0xsender2',
306
+ recipient: '0xrecipient2',
307
+ createdAt: Date.now(),
308
+ updatedAt: Date.now(),
309
+ };
310
+
311
+ const transfer3: Transfer = {
312
+ id: 'transfer-3',
313
+ status: 'in_progress',
314
+ messageId: 'msg-3',
315
+ origin: 3,
316
+ destination: 2,
317
+ amount: 300n,
318
+ sender: '0xsender3',
319
+ recipient: '0xrecipient3',
320
+ createdAt: Date.now(),
321
+ updatedAt: Date.now(),
322
+ };
323
+
324
+ await store.save(transfer1);
325
+ await store.save(transfer2);
326
+ await store.save(transfer3);
327
+
328
+ const toDomain2 = await store.getByDestination(2);
329
+ expect(toDomain2).to.have.lengthOf(2);
330
+ expect(toDomain2).to.deep.include(transfer1);
331
+ expect(toDomain2).to.deep.include(transfer3);
332
+
333
+ const toDomain3 = await store.getByDestination(3);
334
+ expect(toDomain3).to.have.lengthOf(1);
335
+ expect(toDomain3).to.deep.include(transfer2);
336
+ });
337
+ });
338
+ });
@@ -0,0 +1,58 @@
1
+ import type { Domain } from '@hyperlane-xyz/utils';
2
+
3
+ import type { TrackedActionBase } from '../types.js';
4
+
5
+ import type { IStore } from './IStore.js';
6
+
7
+ /**
8
+ * In-memory implementation of the IStore interface.
9
+ * Uses a Map for fast lookups and keeps all data in memory.
10
+ *
11
+ * @template T - The entity type extending TrackedActionBase
12
+ * @template Status - The status enum type for this entity
13
+ */
14
+ export class InMemoryStore<T extends TrackedActionBase, Status extends string>
15
+ implements IStore<T, Status>
16
+ {
17
+ protected data: Map<string, T> = new Map();
18
+
19
+ async save(entity: T): Promise<void> {
20
+ this.data.set(entity.id, entity);
21
+ }
22
+
23
+ async get(id: string): Promise<T | undefined> {
24
+ return this.data.get(id);
25
+ }
26
+
27
+ async getAll(): Promise<T[]> {
28
+ return Array.from(this.data.values());
29
+ }
30
+
31
+ async update(id: string, updates: Partial<T>): Promise<void> {
32
+ const existing = this.data.get(id);
33
+ if (!existing) {
34
+ throw new Error(`Entity ${id} not found`);
35
+ }
36
+ this.data.set(id, {
37
+ ...existing,
38
+ ...updates,
39
+ updatedAt: Date.now(),
40
+ } as T);
41
+ }
42
+
43
+ async delete(id: string): Promise<void> {
44
+ this.data.delete(id);
45
+ }
46
+
47
+ async getByStatus(status: Status): Promise<T[]> {
48
+ return Array.from(this.data.values()).filter(
49
+ (entity) => entity.status === status,
50
+ );
51
+ }
52
+
53
+ async getByDestination(destination: Domain): Promise<T[]> {
54
+ return Array.from(this.data.values()).filter(
55
+ (entity) => entity.destination === destination,
56
+ );
57
+ }
58
+ }
@@ -0,0 +1,2 @@
1
+ export type { IStore } from './IStore.js';
2
+ export { InMemoryStore } from './InMemoryStore.js';
@@ -0,0 +1,74 @@
1
+ import type { Address, Domain } from '@hyperlane-xyz/utils';
2
+
3
+ import type { IStore } from './store/IStore.js';
4
+
5
+ // === Base Interfaces ===
6
+
7
+ export interface Identifiable {
8
+ id: string;
9
+ }
10
+
11
+ export interface CrossChainAction {
12
+ origin: Domain;
13
+ destination: Domain;
14
+ amount: bigint;
15
+ }
16
+
17
+ export interface Timestamped {
18
+ createdAt: number;
19
+ updatedAt: number;
20
+ }
21
+
22
+ export interface TrackedActionBase
23
+ extends Identifiable,
24
+ CrossChainAction,
25
+ Timestamped {
26
+ status: string;
27
+ }
28
+
29
+ // === Status Types ===
30
+
31
+ export type TransferStatus = 'in_progress' | 'complete';
32
+ export type RebalanceIntentStatus =
33
+ | 'not_started'
34
+ | 'in_progress'
35
+ | 'complete'
36
+ | 'cancelled'
37
+ | 'failed';
38
+ export type RebalanceActionStatus = 'in_progress' | 'complete' | 'failed';
39
+
40
+ // === Entity Types ===
41
+
42
+ export interface Transfer extends TrackedActionBase {
43
+ status: TransferStatus;
44
+ messageId: string;
45
+ sender: Address;
46
+ recipient: Address;
47
+ }
48
+
49
+ export interface RebalanceIntent extends TrackedActionBase {
50
+ status: RebalanceIntentStatus;
51
+ fulfilledAmount: bigint;
52
+ bridge?: Address; // Optional - bridge contract used (missing for recovered intents)
53
+ priority?: number; // Optional - missing for recovered intents
54
+ strategyType?: string; // Optional - missing for recovered intents
55
+ }
56
+
57
+ export interface RebalanceAction extends TrackedActionBase {
58
+ status: RebalanceActionStatus;
59
+ intentId: string; // Links to parent RebalanceIntent
60
+ messageId: string; // Hyperlane message ID
61
+ txHash?: string; // Origin transaction hash
62
+ }
63
+
64
+ // === Type Aliases for Stores ===
65
+
66
+ export type ITransferStore = IStore<Transfer, TransferStatus>;
67
+ export type IRebalanceIntentStore = IStore<
68
+ RebalanceIntent,
69
+ RebalanceIntentStatus
70
+ >;
71
+ export type IRebalanceActionStore = IStore<
72
+ RebalanceAction,
73
+ RebalanceActionStatus
74
+ >;