@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
@@ -1,18 +1,18 @@
1
1
  import { fromZodError } from 'zod-validation-error';
2
2
 
3
- import { isObjEmpty } from '@hyperlane-xyz/utils';
4
3
  import { readYamlOrJson } from '@hyperlane-xyz/utils/fs';
5
4
 
6
5
  import {
7
6
  type RebalancerConfigFileInput,
8
7
  RebalancerConfigSchema,
9
8
  type StrategyConfig,
9
+ getStrategyChainNames,
10
10
  } from './types.js';
11
11
 
12
12
  export class RebalancerConfig {
13
13
  constructor(
14
14
  public readonly warpRouteId: string,
15
- public readonly strategyConfig: StrategyConfig,
15
+ public readonly strategyConfig: StrategyConfig[],
16
16
  ) {}
17
17
 
18
18
  /**
@@ -30,7 +30,9 @@ export class RebalancerConfig {
30
30
 
31
31
  const { warpRouteId, strategy } = validationResult.data;
32
32
 
33
- if (isObjEmpty(strategy.chains)) {
33
+ // Check that at least one chain is configured across all strategies
34
+ const chainNames = getStrategyChainNames(strategy);
35
+ if (chainNames.length === 0) {
34
36
  throw new Error('No chains configured');
35
37
  }
36
38
 
@@ -3,6 +3,7 @@ import { z } from 'zod';
3
3
  export enum RebalancerStrategyOptions {
4
4
  Weighted = 'weighted',
5
5
  MinAmount = 'minAmount',
6
+ CollateralDeficit = 'collateralDeficit',
6
7
  }
7
8
 
8
9
  // Weighted strategy config schema
@@ -36,11 +37,8 @@ const RebalancerBridgeConfigSchema = z.object({
36
37
  .number()
37
38
  .positive()
38
39
  .transform((val) => val * 1_000)
39
- .describe('Expected time in seconds for bridge to process a transfer'),
40
- bridgeIsWarp: z
41
- .boolean()
42
40
  .optional()
43
- .describe('True if the bridge is another Warp Route'),
41
+ .describe('Expected time in seconds for bridge to process a transfer'),
44
42
  });
45
43
 
46
44
  export const RebalancerBaseChainConfigSchema =
@@ -59,6 +57,11 @@ const MinAmountChainConfigSchema = RebalancerBaseChainConfigSchema.extend({
59
57
  minAmount: RebalancerMinAmountConfigSchema,
60
58
  });
61
59
 
60
+ const CollateralDeficitChainConfigSchema =
61
+ RebalancerBaseChainConfigSchema.extend({
62
+ buffer: z.string().or(z.number()),
63
+ });
64
+
62
65
  const WeightedStrategySchema = z.object({
63
66
  rebalanceStrategy: z.literal(RebalancerStrategyOptions.Weighted),
64
67
  chains: z.record(z.string(), WeightedChainConfigSchema),
@@ -69,75 +72,122 @@ const MinAmountStrategySchema = z.object({
69
72
  chains: z.record(z.string(), MinAmountChainConfigSchema),
70
73
  });
71
74
 
75
+ const CollateralDeficitStrategySchema = z.object({
76
+ rebalanceStrategy: z.literal(RebalancerStrategyOptions.CollateralDeficit),
77
+ chains: z.record(z.string(), CollateralDeficitChainConfigSchema),
78
+ });
79
+
72
80
  export type WeightedStrategy = z.infer<typeof WeightedStrategySchema>;
73
81
  export type MinAmountStrategy = z.infer<typeof MinAmountStrategySchema>;
82
+ export type CollateralDeficitStrategy = z.infer<
83
+ typeof CollateralDeficitStrategySchema
84
+ >;
74
85
 
75
86
  export type WeightedStrategyConfig = WeightedStrategy['chains'];
76
87
  export type MinAmountStrategyConfig = MinAmountStrategy['chains'];
88
+ export type CollateralDeficitStrategyConfig =
89
+ CollateralDeficitStrategy['chains'];
77
90
 
78
91
  export const StrategyConfigSchema = z.discriminatedUnion('rebalanceStrategy', [
79
92
  WeightedStrategySchema,
80
93
  MinAmountStrategySchema,
94
+ CollateralDeficitStrategySchema,
81
95
  ]);
82
96
 
97
+ // Accept either a single strategy (backwards compatible) or an array of strategies
98
+ // Normalizes to array internally so the rest of the code doesn't need to change
99
+ export const RebalancerStrategySchema = z
100
+ .union([
101
+ StrategyConfigSchema, // Old format: single object
102
+ z.array(StrategyConfigSchema).min(1), // New format: array
103
+ ])
104
+ .transform((val) => (Array.isArray(val) ? val : [val]));
105
+
83
106
  export const RebalancerConfigSchema = z
84
107
  .object({
85
108
  warpRouteId: z.string(),
86
- strategy: StrategyConfigSchema,
109
+ strategy: RebalancerStrategySchema,
87
110
  })
88
111
  .superRefine((config, ctx) => {
89
- const chainNames = new Set(Object.keys(config.strategy.chains));
90
- // Check each chain's overrides
91
- for (const [chainName, chainConfig] of Object.entries(
92
- config.strategy.chains,
93
- )) {
94
- if (chainConfig.override) {
95
- for (const overrideChainName of Object.keys(chainConfig.override)) {
96
- // Each override key must reference a valid chain
97
- if (!chainNames.has(overrideChainName)) {
98
- ctx.addIssue({
99
- code: z.ZodIssueCode.custom,
100
- message: `Chain '${chainName}' has an override for '${overrideChainName}', but '${overrideChainName}' is not defined in the config`,
101
- path: [
102
- 'strategy',
103
- 'chains',
104
- chainName,
105
- 'override',
106
- overrideChainName,
107
- ],
108
- });
109
- }
112
+ // CollateralDeficitStrategy must be first in composite if it is used
113
+ if (config.strategy.length > 1) {
114
+ const hasCollateralDeficit = config.strategy.some(
115
+ (s) =>
116
+ s.rebalanceStrategy === RebalancerStrategyOptions.CollateralDeficit,
117
+ );
118
+ const collateralDeficitFirst =
119
+ config.strategy[0].rebalanceStrategy ===
120
+ RebalancerStrategyOptions.CollateralDeficit;
110
121
 
111
- // Override shouldn't be self-referencing
112
- if (chainName === overrideChainName) {
113
- ctx.addIssue({
114
- code: z.ZodIssueCode.custom,
115
- message: `Chain '${chainName}' has an override for '${chainName}', but '${chainName}' is self-referencing`,
116
- path: [
117
- 'strategy',
118
- 'chains',
119
- chainName,
120
- 'override',
121
- overrideChainName,
122
- ],
123
- });
124
- }
125
- }
122
+ if (hasCollateralDeficit && !collateralDeficitFirst) {
123
+ ctx.addIssue({
124
+ code: z.ZodIssueCode.custom,
125
+ message:
126
+ 'CollateralDeficitStrategy must be first when used in composite strategy',
127
+ path: ['strategy'],
128
+ });
126
129
  }
127
130
  }
128
131
 
129
- if (
130
- config.strategy.rebalanceStrategy === RebalancerStrategyOptions.MinAmount
132
+ // Validate each strategy in the array
133
+ for (
134
+ let strategyIndex = 0;
135
+ strategyIndex < config.strategy.length;
136
+ strategyIndex++
131
137
  ) {
132
- const minAmountChainsTypes = Object.values(config.strategy.chains).map(
133
- (c) => c.minAmount.type,
134
- );
135
- if (new Set(minAmountChainsTypes).size > 1) {
136
- ctx.addIssue({
137
- code: z.ZodIssueCode.custom,
138
- message: `All chains must use the same minAmount type.`,
139
- path: ['strategy', 'chains'],
140
- });
138
+ const strategy = config.strategy[strategyIndex];
139
+ const chainNames = new Set(Object.keys(strategy.chains));
140
+
141
+ // Check each chain's overrides
142
+ for (const [chainName, chainConfig] of Object.entries(strategy.chains)) {
143
+ if ('override' in chainConfig && chainConfig.override) {
144
+ for (const overrideChainName of Object.keys(chainConfig.override)) {
145
+ // Each override key must reference a valid chain
146
+ if (!chainNames.has(overrideChainName)) {
147
+ ctx.addIssue({
148
+ code: z.ZodIssueCode.custom,
149
+ message: `Chain '${chainName}' has an override for '${overrideChainName}', but '${overrideChainName}' is not defined in the config`,
150
+ path: [
151
+ 'strategy',
152
+ strategyIndex,
153
+ 'chains',
154
+ chainName,
155
+ 'override',
156
+ overrideChainName,
157
+ ],
158
+ });
159
+ }
160
+
161
+ // Override shouldn't be self-referencing
162
+ if (chainName === overrideChainName) {
163
+ ctx.addIssue({
164
+ code: z.ZodIssueCode.custom,
165
+ message: `Chain '${chainName}' has an override for '${chainName}', but '${chainName}' is self-referencing`,
166
+ path: [
167
+ 'strategy',
168
+ strategyIndex,
169
+ 'chains',
170
+ chainName,
171
+ 'override',
172
+ overrideChainName,
173
+ ],
174
+ });
175
+ }
176
+ }
177
+ }
178
+ }
179
+
180
+ if (strategy.rebalanceStrategy === RebalancerStrategyOptions.MinAmount) {
181
+ const minAmountChainsTypes = Object.values(strategy.chains).map(
182
+ (c) => c.minAmount.type,
183
+ );
184
+ if (new Set(minAmountChainsTypes).size > 1) {
185
+ ctx.addIssue({
186
+ code: z.ZodIssueCode.custom,
187
+ message: `All chains must use the same minAmount type.`,
188
+ path: ['strategy', strategyIndex, 'chains'],
189
+ });
190
+ }
141
191
  }
142
192
  }
143
193
  });
@@ -149,8 +199,65 @@ export type RebalancerWeightedChainConfig = z.infer<
149
199
  export type RebalancerMinAmountChainConfig = z.infer<
150
200
  typeof RebalancerMinAmountConfigSchema
151
201
  >;
202
+ export type CollateralDeficitChainConfig = z.infer<
203
+ typeof CollateralDeficitChainConfigSchema
204
+ >;
152
205
 
153
206
  export type StrategyConfig = z.infer<typeof StrategyConfigSchema>;
154
207
 
155
208
  export type RebalancerConfig = z.infer<typeof RebalancerConfigSchema>;
156
209
  export type RebalancerConfigFileInput = z.input<typeof RebalancerConfigSchema>;
210
+
211
+ /**
212
+ * Get all unique chain names from strategy config array.
213
+ */
214
+ export function getStrategyChainNames(strategies: StrategyConfig[]): string[] {
215
+ const chainSet = new Set<string>();
216
+ for (const strategy of strategies) {
217
+ Object.keys(strategy.chains).forEach((chain) => chainSet.add(chain));
218
+ }
219
+ return Array.from(chainSet);
220
+ }
221
+
222
+ /**
223
+ * Get chain config from the first strategy that has it.
224
+ * Returns undefined if no strategy has the chain.
225
+ */
226
+ export function getStrategyChainConfig(
227
+ strategies: StrategyConfig[],
228
+ chainName: string,
229
+ ): StrategyConfig['chains'][string] | undefined {
230
+ for (const strategy of strategies) {
231
+ if (chainName in strategy.chains) {
232
+ return strategy.chains[chainName];
233
+ }
234
+ }
235
+ return undefined;
236
+ }
237
+
238
+ /**
239
+ * Get all unique bridge addresses from all strategies and their overrides.
240
+ * This is used by ActionTracker to detect inflight rebalances across all configured bridges.
241
+ */
242
+ export function getAllBridges(strategies: StrategyConfig[]): string[] {
243
+ const bridges = new Set<string>();
244
+
245
+ for (const strategy of strategies) {
246
+ for (const chainConfig of Object.values(strategy.chains)) {
247
+ if (chainConfig.bridge) {
248
+ bridges.add(chainConfig.bridge);
249
+ }
250
+
251
+ if (chainConfig.override) {
252
+ for (const overrideConfig of Object.values(chainConfig.override)) {
253
+ const override = overrideConfig as { bridge?: string };
254
+ if (override.bridge) {
255
+ bridges.add(override.bridge);
256
+ }
257
+ }
258
+ }
259
+ }
260
+ }
261
+
262
+ return Array.from(bridges);
263
+ }