@hyperlane-xyz/rebalancer 0.1.0-beta.5a8bd28ab

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 (202) hide show
  1. package/README.md +178 -0
  2. package/dist/config/RebalancerConfig.d.ts +12 -0
  3. package/dist/config/RebalancerConfig.d.ts.map +1 -0
  4. package/dist/config/RebalancerConfig.js +29 -0
  5. package/dist/config/RebalancerConfig.js.map +1 -0
  6. package/dist/config/RebalancerConfig.test.d.ts +2 -0
  7. package/dist/config/RebalancerConfig.test.d.ts.map +1 -0
  8. package/dist/config/RebalancerConfig.test.js +325 -0
  9. package/dist/config/RebalancerConfig.test.js.map +1 -0
  10. package/dist/core/Rebalancer.d.ts +23 -0
  11. package/dist/core/Rebalancer.d.ts.map +1 -0
  12. package/dist/core/Rebalancer.js +290 -0
  13. package/dist/core/Rebalancer.js.map +1 -0
  14. package/dist/core/RebalancerService.d.ts +115 -0
  15. package/dist/core/RebalancerService.d.ts.map +1 -0
  16. package/dist/core/RebalancerService.js +227 -0
  17. package/dist/core/RebalancerService.js.map +1 -0
  18. package/dist/core/WithInflightGuard.d.ts +20 -0
  19. package/dist/core/WithInflightGuard.d.ts.map +1 -0
  20. package/dist/core/WithInflightGuard.js +47 -0
  21. package/dist/core/WithInflightGuard.js.map +1 -0
  22. package/dist/core/WithInflightGuard.test.d.ts +2 -0
  23. package/dist/core/WithInflightGuard.test.d.ts.map +1 -0
  24. package/dist/core/WithInflightGuard.test.js +64 -0
  25. package/dist/core/WithInflightGuard.test.js.map +1 -0
  26. package/dist/core/WithSemaphore.d.ts +22 -0
  27. package/dist/core/WithSemaphore.d.ts.map +1 -0
  28. package/dist/core/WithSemaphore.js +67 -0
  29. package/dist/core/WithSemaphore.js.map +1 -0
  30. package/dist/core/WithSemaphore.test.d.ts +2 -0
  31. package/dist/core/WithSemaphore.test.d.ts.map +1 -0
  32. package/dist/core/WithSemaphore.test.js +83 -0
  33. package/dist/core/WithSemaphore.test.js.map +1 -0
  34. package/dist/factories/RebalancerContextFactory.d.ts +41 -0
  35. package/dist/factories/RebalancerContextFactory.d.ts.map +1 -0
  36. package/dist/factories/RebalancerContextFactory.js +115 -0
  37. package/dist/factories/RebalancerContextFactory.js.map +1 -0
  38. package/dist/index.d.ts +33 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +35 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/interfaces/IMetrics.d.ts +5 -0
  43. package/dist/interfaces/IMetrics.d.ts.map +1 -0
  44. package/dist/interfaces/IMetrics.js +2 -0
  45. package/dist/interfaces/IMetrics.js.map +1 -0
  46. package/dist/interfaces/IMonitor.d.ts +51 -0
  47. package/dist/interfaces/IMonitor.d.ts.map +1 -0
  48. package/dist/interfaces/IMonitor.js +14 -0
  49. package/dist/interfaces/IMonitor.js.map +1 -0
  50. package/dist/interfaces/IRebalancer.d.ts +15 -0
  51. package/dist/interfaces/IRebalancer.d.ts.map +1 -0
  52. package/dist/interfaces/IRebalancer.js +2 -0
  53. package/dist/interfaces/IRebalancer.js.map +1 -0
  54. package/dist/interfaces/IStrategy.d.ts +11 -0
  55. package/dist/interfaces/IStrategy.d.ts.map +1 -0
  56. package/dist/interfaces/IStrategy.js +2 -0
  57. package/dist/interfaces/IStrategy.js.map +1 -0
  58. package/dist/metrics/Metrics.d.ts +31 -0
  59. package/dist/metrics/Metrics.d.ts.map +1 -0
  60. package/dist/metrics/Metrics.js +302 -0
  61. package/dist/metrics/Metrics.js.map +1 -0
  62. package/dist/metrics/PriceGetter.d.ts +10 -0
  63. package/dist/metrics/PriceGetter.d.ts.map +1 -0
  64. package/dist/metrics/PriceGetter.js +41 -0
  65. package/dist/metrics/PriceGetter.js.map +1 -0
  66. package/dist/metrics/scripts/metrics.d.ts +14 -0
  67. package/dist/metrics/scripts/metrics.d.ts.map +1 -0
  68. package/dist/metrics/scripts/metrics.js +198 -0
  69. package/dist/metrics/scripts/metrics.js.map +1 -0
  70. package/dist/metrics/types.d.ts +24 -0
  71. package/dist/metrics/types.d.ts.map +1 -0
  72. package/dist/metrics/types.js +2 -0
  73. package/dist/metrics/types.js.map +1 -0
  74. package/dist/metrics/utils/metrics.d.ts +12 -0
  75. package/dist/metrics/utils/metrics.d.ts.map +1 -0
  76. package/dist/metrics/utils/metrics.js +28 -0
  77. package/dist/metrics/utils/metrics.js.map +1 -0
  78. package/dist/monitor/Monitor.d.ts +26 -0
  79. package/dist/monitor/Monitor.d.ts.map +1 -0
  80. package/dist/monitor/Monitor.js +116 -0
  81. package/dist/monitor/Monitor.js.map +1 -0
  82. package/dist/service.d.ts +3 -0
  83. package/dist/service.d.ts.map +1 -0
  84. package/dist/service.js +125 -0
  85. package/dist/service.js.map +1 -0
  86. package/dist/strategy/BaseStrategy.d.ts +34 -0
  87. package/dist/strategy/BaseStrategy.d.ts.map +1 -0
  88. package/dist/strategy/BaseStrategy.js +127 -0
  89. package/dist/strategy/BaseStrategy.js.map +1 -0
  90. package/dist/strategy/MinAmountStrategy.d.ts +27 -0
  91. package/dist/strategy/MinAmountStrategy.d.ts.map +1 -0
  92. package/dist/strategy/MinAmountStrategy.js +103 -0
  93. package/dist/strategy/MinAmountStrategy.js.map +1 -0
  94. package/dist/strategy/MinAmountStrategy.test.d.ts +2 -0
  95. package/dist/strategy/MinAmountStrategy.test.d.ts.map +1 -0
  96. package/dist/strategy/MinAmountStrategy.test.js +472 -0
  97. package/dist/strategy/MinAmountStrategy.test.js.map +1 -0
  98. package/dist/strategy/StrategyFactory.d.ts +16 -0
  99. package/dist/strategy/StrategyFactory.d.ts.map +1 -0
  100. package/dist/strategy/StrategyFactory.js +25 -0
  101. package/dist/strategy/StrategyFactory.js.map +1 -0
  102. package/dist/strategy/StrategyFactory.test.d.ts +2 -0
  103. package/dist/strategy/StrategyFactory.test.d.ts.map +1 -0
  104. package/dist/strategy/StrategyFactory.test.js +80 -0
  105. package/dist/strategy/StrategyFactory.test.js.map +1 -0
  106. package/dist/strategy/WeightedStrategy.d.ts +23 -0
  107. package/dist/strategy/WeightedStrategy.d.ts.map +1 -0
  108. package/dist/strategy/WeightedStrategy.js +61 -0
  109. package/dist/strategy/WeightedStrategy.js.map +1 -0
  110. package/dist/strategy/WeightedStrategy.test.d.ts +2 -0
  111. package/dist/strategy/WeightedStrategy.test.d.ts.map +1 -0
  112. package/dist/strategy/WeightedStrategy.test.js +307 -0
  113. package/dist/strategy/WeightedStrategy.test.js.map +1 -0
  114. package/dist/strategy/index.d.ts +5 -0
  115. package/dist/strategy/index.d.ts.map +1 -0
  116. package/dist/strategy/index.js +5 -0
  117. package/dist/strategy/index.js.map +1 -0
  118. package/dist/test/helpers.d.ts +8 -0
  119. package/dist/test/helpers.d.ts.map +1 -0
  120. package/dist/test/helpers.js +33 -0
  121. package/dist/test/helpers.js.map +1 -0
  122. package/dist/utils/ExplorerClient.d.ts +14 -0
  123. package/dist/utils/ExplorerClient.d.ts.map +1 -0
  124. package/dist/utils/ExplorerClient.js +82 -0
  125. package/dist/utils/ExplorerClient.js.map +1 -0
  126. package/dist/utils/balanceUtils.d.ts +13 -0
  127. package/dist/utils/balanceUtils.d.ts.map +1 -0
  128. package/dist/utils/balanceUtils.js +43 -0
  129. package/dist/utils/balanceUtils.js.map +1 -0
  130. package/dist/utils/balanceUtils.test.d.ts +2 -0
  131. package/dist/utils/balanceUtils.test.d.ts.map +1 -0
  132. package/dist/utils/balanceUtils.test.js +54 -0
  133. package/dist/utils/balanceUtils.test.js.map +1 -0
  134. package/dist/utils/bridgeUtils.d.ts +19 -0
  135. package/dist/utils/bridgeUtils.d.ts.map +1 -0
  136. package/dist/utils/bridgeUtils.js +20 -0
  137. package/dist/utils/bridgeUtils.js.map +1 -0
  138. package/dist/utils/bridgeUtils.test.d.ts +2 -0
  139. package/dist/utils/bridgeUtils.test.d.ts.map +1 -0
  140. package/dist/utils/bridgeUtils.test.js +77 -0
  141. package/dist/utils/bridgeUtils.test.js.map +1 -0
  142. package/dist/utils/errors.d.ts +4 -0
  143. package/dist/utils/errors.d.ts.map +1 -0
  144. package/dist/utils/errors.js +6 -0
  145. package/dist/utils/errors.js.map +1 -0
  146. package/dist/utils/files.d.ts +35 -0
  147. package/dist/utils/files.d.ts.map +1 -0
  148. package/dist/utils/files.js +190 -0
  149. package/dist/utils/files.js.map +1 -0
  150. package/dist/utils/generalUtils.d.ts +3 -0
  151. package/dist/utils/generalUtils.d.ts.map +1 -0
  152. package/dist/utils/generalUtils.js +9 -0
  153. package/dist/utils/generalUtils.js.map +1 -0
  154. package/dist/utils/index.d.ts +5 -0
  155. package/dist/utils/index.d.ts.map +1 -0
  156. package/dist/utils/index.js +5 -0
  157. package/dist/utils/index.js.map +1 -0
  158. package/dist/utils/tokenUtils.d.ts +14 -0
  159. package/dist/utils/tokenUtils.d.ts.map +1 -0
  160. package/dist/utils/tokenUtils.js +21 -0
  161. package/dist/utils/tokenUtils.js.map +1 -0
  162. package/package.json +70 -0
  163. package/src/config/RebalancerConfig.test.ts +388 -0
  164. package/src/config/RebalancerConfig.ts +39 -0
  165. package/src/core/Rebalancer.ts +471 -0
  166. package/src/core/RebalancerService.ts +333 -0
  167. package/src/core/WithInflightGuard.test.ts +131 -0
  168. package/src/core/WithInflightGuard.ts +67 -0
  169. package/src/core/WithSemaphore.test.ts +112 -0
  170. package/src/core/WithSemaphore.ts +92 -0
  171. package/src/factories/RebalancerContextFactory.ts +210 -0
  172. package/src/index.ts +68 -0
  173. package/src/interfaces/IMetrics.ts +5 -0
  174. package/src/interfaces/IMonitor.ts +63 -0
  175. package/src/interfaces/IRebalancer.ts +20 -0
  176. package/src/interfaces/IStrategy.ts +13 -0
  177. package/src/metrics/Metrics.ts +558 -0
  178. package/src/metrics/PriceGetter.ts +74 -0
  179. package/src/metrics/scripts/metrics.ts +298 -0
  180. package/src/metrics/types.ts +27 -0
  181. package/src/metrics/utils/metrics.ts +33 -0
  182. package/src/monitor/Monitor.ts +174 -0
  183. package/src/service.ts +154 -0
  184. package/src/strategy/BaseStrategy.ts +210 -0
  185. package/src/strategy/MinAmountStrategy.test.ts +625 -0
  186. package/src/strategy/MinAmountStrategy.ts +170 -0
  187. package/src/strategy/StrategyFactory.test.ts +109 -0
  188. package/src/strategy/StrategyFactory.ts +48 -0
  189. package/src/strategy/WeightedStrategy.test.ts +408 -0
  190. package/src/strategy/WeightedStrategy.ts +93 -0
  191. package/src/strategy/index.ts +4 -0
  192. package/src/test/helpers.ts +46 -0
  193. package/src/utils/ExplorerClient.ts +99 -0
  194. package/src/utils/balanceUtils.test.ts +74 -0
  195. package/src/utils/balanceUtils.ts +69 -0
  196. package/src/utils/bridgeUtils.test.ts +92 -0
  197. package/src/utils/bridgeUtils.ts +42 -0
  198. package/src/utils/errors.ts +5 -0
  199. package/src/utils/files.ts +276 -0
  200. package/src/utils/generalUtils.ts +13 -0
  201. package/src/utils/index.ts +4 -0
  202. package/src/utils/tokenUtils.ts +26 -0
@@ -0,0 +1,210 @@
1
+ import { Logger } from 'pino';
2
+
3
+ import type { ChainName } from '@hyperlane-xyz/sdk';
4
+
5
+ import type {
6
+ IStrategy,
7
+ RawBalances,
8
+ RebalancingRoute,
9
+ } from '../interfaces/IStrategy.js';
10
+ import { Metrics } from '../metrics/Metrics.js';
11
+
12
+ export type Delta = { chain: ChainName; amount: bigint };
13
+
14
+ /**
15
+ * Base abstract class for rebalancing strategies
16
+ */
17
+ export abstract class BaseStrategy implements IStrategy {
18
+ protected readonly chains: ChainName[];
19
+ protected readonly metrics?: Metrics;
20
+ protected readonly logger: Logger;
21
+
22
+ constructor(chains: ChainName[], logger: Logger, metrics?: Metrics) {
23
+ // Rebalancing makes sense only with more than one chain.
24
+ if (chains.length < 2) {
25
+ throw new Error('At least two chains must be configured');
26
+ }
27
+ this.chains = chains;
28
+ this.logger = logger;
29
+ this.metrics = metrics;
30
+ }
31
+
32
+ /**
33
+ * Main method to get rebalancing routes
34
+ */
35
+ getRebalancingRoutes(rawBalances: RawBalances): RebalancingRoute[] {
36
+ this.logger.info(
37
+ {
38
+ context: this.constructor.name,
39
+ rawBalances,
40
+ },
41
+ 'Input rawBalances',
42
+ );
43
+ this.logger.info(
44
+ {
45
+ context: this.constructor.name,
46
+ },
47
+ 'Calculating rebalancing routes',
48
+ );
49
+ this.validateRawBalances(rawBalances);
50
+
51
+ // Get balances categorized by surplus and deficit
52
+ const { surpluses, deficits } = this.getCategorizedBalances(rawBalances);
53
+
54
+ this.logger.debug(
55
+ {
56
+ context: this.constructor.name,
57
+ surpluses,
58
+ },
59
+ 'Surpluses calculated',
60
+ );
61
+ this.logger.debug(
62
+ {
63
+ context: this.constructor.name,
64
+ deficits,
65
+ },
66
+ 'Deficits calculated',
67
+ );
68
+
69
+ // Calculate sums of surpluses and deficits
70
+ const totalSurplus = surpluses.reduce(
71
+ (sum, surplus) => sum + surplus.amount,
72
+ 0n,
73
+ );
74
+ const totalDeficit = deficits.reduce(
75
+ (sum, deficit) => sum + deficit.amount,
76
+ 0n,
77
+ );
78
+
79
+ this.logger.debug(
80
+ {
81
+ context: this.constructor.name,
82
+ totalSurplus: totalSurplus.toString(),
83
+ },
84
+ 'Total surplus calculated',
85
+ );
86
+ this.logger.debug(
87
+ {
88
+ context: this.constructor.name,
89
+ totalDeficit: totalDeficit.toString(),
90
+ },
91
+ 'Total deficit calculated',
92
+ );
93
+
94
+ // If total surplus is less than total deficit, scale down deficits proportionally
95
+ if (totalSurplus < totalDeficit) {
96
+ this.logger.warn(
97
+ {
98
+ context: this.constructor.name,
99
+ totalSurplus: totalSurplus.toString(),
100
+ totalDeficit: totalDeficit.toString(),
101
+ },
102
+ 'Deficits are greater than surpluses. Scaling deficits',
103
+ );
104
+
105
+ // we consider this a failure because we cannot rebalance the route completely
106
+ // however we can still transfer some amount of the deficit to reduce the imbalances
107
+ this.metrics?.recordRebalancerFailure();
108
+
109
+ for (const deficit of deficits) {
110
+ const newAmount = (deficit.amount * totalSurplus) / totalDeficit;
111
+
112
+ deficit.amount = newAmount;
113
+ }
114
+
115
+ this.logger.debug(
116
+ {
117
+ context: this.constructor.name,
118
+ deficits,
119
+ },
120
+ 'Scaled deficits',
121
+ );
122
+ }
123
+
124
+ // Sort from largest to smallest amounts as to always transfer largest amounts
125
+ // first and decrease the amount of routes required
126
+ surpluses.sort((a, b) => (a.amount > b.amount ? -1 : 1));
127
+ deficits.sort((a, b) => (a.amount > b.amount ? -1 : 1));
128
+
129
+ const routes: RebalancingRoute[] = [];
130
+
131
+ // Transfer from surplus to deficit until all deficits are balanced.
132
+ while (deficits.length > 0 && surpluses.length > 0) {
133
+ const surplus = surpluses[0];
134
+ const deficit = deficits[0];
135
+
136
+ // Transfers the whole surplus or just the amount to balance the deficit
137
+ const transferAmount =
138
+ surplus.amount > deficit.amount ? deficit.amount : surplus.amount;
139
+
140
+ // Creates the balancing route
141
+ routes.push({
142
+ origin: surplus.chain,
143
+ destination: deficit.chain,
144
+ amount: transferAmount,
145
+ });
146
+
147
+ // Decreases the amounts for the following iterations
148
+ deficit.amount -= transferAmount;
149
+ surplus.amount -= transferAmount;
150
+
151
+ // Removes the deficit if it is fully balanced
152
+ if (!deficit.amount) {
153
+ deficits.shift();
154
+ }
155
+
156
+ // Removes the surplus if it has been drained
157
+ if (!surplus.amount) {
158
+ surpluses.shift();
159
+ }
160
+ }
161
+
162
+ this.logger.debug(
163
+ {
164
+ context: this.constructor.name,
165
+ routes,
166
+ },
167
+ 'Generated routes',
168
+ );
169
+ this.logger.info(
170
+ {
171
+ context: this.constructor.name,
172
+ numberOfRoutes: routes.length,
173
+ },
174
+ 'Found rebalancing routes',
175
+ );
176
+ return routes;
177
+ }
178
+
179
+ /**
180
+ * Abstract method to get balances categorized by surplus and deficit
181
+ * Each specific strategy should implement its own logic
182
+ */
183
+ protected abstract getCategorizedBalances(rawBalances: RawBalances): {
184
+ surpluses: Delta[];
185
+ deficits: Delta[];
186
+ };
187
+
188
+ /**
189
+ * Validates the raw balances against the chains configuration
190
+ */
191
+ protected validateRawBalances(rawBalances: RawBalances): void {
192
+ const rawBalancesChains = Object.keys(rawBalances);
193
+
194
+ if (this.chains.length !== rawBalancesChains.length) {
195
+ throw new Error('Config chains do not match raw balances chains length');
196
+ }
197
+
198
+ for (const chain of this.chains) {
199
+ const balance: bigint | undefined = rawBalances[chain];
200
+
201
+ if (balance === undefined) {
202
+ throw new Error(`Raw balance for chain ${chain} not found`);
203
+ }
204
+
205
+ if (balance < 0n) {
206
+ throw new Error(`Raw balance for chain ${chain} is negative`);
207
+ }
208
+ }
209
+ }
210
+ }