@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.
- package/README.md +134 -14
- package/dist/config/RebalancerConfig.d.ts +2 -2
- package/dist/config/RebalancerConfig.d.ts.map +1 -1
- package/dist/config/RebalancerConfig.js +4 -3
- package/dist/config/RebalancerConfig.js.map +1 -1
- package/dist/config/RebalancerConfig.test.js +434 -163
- package/dist/config/RebalancerConfig.test.js.map +1 -1
- package/dist/config/types.d.ts +1650 -290
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +124 -46
- package/dist/config/types.js.map +1 -1
- package/dist/core/Rebalancer.d.ts +14 -7
- package/dist/core/Rebalancer.d.ts.map +1 -1
- package/dist/core/Rebalancer.js +168 -99
- package/dist/core/Rebalancer.js.map +1 -1
- package/dist/core/Rebalancer.test.d.ts +2 -0
- package/dist/core/Rebalancer.test.d.ts.map +1 -0
- package/dist/core/Rebalancer.test.js +391 -0
- package/dist/core/Rebalancer.test.js.map +1 -0
- package/dist/core/RebalancerService.d.ts +16 -2
- package/dist/core/RebalancerService.d.ts.map +1 -1
- package/dist/core/RebalancerService.js +164 -21
- package/dist/core/RebalancerService.js.map +1 -1
- package/dist/core/RebalancerService.test.d.ts +2 -0
- package/dist/core/RebalancerService.test.d.ts.map +1 -0
- package/dist/core/RebalancerService.test.js +809 -0
- package/dist/core/RebalancerService.test.js.map +1 -0
- package/dist/factories/RebalancerContextFactory.d.ts +11 -0
- package/dist/factories/RebalancerContextFactory.d.ts.map +1 -1
- package/dist/factories/RebalancerContextFactory.js +60 -13
- package/dist/factories/RebalancerContextFactory.js.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/interfaces/IMonitor.d.ts +6 -8
- package/dist/interfaces/IMonitor.d.ts.map +1 -1
- package/dist/interfaces/IMonitor.js.map +1 -1
- package/dist/interfaces/IRebalancer.d.ts +20 -4
- package/dist/interfaces/IRebalancer.d.ts.map +1 -1
- package/dist/interfaces/IStrategy.d.ts +18 -2
- package/dist/interfaces/IStrategy.d.ts.map +1 -1
- package/dist/metrics/Metrics.d.ts +4 -2
- package/dist/metrics/Metrics.d.ts.map +1 -1
- package/dist/metrics/Metrics.js +21 -1
- package/dist/metrics/Metrics.js.map +1 -1
- package/dist/metrics/scripts/metrics.d.ts +2 -0
- package/dist/metrics/scripts/metrics.d.ts.map +1 -1
- package/dist/metrics/scripts/metrics.js +12 -0
- package/dist/metrics/scripts/metrics.js.map +1 -1
- package/dist/monitor/Monitor.d.ts +8 -3
- package/dist/monitor/Monitor.d.ts.map +1 -1
- package/dist/monitor/Monitor.js +75 -15
- package/dist/monitor/Monitor.js.map +1 -1
- package/dist/strategy/BaseStrategy.d.ts +51 -5
- package/dist/strategy/BaseStrategy.d.ts.map +1 -1
- package/dist/strategy/BaseStrategy.js +199 -19
- package/dist/strategy/BaseStrategy.js.map +1 -1
- package/dist/strategy/CollateralDeficitStrategy.d.ts +65 -0
- package/dist/strategy/CollateralDeficitStrategy.d.ts.map +1 -0
- package/dist/strategy/CollateralDeficitStrategy.js +245 -0
- package/dist/strategy/CollateralDeficitStrategy.js.map +1 -0
- package/dist/strategy/CollateralDeficitStrategy.test.d.ts +2 -0
- package/dist/strategy/CollateralDeficitStrategy.test.d.ts.map +1 -0
- package/dist/strategy/CollateralDeficitStrategy.test.js +364 -0
- package/dist/strategy/CollateralDeficitStrategy.test.js.map +1 -0
- package/dist/strategy/CompositeStrategy.d.ts +18 -0
- package/dist/strategy/CompositeStrategy.d.ts.map +1 -0
- package/dist/strategy/CompositeStrategy.js +63 -0
- package/dist/strategy/CompositeStrategy.js.map +1 -0
- package/dist/strategy/CompositeStrategy.test.d.ts +2 -0
- package/dist/strategy/CompositeStrategy.test.d.ts.map +1 -0
- package/dist/strategy/CompositeStrategy.test.js +265 -0
- package/dist/strategy/CompositeStrategy.test.js.map +1 -0
- package/dist/strategy/MinAmountStrategy.d.ts +12 -5
- package/dist/strategy/MinAmountStrategy.d.ts.map +1 -1
- package/dist/strategy/MinAmountStrategy.js +23 -14
- package/dist/strategy/MinAmountStrategy.js.map +1 -1
- package/dist/strategy/MinAmountStrategy.test.js +88 -20
- package/dist/strategy/MinAmountStrategy.test.js.map +1 -1
- package/dist/strategy/StrategyFactory.d.ts +15 -6
- package/dist/strategy/StrategyFactory.d.ts.map +1 -1
- package/dist/strategy/StrategyFactory.js +48 -10
- package/dist/strategy/StrategyFactory.js.map +1 -1
- package/dist/strategy/StrategyFactory.test.js +2 -2
- package/dist/strategy/StrategyFactory.test.js.map +1 -1
- package/dist/strategy/WeightedStrategy.d.ts +13 -4
- package/dist/strategy/WeightedStrategy.d.ts.map +1 -1
- package/dist/strategy/WeightedStrategy.js +18 -6
- package/dist/strategy/WeightedStrategy.js.map +1 -1
- package/dist/strategy/WeightedStrategy.test.js +108 -18
- package/dist/strategy/WeightedStrategy.test.js.map +1 -1
- package/dist/strategy/index.d.ts +2 -0
- package/dist/strategy/index.d.ts.map +1 -1
- package/dist/strategy/index.js +2 -0
- package/dist/strategy/index.js.map +1 -1
- package/dist/test/helpers.d.ts +93 -3
- package/dist/test/helpers.d.ts.map +1 -1
- package/dist/test/helpers.js +267 -10
- package/dist/test/helpers.js.map +1 -1
- package/dist/tracking/ActionTracker.d.ts +49 -0
- package/dist/tracking/ActionTracker.d.ts.map +1 -0
- package/dist/tracking/ActionTracker.js +422 -0
- package/dist/tracking/ActionTracker.js.map +1 -0
- package/dist/tracking/ActionTracker.test.d.ts +2 -0
- package/dist/tracking/ActionTracker.test.d.ts.map +1 -0
- package/dist/tracking/ActionTracker.test.js +637 -0
- package/dist/tracking/ActionTracker.test.js.map +1 -0
- package/dist/tracking/IActionTracker.d.ts +101 -0
- package/dist/tracking/IActionTracker.d.ts.map +1 -0
- package/dist/tracking/IActionTracker.js +2 -0
- package/dist/tracking/IActionTracker.js.map +1 -0
- package/dist/tracking/InflightContextAdapter.d.ts +18 -0
- package/dist/tracking/InflightContextAdapter.d.ts.map +1 -0
- package/dist/tracking/InflightContextAdapter.js +35 -0
- package/dist/tracking/InflightContextAdapter.js.map +1 -0
- package/dist/tracking/InflightContextAdapter.test.d.ts +2 -0
- package/dist/tracking/InflightContextAdapter.test.d.ts.map +1 -0
- package/dist/tracking/InflightContextAdapter.test.js +172 -0
- package/dist/tracking/InflightContextAdapter.test.js.map +1 -0
- package/dist/tracking/index.d.ts +7 -0
- package/dist/tracking/index.d.ts.map +1 -0
- package/dist/tracking/index.js +6 -0
- package/dist/tracking/index.js.map +1 -0
- package/dist/tracking/store/IStore.d.ts +41 -0
- package/dist/tracking/store/IStore.d.ts.map +1 -0
- package/dist/tracking/store/IStore.js +2 -0
- package/dist/tracking/store/IStore.js.map +1 -0
- package/dist/tracking/store/InMemoryStore.d.ts +21 -0
- package/dist/tracking/store/InMemoryStore.d.ts.map +1 -0
- package/dist/tracking/store/InMemoryStore.js +40 -0
- package/dist/tracking/store/InMemoryStore.js.map +1 -0
- package/dist/tracking/store/InMemoryStore.test.d.ts +2 -0
- package/dist/tracking/store/InMemoryStore.test.d.ts.map +1 -0
- package/dist/tracking/store/InMemoryStore.test.js +290 -0
- package/dist/tracking/store/InMemoryStore.test.js.map +1 -0
- package/dist/tracking/store/index.d.ts +3 -0
- package/dist/tracking/store/index.d.ts.map +1 -0
- package/dist/tracking/store/index.js +2 -0
- package/dist/tracking/store/index.js.map +1 -0
- package/dist/tracking/types.d.ts +43 -0
- package/dist/tracking/types.d.ts.map +1 -0
- package/dist/tracking/types.js +2 -0
- package/dist/tracking/types.js.map +1 -0
- package/dist/utils/ExplorerClient.d.ts +39 -1
- package/dist/utils/ExplorerClient.d.ts.map +1 -1
- package/dist/utils/ExplorerClient.js +205 -2
- package/dist/utils/ExplorerClient.js.map +1 -1
- package/dist/utils/balanceUtils.js +2 -2
- package/dist/utils/balanceUtils.js.map +1 -1
- package/dist/utils/balanceUtils.test.js +1 -0
- package/dist/utils/balanceUtils.test.js.map +1 -1
- package/dist/utils/bridgeUtils.d.ts +1 -3
- package/dist/utils/bridgeUtils.d.ts.map +1 -1
- package/dist/utils/bridgeUtils.js +1 -5
- package/dist/utils/bridgeUtils.js.map +1 -1
- package/dist/utils/bridgeUtils.test.js +3 -14
- package/dist/utils/bridgeUtils.test.js.map +1 -1
- package/package.json +11 -9
- package/src/config/RebalancerConfig.test.ts +459 -163
- package/src/config/RebalancerConfig.ts +5 -3
- package/src/config/types.ts +159 -52
- package/src/core/Rebalancer.test.ts +632 -0
- package/src/core/Rebalancer.ts +247 -157
- package/src/core/RebalancerService.test.ts +1144 -0
- package/src/core/RebalancerService.ts +245 -23
- package/src/factories/RebalancerContextFactory.ts +115 -14
- package/src/index.ts +16 -4
- package/src/interfaces/IMonitor.ts +15 -8
- package/src/interfaces/IRebalancer.ts +22 -4
- package/src/interfaces/IStrategy.ts +23 -2
- package/src/metrics/Metrics.ts +26 -5
- package/src/metrics/scripts/metrics.ts +14 -0
- package/src/monitor/Monitor.ts +109 -22
- package/src/strategy/BaseStrategy.ts +316 -26
- package/src/strategy/CollateralDeficitStrategy.test.ts +551 -0
- package/src/strategy/CollateralDeficitStrategy.ts +390 -0
- package/src/strategy/CompositeStrategy.test.ts +405 -0
- package/src/strategy/CompositeStrategy.ts +102 -0
- package/src/strategy/MinAmountStrategy.test.ts +189 -88
- package/src/strategy/MinAmountStrategy.ts +44 -13
- package/src/strategy/StrategyFactory.test.ts +2 -2
- package/src/strategy/StrategyFactory.ts +91 -8
- package/src/strategy/WeightedStrategy.test.ts +187 -72
- package/src/strategy/WeightedStrategy.ts +41 -7
- package/src/strategy/index.ts +2 -0
- package/src/test/helpers.ts +418 -14
- package/src/tracking/ActionTracker.test.ts +783 -0
- package/src/tracking/ActionTracker.ts +647 -0
- package/src/tracking/IActionTracker.ts +140 -0
- package/src/tracking/InflightContextAdapter.test.ts +203 -0
- package/src/tracking/InflightContextAdapter.ts +42 -0
- package/src/tracking/index.ts +36 -0
- package/src/tracking/store/IStore.ts +48 -0
- package/src/tracking/store/InMemoryStore.test.ts +338 -0
- package/src/tracking/store/InMemoryStore.ts +58 -0
- package/src/tracking/store/index.ts +2 -0
- package/src/tracking/types.ts +74 -0
- package/src/utils/ExplorerClient.ts +266 -3
- package/src/utils/balanceUtils.test.ts +1 -0
- package/src/utils/balanceUtils.ts +2 -2
- package/src/utils/bridgeUtils.test.ts +3 -15
- package/src/utils/bridgeUtils.ts +0 -10
- package/dist/core/WithInflightGuard.d.ts +0 -20
- package/dist/core/WithInflightGuard.d.ts.map +0 -1
- package/dist/core/WithInflightGuard.js +0 -47
- package/dist/core/WithInflightGuard.js.map +0 -1
- package/dist/core/WithInflightGuard.test.d.ts +0 -2
- package/dist/core/WithInflightGuard.test.d.ts.map +0 -1
- package/dist/core/WithInflightGuard.test.js +0 -64
- package/dist/core/WithInflightGuard.test.js.map +0 -1
- package/dist/core/WithSemaphore.d.ts +0 -22
- package/dist/core/WithSemaphore.d.ts.map +0 -1
- package/dist/core/WithSemaphore.js +0 -67
- package/dist/core/WithSemaphore.js.map +0 -1
- package/dist/core/WithSemaphore.test.d.ts +0 -2
- package/dist/core/WithSemaphore.test.d.ts.map +0 -1
- package/dist/core/WithSemaphore.test.js +0 -83
- package/dist/core/WithSemaphore.test.js.map +0 -1
- package/src/core/WithInflightGuard.test.ts +0 -131
- package/src/core/WithInflightGuard.ts +0 -67
- package/src/core/WithSemaphore.test.ts +0 -111
- package/src/core/WithSemaphore.ts +0 -92
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import { type Logger } from 'pino';
|
|
2
2
|
|
|
3
|
-
import type { ChainName } from '@hyperlane-xyz/sdk';
|
|
3
|
+
import type { ChainMap, ChainName, Token } from '@hyperlane-xyz/sdk';
|
|
4
|
+
import { toWei } from '@hyperlane-xyz/utils';
|
|
4
5
|
|
|
5
6
|
import type {
|
|
6
7
|
IStrategy,
|
|
8
|
+
InflightContext,
|
|
7
9
|
RawBalances,
|
|
8
|
-
|
|
10
|
+
Route,
|
|
11
|
+
StrategyRoute,
|
|
9
12
|
} from '../interfaces/IStrategy.js';
|
|
10
13
|
import { type Metrics } from '../metrics/Metrics.js';
|
|
14
|
+
import {
|
|
15
|
+
type BridgeConfig,
|
|
16
|
+
type BridgeConfigWithOverride,
|
|
17
|
+
getBridgeConfig,
|
|
18
|
+
} from '../utils/bridgeUtils.js';
|
|
11
19
|
|
|
12
20
|
export type Delta = { chain: ChainName; amount: bigint };
|
|
13
21
|
|
|
@@ -15,41 +23,81 @@ export type Delta = { chain: ChainName; amount: bigint };
|
|
|
15
23
|
* Base abstract class for rebalancing strategies
|
|
16
24
|
*/
|
|
17
25
|
export abstract class BaseStrategy implements IStrategy {
|
|
26
|
+
abstract readonly name: string;
|
|
18
27
|
protected readonly chains: ChainName[];
|
|
19
28
|
protected readonly metrics?: Metrics;
|
|
20
29
|
protected readonly logger: Logger;
|
|
30
|
+
protected readonly bridgeConfigs: ChainMap<BridgeConfigWithOverride>;
|
|
31
|
+
protected readonly tokensByChainName?: ChainMap<Token>;
|
|
21
32
|
|
|
22
|
-
constructor(
|
|
33
|
+
constructor(
|
|
34
|
+
chains: ChainName[],
|
|
35
|
+
logger: Logger,
|
|
36
|
+
bridgeConfigs: ChainMap<BridgeConfigWithOverride>,
|
|
37
|
+
metrics?: Metrics,
|
|
38
|
+
tokensByChainName?: ChainMap<Token>,
|
|
39
|
+
) {
|
|
23
40
|
// Rebalancing makes sense only with more than one chain.
|
|
24
41
|
if (chains.length < 2) {
|
|
25
42
|
throw new Error('At least two chains must be configured');
|
|
26
43
|
}
|
|
27
44
|
this.chains = chains;
|
|
28
45
|
this.logger = logger;
|
|
46
|
+
this.bridgeConfigs = bridgeConfigs;
|
|
29
47
|
this.metrics = metrics;
|
|
48
|
+
this.tokensByChainName = tokensByChainName;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
protected getBridgeConfigForRoute(
|
|
52
|
+
origin: ChainName,
|
|
53
|
+
destination: ChainName,
|
|
54
|
+
): BridgeConfig {
|
|
55
|
+
return getBridgeConfig(this.bridgeConfigs, origin, destination);
|
|
30
56
|
}
|
|
31
57
|
|
|
32
58
|
/**
|
|
33
59
|
* Main method to get rebalancing routes
|
|
34
60
|
*/
|
|
35
|
-
getRebalancingRoutes(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
61
|
+
getRebalancingRoutes(
|
|
62
|
+
rawBalances: RawBalances,
|
|
63
|
+
inflightContext?: InflightContext,
|
|
64
|
+
): StrategyRoute[] {
|
|
65
|
+
const pendingRebalances = inflightContext?.pendingRebalances ?? [];
|
|
66
|
+
const pendingTransfers = inflightContext?.pendingTransfers ?? [];
|
|
67
|
+
const proposedRebalances = inflightContext?.proposedRebalances ?? [];
|
|
68
|
+
|
|
43
69
|
this.logger.info(
|
|
44
70
|
{
|
|
45
|
-
|
|
71
|
+
strategy: this.name,
|
|
72
|
+
balances: Object.entries(rawBalances).map(([c, b]) => ({
|
|
73
|
+
chain: c,
|
|
74
|
+
balance: b.toString(),
|
|
75
|
+
})),
|
|
76
|
+
pendingRebalances: pendingRebalances.length,
|
|
77
|
+
pendingTransfers: pendingTransfers.length,
|
|
78
|
+
proposedRebalances: proposedRebalances.length,
|
|
46
79
|
},
|
|
47
|
-
'
|
|
80
|
+
'Strategy evaluating',
|
|
48
81
|
);
|
|
49
82
|
this.validateRawBalances(rawBalances);
|
|
50
83
|
|
|
84
|
+
// Store original balances for filtering step
|
|
85
|
+
const actualBalances = rawBalances;
|
|
86
|
+
|
|
87
|
+
// Step 1: Reserve collateral for pending user transfers
|
|
88
|
+
// This prevents draining collateral needed for incoming user transfers
|
|
89
|
+
const effectiveBalances = this.reserveCollateral(
|
|
90
|
+
rawBalances,
|
|
91
|
+
pendingTransfers,
|
|
92
|
+
);
|
|
93
|
+
|
|
51
94
|
// Get balances categorized by surplus and deficit
|
|
52
|
-
|
|
95
|
+
// Pass pending and proposed rebalances so strategy can account for them
|
|
96
|
+
const { surpluses, deficits } = this.getCategorizedBalances(
|
|
97
|
+
effectiveBalances,
|
|
98
|
+
pendingRebalances,
|
|
99
|
+
proposedRebalances,
|
|
100
|
+
);
|
|
53
101
|
|
|
54
102
|
this.logger.debug(
|
|
55
103
|
{
|
|
@@ -126,7 +174,7 @@ export abstract class BaseStrategy implements IStrategy {
|
|
|
126
174
|
surpluses.sort((a, b) => (a.amount > b.amount ? -1 : 1));
|
|
127
175
|
deficits.sort((a, b) => (a.amount > b.amount ? -1 : 1));
|
|
128
176
|
|
|
129
|
-
const routes:
|
|
177
|
+
const routes: StrategyRoute[] = [];
|
|
130
178
|
|
|
131
179
|
// Transfer from surplus to deficit until all deficits are balanced.
|
|
132
180
|
while (deficits.length > 0 && surpluses.length > 0) {
|
|
@@ -137,24 +185,34 @@ export abstract class BaseStrategy implements IStrategy {
|
|
|
137
185
|
const transferAmount =
|
|
138
186
|
surplus.amount > deficit.amount ? deficit.amount : surplus.amount;
|
|
139
187
|
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
188
|
+
// Skip zero-amount routes (can occur after scaling when surpluses < deficits)
|
|
189
|
+
if (transferAmount > 0n) {
|
|
190
|
+
// Get bridge config for this route (with destination-specific overrides)
|
|
191
|
+
const bridgeConfig = this.getBridgeConfigForRoute(
|
|
192
|
+
surplus.chain,
|
|
193
|
+
deficit.chain,
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
// Creates the balancing route
|
|
197
|
+
routes.push({
|
|
198
|
+
origin: surplus.chain,
|
|
199
|
+
destination: deficit.chain,
|
|
200
|
+
amount: transferAmount,
|
|
201
|
+
bridge: bridgeConfig.bridge,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
146
204
|
|
|
147
205
|
// Decreases the amounts for the following iterations
|
|
148
206
|
deficit.amount -= transferAmount;
|
|
149
207
|
surplus.amount -= transferAmount;
|
|
150
208
|
|
|
151
|
-
// Removes the deficit if it is fully balanced
|
|
152
|
-
if (
|
|
209
|
+
// Removes the deficit if it is fully balanced (including scaled-to-zero)
|
|
210
|
+
if (deficit.amount <= 0n) {
|
|
153
211
|
deficits.shift();
|
|
154
212
|
}
|
|
155
213
|
|
|
156
214
|
// Removes the surplus if it has been drained
|
|
157
|
-
if (
|
|
215
|
+
if (surplus.amount <= 0n) {
|
|
158
216
|
surpluses.shift();
|
|
159
217
|
}
|
|
160
218
|
}
|
|
@@ -173,14 +231,40 @@ export abstract class BaseStrategy implements IStrategy {
|
|
|
173
231
|
},
|
|
174
232
|
'Found rebalancing routes',
|
|
175
233
|
);
|
|
176
|
-
|
|
234
|
+
|
|
235
|
+
const filteredRoutes = this.filterRoutes(routes, actualBalances);
|
|
236
|
+
|
|
237
|
+
this.logger.debug(
|
|
238
|
+
{
|
|
239
|
+
context: this.constructor.name,
|
|
240
|
+
filteredRoutesCount: filteredRoutes.length,
|
|
241
|
+
droppedCount: routes.length - filteredRoutes.length,
|
|
242
|
+
},
|
|
243
|
+
'Filtered rebalancing routes',
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
// Record metrics for each intent that passed filtering
|
|
247
|
+
for (const route of filteredRoutes) {
|
|
248
|
+
this.metrics?.recordIntentCreated(route, this.name);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return filteredRoutes;
|
|
177
252
|
}
|
|
178
253
|
|
|
179
254
|
/**
|
|
180
255
|
* Abstract method to get balances categorized by surplus and deficit
|
|
181
256
|
* Each specific strategy should implement its own logic
|
|
257
|
+
*
|
|
258
|
+
* @param balances - Effective balances (after collateral reservation)
|
|
259
|
+
* @param pendingRebalances - In-flight rebalances (origin tx confirmed, balance already deducted)
|
|
260
|
+
* @param proposedRebalances - Routes from earlier strategies in same cycle (not yet executed)
|
|
261
|
+
* @returns Categorized surpluses and deficits as Delta arrays
|
|
182
262
|
*/
|
|
183
|
-
protected abstract getCategorizedBalances(
|
|
263
|
+
protected abstract getCategorizedBalances(
|
|
264
|
+
balances: RawBalances,
|
|
265
|
+
pendingRebalances?: Route[],
|
|
266
|
+
proposedRebalances?: StrategyRoute[],
|
|
267
|
+
): {
|
|
184
268
|
surpluses: Delta[];
|
|
185
269
|
deficits: Delta[];
|
|
186
270
|
};
|
|
@@ -207,4 +291,210 @@ export abstract class BaseStrategy implements IStrategy {
|
|
|
207
291
|
}
|
|
208
292
|
}
|
|
209
293
|
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Reserve collateral for pending user transfers.
|
|
297
|
+
* Subtracts pending transfer amounts from destination balances.
|
|
298
|
+
* This ensures we don't drain collateral needed for incoming transfers.
|
|
299
|
+
*
|
|
300
|
+
* @param rawBalances - Current on-chain balances
|
|
301
|
+
* @param pendingTransfers - Transfers that will need collateral on destination
|
|
302
|
+
* @returns Balances with reserved amounts subtracted
|
|
303
|
+
*/
|
|
304
|
+
protected reserveCollateral(
|
|
305
|
+
rawBalances: RawBalances,
|
|
306
|
+
pendingTransfers: Route[],
|
|
307
|
+
): RawBalances {
|
|
308
|
+
if (pendingTransfers.length === 0) {
|
|
309
|
+
return rawBalances;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const reserved = { ...rawBalances };
|
|
313
|
+
|
|
314
|
+
for (const transfer of pendingTransfers) {
|
|
315
|
+
const destBalance = reserved[transfer.destination] ?? 0n;
|
|
316
|
+
// Reserve the transfer amount from destination
|
|
317
|
+
// Allow negative values to indicate collateral deficits
|
|
318
|
+
reserved[transfer.destination] = destBalance - transfer.amount;
|
|
319
|
+
|
|
320
|
+
this.logger.debug(
|
|
321
|
+
{
|
|
322
|
+
context: this.constructor.name,
|
|
323
|
+
destination: transfer.destination,
|
|
324
|
+
amount: transfer.amount.toString(),
|
|
325
|
+
newBalance: reserved[transfer.destination].toString(),
|
|
326
|
+
},
|
|
327
|
+
'Reserved collateral for pending transfer',
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
this.logger.info(
|
|
332
|
+
{
|
|
333
|
+
reservations: pendingTransfers.map((t) => ({
|
|
334
|
+
destination: t.destination,
|
|
335
|
+
amount: t.amount.toString(),
|
|
336
|
+
})),
|
|
337
|
+
},
|
|
338
|
+
'Collateral reserved for pending transfers',
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
return reserved;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Simulate pending rebalances by adding to destination balances.
|
|
346
|
+
*
|
|
347
|
+
* Only adds to destination - does NOT subtract from origin because:
|
|
348
|
+
* - pendingRebalances only contains in_progress intents (origin tx confirmed)
|
|
349
|
+
* - Origin balance is already deducted on-chain
|
|
350
|
+
*
|
|
351
|
+
* @param rawBalances - Current balances (may already have collateral reserved)
|
|
352
|
+
* @param pendingRebalances - In-flight rebalance operations (in_progress only)
|
|
353
|
+
* @returns Simulated future balances after rebalances complete
|
|
354
|
+
*/
|
|
355
|
+
protected simulatePendingRebalances(
|
|
356
|
+
rawBalances: RawBalances,
|
|
357
|
+
pendingRebalances: Route[],
|
|
358
|
+
): RawBalances {
|
|
359
|
+
if (pendingRebalances.length === 0) {
|
|
360
|
+
return rawBalances;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const simulated = { ...rawBalances };
|
|
364
|
+
|
|
365
|
+
for (const rebalance of pendingRebalances) {
|
|
366
|
+
// Only add to destination - origin is already deducted on-chain
|
|
367
|
+
// (pendingRebalances only contains in_progress intents with confirmed origin tx)
|
|
368
|
+
simulated[rebalance.destination] =
|
|
369
|
+
(simulated[rebalance.destination] ?? 0n) + rebalance.amount;
|
|
370
|
+
|
|
371
|
+
this.logger.debug(
|
|
372
|
+
{
|
|
373
|
+
context: this.constructor.name,
|
|
374
|
+
destination: rebalance.destination,
|
|
375
|
+
amount: rebalance.amount.toString(),
|
|
376
|
+
},
|
|
377
|
+
'Simulated pending rebalance (destination increase)',
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
this.logger.info(
|
|
382
|
+
{
|
|
383
|
+
simulations: pendingRebalances.map((r) => ({
|
|
384
|
+
from: r.origin,
|
|
385
|
+
to: r.destination,
|
|
386
|
+
amount: r.amount.toString(),
|
|
387
|
+
})),
|
|
388
|
+
},
|
|
389
|
+
'Simulated pending rebalances',
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
return simulated;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Simulate proposed rebalances by subtracting from origin AND adding to destination.
|
|
397
|
+
*
|
|
398
|
+
* Unlike pendingRebalances, proposedRebalances are routes from earlier strategies
|
|
399
|
+
* in the same cycle that haven't been executed yet. Therefore:
|
|
400
|
+
* - Origin balance has NOT been deducted on-chain
|
|
401
|
+
* - We must simulate both sides to maintain accurate total balance
|
|
402
|
+
*
|
|
403
|
+
* @param rawBalances - Current balances (may already have pending rebalances simulated)
|
|
404
|
+
* @param proposedRebalances - Routes from earlier strategies (not yet executed)
|
|
405
|
+
* @returns Simulated balances after proposed rebalances complete
|
|
406
|
+
*/
|
|
407
|
+
protected simulateProposedRebalances(
|
|
408
|
+
rawBalances: RawBalances,
|
|
409
|
+
proposedRebalances: Route[],
|
|
410
|
+
): RawBalances {
|
|
411
|
+
if (proposedRebalances.length === 0) {
|
|
412
|
+
return rawBalances;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const simulated = { ...rawBalances };
|
|
416
|
+
|
|
417
|
+
for (const rebalance of proposedRebalances) {
|
|
418
|
+
// Subtract from origin (not yet deducted on-chain)
|
|
419
|
+
simulated[rebalance.origin] =
|
|
420
|
+
(simulated[rebalance.origin] ?? 0n) - rebalance.amount;
|
|
421
|
+
|
|
422
|
+
// Add to destination
|
|
423
|
+
simulated[rebalance.destination] =
|
|
424
|
+
(simulated[rebalance.destination] ?? 0n) + rebalance.amount;
|
|
425
|
+
|
|
426
|
+
this.logger.debug(
|
|
427
|
+
{
|
|
428
|
+
context: this.constructor.name,
|
|
429
|
+
origin: rebalance.origin,
|
|
430
|
+
destination: rebalance.destination,
|
|
431
|
+
amount: rebalance.amount.toString(),
|
|
432
|
+
},
|
|
433
|
+
'Simulated proposed rebalance (origin decrease, destination increase)',
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
this.logger.info(
|
|
438
|
+
{
|
|
439
|
+
simulations: proposedRebalances.map((r) => ({
|
|
440
|
+
from: r.origin,
|
|
441
|
+
to: r.destination,
|
|
442
|
+
amount: r.amount.toString(),
|
|
443
|
+
})),
|
|
444
|
+
},
|
|
445
|
+
'Simulated proposed rebalances',
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
return simulated;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
protected filterRoutes(
|
|
452
|
+
routes: StrategyRoute[],
|
|
453
|
+
actualBalances: RawBalances,
|
|
454
|
+
): StrategyRoute[] {
|
|
455
|
+
return routes.filter((route) => {
|
|
456
|
+
const balance = actualBalances[route.origin] ?? 0n;
|
|
457
|
+
if (balance < route.amount) {
|
|
458
|
+
this.logger.warn(
|
|
459
|
+
{
|
|
460
|
+
context: this.constructor.name,
|
|
461
|
+
origin: route.origin,
|
|
462
|
+
destination: route.destination,
|
|
463
|
+
required: route.amount.toString(),
|
|
464
|
+
available: balance.toString(),
|
|
465
|
+
},
|
|
466
|
+
'Dropping route due to insufficient balance',
|
|
467
|
+
);
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (this.tokensByChainName) {
|
|
472
|
+
const token = this.tokensByChainName[route.origin];
|
|
473
|
+
if (token) {
|
|
474
|
+
const bridgeConfig = this.getBridgeConfigForRoute(
|
|
475
|
+
route.origin,
|
|
476
|
+
route.destination,
|
|
477
|
+
);
|
|
478
|
+
const minAmount = BigInt(
|
|
479
|
+
toWei(bridgeConfig.bridgeMinAcceptedAmount, token.decimals),
|
|
480
|
+
);
|
|
481
|
+
if (route.amount < minAmount) {
|
|
482
|
+
this.logger.info(
|
|
483
|
+
{
|
|
484
|
+
context: this.constructor.name,
|
|
485
|
+
origin: route.origin,
|
|
486
|
+
destination: route.destination,
|
|
487
|
+
amount: route.amount.toString(),
|
|
488
|
+
minAmount: minAmount.toString(),
|
|
489
|
+
},
|
|
490
|
+
'Dropping route below bridgeMinAcceptedAmount',
|
|
491
|
+
);
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return true;
|
|
498
|
+
});
|
|
499
|
+
}
|
|
210
500
|
}
|