@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
package/src/core/Rebalancer.ts
CHANGED
|
@@ -1,37 +1,31 @@
|
|
|
1
|
-
import { type PopulatedTransaction } from 'ethers';
|
|
1
|
+
import { type PopulatedTransaction, type providers } from 'ethers';
|
|
2
2
|
import { type Logger } from 'pino';
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
type ChainMap,
|
|
6
6
|
type ChainMetadata,
|
|
7
|
+
type ChainName,
|
|
8
|
+
type EthJsonRpcBlockParameterTag,
|
|
7
9
|
EvmMovableCollateralAdapter,
|
|
10
|
+
HyperlaneCore,
|
|
8
11
|
type InterchainGasQuote,
|
|
9
12
|
type MultiProvider,
|
|
10
13
|
type Token,
|
|
11
14
|
type WarpCore,
|
|
12
15
|
} from '@hyperlane-xyz/sdk';
|
|
13
|
-
import {
|
|
14
|
-
eqAddress,
|
|
15
|
-
isNullish,
|
|
16
|
-
mapAllSettled,
|
|
17
|
-
toWei,
|
|
18
|
-
} from '@hyperlane-xyz/utils';
|
|
16
|
+
import { eqAddress, isNullish, mapAllSettled } from '@hyperlane-xyz/utils';
|
|
19
17
|
|
|
20
18
|
import type {
|
|
21
19
|
IRebalancer,
|
|
22
20
|
PreparedTransaction,
|
|
21
|
+
RebalanceExecutionResult,
|
|
22
|
+
RebalanceRoute,
|
|
23
23
|
} from '../interfaces/IRebalancer.js';
|
|
24
|
-
import type { RebalancingRoute } from '../interfaces/IStrategy.js';
|
|
25
24
|
import { type Metrics } from '../metrics/Metrics.js';
|
|
26
|
-
import {
|
|
27
|
-
type BridgeConfigWithOverride,
|
|
28
|
-
getBridgeConfig,
|
|
29
|
-
} from '../utils/index.js';
|
|
30
25
|
|
|
31
26
|
export class Rebalancer implements IRebalancer {
|
|
32
27
|
private readonly logger: Logger;
|
|
33
28
|
constructor(
|
|
34
|
-
private readonly bridges: ChainMap<BridgeConfigWithOverride>,
|
|
35
29
|
private readonly warpCore: WarpCore,
|
|
36
30
|
private readonly chainMetadata: ChainMap<ChainMetadata>,
|
|
37
31
|
private readonly tokensByChainName: ChainMap<Token>,
|
|
@@ -42,65 +36,58 @@ export class Rebalancer implements IRebalancer {
|
|
|
42
36
|
this.logger = logger.child({ class: Rebalancer.name });
|
|
43
37
|
}
|
|
44
38
|
|
|
45
|
-
async rebalance(
|
|
39
|
+
async rebalance(
|
|
40
|
+
routes: RebalanceRoute[],
|
|
41
|
+
): Promise<RebalanceExecutionResult[]> {
|
|
46
42
|
if (routes.length === 0) {
|
|
47
43
|
this.logger.info('No routes to execute, exiting');
|
|
48
|
-
return;
|
|
44
|
+
return [];
|
|
49
45
|
}
|
|
50
46
|
|
|
51
47
|
this.logger.info({ numberOfRoutes: routes.length }, 'Rebalance initiated');
|
|
52
48
|
|
|
53
|
-
const { preparedTransactions,
|
|
49
|
+
const { preparedTransactions, preparationFailureResults } =
|
|
54
50
|
await this.prepareTransactions(routes);
|
|
55
51
|
|
|
56
|
-
let
|
|
57
|
-
let transactionFailures = 0;
|
|
58
|
-
let successfulTransactions: PreparedTransaction[] = [];
|
|
52
|
+
let executionResults: RebalanceExecutionResult[] = [];
|
|
59
53
|
|
|
60
54
|
if (preparedTransactions.length > 0) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
55
|
+
executionResults = await this.executeTransactions(preparedTransactions);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Combine preparation failures with execution results
|
|
59
|
+
const allResults = [...preparationFailureResults, ...executionResults];
|
|
60
|
+
|
|
61
|
+
// Record metrics for successful transactions
|
|
62
|
+
const successfulResults = allResults.filter((r) => r.success);
|
|
63
|
+
if (this.metrics && successfulResults.length > 0) {
|
|
64
|
+
for (const result of successfulResults) {
|
|
65
|
+
const token = this.tokensByChainName[result.route.origin];
|
|
66
|
+
if (token) {
|
|
67
|
+
this.metrics.recordRebalanceAmount(
|
|
68
|
+
result.route,
|
|
69
|
+
token.amount(result.route.amount),
|
|
70
|
+
);
|
|
71
|
+
}
|
|
69
72
|
}
|
|
70
73
|
}
|
|
71
74
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
gasEstimationFailures > 0 ||
|
|
75
|
-
transactionFailures > 0
|
|
76
|
-
) {
|
|
75
|
+
const failures = allResults.filter((r) => !r.success);
|
|
76
|
+
if (failures.length > 0) {
|
|
77
77
|
this.logger.error(
|
|
78
|
-
{
|
|
79
|
-
|
|
80
|
-
gasEstimationFailures,
|
|
81
|
-
transactionFailures,
|
|
82
|
-
},
|
|
83
|
-
'A rebalance stage failed.',
|
|
78
|
+
{ failureCount: failures.length, totalRoutes: routes.length },
|
|
79
|
+
'Some rebalance operations failed.',
|
|
84
80
|
);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (this.metrics && successfulTransactions.length > 0) {
|
|
89
|
-
for (const transaction of successfulTransactions) {
|
|
90
|
-
this.metrics.recordRebalanceAmount(
|
|
91
|
-
transaction.route,
|
|
92
|
-
transaction.originTokenAmount,
|
|
93
|
-
);
|
|
94
|
-
}
|
|
81
|
+
} else {
|
|
82
|
+
this.logger.info('✅ Rebalance successful');
|
|
95
83
|
}
|
|
96
84
|
|
|
97
|
-
|
|
98
|
-
return;
|
|
85
|
+
return allResults;
|
|
99
86
|
}
|
|
100
87
|
|
|
101
|
-
private async prepareTransactions(routes:
|
|
88
|
+
private async prepareTransactions(routes: RebalanceRoute[]): Promise<{
|
|
102
89
|
preparedTransactions: PreparedTransaction[];
|
|
103
|
-
|
|
90
|
+
preparationFailureResults: RebalanceExecutionResult[];
|
|
104
91
|
}> {
|
|
105
92
|
this.logger.info(
|
|
106
93
|
{ numRoutes: routes.length },
|
|
@@ -116,15 +103,32 @@ export class Rebalancer implements IRebalancer {
|
|
|
116
103
|
const preparedTransactions = Array.from(fulfilled.values()).filter(
|
|
117
104
|
(tx): tx is PreparedTransaction => !isNullish(tx),
|
|
118
105
|
);
|
|
119
|
-
// Count rejections + null results as failures
|
|
120
|
-
const preparationFailures =
|
|
121
|
-
rejected.size + (fulfilled.size - preparedTransactions.length);
|
|
122
106
|
|
|
123
|
-
|
|
107
|
+
// Create failure results for tracking
|
|
108
|
+
const preparationFailureResults: RebalanceExecutionResult[] = [];
|
|
109
|
+
for (const [i, error] of rejected) {
|
|
110
|
+
preparationFailureResults.push({
|
|
111
|
+
route: routes[i],
|
|
112
|
+
success: false,
|
|
113
|
+
error: String(error),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
// Also track null results (validation failures)
|
|
117
|
+
Array.from(fulfilled.entries()).forEach(([i, tx]) => {
|
|
118
|
+
if (isNullish(tx)) {
|
|
119
|
+
preparationFailureResults.push({
|
|
120
|
+
route: routes[i],
|
|
121
|
+
success: false,
|
|
122
|
+
error: 'Preparation returned null',
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return { preparedTransactions, preparationFailureResults };
|
|
124
128
|
}
|
|
125
129
|
|
|
126
130
|
private async prepareTransaction(
|
|
127
|
-
route:
|
|
131
|
+
route: RebalanceRoute,
|
|
128
132
|
): Promise<PreparedTransaction | null> {
|
|
129
133
|
const { origin, destination, amount } = route;
|
|
130
134
|
|
|
@@ -153,12 +157,8 @@ export class Rebalancer implements IRebalancer {
|
|
|
153
157
|
const originHypAdapter = originToken.getHypAdapter(
|
|
154
158
|
this.warpCore.multiProvider,
|
|
155
159
|
) as EvmMovableCollateralAdapter;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
origin,
|
|
159
|
-
destination,
|
|
160
|
-
this.logger,
|
|
161
|
-
);
|
|
160
|
+
|
|
161
|
+
const { bridge } = route;
|
|
162
162
|
|
|
163
163
|
// 2. Get quotes
|
|
164
164
|
let quotes: InterchainGasQuote[];
|
|
@@ -168,7 +168,6 @@ export class Rebalancer implements IRebalancer {
|
|
|
168
168
|
destinationChainMeta.domainId,
|
|
169
169
|
destinationToken.addressOrDenom,
|
|
170
170
|
amount,
|
|
171
|
-
bridgeIsWarp,
|
|
172
171
|
);
|
|
173
172
|
} catch (error) {
|
|
174
173
|
this.logger.error(
|
|
@@ -210,7 +209,7 @@ export class Rebalancer implements IRebalancer {
|
|
|
210
209
|
return { populatedTx, route, originTokenAmount };
|
|
211
210
|
}
|
|
212
211
|
|
|
213
|
-
private async validateRoute(route:
|
|
212
|
+
private async validateRoute(route: RebalanceRoute): Promise<boolean> {
|
|
214
213
|
const { origin, destination, amount } = route;
|
|
215
214
|
const originToken = this.tokensByChainName[origin];
|
|
216
215
|
const destinationToken = this.tokensByChainName[destination];
|
|
@@ -296,12 +295,8 @@ export class Rebalancer implements IRebalancer {
|
|
|
296
295
|
return false;
|
|
297
296
|
}
|
|
298
297
|
|
|
299
|
-
const { bridge } =
|
|
300
|
-
|
|
301
|
-
origin,
|
|
302
|
-
destination,
|
|
303
|
-
this.logger,
|
|
304
|
-
);
|
|
298
|
+
const { bridge } = route;
|
|
299
|
+
|
|
305
300
|
if (
|
|
306
301
|
!(await originHypAdapter.isBridgeAllowed(
|
|
307
302
|
destinationDomain.domainId,
|
|
@@ -327,149 +322,244 @@ export class Rebalancer implements IRebalancer {
|
|
|
327
322
|
|
|
328
323
|
private async executeTransactions(
|
|
329
324
|
transactions: PreparedTransaction[],
|
|
330
|
-
): Promise<{
|
|
331
|
-
gasEstimationFailures: number;
|
|
332
|
-
transactionFailures: number;
|
|
333
|
-
successfulTransactions: PreparedTransaction[];
|
|
334
|
-
}> {
|
|
325
|
+
): Promise<RebalanceExecutionResult[]> {
|
|
335
326
|
this.logger.info(
|
|
336
327
|
{ numTransactions: transactions.length },
|
|
337
328
|
'Estimating gas for all prepared transactions.',
|
|
338
329
|
);
|
|
339
330
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
331
|
+
const results: RebalanceExecutionResult[] = [];
|
|
332
|
+
|
|
333
|
+
// 1. Estimate gas for rebalance transactions
|
|
334
|
+
const gasEstimateResults = await Promise.allSettled(
|
|
335
|
+
transactions.map(async (transaction) => {
|
|
344
336
|
await this.multiProvider.estimateGas(
|
|
345
337
|
transaction.route.origin,
|
|
346
338
|
transaction.populatedTx,
|
|
347
339
|
);
|
|
348
340
|
return transaction;
|
|
349
|
-
},
|
|
350
|
-
(_, i) => i,
|
|
341
|
+
}),
|
|
351
342
|
);
|
|
352
343
|
|
|
353
|
-
// 2. Filter out failed transactions and
|
|
354
|
-
const validTransactions =
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
failedTransaction.
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
344
|
+
// 2. Filter out failed transactions and track failures
|
|
345
|
+
const validTransactions: PreparedTransaction[] = [];
|
|
346
|
+
gasEstimateResults.forEach((result, i) => {
|
|
347
|
+
if (result.status === 'fulfilled') {
|
|
348
|
+
validTransactions.push(result.value);
|
|
349
|
+
} else {
|
|
350
|
+
const failedTransaction = transactions[i];
|
|
351
|
+
this.logger.error(
|
|
352
|
+
{
|
|
353
|
+
origin: failedTransaction.route.origin,
|
|
354
|
+
destination: failedTransaction.route.destination,
|
|
355
|
+
amount:
|
|
356
|
+
failedTransaction.originTokenAmount.getDecimalFormattedAmount(),
|
|
357
|
+
tokenName: failedTransaction.originTokenAmount.token.name,
|
|
358
|
+
error: result.reason,
|
|
359
|
+
},
|
|
360
|
+
'Gas estimation failed for route.',
|
|
361
|
+
);
|
|
362
|
+
results.push({
|
|
363
|
+
route: failedTransaction.route,
|
|
364
|
+
success: false,
|
|
365
|
+
error: `Gas estimation failed: ${String(result.reason)}`,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
});
|
|
370
369
|
|
|
371
370
|
if (validTransactions.length === 0) {
|
|
372
371
|
this.logger.info('No transactions to execute after gas estimation.');
|
|
373
|
-
return
|
|
374
|
-
gasEstimationFailures,
|
|
375
|
-
transactionFailures: 0,
|
|
376
|
-
successfulTransactions: [],
|
|
377
|
-
};
|
|
372
|
+
return results;
|
|
378
373
|
}
|
|
379
374
|
|
|
380
|
-
//
|
|
375
|
+
// 3. Group transactions by origin chain
|
|
376
|
+
const txsByOrigin = new Map<ChainName, PreparedTransaction[]>();
|
|
377
|
+
for (const tx of validTransactions) {
|
|
378
|
+
const origin = tx.route.origin;
|
|
379
|
+
if (!txsByOrigin.has(origin)) {
|
|
380
|
+
txsByOrigin.set(origin, []);
|
|
381
|
+
}
|
|
382
|
+
txsByOrigin.get(origin)!.push(tx);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// 4. Send transactions - parallel across chains, sequential within each chain
|
|
381
386
|
this.logger.info(
|
|
382
|
-
{
|
|
383
|
-
|
|
387
|
+
{
|
|
388
|
+
numChains: txsByOrigin.size,
|
|
389
|
+
numTransactions: validTransactions.length,
|
|
390
|
+
},
|
|
391
|
+
'Sending transactions (parallel across chains, sequential within chain).',
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
const chainSendResults = await Promise.allSettled(
|
|
395
|
+
Array.from(txsByOrigin.entries()).map(([origin, txs]) =>
|
|
396
|
+
this.sendTransactionsForChain(origin, txs),
|
|
397
|
+
),
|
|
384
398
|
);
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
399
|
+
|
|
400
|
+
// 5. Collect successful sends and record send failures
|
|
401
|
+
const successfulSends: Array<{
|
|
402
|
+
transaction: PreparedTransaction;
|
|
403
|
+
receipt: providers.TransactionReceipt;
|
|
404
|
+
}> = [];
|
|
405
|
+
|
|
406
|
+
chainSendResults.forEach((chainResult) => {
|
|
407
|
+
if (chainResult.status === 'fulfilled') {
|
|
408
|
+
for (const txResult of chainResult.value) {
|
|
409
|
+
if ('receipt' in txResult) {
|
|
410
|
+
successfulSends.push(txResult);
|
|
411
|
+
} else {
|
|
412
|
+
results.push({
|
|
413
|
+
route: txResult.transaction.route,
|
|
414
|
+
success: false,
|
|
415
|
+
error: `Transaction send failed: ${txResult.error}`,
|
|
416
|
+
});
|
|
417
|
+
this.metrics?.recordActionAttempt(
|
|
418
|
+
txResult.transaction.route,
|
|
419
|
+
false,
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
} else {
|
|
424
|
+
// This shouldn't happen since sendTransactionsForChain catches errors internally,
|
|
425
|
+
// but handle it just in case
|
|
426
|
+
this.logger.error(
|
|
427
|
+
{ error: chainResult.reason },
|
|
428
|
+
'Unexpected error during chain transaction sending.',
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
// 6. Build results from confirmed receipts
|
|
434
|
+
for (const { transaction, receipt } of successfulSends) {
|
|
435
|
+
const result = this.buildResult(transaction, receipt);
|
|
436
|
+
results.push(result);
|
|
437
|
+
this.metrics?.recordActionAttempt(result.route, result.success);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return results;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// === Parallel Transaction Sending Methods ===
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Send all transactions for a single origin chain sequentially.
|
|
447
|
+
* Sequential sending is required to avoid nonce contention when using the same signing key.
|
|
448
|
+
*/
|
|
449
|
+
private async sendTransactionsForChain(
|
|
450
|
+
origin: ChainName,
|
|
451
|
+
transactions: PreparedTransaction[],
|
|
452
|
+
): Promise<
|
|
453
|
+
Array<
|
|
454
|
+
| {
|
|
455
|
+
transaction: PreparedTransaction;
|
|
456
|
+
receipt: providers.TransactionReceipt;
|
|
457
|
+
}
|
|
458
|
+
| { transaction: PreparedTransaction; error: string }
|
|
459
|
+
>
|
|
460
|
+
> {
|
|
461
|
+
const results: Array<
|
|
462
|
+
| {
|
|
463
|
+
transaction: PreparedTransaction;
|
|
464
|
+
receipt: providers.TransactionReceipt;
|
|
465
|
+
}
|
|
466
|
+
| { transaction: PreparedTransaction; error: string }
|
|
467
|
+
> = [];
|
|
468
|
+
|
|
469
|
+
// Send sequentially to avoid nonce contention
|
|
470
|
+
for (const transaction of transactions) {
|
|
388
471
|
try {
|
|
389
|
-
const { origin, destination } = transaction.route;
|
|
390
472
|
const decimalFormattedAmount =
|
|
391
473
|
transaction.originTokenAmount.getDecimalFormattedAmount();
|
|
392
474
|
const tokenName = transaction.originTokenAmount.token.name;
|
|
475
|
+
|
|
476
|
+
const reorgPeriod = this.getReorgPeriod(origin);
|
|
477
|
+
|
|
393
478
|
this.logger.info(
|
|
394
479
|
{
|
|
395
480
|
origin,
|
|
396
|
-
destination,
|
|
481
|
+
destination: transaction.route.destination,
|
|
397
482
|
amount: decimalFormattedAmount,
|
|
398
483
|
tokenName,
|
|
484
|
+
reorgPeriod,
|
|
399
485
|
},
|
|
400
|
-
'Sending transaction for
|
|
486
|
+
'Sending rebalance transaction and waiting for reorgPeriod confirmations.',
|
|
401
487
|
);
|
|
488
|
+
|
|
402
489
|
const receipt = await this.multiProvider.sendTransaction(
|
|
403
490
|
origin,
|
|
404
491
|
transaction.populatedTx,
|
|
492
|
+
{
|
|
493
|
+
waitConfirmations: reorgPeriod as
|
|
494
|
+
| number
|
|
495
|
+
| EthJsonRpcBlockParameterTag,
|
|
496
|
+
},
|
|
405
497
|
);
|
|
498
|
+
|
|
406
499
|
this.logger.info(
|
|
407
500
|
{
|
|
408
501
|
origin,
|
|
409
|
-
destination,
|
|
502
|
+
destination: transaction.route.destination,
|
|
410
503
|
amount: decimalFormattedAmount,
|
|
411
504
|
tokenName,
|
|
412
505
|
txHash: receipt.transactionHash,
|
|
413
506
|
},
|
|
414
|
-
'
|
|
507
|
+
'Rebalance transaction confirmed at reorgPeriod depth.',
|
|
415
508
|
);
|
|
416
|
-
|
|
509
|
+
|
|
510
|
+
results.push({ transaction, receipt });
|
|
417
511
|
} catch (error) {
|
|
418
|
-
transactionFailures++;
|
|
419
512
|
this.logger.error(
|
|
420
513
|
{
|
|
421
|
-
origin
|
|
514
|
+
origin,
|
|
422
515
|
destination: transaction.route.destination,
|
|
423
516
|
amount: transaction.originTokenAmount.getDecimalFormattedAmount(),
|
|
424
517
|
tokenName: transaction.originTokenAmount.token.name,
|
|
425
518
|
error,
|
|
426
519
|
},
|
|
427
|
-
'Transaction failed for route.',
|
|
520
|
+
'Transaction send failed for route.',
|
|
428
521
|
);
|
|
522
|
+
results.push({ transaction, error: String(error) });
|
|
429
523
|
}
|
|
430
524
|
}
|
|
431
525
|
|
|
432
|
-
return
|
|
433
|
-
gasEstimationFailures,
|
|
434
|
-
transactionFailures,
|
|
435
|
-
successfulTransactions,
|
|
436
|
-
};
|
|
526
|
+
return results;
|
|
437
527
|
}
|
|
438
528
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
origin,
|
|
453
|
-
|
|
454
|
-
this.logger,
|
|
455
|
-
);
|
|
456
|
-
const minAccepted = BigInt(
|
|
457
|
-
toWei(bridgeMinAcceptedAmount, originToken.decimals),
|
|
529
|
+
/**
|
|
530
|
+
* Build the execution result from a confirmed transaction receipt.
|
|
531
|
+
* Receipt is already confirmed at reorgPeriod depth from sendTransaction.
|
|
532
|
+
*/
|
|
533
|
+
private buildResult(
|
|
534
|
+
transaction: PreparedTransaction,
|
|
535
|
+
receipt: providers.TransactionReceipt,
|
|
536
|
+
): RebalanceExecutionResult {
|
|
537
|
+
const { origin, destination } = transaction.route;
|
|
538
|
+
const dispatchedMessages = HyperlaneCore.getDispatchedMessages(receipt);
|
|
539
|
+
|
|
540
|
+
if (dispatchedMessages.length === 0) {
|
|
541
|
+
this.logger.error(
|
|
542
|
+
{ origin, destination, txHash: receipt.transactionHash },
|
|
543
|
+
'No Dispatch event found in confirmed rebalance receipt',
|
|
458
544
|
);
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
tokenName: originToken.name,
|
|
466
|
-
},
|
|
467
|
-
'Route skipped due to minimum threshold amount not met.',
|
|
468
|
-
);
|
|
469
|
-
continue;
|
|
470
|
-
}
|
|
471
|
-
filteredTransactions.push(transaction);
|
|
545
|
+
return {
|
|
546
|
+
route: transaction.route,
|
|
547
|
+
success: false,
|
|
548
|
+
error: `Transaction confirmed but no Dispatch event found`,
|
|
549
|
+
txHash: receipt.transactionHash,
|
|
550
|
+
};
|
|
472
551
|
}
|
|
473
|
-
|
|
552
|
+
|
|
553
|
+
return {
|
|
554
|
+
route: transaction.route,
|
|
555
|
+
success: true,
|
|
556
|
+
messageId: dispatchedMessages[0].id,
|
|
557
|
+
txHash: receipt.transactionHash,
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
private getReorgPeriod(chainName: string): number | string {
|
|
562
|
+
const metadata = this.multiProvider.getChainMetadata(chainName);
|
|
563
|
+
return metadata.blocks?.reorgPeriod ?? 32;
|
|
474
564
|
}
|
|
475
565
|
}
|