@hyperlane-xyz/rebalancer 2.0.0 → 3.1.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 (213) hide show
  1. package/dist/bridges/LiFiBridge.d.ts +67 -0
  2. package/dist/bridges/LiFiBridge.d.ts.map +1 -0
  3. package/dist/bridges/LiFiBridge.js +386 -0
  4. package/dist/bridges/LiFiBridge.js.map +1 -0
  5. package/dist/config/RebalancerConfig.d.ts +8 -2
  6. package/dist/config/RebalancerConfig.d.ts.map +1 -1
  7. package/dist/config/RebalancerConfig.js +9 -4
  8. package/dist/config/RebalancerConfig.js.map +1 -1
  9. package/dist/config/RebalancerConfig.test.js +135 -1
  10. package/dist/config/RebalancerConfig.test.js.map +1 -1
  11. package/dist/config/types.d.ts +1023 -304
  12. package/dist/config/types.d.ts.map +1 -1
  13. package/dist/config/types.js +113 -10
  14. package/dist/config/types.js.map +1 -1
  15. package/dist/core/InventoryRebalancer.d.ts +190 -0
  16. package/dist/core/InventoryRebalancer.d.ts.map +1 -0
  17. package/dist/core/InventoryRebalancer.js +892 -0
  18. package/dist/core/InventoryRebalancer.js.map +1 -0
  19. package/dist/core/InventoryRebalancer.test.d.ts +2 -0
  20. package/dist/core/InventoryRebalancer.test.d.ts.map +1 -0
  21. package/dist/core/InventoryRebalancer.test.js +1382 -0
  22. package/dist/core/InventoryRebalancer.test.js.map +1 -0
  23. package/dist/core/Rebalancer.d.ts +11 -4
  24. package/dist/core/Rebalancer.d.ts.map +1 -1
  25. package/dist/core/Rebalancer.js +92 -9
  26. package/dist/core/Rebalancer.js.map +1 -1
  27. package/dist/core/Rebalancer.test.js +82 -49
  28. package/dist/core/Rebalancer.test.js.map +1 -1
  29. package/dist/core/RebalancerOrchestrator.d.ts +30 -9
  30. package/dist/core/RebalancerOrchestrator.d.ts.map +1 -1
  31. package/dist/core/RebalancerOrchestrator.js +79 -71
  32. package/dist/core/RebalancerOrchestrator.js.map +1 -1
  33. package/dist/core/RebalancerOrchestrator.test.d.ts +2 -0
  34. package/dist/core/RebalancerOrchestrator.test.d.ts.map +1 -0
  35. package/dist/core/RebalancerOrchestrator.test.js +719 -0
  36. package/dist/core/RebalancerOrchestrator.test.js.map +1 -0
  37. package/dist/core/RebalancerService.d.ts +7 -3
  38. package/dist/core/RebalancerService.d.ts.map +1 -1
  39. package/dist/core/RebalancerService.js +44 -24
  40. package/dist/core/RebalancerService.js.map +1 -1
  41. package/dist/core/RebalancerService.test.js +74 -110
  42. package/dist/core/RebalancerService.test.js.map +1 -1
  43. package/dist/e2e/collateral-deficit.e2e-test.js +1 -3
  44. package/dist/e2e/collateral-deficit.e2e-test.js.map +1 -1
  45. package/dist/e2e/composite.e2e-test.js.map +1 -1
  46. package/dist/e2e/harness/BridgeSetup.d.ts +6 -0
  47. package/dist/e2e/harness/BridgeSetup.d.ts.map +1 -1
  48. package/dist/e2e/harness/BridgeSetup.js +10 -1
  49. package/dist/e2e/harness/BridgeSetup.js.map +1 -1
  50. package/dist/e2e/harness/ForkIndexer.d.ts.map +1 -1
  51. package/dist/e2e/harness/ForkIndexer.js +1 -0
  52. package/dist/e2e/harness/ForkIndexer.js.map +1 -1
  53. package/dist/e2e/harness/TestHelpers.d.ts.map +1 -1
  54. package/dist/e2e/harness/TestHelpers.js +1 -4
  55. package/dist/e2e/harness/TestHelpers.js.map +1 -1
  56. package/dist/e2e/harness/TestRebalancer.d.ts +1 -1
  57. package/dist/e2e/harness/TestRebalancer.d.ts.map +1 -1
  58. package/dist/e2e/harness/TestRebalancer.js +9 -9
  59. package/dist/e2e/harness/TestRebalancer.js.map +1 -1
  60. package/dist/e2e/minAmount.e2e-test.js +0 -1
  61. package/dist/e2e/minAmount.e2e-test.js.map +1 -1
  62. package/dist/e2e/weighted.e2e-test.js +0 -1
  63. package/dist/e2e/weighted.e2e-test.js.map +1 -1
  64. package/dist/factories/RebalancerContextFactory.d.ts +48 -6
  65. package/dist/factories/RebalancerContextFactory.d.ts.map +1 -1
  66. package/dist/factories/RebalancerContextFactory.js +171 -17
  67. package/dist/factories/RebalancerContextFactory.js.map +1 -1
  68. package/dist/index.d.ts +6 -6
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +2 -2
  71. package/dist/index.js.map +1 -1
  72. package/dist/interfaces/IExternalBridge.d.ts +101 -0
  73. package/dist/interfaces/IExternalBridge.d.ts.map +1 -0
  74. package/dist/interfaces/IExternalBridge.js +2 -0
  75. package/dist/interfaces/IExternalBridge.js.map +1 -0
  76. package/dist/interfaces/IMonitor.d.ts +1 -0
  77. package/dist/interfaces/IMonitor.d.ts.map +1 -1
  78. package/dist/interfaces/IRebalancer.d.ts +25 -25
  79. package/dist/interfaces/IRebalancer.d.ts.map +1 -1
  80. package/dist/interfaces/IStrategy.d.ts +36 -3
  81. package/dist/interfaces/IStrategy.d.ts.map +1 -1
  82. package/dist/interfaces/IStrategy.js +12 -1
  83. package/dist/interfaces/IStrategy.js.map +1 -1
  84. package/dist/metrics/PriceGetter.js +1 -1
  85. package/dist/metrics/PriceGetter.js.map +1 -1
  86. package/dist/metrics/scripts/metrics.d.ts +3 -3
  87. package/dist/monitor/Monitor.d.ts +12 -2
  88. package/dist/monitor/Monitor.d.ts.map +1 -1
  89. package/dist/monitor/Monitor.js +46 -1
  90. package/dist/monitor/Monitor.js.map +1 -1
  91. package/dist/service.js +40 -17
  92. package/dist/service.js.map +1 -1
  93. package/dist/strategy/BaseStrategy.d.ts +12 -6
  94. package/dist/strategy/BaseStrategy.d.ts.map +1 -1
  95. package/dist/strategy/BaseStrategy.js +56 -21
  96. package/dist/strategy/BaseStrategy.js.map +1 -1
  97. package/dist/strategy/CollateralDeficitStrategy.d.ts +1 -1
  98. package/dist/strategy/CollateralDeficitStrategy.d.ts.map +1 -1
  99. package/dist/strategy/CollateralDeficitStrategy.js +19 -11
  100. package/dist/strategy/CollateralDeficitStrategy.js.map +1 -1
  101. package/dist/strategy/CollateralDeficitStrategy.test.js +135 -2
  102. package/dist/strategy/CollateralDeficitStrategy.test.js.map +1 -1
  103. package/dist/strategy/CompositeStrategy.test.js +13 -0
  104. package/dist/strategy/CompositeStrategy.test.js.map +1 -1
  105. package/dist/strategy/MinAmountStrategy.test.js +4 -0
  106. package/dist/strategy/MinAmountStrategy.test.js.map +1 -1
  107. package/dist/strategy/StrategyFactory.d.ts +2 -1
  108. package/dist/strategy/StrategyFactory.d.ts.map +1 -1
  109. package/dist/strategy/StrategyFactory.js +24 -8
  110. package/dist/strategy/StrategyFactory.js.map +1 -1
  111. package/dist/strategy/WeightedStrategy.test.js +6 -0
  112. package/dist/strategy/WeightedStrategy.test.js.map +1 -1
  113. package/dist/test/helpers.d.ts +8 -7
  114. package/dist/test/helpers.d.ts.map +1 -1
  115. package/dist/test/helpers.js +23 -5
  116. package/dist/test/helpers.js.map +1 -1
  117. package/dist/test/lifiMocks.d.ts +51 -0
  118. package/dist/test/lifiMocks.d.ts.map +1 -0
  119. package/dist/test/lifiMocks.js +130 -0
  120. package/dist/test/lifiMocks.js.map +1 -0
  121. package/dist/tracking/ActionTracker.d.ts +34 -1
  122. package/dist/tracking/ActionTracker.d.ts.map +1 -1
  123. package/dist/tracking/ActionTracker.js +233 -26
  124. package/dist/tracking/ActionTracker.js.map +1 -1
  125. package/dist/tracking/ActionTracker.test.js +380 -19
  126. package/dist/tracking/ActionTracker.test.js.map +1 -1
  127. package/dist/tracking/IActionTracker.d.ts +48 -3
  128. package/dist/tracking/IActionTracker.d.ts.map +1 -1
  129. package/dist/tracking/InflightContextAdapter.d.ts.map +1 -1
  130. package/dist/tracking/InflightContextAdapter.js +24 -7
  131. package/dist/tracking/InflightContextAdapter.js.map +1 -1
  132. package/dist/tracking/InflightContextAdapter.test.js +7 -4
  133. package/dist/tracking/InflightContextAdapter.test.js.map +1 -1
  134. package/dist/tracking/types.d.ts +33 -2
  135. package/dist/tracking/types.d.ts.map +1 -1
  136. package/dist/utils/ExplorerClient.d.ts +3 -1
  137. package/dist/utils/ExplorerClient.d.ts.map +1 -1
  138. package/dist/utils/ExplorerClient.js +16 -8
  139. package/dist/utils/ExplorerClient.js.map +1 -1
  140. package/dist/utils/bridgeUtils.d.ts +27 -4
  141. package/dist/utils/bridgeUtils.d.ts.map +1 -1
  142. package/dist/utils/bridgeUtils.js +38 -0
  143. package/dist/utils/bridgeUtils.js.map +1 -1
  144. package/dist/utils/bridgeUtils.test.js +9 -0
  145. package/dist/utils/bridgeUtils.test.js.map +1 -1
  146. package/dist/utils/gasEstimation.d.ts +65 -0
  147. package/dist/utils/gasEstimation.d.ts.map +1 -0
  148. package/dist/utils/gasEstimation.js +176 -0
  149. package/dist/utils/gasEstimation.js.map +1 -0
  150. package/dist/utils/tokenUtils.d.ts +9 -1
  151. package/dist/utils/tokenUtils.d.ts.map +1 -1
  152. package/dist/utils/tokenUtils.js +11 -0
  153. package/dist/utils/tokenUtils.js.map +1 -1
  154. package/package.json +9 -7
  155. package/src/bridges/LiFiBridge.ts +538 -0
  156. package/src/config/RebalancerConfig.test.ts +162 -0
  157. package/src/config/RebalancerConfig.ts +21 -3
  158. package/src/config/types.ts +147 -10
  159. package/src/core/InventoryRebalancer.test.ts +1721 -0
  160. package/src/core/InventoryRebalancer.ts +1265 -0
  161. package/src/core/Rebalancer.test.ts +84 -30
  162. package/src/core/Rebalancer.ts +144 -23
  163. package/src/core/RebalancerOrchestrator.test.ts +869 -0
  164. package/src/core/RebalancerOrchestrator.ts +146 -95
  165. package/src/core/RebalancerService.test.ts +86 -124
  166. package/src/core/RebalancerService.ts +67 -33
  167. package/src/e2e/collateral-deficit.e2e-test.ts +2 -4
  168. package/src/e2e/composite.e2e-test.ts +5 -5
  169. package/src/e2e/harness/BridgeSetup.ts +28 -1
  170. package/src/e2e/harness/ForkIndexer.ts +1 -0
  171. package/src/e2e/harness/TestHelpers.ts +1 -4
  172. package/src/e2e/harness/TestRebalancer.ts +10 -7
  173. package/src/e2e/minAmount.e2e-test.ts +1 -2
  174. package/src/e2e/weighted.e2e-test.ts +1 -2
  175. package/src/factories/RebalancerContextFactory.ts +294 -24
  176. package/src/index.ts +22 -5
  177. package/src/interfaces/IExternalBridge.ts +115 -0
  178. package/src/interfaces/IMonitor.ts +1 -0
  179. package/src/interfaces/IRebalancer.ts +45 -29
  180. package/src/interfaces/IStrategy.ts +50 -3
  181. package/src/metrics/PriceGetter.ts +1 -1
  182. package/src/monitor/Monitor.ts +81 -2
  183. package/src/service.ts +59 -18
  184. package/src/strategy/BaseStrategy.ts +77 -24
  185. package/src/strategy/CollateralDeficitStrategy.test.ts +181 -4
  186. package/src/strategy/CollateralDeficitStrategy.ts +42 -15
  187. package/src/strategy/CompositeStrategy.test.ts +13 -0
  188. package/src/strategy/MinAmountStrategy.test.ts +4 -0
  189. package/src/strategy/StrategyFactory.ts +33 -6
  190. package/src/strategy/WeightedStrategy.test.ts +6 -0
  191. package/src/test/helpers.ts +39 -14
  192. package/src/test/lifiMocks.ts +174 -0
  193. package/src/tracking/ActionTracker.test.ts +443 -19
  194. package/src/tracking/ActionTracker.ts +339 -28
  195. package/src/tracking/IActionTracker.ts +59 -3
  196. package/src/tracking/InflightContextAdapter.test.ts +7 -4
  197. package/src/tracking/InflightContextAdapter.ts +42 -9
  198. package/src/tracking/types.ts +45 -2
  199. package/src/utils/ExplorerClient.ts +27 -10
  200. package/src/utils/bridgeUtils.test.ts +9 -0
  201. package/src/utils/bridgeUtils.ts +75 -6
  202. package/src/utils/gasEstimation.ts +272 -0
  203. package/src/utils/tokenUtils.ts +12 -0
  204. package/dist/tracking/index.d.ts +0 -7
  205. package/dist/tracking/index.d.ts.map +0 -1
  206. package/dist/tracking/index.js +0 -6
  207. package/dist/tracking/index.js.map +0 -1
  208. package/dist/utils/index.d.ts +0 -5
  209. package/dist/utils/index.d.ts.map +0 -1
  210. package/dist/utils/index.js +0 -5
  211. package/dist/utils/index.js.map +0 -1
  212. package/src/tracking/index.ts +0 -36
  213. package/src/utils/index.ts +0 -4
@@ -1,6 +1,9 @@
1
1
  import type { MultiProvider } from '@hyperlane-xyz/sdk';
2
2
 
3
- import type { InflightContext } from '../interfaces/IStrategy.js';
3
+ import type {
4
+ InflightContext,
5
+ RouteWithContext,
6
+ } from '../interfaces/IStrategy.js';
4
7
 
5
8
  import type { IActionTracker } from './IActionTracker.js';
6
9
 
@@ -22,14 +25,44 @@ export class InflightContextAdapter {
22
25
  const intents = await this.actionTracker.getActiveRebalanceIntents();
23
26
  const transfers = await this.actionTracker.getInProgressTransfers();
24
27
 
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
- }));
28
+ const pendingRebalances: RouteWithContext[] = await Promise.all(
29
+ intents.map(async (intent) => {
30
+ let deliveredAmount = 0n;
31
+ let awaitingDeliveryAmount = 0n;
32
+
33
+ // For inventory intents, compute delivered and awaiting amounts from actions
34
+ if (intent.executionMethod === 'inventory') {
35
+ const actions = await this.actionTracker.getActionsForIntent(
36
+ intent.id,
37
+ );
38
+
39
+ // Sum of complete inventory_deposit actions (message delivered)
40
+ deliveredAmount = actions
41
+ .filter(
42
+ (a) => a.type === 'inventory_deposit' && a.status === 'complete',
43
+ )
44
+ .reduce((sum, a) => sum + a.amount, 0n);
45
+
46
+ // Sum of in_progress inventory_deposit actions (tx confirmed, message pending)
47
+ awaitingDeliveryAmount = actions
48
+ .filter(
49
+ (a) =>
50
+ a.type === 'inventory_deposit' && a.status === 'in_progress',
51
+ )
52
+ .reduce((sum, a) => sum + a.amount, 0n);
53
+ }
54
+
55
+ return {
56
+ origin: this.multiProvider.getChainName(intent.origin),
57
+ destination: this.multiProvider.getChainName(intent.destination),
58
+ amount: intent.amount,
59
+ deliveredAmount,
60
+ awaitingDeliveryAmount,
61
+ executionMethod: intent.executionMethod,
62
+ bridge: intent.bridge,
63
+ };
64
+ }),
65
+ );
33
66
 
34
67
  const pendingTransfers = transfers.map((transfer) => ({
35
68
  origin: this.multiProvider.getChainName(transfer.origin),
@@ -1,5 +1,7 @@
1
1
  import type { Address, Domain } from '@hyperlane-xyz/utils';
2
2
 
3
+ import type { ExternalBridgeType } from '../config/types.js';
4
+
3
5
  import type { IStore } from './store/IStore.js';
4
6
 
5
7
  // === Base Interfaces ===
@@ -35,6 +37,26 @@ export type RebalanceIntentStatus =
35
37
  | 'failed';
36
38
  export type RebalanceActionStatus = 'in_progress' | 'complete' | 'failed';
37
39
 
40
+ // === Execution Types ===
41
+
42
+ /**
43
+ * Execution method for rebalancing:
44
+ * - `movable_collateral`: Uses MovableCollateralRouter.rebalance() on-chain
45
+ * - `inventory`: Uses external bridges + transferRemote
46
+ */
47
+ export type ExecutionMethod = 'movable_collateral' | 'inventory';
48
+
49
+ /**
50
+ * Type of rebalance action:
51
+ * - `rebalance_message`: Standard movable collateral rebalance (Hyperlane message)
52
+ * - `inventory_movement`: External bridge transfer (e.g., LiFi) to move inventory
53
+ * - `inventory_deposit`: transferRemote to deposit inventory as collateral
54
+ */
55
+ export type ActionType =
56
+ | 'rebalance_message'
57
+ | 'inventory_movement'
58
+ | 'inventory_deposit';
59
+
38
60
  // === Entity Types ===
39
61
 
40
62
  export interface Transfer extends TrackedActionBase {
@@ -46,17 +68,22 @@ export interface Transfer extends TrackedActionBase {
46
68
 
47
69
  export interface RebalanceIntent extends TrackedActionBase {
48
70
  status: RebalanceIntentStatus;
49
- fulfilledAmount: bigint;
50
71
  bridge?: Address; // Optional - bridge contract used (missing for recovered intents)
51
72
  priority?: number; // Optional - missing for recovered intents
52
73
  strategyType?: string; // Optional - missing for recovered intents
74
+ executionMethod?: ExecutionMethod; // Optional - defaults to movable_collateral
75
+ externalBridge?: ExternalBridgeType; // Optional - external bridge type (e.g., LiFi)
53
76
  }
54
77
 
55
78
  export interface RebalanceAction extends TrackedActionBase {
56
79
  status: RebalanceActionStatus;
80
+ type: ActionType; // Type of action (rebalance_message, inventory_movement, inventory_deposit)
57
81
  intentId: string; // Links to parent RebalanceIntent
58
- messageId: string; // Hyperlane message ID
82
+ messageId?: string; // Hyperlane message ID (required for rebalance_message, inventory_deposit)
59
83
  txHash?: string; // Origin transaction hash
84
+ // Fields for inventory_movement (external bridge)
85
+ externalBridgeTransferId?: string; // External bridge transfer ID (e.g., LiFi transfer ID)
86
+ externalBridgeId?: ExternalBridgeType; // External bridge identifier (e.g., 'lifi')
60
87
  }
61
88
 
62
89
  // === Type Aliases for Stores ===
@@ -70,3 +97,19 @@ export type IRebalanceActionStore = IStore<
70
97
  RebalanceAction,
71
98
  RebalanceActionStatus
72
99
  >;
100
+
101
+ // === Derived Types ===
102
+
103
+ /**
104
+ * Represents an inventory intent that has been partially fulfilled
105
+ * and can continue execution. Values are derived from action states.
106
+ */
107
+ export interface PartialInventoryIntent {
108
+ intent: RebalanceIntent;
109
+ /** Sum of complete inventory_deposit action amounts */
110
+ completedAmount: bigint;
111
+ /** Amount remaining to fulfill (0n when final deposit is fully in-flight). Formula: intent.amount - completedAmount - inflightAmount */
112
+ remaining: bigint;
113
+ /** True when intent has in_progress inventory_deposit actions (not safe to continue, but still active) */
114
+ hasInflightDeposit: boolean;
115
+ }
@@ -9,14 +9,15 @@ export type InflightRebalanceQueryParams = {
9
9
 
10
10
  export type UserTransferQueryParams = {
11
11
  routersByDomain: Record<number, string>; // Domain ID → router address (derive routers and domains from this)
12
- excludeTxSender: string; // Rebalancer address to exclude
12
+ excludeTxSenders: string[]; // Addresses to exclude (rebalancer + inventory signer)
13
13
  limit?: number;
14
14
  };
15
15
 
16
16
  export type RebalanceActionQueryParams = {
17
17
  bridges: string[]; // Bridge contract addresses
18
18
  routersByDomain: Record<number, string>; // Domain ID → router address (derive routers and domains from this)
19
- rebalancerAddress: string; // Only include rebalancer's txs
19
+ rebalancerAddress: string; // Include rebalancer's txs
20
+ inventorySignerAddress?: string; // Optional: also include inventory signer's txs (for inventory_deposit actions)
20
21
  limit?: number;
21
22
  };
22
23
 
@@ -31,6 +32,7 @@ export type ExplorerMessage = {
31
32
  origin_tx_recipient: string;
32
33
  is_delivered: boolean;
33
34
  message_body: string;
35
+ send_occurred_at: string | null;
34
36
  };
35
37
 
36
38
  export interface IExplorerClient {
@@ -71,6 +73,7 @@ export class ExplorerClient implements IExplorerClient {
71
73
  origin_tx_recipient: normalizeHex(msg.origin_tx_recipient),
72
74
  is_delivered: msg.is_delivered,
73
75
  message_body: normalizeHex(msg.message_body),
76
+ send_occurred_at: msg.send_occurred_at ?? null,
74
77
  };
75
78
  }
76
79
 
@@ -191,7 +194,7 @@ export class ExplorerClient implements IExplorerClient {
191
194
  params: UserTransferQueryParams,
192
195
  logger: Logger,
193
196
  ): Promise<ExplorerMessage[]> {
194
- const { routersByDomain, excludeTxSender, limit = 100 } = params;
197
+ const { routersByDomain, excludeTxSenders, limit = 100 } = params;
195
198
 
196
199
  // Derive routers and domains from routersByDomain
197
200
  const routers = Object.values(routersByDomain);
@@ -202,7 +205,7 @@ export class ExplorerClient implements IExplorerClient {
202
205
  recipients: routers.map((a) => this.toBytea(a)),
203
206
  originDomains: domains,
204
207
  destDomains: domains,
205
- excludeTxSender: this.toBytea(excludeTxSender),
208
+ excludeTxSenders: excludeTxSenders.map((a) => this.toBytea(a)),
206
209
  limit,
207
210
  };
208
211
 
@@ -214,7 +217,7 @@ export class ExplorerClient implements IExplorerClient {
214
217
  $recipients: [bytea!],
215
218
  $originDomains: [Int!],
216
219
  $destDomains: [Int!],
217
- $excludeTxSender: bytea!,
220
+ $excludeTxSenders: [bytea!],
218
221
  $limit: Int = 100
219
222
  ) {
220
223
  message_view(
@@ -225,7 +228,7 @@ export class ExplorerClient implements IExplorerClient {
225
228
  { recipient: { _in: $recipients } },
226
229
  { origin_domain_id: { _in: $originDomains } },
227
230
  { destination_domain_id: { _in: $destDomains } },
228
- { origin_tx_sender: { _neq: $excludeTxSender } }
231
+ { origin_tx_sender: { _nin: $excludeTxSenders } }
229
232
  ]
230
233
  }
231
234
  order_by: { origin_tx_id: desc }
@@ -241,6 +244,7 @@ export class ExplorerClient implements IExplorerClient {
241
244
  origin_tx_recipient
242
245
  is_delivered
243
246
  message_body
247
+ send_occurred_at
244
248
  }
245
249
  }`;
246
250
 
@@ -284,19 +288,31 @@ export class ExplorerClient implements IExplorerClient {
284
288
  params: RebalanceActionQueryParams,
285
289
  logger: Logger,
286
290
  ): Promise<ExplorerMessage[]> {
287
- const { bridges, routersByDomain, rebalancerAddress, limit = 100 } = params;
291
+ const {
292
+ bridges,
293
+ routersByDomain,
294
+ rebalancerAddress,
295
+ inventorySignerAddress,
296
+ limit = 100,
297
+ } = params;
288
298
 
289
299
  // Derive routers and domains from routersByDomain
290
300
  const routers = Object.values(routersByDomain);
291
301
  const domains = Object.keys(routersByDomain).map(Number);
292
302
 
303
+ // Build list of tx senders to include (rebalancer + optional inventory signer)
304
+ const txSenders = [this.toBytea(rebalancerAddress)];
305
+ if (inventorySignerAddress) {
306
+ txSenders.push(this.toBytea(inventorySignerAddress));
307
+ }
308
+
293
309
  const variables = {
294
310
  senders: bridges.map((a) => this.toBytea(a)),
295
311
  recipients: bridges.map((a) => this.toBytea(a)),
296
312
  originTxRecipients: routers.map((a) => this.toBytea(a)),
297
313
  originDomains: domains,
298
314
  destDomains: domains,
299
- txSender: this.toBytea(rebalancerAddress),
315
+ txSenders,
300
316
  limit,
301
317
  };
302
318
 
@@ -309,7 +325,7 @@ export class ExplorerClient implements IExplorerClient {
309
325
  $originTxRecipients: [bytea!],
310
326
  $originDomains: [Int!],
311
327
  $destDomains: [Int!],
312
- $txSender: bytea!,
328
+ $txSenders: [bytea!],
313
329
  $limit: Int = 100
314
330
  ) {
315
331
  message_view(
@@ -321,7 +337,7 @@ export class ExplorerClient implements IExplorerClient {
321
337
  { origin_tx_recipient: { _in: $originTxRecipients } },
322
338
  { origin_domain_id: { _in: $originDomains } },
323
339
  { destination_domain_id: { _in: $destDomains } },
324
- { origin_tx_sender: { _eq: $txSender } }
340
+ { origin_tx_sender: { _in: $txSenders } }
325
341
  ]
326
342
  }
327
343
  order_by: { origin_tx_id: desc }
@@ -337,6 +353,7 @@ export class ExplorerClient implements IExplorerClient {
337
353
  origin_tx_recipient
338
354
  is_delivered
339
355
  message_body
356
+ send_occurred_at
340
357
  }
341
358
  }`;
342
359
 
@@ -11,10 +11,12 @@ describe('bridgeConfig', () => {
11
11
  it('should return the base bridge config when no overrides exist', () => {
12
12
  const bridges: ChainMap<BridgeConfigWithOverride> = {
13
13
  chain1: {
14
+ executionType: 'movableCollateral',
14
15
  bridge: '0x1234567890123456789012345678901234567890',
15
16
  bridgeMinAcceptedAmount: 1000,
16
17
  },
17
18
  chain2: {
19
+ executionType: 'movableCollateral',
18
20
  bridge: '0x0987654321098765432109876543210987654321',
19
21
  bridgeMinAcceptedAmount: 2000,
20
22
  },
@@ -23,6 +25,7 @@ describe('bridgeConfig', () => {
23
25
  const result = getBridgeConfig(bridges, 'chain1', 'chain2');
24
26
 
25
27
  expect(result).to.deep.equal({
28
+ executionType: 'movableCollateral',
26
29
  bridge: '0x1234567890123456789012345678901234567890',
27
30
  bridgeMinAcceptedAmount: 1000,
28
31
  });
@@ -31,6 +34,7 @@ describe('bridgeConfig', () => {
31
34
  it('should merge base config with overrides when they exist', () => {
32
35
  const bridges: ChainMap<BridgeConfigWithOverride> = {
33
36
  chain1: {
37
+ executionType: 'movableCollateral',
34
38
  bridge: '0x1234567890123456789012345678901234567890',
35
39
  bridgeMinAcceptedAmount: 1000,
36
40
  override: {
@@ -40,6 +44,7 @@ describe('bridgeConfig', () => {
40
44
  },
41
45
  },
42
46
  chain2: {
47
+ executionType: 'movableCollateral',
43
48
  bridge: '0x0987654321098765432109876543210987654321',
44
49
  bridgeMinAcceptedAmount: 2000,
45
50
  },
@@ -48,6 +53,7 @@ describe('bridgeConfig', () => {
48
53
  const result = getBridgeConfig(bridges, 'chain1', 'chain2');
49
54
 
50
55
  expect(result).to.deep.equal({
56
+ executionType: 'movableCollateral',
51
57
  bridge: '0x1234567890123456789012345678901234567890',
52
58
  bridgeMinAcceptedAmount: 5000,
53
59
  });
@@ -56,6 +62,7 @@ describe('bridgeConfig', () => {
56
62
  it('should handle overrides that change the bridge address', () => {
57
63
  const bridges: ChainMap<BridgeConfigWithOverride> = {
58
64
  chain1: {
65
+ executionType: 'movableCollateral',
59
66
  bridge: '0x1234567890123456789012345678901234567890',
60
67
  bridgeMinAcceptedAmount: 1000,
61
68
  override: {
@@ -65,6 +72,7 @@ describe('bridgeConfig', () => {
65
72
  },
66
73
  },
67
74
  chain2: {
75
+ executionType: 'movableCollateral',
68
76
  bridge: '0x0987654321098765432109876543210987654321',
69
77
  bridgeMinAcceptedAmount: 2000,
70
78
  },
@@ -73,6 +81,7 @@ describe('bridgeConfig', () => {
73
81
  const result = getBridgeConfig(bridges, 'chain1', 'chain2');
74
82
 
75
83
  expect(result).to.deep.equal({
84
+ executionType: 'movableCollateral',
76
85
  bridge: '0xABCDEF0123456789ABCDEF0123456789ABCDEF01',
77
86
  bridgeMinAcceptedAmount: 1000,
78
87
  });
@@ -1,12 +1,41 @@
1
1
  import type { ChainMap, ChainName } from '@hyperlane-xyz/sdk';
2
+ import type { Address } from '@hyperlane-xyz/utils';
2
3
 
3
- export type BridgeConfigWithOverride = BridgeConfig & {
4
- override?: ChainMap<Partial<BridgeConfig>>;
4
+ import type { ExternalBridgeType } from '../config/types.js';
5
+ import type { StrategyRoute } from '../interfaces/IStrategy.js';
6
+
7
+ type BaseBridgeConfig = {
8
+ bridgeMinAcceptedAmount?: string | number;
9
+ };
10
+
11
+ export type MovableCollateralBridgeConfig = BaseBridgeConfig & {
12
+ executionType: 'movableCollateral';
13
+ bridge: Address;
5
14
  };
6
15
 
7
- export type BridgeConfig = {
8
- bridge: string;
9
- bridgeMinAcceptedAmount: string | number;
16
+ export type InventoryBridgeConfig = BaseBridgeConfig & {
17
+ executionType: 'inventory';
18
+ externalBridge: ExternalBridgeType;
19
+ };
20
+
21
+ export type BridgeConfig =
22
+ | MovableCollateralBridgeConfig
23
+ | InventoryBridgeConfig;
24
+
25
+ export function isMovableCollateralConfig(
26
+ config: BridgeConfig,
27
+ ): config is MovableCollateralBridgeConfig {
28
+ return config.executionType === 'movableCollateral';
29
+ }
30
+
31
+ export function isInventoryConfig(
32
+ config: BridgeConfig,
33
+ ): config is InventoryBridgeConfig {
34
+ return config.executionType === 'inventory';
35
+ }
36
+
37
+ export type BridgeConfigWithOverride = BridgeConfig & {
38
+ override?: ChainMap<Partial<BridgeConfig>>;
10
39
  };
11
40
 
12
41
  /**
@@ -28,5 +57,45 @@ export function getBridgeConfig(
28
57
  const { override: _, ...baseConfig } = fromConfig;
29
58
 
30
59
  // Return a new object with the base config and any overrides
31
- return { ...baseConfig, ...routeSpecificOverrides };
60
+ return { ...baseConfig, ...routeSpecificOverrides } as BridgeConfig;
61
+ }
62
+
63
+ /**
64
+ * Creates a StrategyRoute from a BridgeConfig with exhaustive type checking
65
+ * @param bridgeConfig The bridge configuration
66
+ * @param origin The origin chain
67
+ * @param destination The destination chain
68
+ * @param amount The amount to transfer
69
+ * @returns A StrategyRoute with the appropriate execution type
70
+ */
71
+ export function createStrategyRoute(
72
+ bridgeConfig: BridgeConfig,
73
+ origin: ChainName,
74
+ destination: ChainName,
75
+ amount: bigint,
76
+ ): StrategyRoute {
77
+ switch (bridgeConfig.executionType) {
78
+ case 'inventory':
79
+ return {
80
+ origin,
81
+ destination,
82
+ amount,
83
+ executionType: 'inventory',
84
+ externalBridge: bridgeConfig.externalBridge,
85
+ };
86
+ case 'movableCollateral':
87
+ return {
88
+ origin,
89
+ destination,
90
+ amount,
91
+ executionType: 'movableCollateral',
92
+ bridge: bridgeConfig.bridge,
93
+ };
94
+ default: {
95
+ const _exhaustive: never = bridgeConfig;
96
+ throw new Error(
97
+ `Unknown execution type: ${(_exhaustive as BridgeConfig).executionType}`,
98
+ );
99
+ }
100
+ }
32
101
  }