@humanode-hyperlane/rebalancer 27.2.2 → 1029.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.
- package/dist/bridges/LiFiBridge.d.ts +3 -3
- package/dist/bridges/LiFiBridge.d.ts.map +1 -1
- package/dist/bridges/LiFiBridge.js +66 -55
- package/dist/bridges/LiFiBridge.js.map +1 -1
- package/dist/bridges/LiFiBridge.test.js +213 -0
- package/dist/bridges/LiFiBridge.test.js.map +1 -1
- package/dist/config/RebalancerConfig.d.ts +4 -3
- package/dist/config/RebalancerConfig.d.ts.map +1 -1
- package/dist/config/RebalancerConfig.js +7 -5
- package/dist/config/RebalancerConfig.js.map +1 -1
- package/dist/config/RebalancerConfig.test.js +278 -2
- package/dist/config/RebalancerConfig.test.js.map +1 -1
- package/dist/config/types.d.ts +707 -2664
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +62 -7
- package/dist/config/types.js.map +1 -1
- package/dist/core/InventoryRebalancer.d.ts +13 -19
- package/dist/core/InventoryRebalancer.d.ts.map +1 -1
- package/dist/core/InventoryRebalancer.js +404 -276
- package/dist/core/InventoryRebalancer.js.map +1 -1
- package/dist/core/InventoryRebalancer.test.js +814 -25
- package/dist/core/InventoryRebalancer.test.js.map +1 -1
- package/dist/core/Rebalancer.d.ts.map +1 -1
- package/dist/core/Rebalancer.js +12 -6
- package/dist/core/Rebalancer.js.map +1 -1
- package/dist/core/Rebalancer.test.js +51 -0
- package/dist/core/Rebalancer.test.js.map +1 -1
- package/dist/core/RebalancerOrchestrator.d.ts.map +1 -1
- package/dist/core/RebalancerOrchestrator.test.js +0 -1
- package/dist/core/RebalancerOrchestrator.test.js.map +1 -1
- package/dist/core/RebalancerService.d.ts.map +1 -1
- package/dist/core/RebalancerService.js +3 -2
- package/dist/core/RebalancerService.js.map +1 -1
- package/dist/core/RebalancerService.test.js +24 -0
- package/dist/core/RebalancerService.test.js.map +1 -1
- package/dist/e2e/fixtures/routes.d.ts +3 -3
- package/dist/e2e/fixtures/routes.d.ts.map +1 -1
- package/dist/e2e/harness/BaseLocalDeploymentManager.d.ts.map +1 -1
- package/dist/e2e/harness/BaseLocalDeploymentManager.js +3 -2
- package/dist/e2e/harness/BaseLocalDeploymentManager.js.map +1 -1
- package/dist/e2e/harness/Erc20InventoryLocalDeploymentManager.d.ts.map +1 -1
- package/dist/e2e/harness/Erc20InventoryLocalDeploymentManager.js.map +1 -1
- package/dist/e2e/harness/Erc20LocalDeploymentManager.d.ts.map +1 -1
- package/dist/e2e/harness/ForkIndexer.d.ts.map +1 -1
- package/dist/e2e/harness/ForkIndexer.js.map +1 -1
- package/dist/e2e/harness/LocalProvider.d.ts +3 -0
- package/dist/e2e/harness/LocalProvider.d.ts.map +1 -0
- package/dist/e2e/harness/LocalProvider.js +9 -0
- package/dist/e2e/harness/LocalProvider.js.map +1 -0
- package/dist/e2e/harness/LocalProvider.test.d.ts +2 -0
- package/dist/e2e/harness/LocalProvider.test.d.ts.map +1 -0
- package/dist/e2e/harness/LocalProvider.test.js +14 -0
- package/dist/e2e/harness/LocalProvider.test.js.map +1 -0
- package/dist/e2e/harness/MixedLocalDeploymentManager.d.ts.map +1 -1
- package/dist/e2e/harness/MixedLocalDeploymentManager.js.map +1 -1
- package/dist/e2e/harness/MockExplorerClient.d.ts.map +1 -1
- package/dist/e2e/harness/MockExplorerClient.js.map +1 -1
- package/dist/e2e/harness/MockExternalBridge.d.ts.map +1 -1
- package/dist/e2e/harness/MockExternalBridge.js.map +1 -1
- package/dist/e2e/harness/NativeLocalDeploymentManager.d.ts.map +1 -1
- package/dist/e2e/harness/NativeLocalDeploymentManager.js +2 -1
- package/dist/e2e/harness/NativeLocalDeploymentManager.js.map +1 -1
- package/dist/e2e/harness/SnapshotHelper.d.ts.map +1 -1
- package/dist/e2e/harness/SnapshotHelper.js +3 -5
- package/dist/e2e/harness/SnapshotHelper.js.map +1 -1
- package/dist/e2e/harness/TestHelpers.js +1 -2
- package/dist/e2e/harness/TestHelpers.js.map +1 -1
- package/dist/e2e/harness/TestRebalancer.d.ts +5 -1
- package/dist/e2e/harness/TestRebalancer.d.ts.map +1 -1
- package/dist/e2e/harness/TestRebalancer.js +14 -4
- package/dist/e2e/harness/TestRebalancer.js.map +1 -1
- package/dist/e2e/relayer-denylist.e2e-test.d.ts +2 -0
- package/dist/e2e/relayer-denylist.e2e-test.d.ts.map +1 -0
- package/dist/e2e/relayer-denylist.e2e-test.js +388 -0
- package/dist/e2e/relayer-denylist.e2e-test.js.map +1 -0
- package/dist/factories/RebalancerContextFactory.d.ts.map +1 -1
- package/dist/factories/RebalancerContextFactory.js +61 -31
- package/dist/factories/RebalancerContextFactory.js.map +1 -1
- package/dist/factories/RebalancerContextFactory.test.js +336 -5
- package/dist/factories/RebalancerContextFactory.test.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/interfaces/IRebalancer.d.ts +4 -2
- package/dist/interfaces/IRebalancer.d.ts.map +1 -1
- package/dist/metrics/Metrics.d.ts.map +1 -1
- package/dist/metrics/Metrics.js +1 -1
- package/dist/metrics/Metrics.js.map +1 -1
- package/dist/metrics/PriceGetter.d.ts +1 -0
- package/dist/metrics/PriceGetter.d.ts.map +1 -1
- package/dist/metrics/PriceGetter.js +14 -4
- package/dist/metrics/PriceGetter.js.map +1 -1
- package/dist/metrics/PriceGetter.test.d.ts +2 -0
- package/dist/metrics/PriceGetter.test.d.ts.map +1 -0
- package/dist/metrics/PriceGetter.test.js +84 -0
- package/dist/metrics/PriceGetter.test.js.map +1 -0
- package/dist/metrics/scripts/metrics.d.ts +4 -4
- package/dist/monitor/Monitor.d.ts +1 -0
- package/dist/monitor/Monitor.d.ts.map +1 -1
- package/dist/monitor/Monitor.js +55 -46
- package/dist/monitor/Monitor.js.map +1 -1
- package/dist/monitor/Monitor.test.d.ts +2 -0
- package/dist/monitor/Monitor.test.d.ts.map +1 -0
- package/dist/monitor/Monitor.test.js +346 -0
- package/dist/monitor/Monitor.test.js.map +1 -0
- package/dist/service.d.ts +1 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +9 -5
- package/dist/service.js.map +1 -1
- package/dist/strategy/BaseStrategy.d.ts.map +1 -1
- package/dist/strategy/BaseStrategy.js +13 -11
- package/dist/strategy/BaseStrategy.js.map +1 -1
- package/dist/strategy/CollateralDeficitStrategy.d.ts.map +1 -1
- package/dist/strategy/CollateralDeficitStrategy.js +2 -2
- package/dist/strategy/CollateralDeficitStrategy.js.map +1 -1
- package/dist/strategy/CompositeStrategy.d.ts.map +1 -1
- package/dist/strategy/CompositeStrategy.js.map +1 -1
- package/dist/strategy/CompositeStrategy.test.js.map +1 -1
- package/dist/strategy/MinAmountStrategy.d.ts +1 -0
- package/dist/strategy/MinAmountStrategy.d.ts.map +1 -1
- package/dist/strategy/MinAmountStrategy.js +12 -8
- package/dist/strategy/MinAmountStrategy.js.map +1 -1
- package/dist/strategy/MinAmountStrategy.test.js +189 -2
- package/dist/strategy/MinAmountStrategy.test.js.map +1 -1
- package/dist/strategy/StrategyFactory.d.ts.map +1 -1
- package/dist/strategy/StrategyFactory.js.map +1 -1
- package/dist/strategy/WeightedStrategy.d.ts.map +1 -1
- package/dist/test/helpers.d.ts +11 -3
- package/dist/test/helpers.d.ts.map +1 -1
- package/dist/test/helpers.js +9 -11
- package/dist/test/helpers.js.map +1 -1
- package/dist/test/lifiMocks.d.ts +6 -6
- package/dist/test/lifiMocks.d.ts.map +1 -1
- package/dist/test/lifiMocks.js +5 -2
- package/dist/test/lifiMocks.js.map +1 -1
- package/dist/test/pendingTransferMiddlewareMocks.d.ts +17 -0
- package/dist/test/pendingTransferMiddlewareMocks.d.ts.map +1 -0
- package/dist/test/pendingTransferMiddlewareMocks.js +43 -0
- package/dist/test/pendingTransferMiddlewareMocks.js.map +1 -0
- package/dist/tracking/ActionTracker.d.ts +4 -0
- package/dist/tracking/ActionTracker.d.ts.map +1 -1
- package/dist/tracking/ActionTracker.js +76 -9
- package/dist/tracking/ActionTracker.js.map +1 -1
- package/dist/tracking/ActionTracker.test.js +418 -1
- package/dist/tracking/ActionTracker.test.js.map +1 -1
- package/dist/tracking/IActionTracker.d.ts +4 -2
- package/dist/tracking/IActionTracker.d.ts.map +1 -1
- package/dist/tracking/InflightContextAdapter.d.ts.map +1 -1
- package/dist/tracking/InflightContextAdapter.js.map +1 -1
- package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.d.ts +64 -0
- package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.d.ts.map +1 -0
- package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.js +474 -0
- package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.js.map +1 -0
- package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.test.d.ts +2 -0
- package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.test.d.ts.map +1 -0
- package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.test.js +1168 -0
- package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.test.js.map +1 -0
- package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.d.ts +61 -0
- package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.d.ts.map +1 -0
- package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.js +160 -0
- package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.js.map +1 -0
- package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.test.d.ts +2 -0
- package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.test.d.ts.map +1 -0
- package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.test.js +369 -0
- package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.test.js.map +1 -0
- package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.d.ts +60 -0
- package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.d.ts.map +1 -0
- package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.js +288 -0
- package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.js.map +1 -0
- package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.test.d.ts +2 -0
- package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.test.d.ts.map +1 -0
- package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.test.js +606 -0
- package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.test.js.map +1 -0
- package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.d.ts +25 -0
- package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.d.ts.map +1 -0
- package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.js +41 -0
- package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.js.map +1 -0
- package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.test.d.ts +2 -0
- package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.test.d.ts.map +1 -0
- package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.test.js +141 -0
- package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.test.js.map +1 -0
- package/dist/tracking/pendingTransferMiddlewares/matchBody.d.ts +12 -0
- package/dist/tracking/pendingTransferMiddlewares/matchBody.d.ts.map +1 -0
- package/dist/tracking/pendingTransferMiddlewares/matchBody.js +29 -0
- package/dist/tracking/pendingTransferMiddlewares/matchBody.js.map +1 -0
- package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.d.ts +59 -0
- package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.d.ts.map +1 -0
- package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.js +661 -0
- package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.js.map +1 -0
- package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.test.d.ts +2 -0
- package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.test.d.ts.map +1 -0
- package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.test.js +1093 -0
- package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.test.js.map +1 -0
- package/dist/tracking/pendingTransferMiddlewares/types.d.ts +2 -0
- package/dist/tracking/pendingTransferMiddlewares/types.d.ts.map +1 -0
- package/dist/tracking/pendingTransferMiddlewares/types.js +4 -0
- package/dist/tracking/pendingTransferMiddlewares/types.js.map +1 -0
- package/dist/tracking/store/InMemoryStore.d.ts.map +1 -1
- package/dist/utils/ExplorerClient.d.ts +1 -0
- package/dist/utils/ExplorerClient.d.ts.map +1 -1
- package/dist/utils/ExplorerClient.js +8 -3
- package/dist/utils/ExplorerClient.js.map +1 -1
- package/dist/utils/ExplorerClient.test.js +25 -0
- package/dist/utils/ExplorerClient.test.js.map +1 -1
- package/dist/utils/balanceUtils.d.ts +7 -1
- package/dist/utils/balanceUtils.d.ts.map +1 -1
- package/dist/utils/balanceUtils.js +39 -1
- package/dist/utils/balanceUtils.js.map +1 -1
- package/dist/utils/balanceUtils.test.js +55 -1
- package/dist/utils/balanceUtils.test.js.map +1 -1
- package/dist/utils/blockTag.d.ts +5 -4
- package/dist/utils/blockTag.d.ts.map +1 -1
- package/dist/utils/blockTag.js +26 -10
- package/dist/utils/blockTag.js.map +1 -1
- package/dist/utils/blockTag.test.d.ts +2 -0
- package/dist/utils/blockTag.test.d.ts.map +1 -0
- package/dist/utils/blockTag.test.js +132 -0
- package/dist/utils/blockTag.test.js.map +1 -0
- package/dist/utils/bridgeUtils.js.map +1 -1
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/gasEstimation.d.ts +2 -1
- package/dist/utils/gasEstimation.d.ts.map +1 -1
- package/dist/utils/gasEstimation.js +16 -13
- package/dist/utils/gasEstimation.js.map +1 -1
- package/dist/utils/gasEstimation.test.d.ts +2 -0
- package/dist/utils/gasEstimation.test.d.ts.map +1 -0
- package/dist/utils/gasEstimation.test.js +104 -0
- package/dist/utils/gasEstimation.test.js.map +1 -0
- package/dist/utils/tokenUtils.d.ts.map +1 -1
- package/dist/utils/tokenUtils.js +5 -2
- package/dist/utils/tokenUtils.js.map +1 -1
- package/package.json +23 -22
- package/src/bridges/LiFiBridge.test.ts +270 -0
- package/src/bridges/LiFiBridge.ts +90 -71
- package/src/config/RebalancerConfig.test.ts +315 -1
- package/src/config/RebalancerConfig.ts +6 -3
- package/src/config/types.ts +98 -8
- package/src/core/InventoryRebalancer.test.ts +1092 -38
- package/src/core/InventoryRebalancer.ts +584 -351
- package/src/core/Rebalancer.test.ts +84 -0
- package/src/core/Rebalancer.ts +22 -6
- package/src/core/RebalancerOrchestrator.test.ts +0 -1
- package/src/core/RebalancerService.test.ts +35 -0
- package/src/core/RebalancerService.ts +3 -2
- package/src/e2e/harness/BaseLocalDeploymentManager.ts +3 -1
- package/src/e2e/harness/LocalProvider.test.ts +16 -0
- package/src/e2e/harness/LocalProvider.ts +10 -0
- package/src/e2e/harness/NativeLocalDeploymentManager.ts +2 -1
- package/src/e2e/harness/SnapshotHelper.ts +2 -5
- package/src/e2e/harness/TestHelpers.ts +3 -3
- package/src/e2e/harness/TestRebalancer.ts +21 -1
- package/src/e2e/relayer-denylist.e2e-test.ts +558 -0
- package/src/factories/RebalancerContextFactory.test.ts +489 -8
- package/src/factories/RebalancerContextFactory.ts +86 -36
- package/src/index.ts +2 -0
- package/src/interfaces/IRebalancer.ts +4 -1
- package/src/metrics/Metrics.ts +1 -0
- package/src/metrics/PriceGetter.test.ts +113 -0
- package/src/metrics/PriceGetter.ts +18 -3
- package/src/monitor/Monitor.test.ts +408 -0
- package/src/monitor/Monitor.ts +123 -97
- package/src/service.ts +14 -8
- package/src/strategy/BaseStrategy.ts +18 -15
- package/src/strategy/CollateralDeficitStrategy.ts +4 -3
- package/src/strategy/MinAmountStrategy.test.ts +238 -2
- package/src/strategy/MinAmountStrategy.ts +29 -17
- package/src/test/helpers.ts +13 -12
- package/src/test/lifiMocks.ts +5 -2
- package/src/test/pendingTransferMiddlewareMocks.ts +65 -0
- package/src/tracking/ActionTracker.test.ts +635 -3
- package/src/tracking/ActionTracker.ts +92 -12
- package/src/tracking/IActionTracker.ts +4 -2
- package/src/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.test.ts +1693 -0
- package/src/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.ts +784 -0
- package/src/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.test.ts +554 -0
- package/src/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.ts +233 -0
- package/src/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.test.ts +894 -0
- package/src/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.ts +404 -0
- package/src/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.test.ts +205 -0
- package/src/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.ts +89 -0
- package/src/tracking/pendingTransferMiddlewares/fixtures/mainnet-denylist.json +3556 -0
- package/src/tracking/pendingTransferMiddlewares/matchBody.ts +43 -0
- package/src/tracking/pendingTransferMiddlewares/relayerDenylistRules.test.ts +1220 -0
- package/src/tracking/pendingTransferMiddlewares/relayerDenylistRules.ts +934 -0
- package/src/tracking/pendingTransferMiddlewares/types.ts +3 -0
- package/src/utils/ExplorerClient.test.ts +42 -0
- package/src/utils/ExplorerClient.ts +14 -3
- package/src/utils/balanceUtils.test.ts +87 -1
- package/src/utils/balanceUtils.ts +73 -2
- package/src/utils/blockTag.test.ts +173 -0
- package/src/utils/blockTag.ts +35 -15
- package/src/utils/gasEstimation.test.ts +192 -0
- package/src/utils/gasEstimation.ts +17 -11
- package/src/utils/tokenUtils.ts +5 -2
|
@@ -3,14 +3,12 @@ import type { Logger } from 'pino';
|
|
|
3
3
|
import {
|
|
4
4
|
type ChainName,
|
|
5
5
|
HyperlaneCore,
|
|
6
|
-
type InterchainGasQuote,
|
|
7
6
|
type MultiProtocolSignerSignerAccountInfo,
|
|
8
7
|
type MultiProvider,
|
|
9
|
-
Token,
|
|
10
8
|
ProviderType,
|
|
11
9
|
SealevelCoreAdapter,
|
|
12
10
|
TOKEN_COLLATERALIZED_STANDARDS,
|
|
13
|
-
|
|
11
|
+
type Token,
|
|
14
12
|
type WarpTypedTransaction,
|
|
15
13
|
type WarpCore,
|
|
16
14
|
WarpTxCategory,
|
|
@@ -21,7 +19,8 @@ import {
|
|
|
21
19
|
ProtocolType,
|
|
22
20
|
assert,
|
|
23
21
|
ensure0x,
|
|
24
|
-
|
|
22
|
+
fromWei,
|
|
23
|
+
isEVMLike,
|
|
25
24
|
} from '@humanode-hyperlane/utils';
|
|
26
25
|
|
|
27
26
|
import type { ExternalBridgeType } from '../config/types.js';
|
|
@@ -50,6 +49,11 @@ import {
|
|
|
50
49
|
} from '../utils/tokenUtils.js';
|
|
51
50
|
import { parseSolanaPrivateKey } from '../utils/solanaKeyParser.js';
|
|
52
51
|
import { toProtocolTransaction } from '../utils/transactionUtils.js';
|
|
52
|
+
import {
|
|
53
|
+
alignLocalToCanonical,
|
|
54
|
+
denormalizeToLocal,
|
|
55
|
+
normalizeToCanonical,
|
|
56
|
+
} from '../utils/balanceUtils.js';
|
|
53
57
|
|
|
54
58
|
/**
|
|
55
59
|
* Buffer percentage to add when bridging inventory.
|
|
@@ -71,6 +75,86 @@ const GAS_COST_MULTIPLIER = 20n;
|
|
|
71
75
|
*/
|
|
72
76
|
const MAX_GAS_PERCENT_THRESHOLD = 10n;
|
|
73
77
|
|
|
78
|
+
type BridgeCapacity = {
|
|
79
|
+
maxSourceInput: bigint;
|
|
80
|
+
maxTargetOutput: bigint;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
type BridgeQuoteMode = 'forward' | 'reverse';
|
|
84
|
+
|
|
85
|
+
type InventoryMovementExecutionResult =
|
|
86
|
+
| {
|
|
87
|
+
success: true;
|
|
88
|
+
txHash: string;
|
|
89
|
+
inputRequired: bigint;
|
|
90
|
+
quotedOutput: bigint;
|
|
91
|
+
quotedOutputMin: bigint;
|
|
92
|
+
quoteModeUsed: BridgeQuoteMode;
|
|
93
|
+
}
|
|
94
|
+
| {
|
|
95
|
+
success: false;
|
|
96
|
+
error: string;
|
|
97
|
+
};
|
|
98
|
+
const RECOVERABLE_MAX_TRANSFER_ERROR_MESSAGES = [
|
|
99
|
+
'balance may be insufficient',
|
|
100
|
+
'transfer amount exceeds balance',
|
|
101
|
+
'insufficient balance',
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
function hasRecoverableMaxTransferErrorMessage(message: string): boolean {
|
|
105
|
+
const normalized = message.toLowerCase();
|
|
106
|
+
return (
|
|
107
|
+
normalized.includes('unpredictable_gas_limit') ||
|
|
108
|
+
RECOVERABLE_MAX_TRANSFER_ERROR_MESSAGES.some((pattern) =>
|
|
109
|
+
normalized.includes(pattern),
|
|
110
|
+
)
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function isRecoverableMaxTransferProbeError(error: unknown): boolean {
|
|
115
|
+
const seen = new Set<unknown>();
|
|
116
|
+
const stack: unknown[] = [error];
|
|
117
|
+
|
|
118
|
+
while (stack.length > 0) {
|
|
119
|
+
const current = stack.pop();
|
|
120
|
+
if (current == null) continue;
|
|
121
|
+
|
|
122
|
+
if (typeof current === 'string') {
|
|
123
|
+
if (hasRecoverableMaxTransferErrorMessage(current)) return true;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (typeof current !== 'object') continue;
|
|
128
|
+
if (seen.has(current)) continue;
|
|
129
|
+
seen.add(current);
|
|
130
|
+
|
|
131
|
+
const candidate = current as {
|
|
132
|
+
code?: unknown;
|
|
133
|
+
message?: unknown;
|
|
134
|
+
cause?: unknown;
|
|
135
|
+
error?: unknown;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
if (
|
|
139
|
+
typeof candidate.code === 'string' &&
|
|
140
|
+
candidate.code.toUpperCase() === 'UNPREDICTABLE_GAS_LIMIT'
|
|
141
|
+
) {
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (
|
|
146
|
+
typeof candidate.message === 'string' &&
|
|
147
|
+
hasRecoverableMaxTransferErrorMessage(candidate.message)
|
|
148
|
+
) {
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
stack.push(candidate.cause, candidate.error);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
|
|
74
158
|
/**
|
|
75
159
|
* Configuration for the InventoryRebalancer.
|
|
76
160
|
*/
|
|
@@ -287,6 +371,10 @@ export class InventoryRebalancer implements IInventoryRebalancer {
|
|
|
287
371
|
return total;
|
|
288
372
|
}
|
|
289
373
|
|
|
374
|
+
private formatLocalAmount(amount: bigint, token: Token): string {
|
|
375
|
+
return fromWei(amount.toString(), token.decimals);
|
|
376
|
+
}
|
|
377
|
+
|
|
290
378
|
/**
|
|
291
379
|
* Get the effective available inventory for a chain, accounting for
|
|
292
380
|
* inventory already consumed during this execution cycle.
|
|
@@ -532,12 +620,18 @@ export class InventoryRebalancer implements IInventoryRebalancer {
|
|
|
532
620
|
{
|
|
533
621
|
strategyRoute: `${origin} (surplus) → ${destination} (deficit)`,
|
|
534
622
|
executionRoute: `transferRemote FROM ${destination} TO ${origin}`,
|
|
535
|
-
|
|
623
|
+
canonicalAmount: amount.toString(),
|
|
536
624
|
intentId: intent.id,
|
|
537
625
|
},
|
|
538
626
|
'Executing inventory route',
|
|
539
627
|
);
|
|
540
628
|
|
|
629
|
+
const sourceToken = this.getTokenForChain(destination);
|
|
630
|
+
assert(sourceToken, `No token found for source chain: ${destination}`);
|
|
631
|
+
const requestedLocalAmount = denormalizeToLocal(amount, sourceToken);
|
|
632
|
+
const executionSender = this.getInventorySignerAddress(destination);
|
|
633
|
+
const executionRecipient = this.getInventorySignerAddress(origin);
|
|
634
|
+
|
|
541
635
|
// Check available inventory on the DESTINATION (deficit) chain
|
|
542
636
|
// We need inventory here because transferRemote is called FROM this chain
|
|
543
637
|
const availableInventory = this.getEffectiveAvailableInventory(destination);
|
|
@@ -546,9 +640,15 @@ export class InventoryRebalancer implements IInventoryRebalancer {
|
|
|
546
640
|
{
|
|
547
641
|
checkingChain: destination,
|
|
548
642
|
availableInventory: availableInventory.toString(),
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
643
|
+
availableInventoryFormatted: this.formatLocalAmount(
|
|
644
|
+
availableInventory,
|
|
645
|
+
sourceToken,
|
|
646
|
+
),
|
|
647
|
+
requiredAmount: requestedLocalAmount.toString(),
|
|
648
|
+
requiredAmountFormatted: this.formatLocalAmount(
|
|
649
|
+
requestedLocalAmount,
|
|
650
|
+
sourceToken,
|
|
651
|
+
),
|
|
552
652
|
},
|
|
553
653
|
'Checking effective inventory on destination (deficit) chain',
|
|
554
654
|
);
|
|
@@ -559,15 +659,72 @@ export class InventoryRebalancer implements IInventoryRebalancer {
|
|
|
559
659
|
destination, // FROM chain (where transferRemote is called)
|
|
560
660
|
origin, // TO chain (where Hyperlane message goes)
|
|
561
661
|
availableInventory,
|
|
562
|
-
|
|
662
|
+
requestedLocalAmount,
|
|
563
663
|
this.multiProvider,
|
|
564
664
|
this.warpCore.multiProvider,
|
|
565
665
|
this.getTokenForChain.bind(this),
|
|
566
|
-
|
|
666
|
+
executionSender,
|
|
567
667
|
isNativeTokenStandard,
|
|
568
668
|
this.logger,
|
|
569
669
|
);
|
|
570
|
-
const {
|
|
670
|
+
const { minViableTransfer } = costs;
|
|
671
|
+
let maxTransferable = costs.maxTransferable;
|
|
672
|
+
|
|
673
|
+
if (!isNativeTokenStandard(sourceToken.standard)) {
|
|
674
|
+
if (availableInventory === 0n) {
|
|
675
|
+
maxTransferable = 0n;
|
|
676
|
+
this.logger.debug(
|
|
677
|
+
{
|
|
678
|
+
fromChain: destination,
|
|
679
|
+
toChain: origin,
|
|
680
|
+
requestedAmount: requestedLocalAmount.toString(),
|
|
681
|
+
},
|
|
682
|
+
'Skipping fee-aware max transferable probe because destination inventory is zero',
|
|
683
|
+
);
|
|
684
|
+
} else {
|
|
685
|
+
try {
|
|
686
|
+
const feeAwareMaxTransfer = await this.warpCore.getMaxTransferAmount({
|
|
687
|
+
balance: sourceToken.amount(availableInventory),
|
|
688
|
+
destination: origin,
|
|
689
|
+
sender: executionSender,
|
|
690
|
+
recipient: executionRecipient,
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
maxTransferable =
|
|
694
|
+
feeAwareMaxTransfer.amount < requestedLocalAmount
|
|
695
|
+
? feeAwareMaxTransfer.amount
|
|
696
|
+
: requestedLocalAmount;
|
|
697
|
+
|
|
698
|
+
this.logger.debug(
|
|
699
|
+
{
|
|
700
|
+
fromChain: destination,
|
|
701
|
+
toChain: origin,
|
|
702
|
+
availableInventory: availableInventory.toString(),
|
|
703
|
+
requestedAmount: requestedLocalAmount.toString(),
|
|
704
|
+
feeAwareMaxTransferable: maxTransferable.toString(),
|
|
705
|
+
},
|
|
706
|
+
'Calculated fee-aware max transferable amount for non-native route',
|
|
707
|
+
);
|
|
708
|
+
} catch (error) {
|
|
709
|
+
if (!isRecoverableMaxTransferProbeError(error)) {
|
|
710
|
+
throw error;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
maxTransferable = 0n;
|
|
714
|
+
this.logger.warn(
|
|
715
|
+
{
|
|
716
|
+
fromChain: destination,
|
|
717
|
+
toChain: origin,
|
|
718
|
+
availableInventory: availableInventory.toString(),
|
|
719
|
+
requestedAmount: requestedLocalAmount.toString(),
|
|
720
|
+
error: error instanceof Error ? error.message : String(error),
|
|
721
|
+
intentId: intent.id,
|
|
722
|
+
},
|
|
723
|
+
'Fee-aware max transferable probe failed due to insufficient balance, falling back to external bridge',
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
571
728
|
|
|
572
729
|
// Calculate total inventory across all chains
|
|
573
730
|
// Note: consumedInventory tracking is handled separately within this cycle
|
|
@@ -577,24 +734,36 @@ export class InventoryRebalancer implements IInventoryRebalancer {
|
|
|
577
734
|
{
|
|
578
735
|
fromChain: destination,
|
|
579
736
|
toChain: origin,
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
737
|
+
availableInventoryFormatted: this.formatLocalAmount(
|
|
738
|
+
availableInventory,
|
|
739
|
+
sourceToken,
|
|
740
|
+
),
|
|
741
|
+
requestedAmountFormatted: this.formatLocalAmount(
|
|
742
|
+
requestedLocalAmount,
|
|
743
|
+
sourceToken,
|
|
744
|
+
),
|
|
745
|
+
maxTransferableFormatted: this.formatLocalAmount(
|
|
746
|
+
maxTransferable,
|
|
747
|
+
sourceToken,
|
|
748
|
+
),
|
|
749
|
+
minViableTransferFormatted: this.formatLocalAmount(
|
|
750
|
+
minViableTransfer,
|
|
751
|
+
sourceToken,
|
|
752
|
+
),
|
|
753
|
+
canFullyFulfill: maxTransferable >= requestedLocalAmount,
|
|
586
754
|
canPartialFulfill: maxTransferable >= minViableTransfer,
|
|
755
|
+
totalInventory: totalInventory.toString(),
|
|
587
756
|
},
|
|
588
757
|
'Calculated max transferable amount with cost-based threshold',
|
|
589
758
|
);
|
|
590
759
|
|
|
591
760
|
// Early exit: If remaining amount is below minViableTransfer, complete the intent
|
|
592
761
|
// This prevents infinite loops when the remaining amount is too small to economically bridge
|
|
593
|
-
if (
|
|
762
|
+
if (requestedLocalAmount < minViableTransfer) {
|
|
594
763
|
this.logger.info(
|
|
595
764
|
{
|
|
596
765
|
intentId: intent.id,
|
|
597
|
-
amount:
|
|
766
|
+
amount: requestedLocalAmount.toString(),
|
|
598
767
|
minViableTransfer: minViableTransfer.toString(),
|
|
599
768
|
},
|
|
600
769
|
'Remaining amount below minViableTransfer, completing intent with acceptable loss',
|
|
@@ -615,227 +784,291 @@ export class InventoryRebalancer implements IInventoryRebalancer {
|
|
|
615
784
|
...route,
|
|
616
785
|
origin: destination, // transferRemote called FROM here
|
|
617
786
|
destination: origin, // Hyperlane message goes TO here
|
|
787
|
+
amount: requestedLocalAmount,
|
|
618
788
|
};
|
|
619
789
|
|
|
620
|
-
if (maxTransferable >=
|
|
790
|
+
if (maxTransferable >= requestedLocalAmount) {
|
|
621
791
|
// Sufficient inventory on destination - execute transferRemote directly
|
|
792
|
+
const fulfilledCanonicalAmount = normalizeToCanonical(
|
|
793
|
+
requestedLocalAmount,
|
|
794
|
+
sourceToken,
|
|
795
|
+
);
|
|
622
796
|
const result = await this.executeTransferRemote(
|
|
623
797
|
swappedRoute,
|
|
624
798
|
intent,
|
|
625
|
-
|
|
799
|
+
fulfilledCanonicalAmount,
|
|
626
800
|
);
|
|
627
801
|
// Return original strategy route in result (not the swapped execution route)
|
|
628
802
|
return { ...result, route };
|
|
629
803
|
} else if (maxTransferable > 0n && maxTransferable >= minViableTransfer) {
|
|
630
804
|
// Partial transfer: Transfer available inventory when economically viable
|
|
631
|
-
const
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
};
|
|
635
|
-
const result = await this.executeTransferRemote(
|
|
636
|
-
partialSwappedRoute,
|
|
637
|
-
intent,
|
|
638
|
-
costs.gasQuote!,
|
|
639
|
-
);
|
|
640
|
-
|
|
641
|
-
this.logger.info(
|
|
642
|
-
{
|
|
643
|
-
intentId: intent.id,
|
|
644
|
-
partialAmount: maxTransferable.toString(),
|
|
645
|
-
requestedAmount: amount.toString(),
|
|
646
|
-
remainingAmount: (amount - maxTransferable).toString(),
|
|
647
|
-
},
|
|
648
|
-
'Executed partial inventory deposit, remaining will be handled in future cycles',
|
|
805
|
+
const alignedExecution = alignLocalToCanonical(
|
|
806
|
+
maxTransferable,
|
|
807
|
+
sourceToken,
|
|
649
808
|
);
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
return { ...result, route };
|
|
653
|
-
} else {
|
|
654
|
-
// Inventory below cost-based threshold - trigger ExternalBridge movement TO destination chain
|
|
655
|
-
this.logger.info(
|
|
656
|
-
{
|
|
657
|
-
targetChain: destination,
|
|
658
|
-
maxTransferable: maxTransferable.toString(),
|
|
659
|
-
minViableTransfer: minViableTransfer.toString(),
|
|
660
|
-
costMultiplier: MIN_VIABLE_COST_MULTIPLIER.toString(),
|
|
661
|
-
intentId: intent.id,
|
|
662
|
-
},
|
|
663
|
-
'Inventory below cost-based threshold on destination, triggering LiFi movement',
|
|
664
|
-
);
|
|
665
|
-
|
|
666
|
-
// Get all available source chains with raw inventory
|
|
667
|
-
const allSources = this.selectAllSourceChains(destination);
|
|
668
|
-
|
|
669
|
-
if (allSources.length === 0) {
|
|
670
|
-
this.logger.warn(
|
|
809
|
+
if (alignedExecution.messageAmount === 0n) {
|
|
810
|
+
this.logger.info(
|
|
671
811
|
{
|
|
672
|
-
origin,
|
|
673
|
-
destination,
|
|
674
|
-
amount: amount.toString(),
|
|
675
812
|
intentId: intent.id,
|
|
813
|
+
maxTransferable: maxTransferable.toString(),
|
|
676
814
|
},
|
|
677
|
-
'
|
|
815
|
+
'Skipping partial transferRemote because available local amount cannot produce canonical progress',
|
|
678
816
|
);
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
error: 'No inventory available on any monitored chain',
|
|
817
|
+
} else {
|
|
818
|
+
const partialSwappedRoute: InventoryRoute = {
|
|
819
|
+
...swappedRoute,
|
|
820
|
+
amount: alignedExecution.localAmount,
|
|
684
821
|
};
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
const viableSources: Array<{ chain: ChainName; maxViable: bigint }> = [];
|
|
690
|
-
|
|
691
|
-
for (const source of allSources) {
|
|
692
|
-
const maxViable = await this.calculateMaxViableBridgeAmount(
|
|
693
|
-
source.chain,
|
|
694
|
-
destination,
|
|
695
|
-
source.availableAmount,
|
|
696
|
-
route.externalBridge,
|
|
822
|
+
const result = await this.executeTransferRemote(
|
|
823
|
+
partialSwappedRoute,
|
|
824
|
+
intent,
|
|
825
|
+
alignedExecution.messageAmount,
|
|
697
826
|
);
|
|
698
827
|
|
|
699
|
-
|
|
700
|
-
viableSources.push({ chain: source.chain, maxViable });
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
// Sort by max viable descending (bridge from largest sources first)
|
|
705
|
-
viableSources.sort((a, b) => (a.maxViable > b.maxViable ? -1 : 1));
|
|
706
|
-
|
|
707
|
-
if (viableSources.length === 0) {
|
|
708
|
-
this.logger.warn(
|
|
828
|
+
this.logger.info(
|
|
709
829
|
{
|
|
710
|
-
targetChain: destination,
|
|
711
|
-
sourcesChecked: allSources.length,
|
|
712
830
|
intentId: intent.id,
|
|
831
|
+
partialAmount: alignedExecution.localAmount.toString(),
|
|
832
|
+
partialAmountCanonical: alignedExecution.messageAmount.toString(),
|
|
833
|
+
requestedAmount: requestedLocalAmount.toString(),
|
|
834
|
+
requestedAmountCanonical: amount.toString(),
|
|
835
|
+
remainingAmountCanonical: (amount > alignedExecution.messageAmount
|
|
836
|
+
? amount - alignedExecution.messageAmount
|
|
837
|
+
: 0n
|
|
838
|
+
).toString(),
|
|
713
839
|
},
|
|
714
|
-
'
|
|
840
|
+
'Executed partial inventory deposit, remaining will be handled in future cycles',
|
|
715
841
|
);
|
|
716
842
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
success: false,
|
|
720
|
-
error: 'No viable bridge sources available',
|
|
721
|
-
};
|
|
843
|
+
// Return original strategy route in result (not the swapped execution route)
|
|
844
|
+
return { ...result, route };
|
|
722
845
|
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// Inventory below cost-based threshold - trigger ExternalBridge movement TO destination chain
|
|
849
|
+
this.logger.info(
|
|
850
|
+
{
|
|
851
|
+
targetChain: destination,
|
|
852
|
+
maxTransferable: maxTransferable.toString(),
|
|
853
|
+
minViableTransfer: minViableTransfer.toString(),
|
|
854
|
+
costMultiplier: MIN_VIABLE_COST_MULTIPLIER.toString(),
|
|
855
|
+
intentId: intent.id,
|
|
856
|
+
},
|
|
857
|
+
'Inventory below cost-based threshold on destination, triggering LiFi movement',
|
|
858
|
+
);
|
|
859
|
+
|
|
860
|
+
// Get all available source chains with raw inventory
|
|
861
|
+
const allSources = this.selectAllSourceChains(destination);
|
|
862
|
+
|
|
863
|
+
if (allSources.length === 0) {
|
|
864
|
+
this.logger.warn(
|
|
865
|
+
{
|
|
866
|
+
origin,
|
|
867
|
+
destination,
|
|
868
|
+
amount: requestedLocalAmount.toString(),
|
|
869
|
+
intentId: intent.id,
|
|
870
|
+
},
|
|
871
|
+
'No inventory available on any monitored chain',
|
|
872
|
+
);
|
|
723
873
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
874
|
+
return {
|
|
875
|
+
route,
|
|
876
|
+
success: false,
|
|
877
|
+
error: 'No inventory available on any monitored chain',
|
|
878
|
+
};
|
|
879
|
+
}
|
|
729
880
|
|
|
730
|
-
|
|
731
|
-
|
|
881
|
+
// Calculate source capacities in destination-local units.
|
|
882
|
+
const viableSources: Array<{
|
|
883
|
+
chain: ChainName;
|
|
884
|
+
maxSourceInput: bigint;
|
|
885
|
+
maxTargetOutput: bigint;
|
|
886
|
+
}> = [];
|
|
732
887
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
888
|
+
for (const source of allSources) {
|
|
889
|
+
const capacity = await this.calculateBridgeCapacity(
|
|
890
|
+
source.chain,
|
|
891
|
+
destination,
|
|
892
|
+
source.availableAmount,
|
|
893
|
+
route.externalBridge,
|
|
894
|
+
);
|
|
736
895
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
amount: amountFromSource,
|
|
740
|
-
});
|
|
741
|
-
totalPlanned += amountFromSource;
|
|
896
|
+
if (capacity.maxTargetOutput > 0n) {
|
|
897
|
+
viableSources.push({ chain: source.chain, ...capacity });
|
|
742
898
|
}
|
|
899
|
+
}
|
|
743
900
|
|
|
744
|
-
|
|
901
|
+
// Sort by destination output descending.
|
|
902
|
+
viableSources.sort((a, b) =>
|
|
903
|
+
a.maxTargetOutput > b.maxTargetOutput ? -1 : 1,
|
|
904
|
+
);
|
|
905
|
+
|
|
906
|
+
if (viableSources.length === 0) {
|
|
907
|
+
this.logger.warn(
|
|
745
908
|
{
|
|
746
909
|
targetChain: destination,
|
|
747
|
-
|
|
748
|
-
chain: s.chain,
|
|
749
|
-
maxViable: s.maxViable.toString(),
|
|
750
|
-
maxViableEth: (Number(s.maxViable) / 1e18).toFixed(6),
|
|
751
|
-
})),
|
|
752
|
-
bridgePlans: bridgePlans.map((p) => ({
|
|
753
|
-
chain: p.chain,
|
|
754
|
-
amount: p.amount.toString(),
|
|
755
|
-
amountEth: (Number(p.amount) / 1e18).toFixed(6),
|
|
756
|
-
})),
|
|
757
|
-
totalPlanned: totalPlanned.toString(),
|
|
758
|
-
targetWithBuffer: targetWithBuffer.toString(),
|
|
910
|
+
sourcesChecked: allSources.length,
|
|
759
911
|
intentId: intent.id,
|
|
760
912
|
},
|
|
761
|
-
'
|
|
913
|
+
'No viable bridge sources - all chains have insufficient inventory or high gas costs',
|
|
762
914
|
);
|
|
763
915
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
916
|
+
return {
|
|
917
|
+
route,
|
|
918
|
+
success: false,
|
|
919
|
+
error: 'No viable bridge sources available',
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// Create bridge plans using destination-local output amounts.
|
|
924
|
+
const shortfall =
|
|
925
|
+
requestedLocalAmount > availableInventory
|
|
926
|
+
? requestedLocalAmount - availableInventory
|
|
927
|
+
: 0n;
|
|
928
|
+
const targetWithBuffer =
|
|
929
|
+
((shortfall + costs.totalCost) * (100n + BRIDGE_BUFFER_PERCENT)) / 100n;
|
|
930
|
+
const bridgePlans: Array<{
|
|
931
|
+
chain: ChainName;
|
|
932
|
+
maxSourceInput: bigint;
|
|
933
|
+
targetOutput: bigint;
|
|
934
|
+
quoteMode: BridgeQuoteMode;
|
|
935
|
+
}> = [];
|
|
936
|
+
let totalPlanned = 0n;
|
|
937
|
+
|
|
938
|
+
for (const source of viableSources) {
|
|
939
|
+
if (totalPlanned >= targetWithBuffer) break;
|
|
940
|
+
|
|
941
|
+
const remaining = targetWithBuffer - totalPlanned;
|
|
942
|
+
const targetOutput =
|
|
943
|
+
source.maxTargetOutput >= remaining
|
|
944
|
+
? remaining
|
|
945
|
+
: source.maxTargetOutput;
|
|
946
|
+
const quoteMode: BridgeQuoteMode =
|
|
947
|
+
source.maxTargetOutput > remaining ? 'reverse' : 'forward';
|
|
948
|
+
|
|
949
|
+
bridgePlans.push({
|
|
950
|
+
chain: source.chain,
|
|
951
|
+
maxSourceInput: source.maxSourceInput,
|
|
952
|
+
targetOutput,
|
|
953
|
+
quoteMode,
|
|
954
|
+
});
|
|
955
|
+
totalPlanned += targetOutput;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
this.logger.info(
|
|
959
|
+
{
|
|
960
|
+
targetChain: destination,
|
|
961
|
+
viableSources: viableSources.map((s) => ({
|
|
962
|
+
chain: s.chain,
|
|
963
|
+
maxSourceInput: s.maxSourceInput.toString(),
|
|
964
|
+
maxTargetOutput: s.maxTargetOutput.toString(),
|
|
965
|
+
})),
|
|
966
|
+
bridgePlans: bridgePlans.map((p) => ({
|
|
967
|
+
chain: p.chain,
|
|
968
|
+
maxSourceInput: p.maxSourceInput.toString(),
|
|
969
|
+
targetOutput: p.targetOutput.toString(),
|
|
970
|
+
quoteMode: p.quoteMode,
|
|
971
|
+
})),
|
|
972
|
+
totalPlanned: totalPlanned.toString(),
|
|
973
|
+
shortfall: shortfall.toString(),
|
|
974
|
+
targetWithBuffer: targetWithBuffer.toString(),
|
|
975
|
+
intentId: intent.id,
|
|
976
|
+
},
|
|
977
|
+
'Created bridge plans using gas-adjusted viable amounts',
|
|
978
|
+
);
|
|
979
|
+
|
|
980
|
+
// Execute all bridges in parallel
|
|
981
|
+
const bridgeResults = await Promise.allSettled(
|
|
982
|
+
bridgePlans.map((plan) =>
|
|
983
|
+
this.executeInventoryMovement(
|
|
984
|
+
plan.chain,
|
|
985
|
+
destination,
|
|
986
|
+
plan.targetOutput,
|
|
987
|
+
plan.maxSourceInput,
|
|
988
|
+
plan.quoteMode,
|
|
989
|
+
intent,
|
|
990
|
+
route.externalBridge,
|
|
774
991
|
),
|
|
775
|
-
)
|
|
992
|
+
),
|
|
993
|
+
);
|
|
776
994
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
995
|
+
// Process results
|
|
996
|
+
let successCount = 0;
|
|
997
|
+
let totalQuotedOutputMin = 0n;
|
|
998
|
+
const failedErrors: string[] = [];
|
|
781
999
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
1000
|
+
for (let i = 0; i < bridgeResults.length; i++) {
|
|
1001
|
+
const result = bridgeResults[i];
|
|
1002
|
+
const plan = bridgePlans[i];
|
|
785
1003
|
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
1004
|
+
if (result.status === 'fulfilled' && result.value.success) {
|
|
1005
|
+
successCount++;
|
|
1006
|
+
totalQuotedOutputMin += result.value.quotedOutputMin;
|
|
1007
|
+
this.logger.info(
|
|
1008
|
+
{
|
|
1009
|
+
sourceChain: plan.chain,
|
|
1010
|
+
plannedTargetOutput: plan.targetOutput.toString(),
|
|
1011
|
+
quotedOutput: result.value.quotedOutput.toString(),
|
|
1012
|
+
quotedOutputMin: result.value.quotedOutputMin.toString(),
|
|
1013
|
+
quoteModeUsed: result.value.quoteModeUsed,
|
|
1014
|
+
txHash: result.value.txHash,
|
|
1015
|
+
},
|
|
1016
|
+
'Inventory movement succeeded',
|
|
1017
|
+
);
|
|
1018
|
+
} else {
|
|
1019
|
+
let error: string | undefined;
|
|
1020
|
+
if (result.status === 'rejected') {
|
|
1021
|
+
if (result.reason instanceof Error) {
|
|
1022
|
+
error = result.reason.message;
|
|
1023
|
+
} else if (typeof result.reason === 'string') {
|
|
1024
|
+
error = result.reason;
|
|
1025
|
+
} else {
|
|
1026
|
+
try {
|
|
1027
|
+
error = JSON.stringify(result.reason) ?? String(result.reason);
|
|
1028
|
+
} catch {
|
|
1029
|
+
error = String(result.reason);
|
|
1030
|
+
}
|
|
804
1031
|
}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
sourceChain: plan.chain,
|
|
808
|
-
amount: plan.amount.toString(),
|
|
809
|
-
error,
|
|
810
|
-
},
|
|
811
|
-
'Inventory movement failed',
|
|
812
|
-
);
|
|
1032
|
+
} else if (!result.value.success) {
|
|
1033
|
+
error = result.value.error;
|
|
813
1034
|
}
|
|
1035
|
+
if (error) {
|
|
1036
|
+
failedErrors.push(`${plan.chain}: ${error}`);
|
|
1037
|
+
}
|
|
1038
|
+
this.logger.warn(
|
|
1039
|
+
{
|
|
1040
|
+
sourceChain: plan.chain,
|
|
1041
|
+
plannedTargetOutput: plan.targetOutput.toString(),
|
|
1042
|
+
error,
|
|
1043
|
+
},
|
|
1044
|
+
'Inventory movement failed',
|
|
1045
|
+
);
|
|
814
1046
|
}
|
|
1047
|
+
}
|
|
815
1048
|
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
1049
|
+
if (successCount === 0) {
|
|
1050
|
+
const errorDetails =
|
|
1051
|
+
failedErrors.length > 0 ? ` (${failedErrors.join('; ')})` : '';
|
|
1052
|
+
return {
|
|
1053
|
+
route,
|
|
1054
|
+
success: false,
|
|
1055
|
+
error: `All inventory movements failed${errorDetails}`,
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
825
1058
|
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1059
|
+
this.logger.info(
|
|
1060
|
+
{
|
|
1061
|
+
targetChain: destination,
|
|
1062
|
+
successCount,
|
|
1063
|
+
totalQuotedOutputMin: totalQuotedOutputMin.toString(),
|
|
1064
|
+
targetAmount: requestedLocalAmount.toString(),
|
|
1065
|
+
targetAmountCanonical: amount.toString(),
|
|
1066
|
+
intentId: intent.id,
|
|
1067
|
+
},
|
|
1068
|
+
'Parallel inventory movements completed, transferRemote will execute after bridges complete',
|
|
1069
|
+
);
|
|
836
1070
|
|
|
837
|
-
|
|
838
|
-
}
|
|
1071
|
+
return { route, success: true };
|
|
839
1072
|
}
|
|
840
1073
|
|
|
841
1074
|
/**
|
|
@@ -851,12 +1084,11 @@ export class InventoryRebalancer implements IInventoryRebalancer {
|
|
|
851
1084
|
*
|
|
852
1085
|
* @param route - The transfer route (swapped direction)
|
|
853
1086
|
* @param intent - The rebalance intent being executed
|
|
854
|
-
* @param gasQuote - Pre-calculated gas quote from calculateTransferCosts
|
|
855
1087
|
*/
|
|
856
1088
|
private async executeTransferRemote(
|
|
857
1089
|
route: InventoryRoute,
|
|
858
1090
|
intent: RebalanceIntent,
|
|
859
|
-
|
|
1091
|
+
fulfilledCanonicalAmount: bigint,
|
|
860
1092
|
): Promise<InventoryExecutionResult> {
|
|
861
1093
|
const { origin, destination, amount } = route;
|
|
862
1094
|
|
|
@@ -868,46 +1100,16 @@ export class InventoryRebalancer implements IInventoryRebalancer {
|
|
|
868
1100
|
const destinationDomain = this.multiProvider.getDomainId(destination);
|
|
869
1101
|
|
|
870
1102
|
this.logger.debug(
|
|
871
|
-
{
|
|
872
|
-
|
|
873
|
-
destination,
|
|
874
|
-
amount: amount.toString(),
|
|
875
|
-
gasQuote: {
|
|
876
|
-
igpQuote: gasQuote.igpQuote.amount.toString(),
|
|
877
|
-
tokenFeeQuote: gasQuote.tokenFeeQuote?.amount?.toString() ?? 'none',
|
|
878
|
-
},
|
|
879
|
-
},
|
|
880
|
-
'Using pre-calculated gas quote for transferRemote',
|
|
1103
|
+
{ origin, destination, amount: amount.toString() },
|
|
1104
|
+
'Building transferRemote transactions for exact execution amount',
|
|
881
1105
|
);
|
|
882
1106
|
|
|
883
|
-
// Convert pre-calculated gas quote to TokenAmount for WarpCore
|
|
884
|
-
const originChainMetadata = this.multiProvider.getChainMetadata(origin);
|
|
885
|
-
const igpAddressOrDenom = gasQuote.igpQuote.addressOrDenom;
|
|
886
|
-
const igpToken =
|
|
887
|
-
!igpAddressOrDenom || isZeroishAddress(igpAddressOrDenom)
|
|
888
|
-
? Token.FromChainMetadataNativeToken(originChainMetadata)
|
|
889
|
-
: this.warpCore.findToken(origin, igpAddressOrDenom);
|
|
890
|
-
assert(igpToken, `IGP fee token ${igpAddressOrDenom} is unknown`);
|
|
891
|
-
const interchainFee = new TokenAmount(gasQuote.igpQuote.amount, igpToken);
|
|
892
|
-
|
|
893
|
-
let tokenFeeQuote: TokenAmount | undefined;
|
|
894
|
-
if (gasQuote.tokenFeeQuote?.amount) {
|
|
895
|
-
const feeAddress = gasQuote.tokenFeeQuote.addressOrDenom;
|
|
896
|
-
const feeToken =
|
|
897
|
-
!feeAddress || isZeroishAddress(feeAddress)
|
|
898
|
-
? Token.FromChainMetadataNativeToken(originChainMetadata)
|
|
899
|
-
: originToken;
|
|
900
|
-
tokenFeeQuote = new TokenAmount(gasQuote.tokenFeeQuote.amount, feeToken);
|
|
901
|
-
}
|
|
902
|
-
|
|
903
1107
|
const originTokenAmount = originToken.amount(amount);
|
|
904
1108
|
const transferTxs = await this.warpCore.getTransferRemoteTxs({
|
|
905
1109
|
originTokenAmount,
|
|
906
1110
|
destination,
|
|
907
1111
|
sender: this.getInventorySignerAddress(origin),
|
|
908
1112
|
recipient: this.getInventorySignerAddress(destination),
|
|
909
|
-
interchainFee,
|
|
910
|
-
tokenFeeQuote,
|
|
911
1113
|
});
|
|
912
1114
|
assert(
|
|
913
1115
|
transferTxs.length > 0,
|
|
@@ -967,7 +1169,7 @@ export class InventoryRebalancer implements IInventoryRebalancer {
|
|
|
967
1169
|
intentId: intent.id,
|
|
968
1170
|
origin: this.multiProvider.getDomainId(origin),
|
|
969
1171
|
destination: destinationDomain,
|
|
970
|
-
amount,
|
|
1172
|
+
amount: fulfilledCanonicalAmount,
|
|
971
1173
|
type: 'inventory_deposit',
|
|
972
1174
|
txHash: transferTxHash,
|
|
973
1175
|
messageId,
|
|
@@ -1024,6 +1226,7 @@ export class InventoryRebalancer implements IInventoryRebalancer {
|
|
|
1024
1226
|
): MultiProtocolSignerSignerAccountInfo {
|
|
1025
1227
|
void chain;
|
|
1026
1228
|
switch (protocol) {
|
|
1229
|
+
case ProtocolType.Tron:
|
|
1027
1230
|
case ProtocolType.Ethereum:
|
|
1028
1231
|
return { protocol, privateKey: ensure0x(key) };
|
|
1029
1232
|
case ProtocolType.Sealevel:
|
|
@@ -1063,7 +1266,7 @@ export class InventoryRebalancer implements IInventoryRebalancer {
|
|
|
1063
1266
|
try {
|
|
1064
1267
|
const protocol = this.getProtocolForChain(origin);
|
|
1065
1268
|
|
|
1066
|
-
if (protocol
|
|
1269
|
+
if (isEVMLike(protocol)) {
|
|
1067
1270
|
const provider =
|
|
1068
1271
|
this.warpCore.multiProvider.getEthersV5Provider(origin);
|
|
1069
1272
|
const receipt = await provider.getTransactionReceipt(txHash);
|
|
@@ -1076,7 +1279,9 @@ export class InventoryRebalancer implements IInventoryRebalancer {
|
|
|
1076
1279
|
this.warpCore.multiProvider.getSolanaWeb3Provider(origin);
|
|
1077
1280
|
const receipt = await provider.getTransaction(txHash, {
|
|
1078
1281
|
commitment: 'confirmed',
|
|
1079
|
-
maxSupportedTransactionVersion:
|
|
1282
|
+
maxSupportedTransactionVersion:
|
|
1283
|
+
this.warpCore.multiProvider.getChainMetadata(origin)
|
|
1284
|
+
.maxSupportedTransactionVersion ?? 0,
|
|
1080
1285
|
});
|
|
1081
1286
|
if (!receipt) return undefined;
|
|
1082
1287
|
return { type: ProviderType.SolanaWeb3, receipt };
|
|
@@ -1122,40 +1327,30 @@ export class InventoryRebalancer implements IInventoryRebalancer {
|
|
|
1122
1327
|
}
|
|
1123
1328
|
|
|
1124
1329
|
/**
|
|
1125
|
-
* Calculate the
|
|
1126
|
-
* Uses LiFi
|
|
1127
|
-
*
|
|
1128
|
-
*
|
|
1129
|
-
* This is the key method for the gas-aware planning approach:
|
|
1130
|
-
* - Gets a quote for the full raw inventory to determine actual gas costs
|
|
1131
|
-
* - Applies conservative 20x buffer (LiFi underestimates by ~14x historically)
|
|
1132
|
-
* - Returns 0 if gas > 10% of inventory (not worth bridging)
|
|
1133
|
-
* - Returns inventory - estimatedGas if viable
|
|
1330
|
+
* Calculate the bridge capacity from a source chain in destination-local units.
|
|
1331
|
+
* Uses LiFi quotes to conservatively estimate the destination output available
|
|
1332
|
+
* from the source chain's current local inventory.
|
|
1134
1333
|
*
|
|
1135
|
-
*
|
|
1136
|
-
*
|
|
1137
|
-
* @param rawInventory - Raw available inventory on source chain
|
|
1138
|
-
* @param externalBridgeType - External bridge type to use
|
|
1139
|
-
* @returns Maximum viable bridge amount (0 if not viable)
|
|
1334
|
+
* For native-token sources, gas is reserved from the source inventory and the
|
|
1335
|
+
* output capacity is re-quoted from the remaining source input.
|
|
1140
1336
|
*/
|
|
1141
|
-
private async
|
|
1337
|
+
private async calculateBridgeCapacity(
|
|
1142
1338
|
sourceChain: ChainName,
|
|
1143
1339
|
targetChain: ChainName,
|
|
1144
1340
|
rawInventory: bigint,
|
|
1145
1341
|
externalBridgeType: ExternalBridgeType,
|
|
1146
|
-
): Promise<
|
|
1342
|
+
): Promise<BridgeCapacity> {
|
|
1147
1343
|
const sourceToken = this.getTokenForChain(sourceChain);
|
|
1148
1344
|
const targetToken = this.getTokenForChain(targetChain);
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
// Only applies to native tokens (need gas from same balance)
|
|
1153
|
-
if (!isNativeTokenStandard(sourceToken.standard)) {
|
|
1154
|
-
return rawInventory; // ERC20s don't compete with gas
|
|
1155
|
-
}
|
|
1345
|
+
assert(sourceToken, `No token found for source chain: ${sourceChain}`);
|
|
1346
|
+
assert(targetToken, `No token found for target chain: ${targetChain}`);
|
|
1156
1347
|
|
|
1157
1348
|
// Convert HypNative token addresses to the external bridge's native token representation
|
|
1158
|
-
const fromTokenAddress =
|
|
1349
|
+
const fromTokenAddress = getExternalBridgeTokenAddress(
|
|
1350
|
+
sourceToken,
|
|
1351
|
+
externalBridgeType,
|
|
1352
|
+
this.getNativeTokenAddress.bind(this),
|
|
1353
|
+
);
|
|
1159
1354
|
const toTokenAddress = getExternalBridgeTokenAddress(
|
|
1160
1355
|
targetToken,
|
|
1161
1356
|
externalBridgeType,
|
|
@@ -1167,7 +1362,7 @@ export class InventoryRebalancer implements IInventoryRebalancer {
|
|
|
1167
1362
|
|
|
1168
1363
|
try {
|
|
1169
1364
|
const externalBridge = this.getExternalBridge(externalBridgeType);
|
|
1170
|
-
const
|
|
1365
|
+
const initialQuote = await externalBridge.quote({
|
|
1171
1366
|
fromChain: sourceChainId,
|
|
1172
1367
|
toChain: targetChainId,
|
|
1173
1368
|
fromToken: fromTokenAddress,
|
|
@@ -1177,48 +1372,58 @@ export class InventoryRebalancer implements IInventoryRebalancer {
|
|
|
1177
1372
|
toAddress: this.getInventorySignerAddress(targetChain),
|
|
1178
1373
|
});
|
|
1179
1374
|
|
|
1180
|
-
|
|
1181
|
-
|
|
1375
|
+
let maxSourceInput = rawInventory;
|
|
1376
|
+
let outputQuote = initialQuote;
|
|
1182
1377
|
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1378
|
+
if (isNativeTokenStandard(sourceToken.standard)) {
|
|
1379
|
+
const estimatedGas = initialQuote.gasCosts * GAS_COST_MULTIPLIER;
|
|
1380
|
+
const maxGasThreshold = rawInventory / MAX_GAS_PERCENT_THRESHOLD;
|
|
1381
|
+
if (estimatedGas > maxGasThreshold) {
|
|
1382
|
+
this.logger.info(
|
|
1383
|
+
{
|
|
1384
|
+
sourceChain,
|
|
1385
|
+
targetChain,
|
|
1386
|
+
rawInventory: rawInventory.toString(),
|
|
1387
|
+
quotedGas: initialQuote.gasCosts.toString(),
|
|
1388
|
+
estimatedGas: estimatedGas.toString(),
|
|
1389
|
+
maxGasThreshold: maxGasThreshold.toString(),
|
|
1390
|
+
},
|
|
1391
|
+
'Bridge not viable - gas cost exceeds 10% of inventory',
|
|
1392
|
+
);
|
|
1393
|
+
return { maxSourceInput: 0n, maxTargetOutput: 0n };
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
maxSourceInput = rawInventory - estimatedGas;
|
|
1397
|
+
if (maxSourceInput <= 0n) {
|
|
1398
|
+
return { maxSourceInput: 0n, maxTargetOutput: 0n };
|
|
1399
|
+
}
|
|
1202
1400
|
|
|
1203
|
-
|
|
1204
|
-
|
|
1401
|
+
outputQuote = await externalBridge.quote({
|
|
1402
|
+
fromChain: sourceChainId,
|
|
1403
|
+
toChain: targetChainId,
|
|
1404
|
+
fromToken: fromTokenAddress,
|
|
1405
|
+
toToken: toTokenAddress,
|
|
1406
|
+
fromAmount: maxSourceInput,
|
|
1407
|
+
fromAddress: this.getInventorySignerAddress(sourceChain),
|
|
1408
|
+
toAddress: this.getInventorySignerAddress(targetChain),
|
|
1409
|
+
});
|
|
1410
|
+
}
|
|
1205
1411
|
|
|
1206
1412
|
this.logger.info(
|
|
1207
1413
|
{
|
|
1208
1414
|
sourceChain,
|
|
1209
1415
|
targetChain,
|
|
1210
1416
|
rawInventory: rawInventory.toString(),
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
estimatedGas: estimatedGas.toString(),
|
|
1214
|
-
estimatedGasEth: (Number(estimatedGas) / 1e18).toFixed(6),
|
|
1215
|
-
maxViable: maxViable.toString(),
|
|
1216
|
-
maxViableEth: (Number(maxViable) / 1e18).toFixed(6),
|
|
1417
|
+
maxSourceInput: maxSourceInput.toString(),
|
|
1418
|
+
maxTargetOutput: outputQuote.toAmountMin.toString(),
|
|
1217
1419
|
},
|
|
1218
|
-
'Calculated
|
|
1420
|
+
'Calculated bridge capacity',
|
|
1219
1421
|
);
|
|
1220
1422
|
|
|
1221
|
-
return
|
|
1423
|
+
return {
|
|
1424
|
+
maxSourceInput,
|
|
1425
|
+
maxTargetOutput: outputQuote.toAmountMin,
|
|
1426
|
+
};
|
|
1222
1427
|
} catch (error) {
|
|
1223
1428
|
this.logger.warn(
|
|
1224
1429
|
{
|
|
@@ -1226,21 +1431,24 @@ export class InventoryRebalancer implements IInventoryRebalancer {
|
|
|
1226
1431
|
targetChain,
|
|
1227
1432
|
error: (error as Error).message,
|
|
1228
1433
|
},
|
|
1229
|
-
'Failed to calculate
|
|
1434
|
+
'Failed to calculate bridge capacity, skipping chain',
|
|
1230
1435
|
);
|
|
1231
|
-
return 0n;
|
|
1436
|
+
return { maxSourceInput: 0n, maxTargetOutput: 0n };
|
|
1232
1437
|
}
|
|
1233
1438
|
}
|
|
1234
1439
|
|
|
1235
1440
|
/**
|
|
1236
1441
|
* Execute inventory movement from source chain to target chain via LiFi bridge.
|
|
1237
1442
|
*
|
|
1238
|
-
*
|
|
1239
|
-
*
|
|
1443
|
+
* Quote mode is chosen during planning:
|
|
1444
|
+
* - `reverse`: request an exact target-chain output when the source has headroom
|
|
1445
|
+
* - `forward`: spend the source cap directly when source inventory is the limiter
|
|
1240
1446
|
*
|
|
1241
1447
|
* @param sourceChain - Chain to move inventory from
|
|
1242
1448
|
* @param targetChain - Chain to move inventory to (origin chain for rebalancing)
|
|
1243
|
-
* @param
|
|
1449
|
+
* @param targetOutputAmount - Destination-local amount to receive
|
|
1450
|
+
* @param maxSourceInput - Maximum source-local amount available for this plan
|
|
1451
|
+
* @param quoteMode - Whether to execute this bridge plan as exact-input or exact-output
|
|
1244
1452
|
* @param intent - Rebalance intent for tracking
|
|
1245
1453
|
* @param externalBridgeType - External bridge type to use
|
|
1246
1454
|
* @returns Result with success status and optional txHash/error
|
|
@@ -1248,10 +1456,12 @@ export class InventoryRebalancer implements IInventoryRebalancer {
|
|
|
1248
1456
|
private async executeInventoryMovement(
|
|
1249
1457
|
sourceChain: ChainName,
|
|
1250
1458
|
targetChain: ChainName,
|
|
1251
|
-
|
|
1459
|
+
targetOutputAmount: bigint,
|
|
1460
|
+
maxSourceInput: bigint,
|
|
1461
|
+
quoteMode: BridgeQuoteMode,
|
|
1252
1462
|
intent: RebalanceIntent,
|
|
1253
1463
|
externalBridgeType: ExternalBridgeType,
|
|
1254
|
-
): Promise<
|
|
1464
|
+
): Promise<InventoryMovementExecutionResult> {
|
|
1255
1465
|
const sourceToken = this.getTokenForChain(sourceChain);
|
|
1256
1466
|
if (!sourceToken) {
|
|
1257
1467
|
return {
|
|
@@ -1297,57 +1507,53 @@ export class InventoryRebalancer implements IInventoryRebalancer {
|
|
|
1297
1507
|
'Resolved token addresses for LiFi bridge',
|
|
1298
1508
|
);
|
|
1299
1509
|
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1510
|
+
try {
|
|
1511
|
+
const externalBridge = this.getExternalBridge(externalBridgeType);
|
|
1512
|
+
const fromAddress = this.getInventorySignerAddress(sourceChain);
|
|
1513
|
+
const toAddress = this.getInventorySignerAddress(targetChain);
|
|
1514
|
+
const quoteWithMode = async (mode: BridgeQuoteMode) =>
|
|
1515
|
+
externalBridge.quote({
|
|
1516
|
+
fromChain: sourceChainId,
|
|
1517
|
+
toChain: targetChainId,
|
|
1518
|
+
fromToken: fromTokenAddress,
|
|
1519
|
+
toToken: toTokenAddress,
|
|
1520
|
+
...(mode === 'forward'
|
|
1521
|
+
? { fromAmount: maxSourceInput }
|
|
1522
|
+
: { toAmount: targetOutputAmount }),
|
|
1523
|
+
fromAddress,
|
|
1524
|
+
toAddress,
|
|
1525
|
+
});
|
|
1316
1526
|
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
const effectiveAmount =
|
|
1320
|
-
amount < minViableTransfer ? minViableTransfer : amount;
|
|
1527
|
+
let quoteModeUsed = quoteMode;
|
|
1528
|
+
let quote = await quoteWithMode(quoteModeUsed);
|
|
1321
1529
|
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
);
|
|
1336
|
-
}
|
|
1530
|
+
if (quoteModeUsed === 'reverse' && quote.fromAmount > maxSourceInput) {
|
|
1531
|
+
this.logger.warn(
|
|
1532
|
+
{
|
|
1533
|
+
sourceChain,
|
|
1534
|
+
targetChain,
|
|
1535
|
+
plannedQuoteMode: quoteMode,
|
|
1536
|
+
requestedTargetOutput: targetOutputAmount.toString(),
|
|
1537
|
+
quotedInput: quote.fromAmount.toString(),
|
|
1538
|
+
maxSourceInput: maxSourceInput.toString(),
|
|
1539
|
+
intentId: intent.id,
|
|
1540
|
+
},
|
|
1541
|
+
'Reverse bridge quote exceeded source capacity, retrying with forward quote',
|
|
1542
|
+
);
|
|
1337
1543
|
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
fromToken: fromTokenAddress,
|
|
1344
|
-
toToken: toTokenAddress,
|
|
1345
|
-
fromAmount: effectiveAmount,
|
|
1346
|
-
fromAddress: this.getInventorySignerAddress(sourceChain),
|
|
1347
|
-
toAddress: this.getInventorySignerAddress(targetChain),
|
|
1348
|
-
});
|
|
1544
|
+
// Spend the full source cap on fallback; minor output drift is acceptable
|
|
1545
|
+
// and will be reconciled by later cycles rather than risking livelock.
|
|
1546
|
+
quoteModeUsed = 'forward';
|
|
1547
|
+
quote = await quoteWithMode(quoteModeUsed);
|
|
1548
|
+
}
|
|
1349
1549
|
|
|
1350
1550
|
const inputRequired = quote.fromAmount;
|
|
1551
|
+
if (inputRequired > maxSourceInput) {
|
|
1552
|
+
return {
|
|
1553
|
+
success: false,
|
|
1554
|
+
error: `Bridge input ${inputRequired} exceeded planned source capacity ${maxSourceInput}`,
|
|
1555
|
+
};
|
|
1556
|
+
}
|
|
1351
1557
|
|
|
1352
1558
|
this.logger.info(
|
|
1353
1559
|
{
|
|
@@ -1355,19 +1561,35 @@ export class InventoryRebalancer implements IInventoryRebalancer {
|
|
|
1355
1561
|
targetChain,
|
|
1356
1562
|
sourceChainId,
|
|
1357
1563
|
targetChainId,
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1564
|
+
requestedTargetOutput: targetOutputAmount.toString(),
|
|
1565
|
+
requestedTargetOutputFormatted: this.formatLocalAmount(
|
|
1566
|
+
targetOutputAmount,
|
|
1567
|
+
targetToken,
|
|
1568
|
+
),
|
|
1569
|
+
quoteModePlanned: quoteMode,
|
|
1570
|
+
quoteModeUsed,
|
|
1571
|
+
retriedAsForward:
|
|
1572
|
+
quoteMode === 'reverse' && quoteModeUsed === 'forward',
|
|
1362
1573
|
inputRequired: inputRequired.toString(),
|
|
1363
|
-
|
|
1364
|
-
|
|
1574
|
+
inputRequiredFormatted: this.formatLocalAmount(
|
|
1575
|
+
inputRequired,
|
|
1576
|
+
sourceToken,
|
|
1577
|
+
),
|
|
1578
|
+
quotedOutput: quote.toAmount.toString(),
|
|
1579
|
+
quotedOutputMin: quote.toAmountMin.toString(),
|
|
1580
|
+
quotedOutputFormatted: this.formatLocalAmount(
|
|
1581
|
+
quote.toAmount,
|
|
1582
|
+
targetToken,
|
|
1583
|
+
),
|
|
1584
|
+
quotedOutputMinFormatted: this.formatLocalAmount(
|
|
1585
|
+
quote.toAmountMin,
|
|
1586
|
+
targetToken,
|
|
1587
|
+
),
|
|
1365
1588
|
gasCosts: quote.gasCosts.toString(),
|
|
1366
1589
|
feeCosts: quote.feeCosts.toString(),
|
|
1367
1590
|
intentId: intent.id,
|
|
1368
|
-
adjustedForMinViable: effectiveAmount > amount,
|
|
1369
1591
|
},
|
|
1370
|
-
'Executing inventory movement via
|
|
1592
|
+
'Executing inventory movement via bridge quote',
|
|
1371
1593
|
);
|
|
1372
1594
|
|
|
1373
1595
|
this.logger.debug(
|
|
@@ -1410,6 +1632,8 @@ export class InventoryRebalancer implements IInventoryRebalancer {
|
|
|
1410
1632
|
'Inventory movement transaction executed',
|
|
1411
1633
|
);
|
|
1412
1634
|
|
|
1635
|
+
// Keep bridge consumption in source-local units; intent fulfillment only
|
|
1636
|
+
// advances from canonical inventory_deposit amounts after transferRemote.
|
|
1413
1637
|
await this.actionTracker.createRebalanceAction({
|
|
1414
1638
|
intentId: intent.id,
|
|
1415
1639
|
origin: this.multiProvider.getDomainId(sourceChain),
|
|
@@ -1433,22 +1657,31 @@ export class InventoryRebalancer implements IInventoryRebalancer {
|
|
|
1433
1657
|
'Updated consumed inventory after LiFi bridge',
|
|
1434
1658
|
);
|
|
1435
1659
|
|
|
1436
|
-
return {
|
|
1660
|
+
return {
|
|
1661
|
+
success: true,
|
|
1662
|
+
txHash: result.txHash,
|
|
1663
|
+
inputRequired,
|
|
1664
|
+
quotedOutput: quote.toAmount,
|
|
1665
|
+
quotedOutputMin: quote.toAmountMin,
|
|
1666
|
+
quoteModeUsed,
|
|
1667
|
+
};
|
|
1437
1668
|
} catch (error) {
|
|
1669
|
+
const errorMessage =
|
|
1670
|
+
error instanceof Error ? error.message : String(error);
|
|
1438
1671
|
this.logger.error(
|
|
1439
1672
|
{
|
|
1440
1673
|
sourceChain,
|
|
1441
1674
|
targetChain,
|
|
1442
|
-
amount:
|
|
1675
|
+
amount: targetOutputAmount.toString(),
|
|
1443
1676
|
intentId: intent.id,
|
|
1444
|
-
error:
|
|
1677
|
+
error: errorMessage,
|
|
1445
1678
|
},
|
|
1446
1679
|
'Failed to execute inventory movement',
|
|
1447
1680
|
);
|
|
1448
1681
|
|
|
1449
1682
|
return {
|
|
1450
1683
|
success: false,
|
|
1451
|
-
error:
|
|
1684
|
+
error: errorMessage,
|
|
1452
1685
|
};
|
|
1453
1686
|
}
|
|
1454
1687
|
}
|