@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
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { Address, Domain } from '@hyperlane-xyz/utils';
|
|
2
|
+
|
|
3
|
+
import type { ConfirmedBlockTags } from '../interfaces/IMonitor.js';
|
|
4
|
+
|
|
5
|
+
import type { RebalanceAction, RebalanceIntent, Transfer } from './types.js';
|
|
6
|
+
|
|
7
|
+
export interface CreateRebalanceIntentParams {
|
|
8
|
+
origin: Domain;
|
|
9
|
+
destination: Domain;
|
|
10
|
+
amount: bigint;
|
|
11
|
+
bridge?: Address;
|
|
12
|
+
priority?: number;
|
|
13
|
+
strategyType?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface CreateRebalanceActionParams {
|
|
17
|
+
intentId: string;
|
|
18
|
+
origin: Domain;
|
|
19
|
+
destination: Domain;
|
|
20
|
+
amount: bigint;
|
|
21
|
+
messageId: string;
|
|
22
|
+
txHash?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* ActionTracker manages the lifecycle of tracked entities:
|
|
27
|
+
* - Transfers: Inflight user warp transfers
|
|
28
|
+
* - RebalanceIntents: Intents to move collateral
|
|
29
|
+
* - RebalanceActions: On-chain actions to fulfill intents
|
|
30
|
+
*/
|
|
31
|
+
export interface IActionTracker {
|
|
32
|
+
// === Lifecycle ===
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Initialize the tracker by loading state from Explorer and on-chain data.
|
|
36
|
+
* Called once on startup.
|
|
37
|
+
*/
|
|
38
|
+
initialize(): Promise<void>;
|
|
39
|
+
|
|
40
|
+
// === Sync Operations ===
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Sync inflight user transfers from Explorer and verify delivery status.
|
|
44
|
+
* @param confirmedBlockTags Optional block tags from Monitor for consistent state queries
|
|
45
|
+
*/
|
|
46
|
+
syncTransfers(confirmedBlockTags?: ConfirmedBlockTags): Promise<void>;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Sync rebalance intents by checking fulfillment status.
|
|
50
|
+
*/
|
|
51
|
+
syncRebalanceIntents(): Promise<void>;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Sync rebalance actions by verifying on-chain message delivery.
|
|
55
|
+
* @param confirmedBlockTags Optional block tags from Monitor for consistent state queries
|
|
56
|
+
*/
|
|
57
|
+
syncRebalanceActions(confirmedBlockTags?: ConfirmedBlockTags): Promise<void>;
|
|
58
|
+
|
|
59
|
+
// === Transfer Queries ===
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Get all transfers currently in progress.
|
|
63
|
+
*/
|
|
64
|
+
getInProgressTransfers(): Promise<Transfer[]>;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get all transfers destined for a specific domain.
|
|
68
|
+
*/
|
|
69
|
+
getTransfersByDestination(destination: Domain): Promise<Transfer[]>;
|
|
70
|
+
|
|
71
|
+
// === RebalanceIntent Queries ===
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get all active rebalance intents (not_started + in_progress).
|
|
75
|
+
*/
|
|
76
|
+
getActiveRebalanceIntents(): Promise<RebalanceIntent[]>;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get all rebalance intents destined for a specific domain.
|
|
80
|
+
*/
|
|
81
|
+
getRebalanceIntentsByDestination(
|
|
82
|
+
destination: Domain,
|
|
83
|
+
): Promise<RebalanceIntent[]>;
|
|
84
|
+
|
|
85
|
+
// === RebalanceIntent Management ===
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Create a new rebalance intent.
|
|
89
|
+
* Initial status: 'not_started'
|
|
90
|
+
*/
|
|
91
|
+
createRebalanceIntent(
|
|
92
|
+
params: CreateRebalanceIntentParams,
|
|
93
|
+
): Promise<RebalanceIntent>;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Mark a rebalance intent as complete.
|
|
97
|
+
*/
|
|
98
|
+
completeRebalanceIntent(id: string): Promise<void>;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Cancel a rebalance intent.
|
|
102
|
+
* Used for deliberate stops (e.g., stale fulfillment).
|
|
103
|
+
*/
|
|
104
|
+
cancelRebalanceIntent(id: string): Promise<void>;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Mark a rebalance intent as failed.
|
|
108
|
+
* Used when tx execution was attempted but failed.
|
|
109
|
+
*/
|
|
110
|
+
failRebalanceIntent(id: string): Promise<void>;
|
|
111
|
+
|
|
112
|
+
// === RebalanceAction Management ===
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Create a new rebalance action.
|
|
116
|
+
* Initial status: 'in_progress'
|
|
117
|
+
* Also transitions parent intent from 'not_started' to 'in_progress'.
|
|
118
|
+
*/
|
|
119
|
+
createRebalanceAction(
|
|
120
|
+
params: CreateRebalanceActionParams,
|
|
121
|
+
): Promise<RebalanceAction>;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Mark a rebalance action as complete.
|
|
125
|
+
* Updates parent intent's fulfilledAmount.
|
|
126
|
+
*/
|
|
127
|
+
completeRebalanceAction(id: string): Promise<void>;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Mark a rebalance action as failed.
|
|
131
|
+
*/
|
|
132
|
+
failRebalanceAction(id: string): Promise<void>;
|
|
133
|
+
|
|
134
|
+
// === Debug ===
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Log the contents of all stores for debugging purposes.
|
|
138
|
+
*/
|
|
139
|
+
logStoreContents(): Promise<void>;
|
|
140
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { expect } from 'chai';
|
|
2
|
+
import Sinon from 'sinon';
|
|
3
|
+
|
|
4
|
+
import type { MultiProvider } from '@hyperlane-xyz/sdk';
|
|
5
|
+
|
|
6
|
+
import type { IActionTracker } from './IActionTracker.js';
|
|
7
|
+
import { InflightContextAdapter } from './InflightContextAdapter.js';
|
|
8
|
+
import type { RebalanceIntent, Transfer } from './types.js';
|
|
9
|
+
|
|
10
|
+
describe('InflightContextAdapter', () => {
|
|
11
|
+
let actionTracker: Sinon.SinonStubbedInstance<IActionTracker>;
|
|
12
|
+
let multiProvider: Sinon.SinonStubbedInstance<MultiProvider>;
|
|
13
|
+
let adapter: InflightContextAdapter;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
actionTracker = {
|
|
17
|
+
getActiveRebalanceIntents: Sinon.stub(),
|
|
18
|
+
getInProgressTransfers: Sinon.stub(),
|
|
19
|
+
} as any;
|
|
20
|
+
|
|
21
|
+
multiProvider = {
|
|
22
|
+
getChainName: Sinon.stub(),
|
|
23
|
+
} as any;
|
|
24
|
+
|
|
25
|
+
adapter = new InflightContextAdapter(
|
|
26
|
+
actionTracker as any,
|
|
27
|
+
multiProvider as any,
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
Sinon.restore();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('getInflightContext', () => {
|
|
36
|
+
it('should return both pendingRebalances and pendingTransfers', async () => {
|
|
37
|
+
const mockIntents: RebalanceIntent[] = [
|
|
38
|
+
{
|
|
39
|
+
id: 'intent1',
|
|
40
|
+
origin: 1,
|
|
41
|
+
destination: 2,
|
|
42
|
+
amount: 1000n,
|
|
43
|
+
fulfilledAmount: 0n,
|
|
44
|
+
status: 'not_started',
|
|
45
|
+
createdAt: Date.now(),
|
|
46
|
+
updatedAt: Date.now(),
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const mockTransfers: Transfer[] = [
|
|
51
|
+
{
|
|
52
|
+
id: 'transfer1',
|
|
53
|
+
origin: 1,
|
|
54
|
+
destination: 2,
|
|
55
|
+
amount: 500n,
|
|
56
|
+
messageId: '0x123',
|
|
57
|
+
sender: '0xabc' as any,
|
|
58
|
+
recipient: '0xdef' as any,
|
|
59
|
+
status: 'in_progress',
|
|
60
|
+
createdAt: Date.now(),
|
|
61
|
+
updatedAt: Date.now(),
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
actionTracker.getActiveRebalanceIntents.resolves(mockIntents);
|
|
66
|
+
actionTracker.getInProgressTransfers.resolves(mockTransfers);
|
|
67
|
+
multiProvider.getChainName.withArgs(1).returns('ethereum');
|
|
68
|
+
multiProvider.getChainName.withArgs(2).returns('arbitrum');
|
|
69
|
+
|
|
70
|
+
const result = await adapter.getInflightContext();
|
|
71
|
+
|
|
72
|
+
expect(result.pendingRebalances).to.have.lengthOf(1);
|
|
73
|
+
expect(result.pendingRebalances[0]).to.deep.equal({
|
|
74
|
+
origin: 'ethereum',
|
|
75
|
+
destination: 'arbitrum',
|
|
76
|
+
amount: 1000n,
|
|
77
|
+
bridge: undefined,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
expect(result.pendingTransfers).to.have.lengthOf(1);
|
|
81
|
+
expect(result.pendingTransfers[0]).to.deep.equal({
|
|
82
|
+
origin: 'ethereum',
|
|
83
|
+
destination: 'arbitrum',
|
|
84
|
+
amount: 500n,
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should handle empty arrays', async () => {
|
|
89
|
+
actionTracker.getActiveRebalanceIntents.resolves([]);
|
|
90
|
+
actionTracker.getInProgressTransfers.resolves([]);
|
|
91
|
+
|
|
92
|
+
const result = await adapter.getInflightContext();
|
|
93
|
+
|
|
94
|
+
expect(result.pendingRebalances).to.be.an('array').that.is.empty;
|
|
95
|
+
expect(result.pendingTransfers).to.be.an('array').that.is.empty;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should correctly convert domain IDs to chain names', async () => {
|
|
99
|
+
const mockIntents: RebalanceIntent[] = [
|
|
100
|
+
{
|
|
101
|
+
id: 'intent1',
|
|
102
|
+
origin: 137,
|
|
103
|
+
destination: 10,
|
|
104
|
+
amount: 2000n,
|
|
105
|
+
fulfilledAmount: 0n,
|
|
106
|
+
status: 'not_started',
|
|
107
|
+
createdAt: Date.now(),
|
|
108
|
+
updatedAt: Date.now(),
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
const mockTransfers: Transfer[] = [
|
|
113
|
+
{
|
|
114
|
+
id: 'transfer1',
|
|
115
|
+
origin: 137,
|
|
116
|
+
destination: 10,
|
|
117
|
+
amount: 300n,
|
|
118
|
+
messageId: '0x456',
|
|
119
|
+
sender: '0x111' as any,
|
|
120
|
+
recipient: '0x222' as any,
|
|
121
|
+
status: 'in_progress',
|
|
122
|
+
createdAt: Date.now(),
|
|
123
|
+
updatedAt: Date.now(),
|
|
124
|
+
},
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
actionTracker.getActiveRebalanceIntents.resolves(mockIntents);
|
|
128
|
+
actionTracker.getInProgressTransfers.resolves(mockTransfers);
|
|
129
|
+
multiProvider.getChainName.withArgs(137).returns('polygon');
|
|
130
|
+
multiProvider.getChainName.withArgs(10).returns('optimism');
|
|
131
|
+
|
|
132
|
+
const result = await adapter.getInflightContext();
|
|
133
|
+
|
|
134
|
+
expect(result.pendingRebalances[0].origin).to.equal('polygon');
|
|
135
|
+
expect(result.pendingRebalances[0].destination).to.equal('optimism');
|
|
136
|
+
expect(result.pendingTransfers[0].origin).to.equal('polygon');
|
|
137
|
+
expect(result.pendingTransfers[0].destination).to.equal('optimism');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should handle multiple intents and transfers', async () => {
|
|
141
|
+
const mockIntents: RebalanceIntent[] = [
|
|
142
|
+
{
|
|
143
|
+
id: 'intent1',
|
|
144
|
+
origin: 1,
|
|
145
|
+
destination: 2,
|
|
146
|
+
amount: 1000n,
|
|
147
|
+
fulfilledAmount: 0n,
|
|
148
|
+
status: 'not_started',
|
|
149
|
+
createdAt: Date.now(),
|
|
150
|
+
updatedAt: Date.now(),
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: 'intent2',
|
|
154
|
+
origin: 2,
|
|
155
|
+
destination: 3,
|
|
156
|
+
amount: 1500n,
|
|
157
|
+
fulfilledAmount: 0n,
|
|
158
|
+
status: 'in_progress',
|
|
159
|
+
createdAt: Date.now(),
|
|
160
|
+
updatedAt: Date.now(),
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
const mockTransfers: Transfer[] = [
|
|
165
|
+
{
|
|
166
|
+
id: 'transfer1',
|
|
167
|
+
origin: 1,
|
|
168
|
+
destination: 2,
|
|
169
|
+
amount: 500n,
|
|
170
|
+
messageId: '0x123',
|
|
171
|
+
sender: '0xabc' as any,
|
|
172
|
+
recipient: '0xdef' as any,
|
|
173
|
+
status: 'in_progress',
|
|
174
|
+
createdAt: Date.now(),
|
|
175
|
+
updatedAt: Date.now(),
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
id: 'transfer2',
|
|
179
|
+
origin: 3,
|
|
180
|
+
destination: 1,
|
|
181
|
+
amount: 750n,
|
|
182
|
+
messageId: '0x789',
|
|
183
|
+
sender: '0x333' as any,
|
|
184
|
+
recipient: '0x444' as any,
|
|
185
|
+
status: 'in_progress',
|
|
186
|
+
createdAt: Date.now(),
|
|
187
|
+
updatedAt: Date.now(),
|
|
188
|
+
},
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
actionTracker.getActiveRebalanceIntents.resolves(mockIntents);
|
|
192
|
+
actionTracker.getInProgressTransfers.resolves(mockTransfers);
|
|
193
|
+
multiProvider.getChainName.withArgs(1).returns('ethereum');
|
|
194
|
+
multiProvider.getChainName.withArgs(2).returns('arbitrum');
|
|
195
|
+
multiProvider.getChainName.withArgs(3).returns('optimism');
|
|
196
|
+
|
|
197
|
+
const result = await adapter.getInflightContext();
|
|
198
|
+
|
|
199
|
+
expect(result.pendingRebalances).to.have.lengthOf(2);
|
|
200
|
+
expect(result.pendingTransfers).to.have.lengthOf(2);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { MultiProvider } from '@hyperlane-xyz/sdk';
|
|
2
|
+
|
|
3
|
+
import type { InflightContext } from '../interfaces/IStrategy.js';
|
|
4
|
+
|
|
5
|
+
import type { IActionTracker } from './IActionTracker.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Adapter that converts ActionTracker data to strategy-consumable InflightContext.
|
|
9
|
+
* Handles conversion from Domain IDs (used by ActionTracker) to ChainNames (used by Strategy).
|
|
10
|
+
*/
|
|
11
|
+
export class InflightContextAdapter {
|
|
12
|
+
constructor(
|
|
13
|
+
private readonly actionTracker: IActionTracker,
|
|
14
|
+
private readonly multiProvider: MultiProvider,
|
|
15
|
+
) {}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get inflight context for strategy decision-making.
|
|
19
|
+
* Includes active rebalance intents and in-progress user transfers.
|
|
20
|
+
*/
|
|
21
|
+
async getInflightContext(): Promise<InflightContext> {
|
|
22
|
+
const intents = await this.actionTracker.getActiveRebalanceIntents();
|
|
23
|
+
const transfers = await this.actionTracker.getInProgressTransfers();
|
|
24
|
+
|
|
25
|
+
const pendingRebalances = intents.map((intent) => ({
|
|
26
|
+
origin: this.multiProvider.getChainName(intent.origin),
|
|
27
|
+
destination: this.multiProvider.getChainName(intent.destination),
|
|
28
|
+
// TODO: Review once inventory rebalancing is implemented and we expect
|
|
29
|
+
// partially fulfilled intents. May need to use (amount - fulfilledAmount).
|
|
30
|
+
amount: intent.amount,
|
|
31
|
+
bridge: intent.bridge,
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
const pendingTransfers = transfers.map((transfer) => ({
|
|
35
|
+
origin: this.multiProvider.getChainName(transfer.origin),
|
|
36
|
+
destination: this.multiProvider.getChainName(transfer.destination),
|
|
37
|
+
amount: transfer.amount,
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
return { pendingRebalances, pendingTransfers };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Export all store components
|
|
2
|
+
export type { IStore } from './store/index.js';
|
|
3
|
+
export { InMemoryStore } from './store/index.js';
|
|
4
|
+
|
|
5
|
+
// Export types
|
|
6
|
+
export type {
|
|
7
|
+
// Base interfaces
|
|
8
|
+
Identifiable,
|
|
9
|
+
CrossChainAction,
|
|
10
|
+
Timestamped,
|
|
11
|
+
TrackedActionBase,
|
|
12
|
+
// Status types
|
|
13
|
+
TransferStatus,
|
|
14
|
+
RebalanceIntentStatus,
|
|
15
|
+
RebalanceActionStatus,
|
|
16
|
+
// Entity types
|
|
17
|
+
Transfer,
|
|
18
|
+
RebalanceIntent,
|
|
19
|
+
RebalanceAction,
|
|
20
|
+
// Store type aliases
|
|
21
|
+
ITransferStore,
|
|
22
|
+
IRebalanceIntentStore,
|
|
23
|
+
IRebalanceActionStore,
|
|
24
|
+
} from './types.js';
|
|
25
|
+
|
|
26
|
+
// Export ActionTracker components
|
|
27
|
+
export { ActionTracker, type ActionTrackerConfig } from './ActionTracker.js';
|
|
28
|
+
|
|
29
|
+
export type {
|
|
30
|
+
IActionTracker,
|
|
31
|
+
CreateRebalanceIntentParams,
|
|
32
|
+
CreateRebalanceActionParams,
|
|
33
|
+
} from './IActionTracker.js';
|
|
34
|
+
|
|
35
|
+
// Export InflightContextAdapter
|
|
36
|
+
export { InflightContextAdapter } from './InflightContextAdapter.js';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Domain } from '@hyperlane-xyz/utils';
|
|
2
|
+
|
|
3
|
+
import type { TrackedActionBase } from '../types.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generic store interface for tracking entities.
|
|
7
|
+
* Provides CRUD operations and query methods for any tracked entity type.
|
|
8
|
+
*
|
|
9
|
+
* @template T - The entity type extending TrackedActionBase
|
|
10
|
+
* @template Status - The status enum type for this entity
|
|
11
|
+
*/
|
|
12
|
+
export interface IStore<T extends TrackedActionBase, Status extends string> {
|
|
13
|
+
/**
|
|
14
|
+
* Save a new entity or update an existing one.
|
|
15
|
+
*/
|
|
16
|
+
save(entity: T): Promise<void>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Retrieve an entity by ID.
|
|
20
|
+
*/
|
|
21
|
+
get(id: string): Promise<T | undefined>;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Retrieve all entities.
|
|
25
|
+
*/
|
|
26
|
+
getAll(): Promise<T[]>;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Update an entity with partial data.
|
|
30
|
+
* Automatically updates the `updatedAt` timestamp.
|
|
31
|
+
*/
|
|
32
|
+
update(id: string, updates: Partial<T>): Promise<void>;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Delete an entity by ID.
|
|
36
|
+
*/
|
|
37
|
+
delete(id: string): Promise<void>;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Query entities by status.
|
|
41
|
+
*/
|
|
42
|
+
getByStatus(status: Status): Promise<T[]>;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Query entities by destination domain.
|
|
46
|
+
*/
|
|
47
|
+
getByDestination(destination: Domain): Promise<T[]>;
|
|
48
|
+
}
|