upshift-finance 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +349 -0
- package/lib/abis/AddressResolver.d.ts +28 -0
- package/lib/abis/AddressResolver.js +23 -0
- package/lib/abis/ChainlinkV3.d.ts +87 -0
- package/lib/abis/ChainlinkV3.js +117 -0
- package/lib/abis/ERC20.d.ts +168 -0
- package/lib/abis/ERC20.js +226 -0
- package/lib/abis/ERC20_Bytes32.d.ts +139 -0
- package/lib/abis/ERC20_Bytes32.js +196 -0
- package/lib/abis/ERC4626.d.ts +364 -0
- package/lib/abis/ERC4626.js +507 -0
- package/lib/abis/ERC721.d.ts +231 -0
- package/lib/abis/ERC721.js +321 -0
- package/lib/abis/FeeOracle.d.ts +120 -0
- package/lib/abis/FeeOracle.js +162 -0
- package/lib/abis/LendingPool.d.ts +1393 -0
- package/lib/abis/LendingPool.js +1807 -0
- package/lib/abis/LendingPoolV2.d.ts +1413 -0
- package/lib/abis/LendingPoolV2.js +1833 -0
- package/lib/abis/LendingPoolV3.d.ts +1677 -0
- package/lib/abis/LendingPoolV3.js +1160 -0
- package/lib/abis/Loan.d.ts +837 -0
- package/lib/abis/Loan.js +1080 -0
- package/lib/abis/MultiAssetNativeDepositWrapper.d.ts +137 -0
- package/lib/abis/MultiAssetNativeDepositWrapper.js +125 -0
- package/lib/abis/Multicall3.d.ts +30 -0
- package/lib/abis/Multicall3.js +97 -0
- package/lib/abis/OFT.d.ts +116 -0
- package/lib/abis/OFT.js +85 -0
- package/lib/abis/PoolAdapter.d.ts +36 -0
- package/lib/abis/PoolAdapter.js +51 -0
- package/lib/abis/RewardDistributor.d.ts +267 -0
- package/lib/abis/RewardDistributor.js +352 -0
- package/lib/abis/RwaRedeemSubaccount.d.ts +747 -0
- package/lib/abis/RwaRedeemSubaccount.js +548 -0
- package/lib/abis/SmartAccount.d.ts +17 -0
- package/lib/abis/SmartAccount.js +19 -0
- package/lib/abis/SwapRouter.d.ts +1043 -0
- package/lib/abis/SwapRouter.js +740 -0
- package/lib/abis/TextResolver.d.ts +16 -0
- package/lib/abis/TextResolver.js +16 -0
- package/lib/abis/TokenizedVaultV2.d.ts +1364 -0
- package/lib/abis/TokenizedVaultV2.js +1041 -0
- package/lib/abis/TokenizedVaultV2DepositWithPermit.d.ts +1456 -0
- package/lib/abis/TokenizedVaultV2DepositWithPermit.js +1878 -0
- package/lib/abis/TokenizedVaultV2Receipt.d.ts +1568 -0
- package/lib/abis/TokenizedVaultV2Receipt.js +1061 -0
- package/lib/abis/TokenizedVaultV2SenderAllocationWhitelist.d.ts +454 -0
- package/lib/abis/TokenizedVaultV2SenderAllocationWhitelist.js +327 -0
- package/lib/abis/TokenizedVaultV2WhitelistedAllocation.d.ts +1466 -0
- package/lib/abis/TokenizedVaultV2WhitelistedAllocation.js +1092 -0
- package/lib/abis/TokenizedVaultV2WhitelistedAssets.d.ts +274 -0
- package/lib/abis/TokenizedVaultV2WhitelistedAssets.js +167 -0
- package/lib/abis/UniversalResolverResolve.d.ts +69 -0
- package/lib/abis/UniversalResolverResolve.js +35 -0
- package/lib/abis/UniversalSignatureValidator.d.ts +17 -0
- package/lib/abis/UniversalSignatureValidator.js +30 -0
- package/lib/abis/WrapperAdapter.d.ts +71 -0
- package/lib/abis/WrapperAdapter.js +77 -0
- package/lib/abis/index.d.ts +34 -0
- package/lib/abis/index.js +51 -0
- package/lib/adapters/evm/getters.d.ts +19 -0
- package/lib/adapters/evm/getters.js +209 -0
- package/lib/adapters/evm/index.d.ts +353 -0
- package/lib/adapters/evm/index.js +434 -0
- package/lib/adapters/evm/utils.d.ts +8 -0
- package/lib/adapters/evm/utils.js +51 -0
- package/lib/adapters/solana/constants.d.ts +33 -0
- package/lib/adapters/solana/constants.js +52 -0
- package/lib/adapters/solana/getters.d.ts +11 -0
- package/lib/adapters/solana/getters.js +165 -0
- package/lib/adapters/solana/idl/vault-idl.d.ts +272 -0
- package/lib/adapters/solana/idl/vault-idl.js +1084 -0
- package/lib/adapters/solana/index.d.ts +233 -0
- package/lib/adapters/solana/index.js +291 -0
- package/lib/adapters/solana/types.d.ts +67 -0
- package/lib/adapters/solana/types.js +3 -0
- package/lib/adapters/solana/utils.d.ts +141 -0
- package/lib/adapters/solana/utils.js +595 -0
- package/lib/adapters/solana/vault.actions.d.ts +57 -0
- package/lib/adapters/solana/vault.actions.js +379 -0
- package/lib/adapters/stellar/actions.d.ts +28 -0
- package/lib/adapters/stellar/actions.js +77 -0
- package/lib/adapters/stellar/constants.d.ts +43 -0
- package/lib/adapters/stellar/constants.js +53 -0
- package/lib/adapters/stellar/getters.d.ts +68 -0
- package/lib/adapters/stellar/getters.js +290 -0
- package/lib/adapters/stellar/index.d.ts +114 -0
- package/lib/adapters/stellar/index.js +175 -0
- package/lib/adapters/stellar/soroban.d.ts +123 -0
- package/lib/adapters/stellar/soroban.js +613 -0
- package/lib/adapters/stellar/submit.d.ts +34 -0
- package/lib/adapters/stellar/submit.js +149 -0
- package/lib/adapters/stellar/types.d.ts +58 -0
- package/lib/adapters/stellar/types.js +6 -0
- package/lib/adapters/stellar/utils.d.ts +24 -0
- package/lib/adapters/stellar/utils.js +34 -0
- package/lib/adapters/sui/constants.d.ts +14 -0
- package/lib/adapters/sui/constants.js +29 -0
- package/lib/adapters/sui/getters.d.ts +9 -0
- package/lib/adapters/sui/getters.js +60 -0
- package/lib/adapters/sui/index.d.ts +45 -0
- package/lib/adapters/sui/index.js +101 -0
- package/lib/adapters/sui/transformer.d.ts +10 -0
- package/lib/adapters/sui/transformer.js +107 -0
- package/lib/adapters/sui/types.d.ts +66 -0
- package/lib/adapters/sui/types.js +3 -0
- package/lib/adapters/sui/utils.d.ts +10 -0
- package/lib/adapters/sui/utils.js +29 -0
- package/lib/core/analytics/chain-name.d.ts +9 -0
- package/lib/core/analytics/chain-name.js +34 -0
- package/lib/core/analytics/constants.d.ts +5 -0
- package/lib/core/analytics/constants.js +9 -0
- package/lib/core/analytics/env.d.ts +29 -0
- package/lib/core/analytics/env.js +59 -0
- package/lib/core/analytics/index.d.ts +36 -0
- package/lib/core/analytics/index.js +79 -0
- package/lib/core/analytics/instrumentation.d.ts +29 -0
- package/lib/core/analytics/instrumentation.js +277 -0
- package/lib/core/analytics/method-taxonomy.d.ts +19 -0
- package/lib/core/analytics/method-taxonomy.js +128 -0
- package/lib/core/analytics/metrics.d.ts +33 -0
- package/lib/core/analytics/metrics.js +116 -0
- package/lib/core/analytics/sanitize.d.ts +44 -0
- package/lib/core/analytics/sanitize.js +260 -0
- package/lib/core/analytics/sentry-runtime.d.ts +15 -0
- package/lib/core/analytics/sentry-runtime.js +97 -0
- package/lib/core/analytics/sentry.d.ts +67 -0
- package/lib/core/analytics/sentry.js +613 -0
- package/lib/core/analytics/types.d.ts +48 -0
- package/lib/core/analytics/types.js +3 -0
- package/lib/core/analytics/user-identity.d.ts +41 -0
- package/lib/core/analytics/user-identity.js +141 -0
- package/lib/core/analytics/version.d.ts +6 -0
- package/lib/core/analytics/version.js +10 -0
- package/lib/core/attribution.d.ts +111 -0
- package/lib/core/attribution.js +142 -0
- package/lib/core/auth/index.d.ts +1 -0
- package/lib/core/auth/index.js +18 -0
- package/lib/core/auth/verify.d.ts +2 -0
- package/lib/core/auth/verify.js +31 -0
- package/lib/core/base.class.d.ts +152 -0
- package/lib/core/base.class.js +172 -0
- package/lib/core/cache.d.ts +9 -0
- package/lib/core/cache.js +31 -0
- package/lib/core/constants/adapters.d.ts +103 -0
- package/lib/core/constants/adapters.js +180 -0
- package/lib/core/constants/core.d.ts +133 -0
- package/lib/core/constants/core.js +209 -0
- package/lib/core/constants/swap-router.d.ts +150 -0
- package/lib/core/constants/swap-router.js +169 -0
- package/lib/core/constants/vaults.d.ts +93 -0
- package/lib/core/constants/vaults.js +273 -0
- package/lib/core/constants/web3.d.ts +91 -0
- package/lib/core/constants/web3.js +229 -0
- package/lib/core/errors/index.d.ts +114 -0
- package/lib/core/errors/index.js +183 -0
- package/lib/core/fetcher.d.ts +198 -0
- package/lib/core/fetcher.js +903 -0
- package/lib/core/helpers/adapters.d.ts +13 -0
- package/lib/core/helpers/adapters.js +39 -0
- package/lib/core/helpers/chain-address.d.ts +13 -0
- package/lib/core/helpers/chain-address.js +47 -0
- package/lib/core/helpers/chain-error.d.ts +207 -0
- package/lib/core/helpers/chain-error.js +682 -0
- package/lib/core/helpers/chain-support.d.ts +80 -0
- package/lib/core/helpers/chain-support.js +115 -0
- package/lib/core/helpers/core.d.ts +68 -0
- package/lib/core/helpers/core.js +285 -0
- package/lib/core/helpers/explorer-link.d.ts +16 -0
- package/lib/core/helpers/explorer-link.js +26 -0
- package/lib/core/helpers/multicall.d.ts +68 -0
- package/lib/core/helpers/multicall.js +103 -0
- package/lib/core/helpers/revert-decode.d.ts +248 -0
- package/lib/core/helpers/revert-decode.js +515 -0
- package/lib/core/helpers/signer.d.ts +52 -0
- package/lib/core/helpers/signer.js +145 -0
- package/lib/core/helpers/swap-router.d.ts +211 -0
- package/lib/core/helpers/swap-router.js +480 -0
- package/lib/core/helpers/vault-version.d.ts +23 -0
- package/lib/core/helpers/vault-version.js +75 -0
- package/lib/core/helpers/vaults.d.ts +89 -0
- package/lib/core/helpers/vaults.js +235 -0
- package/lib/core/helpers/web3.d.ts +353 -0
- package/lib/core/helpers/web3.js +992 -0
- package/lib/core/index.d.ts +23 -0
- package/lib/core/index.js +40 -0
- package/lib/core/logger/index.d.ts +98 -0
- package/lib/core/logger/index.js +144 -0
- package/lib/core/logger/slack.d.ts +16 -0
- package/lib/core/logger/slack.js +57 -0
- package/lib/core/vault-metadata.d.ts +12 -0
- package/lib/core/vault-metadata.js +42 -0
- package/lib/core/version-check.d.ts +58 -0
- package/lib/core/version-check.js +182 -0
- package/lib/evm/index.d.ts +2 -0
- package/lib/evm/index.js +19 -0
- package/lib/evm/methods/crossChainVault.d.ts +128 -0
- package/lib/evm/methods/crossChainVault.js +853 -0
- package/lib/evm/methods/crossChainVaultRegistry.d.ts +93 -0
- package/lib/evm/methods/crossChainVaultRegistry.js +240 -0
- package/lib/evm/methods/index.d.ts +2 -0
- package/lib/evm/methods/index.js +19 -0
- package/lib/evm/types/crossChain.d.ts +363 -0
- package/lib/evm/types/crossChain.js +20 -0
- package/lib/evm/types/index.d.ts +1 -0
- package/lib/evm/types/index.js +18 -0
- package/lib/index.d.ts +30 -0
- package/lib/index.js +52 -0
- package/lib/main.d.ts +527 -0
- package/lib/main.js +601 -0
- package/lib/modules/api/fetcher.d.ts +82 -0
- package/lib/modules/api/fetcher.js +150 -0
- package/lib/modules/api/index.d.ts +1 -0
- package/lib/modules/api/index.js +6 -0
- package/lib/modules/api/main.d.ts +313 -0
- package/lib/modules/api/main.js +479 -0
- package/lib/modules/sub-accounts/fetcher.d.ts +53 -0
- package/lib/modules/sub-accounts/fetcher.js +120 -0
- package/lib/modules/sub-accounts/index.d.ts +2 -0
- package/lib/modules/sub-accounts/index.js +19 -0
- package/lib/modules/sub-accounts/main.d.ts +243 -0
- package/lib/modules/sub-accounts/main.js +205 -0
- package/lib/modules/sub-accounts/utils.d.ts +106 -0
- package/lib/modules/sub-accounts/utils.js +112 -0
- package/lib/modules/vaults/adapter.helpers.d.ts +64 -0
- package/lib/modules/vaults/adapter.helpers.js +184 -0
- package/lib/modules/vaults/fetcher.d.ts +147 -0
- package/lib/modules/vaults/fetcher.js +368 -0
- package/lib/modules/vaults/getters.d.ts +570 -0
- package/lib/modules/vaults/getters.js +3051 -0
- package/lib/modules/vaults/index.d.ts +20 -0
- package/lib/modules/vaults/index.js +44 -0
- package/lib/modules/vaults/main.d.ts +601 -0
- package/lib/modules/vaults/main.js +1623 -0
- package/lib/modules/vaults/prefetch.d.ts +65 -0
- package/lib/modules/vaults/prefetch.js +120 -0
- package/lib/modules/vaults/read.actions.d.ts +225 -0
- package/lib/modules/vaults/read.actions.js +596 -0
- package/lib/modules/vaults/types.d.ts +71 -0
- package/lib/modules/vaults/types.js +3 -0
- package/lib/modules/vaults/utils/call-data-decoder.d.ts +61 -0
- package/lib/modules/vaults/utils/call-data-decoder.js +194 -0
- package/lib/modules/vaults/utils/date-utils.d.ts +50 -0
- package/lib/modules/vaults/utils/date-utils.js +84 -0
- package/lib/modules/vaults/utils.d.ts +140 -0
- package/lib/modules/vaults/utils.js +799 -0
- package/lib/modules/vaults/write.actions.d.ts +529 -0
- package/lib/modules/vaults/write.actions.js +1749 -0
- package/lib/polyfills.d.ts +1 -0
- package/lib/polyfills.js +12 -0
- package/lib/sdk.d.ts +26721 -0
- package/lib/services/coingecko/fetcher.d.ts +15 -0
- package/lib/services/coingecko/fetcher.js +67 -0
- package/lib/services/coingecko/index.d.ts +2 -0
- package/lib/services/coingecko/index.js +19 -0
- package/lib/services/coingecko/utils.d.ts +1 -0
- package/lib/services/coingecko/utils.js +24 -0
- package/lib/services/debank/fetcher.d.ts +126 -0
- package/lib/services/debank/fetcher.js +47 -0
- package/lib/services/debank/index.d.ts +2 -0
- package/lib/services/debank/index.js +19 -0
- package/lib/services/debank/utils.d.ts +38 -0
- package/lib/services/debank/utils.js +297 -0
- package/lib/services/layerzero/deposits.d.ts +49 -0
- package/lib/services/layerzero/deposits.js +166 -0
- package/lib/services/layerzero/redeems.d.ts +20 -0
- package/lib/services/layerzero/redeems.js +92 -0
- package/lib/services/layerzero/utils.d.ts +9 -0
- package/lib/services/layerzero/utils.js +22 -0
- package/lib/services/octavfi/fetcher.d.ts +9 -0
- package/lib/services/octavfi/fetcher.js +103 -0
- package/lib/services/octavfi/index.d.ts +3 -0
- package/lib/services/octavfi/index.js +20 -0
- package/lib/services/octavfi/types.d.ts +34 -0
- package/lib/services/octavfi/types.js +3 -0
- package/lib/services/octavfi/utils.d.ts +16 -0
- package/lib/services/octavfi/utils.js +212 -0
- package/lib/services/subgraph/fetcher.d.ts +2 -0
- package/lib/services/subgraph/fetcher.js +61 -0
- package/lib/services/subgraph/index.d.ts +2 -0
- package/lib/services/subgraph/index.js +19 -0
- package/lib/services/subgraph/schema.d.ts +45 -0
- package/lib/services/subgraph/schema.js +75 -0
- package/lib/services/subgraph/vaults.d.ts +25 -0
- package/lib/services/subgraph/vaults.js +1106 -0
- package/lib/services/swap-quotes/index.d.ts +105 -0
- package/lib/services/swap-quotes/index.js +77 -0
- package/lib/services/swap-quotes/paraswap.d.ts +51 -0
- package/lib/services/swap-quotes/paraswap.js +138 -0
- package/lib/types/api.d.ts +280 -0
- package/lib/types/api.js +3 -0
- package/lib/types/index.d.ts +12 -0
- package/lib/types/index.js +28 -0
- package/lib/types/points.d.ts +28 -0
- package/lib/types/points.js +3 -0
- package/lib/types/pools.d.ts +144 -0
- package/lib/types/pools.js +3 -0
- package/lib/types/staking.d.ts +28 -0
- package/lib/types/staking.js +3 -0
- package/lib/types/sub-accounts.d.ts +140 -0
- package/lib/types/sub-accounts.js +3 -0
- package/lib/types/subgraph.d.ts +74 -0
- package/lib/types/subgraph.js +3 -0
- package/lib/types/typed-contract.d.ts +102 -0
- package/lib/types/typed-contract.js +3 -0
- package/lib/types/user.d.ts +1 -0
- package/lib/types/user.js +3 -0
- package/lib/types/vaults.d.ts +714 -0
- package/lib/types/vaults.js +24 -0
- package/lib/types/web3.d.ts +51 -0
- package/lib/types/web3.js +14 -0
- package/lib/types/webserver.d.ts +698 -0
- package/lib/types/webserver.js +3 -0
- package/package.json +78 -0
|
@@ -0,0 +1,853 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Cross-Chain Vault Methods
|
|
4
|
+
*
|
|
5
|
+
* Implements LayerZero OVault cross-chain deposit and redemption operations.
|
|
6
|
+
* These functions are generic — callers provide their own ICrossChainVaultConfig
|
|
7
|
+
* rather than relying on hardcoded vault addresses.
|
|
8
|
+
*
|
|
9
|
+
* @module evm/methods/crossChainVault
|
|
10
|
+
*/
|
|
11
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
14
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
15
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
16
|
+
}
|
|
17
|
+
Object.defineProperty(o, k2, desc);
|
|
18
|
+
}) : (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
o[k2] = m[k];
|
|
21
|
+
}));
|
|
22
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
23
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
24
|
+
}) : function(o, v) {
|
|
25
|
+
o["default"] = v;
|
|
26
|
+
});
|
|
27
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
28
|
+
var ownKeys = function(o) {
|
|
29
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
30
|
+
var ar = [];
|
|
31
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
32
|
+
return ar;
|
|
33
|
+
};
|
|
34
|
+
return ownKeys(o);
|
|
35
|
+
};
|
|
36
|
+
return function (mod) {
|
|
37
|
+
if (mod && mod.__esModule) return mod;
|
|
38
|
+
var result = {};
|
|
39
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
40
|
+
__setModuleDefault(result, mod);
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
43
|
+
})();
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.LAYERZERO_ENDPOINT_IDS = void 0;
|
|
46
|
+
exports.buildCrossChainVaultTx = buildCrossChainVaultTx;
|
|
47
|
+
exports.quoteCrossChainDeposit = quoteCrossChainDeposit;
|
|
48
|
+
exports.quoteCrossChainRedeem = quoteCrossChainRedeem;
|
|
49
|
+
exports.needsCrossChainApproval = needsCrossChainApproval;
|
|
50
|
+
exports.approveCrossChain = approveCrossChain;
|
|
51
|
+
exports.crossChainVaultDeposit = crossChainVaultDeposit;
|
|
52
|
+
exports.crossChainVaultRedeem = crossChainVaultRedeem;
|
|
53
|
+
exports.isNoPeerError = isNoPeerError;
|
|
54
|
+
exports.classifyCrossChainError = classifyCrossChainError;
|
|
55
|
+
exports.canDepositFromChain = canDepositFromChain;
|
|
56
|
+
exports.canRedeemFromChain = canRedeemFromChain;
|
|
57
|
+
exports.isCrossChainOperation = isCrossChainOperation;
|
|
58
|
+
exports.getAvailableChains = getAvailableChains;
|
|
59
|
+
exports.formatCrossChainError = formatCrossChainError;
|
|
60
|
+
exports.getLayerZeroScanUrl = getLayerZeroScanUrl;
|
|
61
|
+
const attribution_1 = require("../../core/attribution");
|
|
62
|
+
const core_1 = require("../../core");
|
|
63
|
+
const crossChain_1 = require("../types/crossChain");
|
|
64
|
+
const OFT_1 = require("../../abis/OFT");
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Constants
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
/**
|
|
69
|
+
* lzReceive option hex for 500,000 gas.
|
|
70
|
+
* The Agora OFT has no enforced options for SEND_AND_CALL, so we must provide
|
|
71
|
+
* lzReceive gas explicitly or the executor reverts with Executor_ZeroLzReceiveGasProvided.
|
|
72
|
+
*/
|
|
73
|
+
const LZ_RECEIVE_OPTION_HEX = '010011010000000000000000000000000007a120';
|
|
74
|
+
/** Default fee buffer percentage applied to LayerZero native fees */
|
|
75
|
+
const DEFAULT_FEE_BUFFER_PERCENT = 50;
|
|
76
|
+
/** Default slippage in basis points (1%) */
|
|
77
|
+
const DEFAULT_SLIPPAGE_BPS = 100;
|
|
78
|
+
/** Default gas buffer for deposits (1.5x) */
|
|
79
|
+
const DEFAULT_DEPOSIT_GAS_BUFFER_PERCENT = 150;
|
|
80
|
+
/** Default gas buffer for redeems (2x) */
|
|
81
|
+
const DEFAULT_REDEEM_GAS_BUFFER_PERCENT = 200;
|
|
82
|
+
/** Default minimum gas limit for redeem operations */
|
|
83
|
+
const DEFAULT_MIN_REDEEM_GAS = 5000000n;
|
|
84
|
+
/** Default hub lzCompose gas limit */
|
|
85
|
+
const DEFAULT_HUB_LZ_COMPOSE_GAS_LIMIT = 2000000n;
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// Mutex for SDK monkey-patches
|
|
88
|
+
//
|
|
89
|
+
// Multi-asset and hub-redeem flows mutate the shared `OVaultSyncMessageBuilder`
|
|
90
|
+
// — module-level bindings inside the SDK mean a shallow clone is unsafe, so we
|
|
91
|
+
// serialize patched calls. The timeout THROWS rather than silently racing
|
|
92
|
+
// concurrent callers' patches.
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
const PATCH_LOCK_TIMEOUT_MS = 30_000;
|
|
95
|
+
let patchLock = Promise.resolve();
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Internal helpers
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
/**
|
|
100
|
+
* Try available previewRedeem signatures, falling back to 1:1 if the vault
|
|
101
|
+
* does not expose a working preview function.
|
|
102
|
+
*/
|
|
103
|
+
async function redeemPreview(publicClient, vaultAddress, shares) {
|
|
104
|
+
// 1. previewRedemption(uint256, bool) — multi-asset vault
|
|
105
|
+
try {
|
|
106
|
+
const result = await publicClient.readContract({
|
|
107
|
+
address: vaultAddress,
|
|
108
|
+
abi: OFT_1.ABI_MULTI_ASSET_PREVIEW_REDEEM,
|
|
109
|
+
functionName: 'previewRedemption',
|
|
110
|
+
args: [shares, false],
|
|
111
|
+
});
|
|
112
|
+
const [, assetsAfterFee] = result;
|
|
113
|
+
return assetsAfterFee;
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
/* try next */
|
|
117
|
+
}
|
|
118
|
+
// 2. Standard ERC4626: previewRedeem(uint256)
|
|
119
|
+
try {
|
|
120
|
+
return (await publicClient.readContract({
|
|
121
|
+
address: vaultAddress,
|
|
122
|
+
abi: OFT_1.ABI_STANDARD_PREVIEW_REDEEM,
|
|
123
|
+
functionName: 'previewRedeem',
|
|
124
|
+
args: [shares],
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
/* try next */
|
|
129
|
+
}
|
|
130
|
+
// 3. Fallback: 1:1 ratio. Slippage protects against discrepancy.
|
|
131
|
+
return shares;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Wraps generateOVaultInputs with monkey-patches for:
|
|
135
|
+
* 1. REDEEM from hub — skip quoteOFT (reverts for same-chain)
|
|
136
|
+
* 2. Multi-asset DEPOSIT — previewDeposit(address, uint256)
|
|
137
|
+
* 3. Multi-asset REDEEM — try previewRedeem variants, fallback to 1:1
|
|
138
|
+
* 4. Multi-asset composed messages — add lzReceive gas to SEND_AND_CALL
|
|
139
|
+
*/
|
|
140
|
+
async function generateOVaultInputsWithOverride(input, config, hubPublicClient, ovaultSdk) {
|
|
141
|
+
const OVaultSyncMessageBuilder = ovaultSdk.OVaultSyncMessageBuilder;
|
|
142
|
+
const OVaultSyncOperations = ovaultSdk.OVaultSyncOperations;
|
|
143
|
+
const isRedeemFromHub = input.operation === OVaultSyncOperations.REDEEM &&
|
|
144
|
+
input.srcEid === input.hubEid;
|
|
145
|
+
const isMultiAsset = !!config.multiAsset;
|
|
146
|
+
// Fast path — no patches needed
|
|
147
|
+
if (!isMultiAsset && !isRedeemFromHub) {
|
|
148
|
+
return OVaultSyncMessageBuilder.generateOVaultInputs(input);
|
|
149
|
+
}
|
|
150
|
+
// Serialize: wait for the prior holder or REJECT on timeout (silent races
|
|
151
|
+
// produced wrong cross-chain quotes).
|
|
152
|
+
const prev = patchLock;
|
|
153
|
+
let unlock;
|
|
154
|
+
patchLock = new Promise((r) => {
|
|
155
|
+
unlock = r;
|
|
156
|
+
});
|
|
157
|
+
let timeoutHandle;
|
|
158
|
+
try {
|
|
159
|
+
await Promise.race([
|
|
160
|
+
prev,
|
|
161
|
+
new Promise((_resolve, reject) => {
|
|
162
|
+
timeoutHandle = setTimeout(() => reject(new Error(`crossChainVault patch lock timed out after ${PATCH_LOCK_TIMEOUT_MS}ms — a prior call may be hung`)), PATCH_LOCK_TIMEOUT_MS);
|
|
163
|
+
}),
|
|
164
|
+
]);
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
unlock();
|
|
168
|
+
if (timeoutHandle)
|
|
169
|
+
clearTimeout(timeoutHandle);
|
|
170
|
+
throw err;
|
|
171
|
+
}
|
|
172
|
+
if (timeoutHandle)
|
|
173
|
+
clearTimeout(timeoutHandle);
|
|
174
|
+
const Builder = OVaultSyncMessageBuilder;
|
|
175
|
+
const origGetOutputAmount = Builder.getOutputAmount;
|
|
176
|
+
const origQuoteOVaultOutput = Builder.quoteOVaultOutput;
|
|
177
|
+
const origBuildSendParams = Builder.buildSendParams;
|
|
178
|
+
const origBuildApproval = Builder.buildApproval;
|
|
179
|
+
try {
|
|
180
|
+
if (isRedeemFromHub) {
|
|
181
|
+
// Patch 1a: skip quoteOFT (reverts for same-chain calls).
|
|
182
|
+
Builder.getOutputAmount = async () => input.amount;
|
|
183
|
+
// Patch 1b: skip SDK's allowance check — multi-asset vault contracts
|
|
184
|
+
// may not implement ERC20. Caller handles approval directly.
|
|
185
|
+
Builder.buildApproval = async () => undefined;
|
|
186
|
+
}
|
|
187
|
+
if (isMultiAsset) {
|
|
188
|
+
const { hubAssetAddress } = config.multiAsset;
|
|
189
|
+
// Patch 2/3: Multi-asset quoteOVaultOutput
|
|
190
|
+
Builder.quoteOVaultOutput = async (quoteInput) => {
|
|
191
|
+
const getOutputAmount = Builder.getOutputAmount;
|
|
192
|
+
const sourceAmount = await getOutputAmount({
|
|
193
|
+
...quoteInput,
|
|
194
|
+
dstAmount: 0n,
|
|
195
|
+
minDstAmount: 0n,
|
|
196
|
+
dstAddress: quoteInput.walletAddress,
|
|
197
|
+
tokenAddress: quoteInput.tokenAddress ?? quoteInput.oftAddress,
|
|
198
|
+
});
|
|
199
|
+
let outputAmount;
|
|
200
|
+
if (quoteInput.operation === OVaultSyncOperations.REDEEM) {
|
|
201
|
+
outputAmount = await redeemPreview(hubPublicClient, config.vaultAddress, sourceAmount);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
const [depositOutput] = (await hubPublicClient.readContract({
|
|
205
|
+
address: config.vaultAddress,
|
|
206
|
+
abi: OFT_1.ABI_MULTI_ASSET_PREVIEW_DEPOSIT,
|
|
207
|
+
functionName: 'previewDeposit',
|
|
208
|
+
args: [hubAssetAddress, sourceAmount],
|
|
209
|
+
}));
|
|
210
|
+
outputAmount = depositOutput;
|
|
211
|
+
}
|
|
212
|
+
const slippageBps = BigInt(Math.round(quoteInput.slippage * 10000));
|
|
213
|
+
const slippageAmount = (outputAmount * slippageBps) / 10000n;
|
|
214
|
+
return {
|
|
215
|
+
dstAmount: outputAmount,
|
|
216
|
+
minDstAmount: outputAmount - slippageAmount,
|
|
217
|
+
};
|
|
218
|
+
};
|
|
219
|
+
// Patch 4: Ensure lzReceive gas in extraOptions for composed messages
|
|
220
|
+
Builder.buildSendParams = async (spInput) => {
|
|
221
|
+
const origFn = origBuildSendParams;
|
|
222
|
+
const result = await origFn.call(Builder, spInput);
|
|
223
|
+
if (result.composeMsg && result.composeMsg !== '0x') {
|
|
224
|
+
const opts = result.extraOptions;
|
|
225
|
+
if (opts && opts.length >= 6 && opts.startsWith('0x0003')) {
|
|
226
|
+
const body = opts.slice(6);
|
|
227
|
+
const hasLzReceive = body.startsWith('01001101');
|
|
228
|
+
if (!hasLzReceive) {
|
|
229
|
+
result.extraOptions = `0x0003${LZ_RECEIVE_OPTION_HEX}${body}`;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return result;
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
return (await OVaultSyncMessageBuilder.generateOVaultInputs(input));
|
|
237
|
+
}
|
|
238
|
+
finally {
|
|
239
|
+
Builder.getOutputAmount = origGetOutputAmount;
|
|
240
|
+
Builder.quoteOVaultOutput = origQuoteOVaultOutput;
|
|
241
|
+
Builder.buildSendParams = origBuildSendParams;
|
|
242
|
+
Builder.buildApproval = origBuildApproval;
|
|
243
|
+
unlock();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// ---------------------------------------------------------------------------
|
|
247
|
+
// Public API: Build
|
|
248
|
+
// ---------------------------------------------------------------------------
|
|
249
|
+
/**
|
|
250
|
+
* Build LayerZero OVault transaction inputs for cross-chain deposits and
|
|
251
|
+
* redemptions. Does NOT execute the transaction.
|
|
252
|
+
*
|
|
253
|
+
* Overridable defaults on `props`: `slippageBps` = 100 (range 1–10000),
|
|
254
|
+
* `feeBufferPercent` = 50% (added to LZ `nativeFee`),
|
|
255
|
+
* `hubLzComposeGasLimit` = 2_000_000n (multi-asset composed messages).
|
|
256
|
+
*
|
|
257
|
+
* @returns Transaction inputs or `{ success: false, error }`.
|
|
258
|
+
*/
|
|
259
|
+
async function buildCrossChainVaultTx(props) {
|
|
260
|
+
try {
|
|
261
|
+
const { config } = props;
|
|
262
|
+
// Validate slippage at the public boundary (BPS, not decimal)
|
|
263
|
+
const slippageBps = props.slippageBps ?? DEFAULT_SLIPPAGE_BPS;
|
|
264
|
+
if (slippageBps < 1 || slippageBps > 10000) {
|
|
265
|
+
return {
|
|
266
|
+
success: false,
|
|
267
|
+
error: `Slippage must be between 1 and 10000 basis points (got ${slippageBps})`,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
// Validate chains
|
|
271
|
+
const hubChain = props.hubChain;
|
|
272
|
+
const sourceChain = props.sourceChain;
|
|
273
|
+
if (!hubChain || !sourceChain) {
|
|
274
|
+
return {
|
|
275
|
+
success: false,
|
|
276
|
+
error: 'Hub chain and source chain are required',
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
// Reject unknown chain IDs up front so a typo can't fall through to
|
|
280
|
+
// `|| hub` and route shares to a chain the user didn't pick.
|
|
281
|
+
const supportedChains = [
|
|
282
|
+
config.hubChainId,
|
|
283
|
+
...Object.keys(config.layerZeroEids.spokes).map((k) => Number(k)),
|
|
284
|
+
];
|
|
285
|
+
const supportedChainsList = supportedChains.join(', ');
|
|
286
|
+
if (props.destinationChainId !== undefined &&
|
|
287
|
+
props.destinationChainId !== config.hubChainId &&
|
|
288
|
+
!config.layerZeroEids.spokes[props.destinationChainId]) {
|
|
289
|
+
return {
|
|
290
|
+
success: false,
|
|
291
|
+
error: `Destination chain ${props.destinationChainId} is not configured for this vault. Supported chains: ${supportedChainsList}.`,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
if (props.operation === 'DEPOSIT' &&
|
|
295
|
+
props.userChainId !== config.hubChainId &&
|
|
296
|
+
!config.layerZeroEids.spokes[props.userChainId]) {
|
|
297
|
+
return {
|
|
298
|
+
success: false,
|
|
299
|
+
error: `Source chain ${props.userChainId} is not configured for this vault. Supported chains: ${supportedChainsList}.`,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
const resolveDstEid = (chainId) => {
|
|
303
|
+
if (chainId === undefined)
|
|
304
|
+
return undefined;
|
|
305
|
+
if (chainId === config.hubChainId)
|
|
306
|
+
return config.layerZeroEids.hub;
|
|
307
|
+
return config.layerZeroEids.spokes[chainId];
|
|
308
|
+
};
|
|
309
|
+
// Determine source and destination EIDs based on operation
|
|
310
|
+
let srcEid;
|
|
311
|
+
let dstEid;
|
|
312
|
+
let tokenAddress;
|
|
313
|
+
let oftAddress;
|
|
314
|
+
switch (props.operation) {
|
|
315
|
+
case 'DEPOSIT': {
|
|
316
|
+
const resolvedSrc = resolveDstEid(props.userChainId);
|
|
317
|
+
srcEid =
|
|
318
|
+
resolvedSrc !== undefined ? resolvedSrc : config.layerZeroEids.hub;
|
|
319
|
+
const resolved = resolveDstEid(props.destinationChainId);
|
|
320
|
+
dstEid = resolved !== undefined ? resolved : srcEid;
|
|
321
|
+
tokenAddress = config.contracts.assetToken[props.userChainId];
|
|
322
|
+
oftAddress = config.contracts.assetOFT[props.userChainId];
|
|
323
|
+
if (!tokenAddress || !oftAddress) {
|
|
324
|
+
return {
|
|
325
|
+
success: false,
|
|
326
|
+
error: `Chain ${props.userChainId} is not configured for deposits on this vault`,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
case 'REDEEM': {
|
|
332
|
+
srcEid = config.layerZeroEids.hub;
|
|
333
|
+
const resolved = resolveDstEid(props.destinationChainId) ??
|
|
334
|
+
resolveDstEid(props.userChainId);
|
|
335
|
+
dstEid = resolved !== undefined ? resolved : config.layerZeroEids.hub;
|
|
336
|
+
tokenAddress = config.vaultAddress;
|
|
337
|
+
oftAddress = config.contracts.shareOFTAdapter;
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
default:
|
|
341
|
+
return {
|
|
342
|
+
success: false,
|
|
343
|
+
error: `Invalid operation: ${props.operation}`,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
// Normalize amount
|
|
347
|
+
const normalizedAmount = (0, core_1.toNormalizedBn)(props.amount, props.decimals);
|
|
348
|
+
// Build LayerZero OVault inputs
|
|
349
|
+
const input = {
|
|
350
|
+
srcEid,
|
|
351
|
+
hubEid: config.layerZeroEids.hub,
|
|
352
|
+
dstEid,
|
|
353
|
+
dstAddress: props.recipientAddress || props.walletAddress,
|
|
354
|
+
walletAddress: props.walletAddress,
|
|
355
|
+
vaultAddress: config.vaultAddress,
|
|
356
|
+
composerAddress: config.contracts.composerSync,
|
|
357
|
+
hubChain: hubChain,
|
|
358
|
+
sourceChain: sourceChain,
|
|
359
|
+
operation: props.operation === 'DEPOSIT'
|
|
360
|
+
? props.ovaultSdk.OVaultSyncOperations.DEPOSIT
|
|
361
|
+
: props.ovaultSdk.OVaultSyncOperations.REDEEM,
|
|
362
|
+
amount: BigInt(normalizedAmount.raw),
|
|
363
|
+
slippage: slippageBps / 10000,
|
|
364
|
+
tokenAddress,
|
|
365
|
+
oftAddress,
|
|
366
|
+
hubLzComposeGasLimit: props.hubLzComposeGasLimit ?? DEFAULT_HUB_LZ_COMPOSE_GAS_LIMIT,
|
|
367
|
+
};
|
|
368
|
+
// Create a minimal public client for hub chain reads (multi-asset patches)
|
|
369
|
+
const hubRpc = hubChain?.rpcUrls;
|
|
370
|
+
const hubRpcUrl = hubRpc?.default?.http?.[0];
|
|
371
|
+
let hubPublicClient;
|
|
372
|
+
try {
|
|
373
|
+
// Dynamic import to keep viem optional at the module level
|
|
374
|
+
const viem = await Promise.resolve().then(() => __importStar(require('viem')));
|
|
375
|
+
hubPublicClient = viem.createPublicClient({
|
|
376
|
+
chain: hubChain,
|
|
377
|
+
transport: viem.http(hubRpcUrl),
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
catch {
|
|
381
|
+
// If viem is not available, provide a stub that will fail on multi-asset only
|
|
382
|
+
hubPublicClient = {
|
|
383
|
+
readContract: async () => {
|
|
384
|
+
throw new Error('viem is required for multi-asset vault operations. Install viem as a peer dependency.');
|
|
385
|
+
},
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
// Generate OVault transaction inputs (with monkey-patches for multi-asset)
|
|
389
|
+
const inputs = await generateOVaultInputsWithOverride(input, config, hubPublicClient, props.ovaultSdk);
|
|
390
|
+
// Apply fee buffer to LZ fees (post-hoc to avoid SDK double-compounding)
|
|
391
|
+
const feeBufferPercent = props.feeBufferPercent ?? DEFAULT_FEE_BUFFER_PERCENT;
|
|
392
|
+
const rawNativeFee = inputs.messageFee.nativeFee;
|
|
393
|
+
const rawMessageValue = inputs.messageValue;
|
|
394
|
+
const feeBuffer = (rawNativeFee * BigInt(feeBufferPercent)) / 100n;
|
|
395
|
+
inputs.messageFee.nativeFee = rawNativeFee + feeBuffer;
|
|
396
|
+
inputs.messageValue = rawMessageValue + feeBuffer;
|
|
397
|
+
// Patch fee into txArgs in place for OFT.send(); warn if the LayerZero
|
|
398
|
+
// SDK ever returns a different entry-point name (silent no-op would
|
|
399
|
+
// underpay the LZ fee).
|
|
400
|
+
if (inputs.contractFunctionName === 'send') {
|
|
401
|
+
const sendFee = inputs.txArgs[1];
|
|
402
|
+
sendFee.nativeFee = inputs.messageFee.nativeFee;
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
core_1.Logger.log.warn('crossChainVault:fee-patch', `Unexpected contractFunctionName "${inputs.contractFunctionName}" from LayerZero SDK — fee buffer not applied to txArgs in-place`, { contractFunctionName: inputs.contractFunctionName });
|
|
406
|
+
}
|
|
407
|
+
return {
|
|
408
|
+
success: true,
|
|
409
|
+
data: {
|
|
410
|
+
contractAddress: inputs.contractAddress,
|
|
411
|
+
contractFunctionName: inputs.contractFunctionName,
|
|
412
|
+
abi: inputs.abi,
|
|
413
|
+
txArgs: inputs.txArgs,
|
|
414
|
+
messageValue: inputs.messageValue,
|
|
415
|
+
messageFee: {
|
|
416
|
+
nativeFee: inputs.messageFee.nativeFee,
|
|
417
|
+
lzTokenFee: inputs.messageFee.lzTokenFee,
|
|
418
|
+
},
|
|
419
|
+
dstAmount: inputs.dstAmount.amount,
|
|
420
|
+
minDstAmount: inputs.dstAmount.minAmount,
|
|
421
|
+
approval: inputs.approval
|
|
422
|
+
? {
|
|
423
|
+
tokenAddress: inputs.approval.tokenAddress,
|
|
424
|
+
spender: inputs.approval.spender,
|
|
425
|
+
amount: inputs.approval.amount,
|
|
426
|
+
}
|
|
427
|
+
: undefined,
|
|
428
|
+
},
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
return {
|
|
433
|
+
success: false,
|
|
434
|
+
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
// ---------------------------------------------------------------------------
|
|
439
|
+
// Public API: Quote
|
|
440
|
+
// ---------------------------------------------------------------------------
|
|
441
|
+
/**
|
|
442
|
+
* Estimate fees and expected shares for a cross-chain deposit. The returned
|
|
443
|
+
* quote carries `quotedAt`/`expiresAt`; re-quote via {@link isQuoteStale}
|
|
444
|
+
* before submitting.
|
|
445
|
+
*/
|
|
446
|
+
async function quoteCrossChainDeposit(props) {
|
|
447
|
+
const result = await buildCrossChainVaultTx({
|
|
448
|
+
ovaultSdk: props.ovaultSdk,
|
|
449
|
+
config: props.config,
|
|
450
|
+
operation: 'DEPOSIT',
|
|
451
|
+
amount: props.amount,
|
|
452
|
+
decimals: props.decimals,
|
|
453
|
+
userChainId: props.userChainId,
|
|
454
|
+
walletAddress: props.walletAddress,
|
|
455
|
+
destinationChainId: props.destinationChainId,
|
|
456
|
+
slippageBps: props.slippageBps,
|
|
457
|
+
feeBufferPercent: props.feeBufferPercent,
|
|
458
|
+
hubChain: props.hubChain,
|
|
459
|
+
sourceChain: props.sourceChain,
|
|
460
|
+
});
|
|
461
|
+
if (!result.success || !result.data)
|
|
462
|
+
return null;
|
|
463
|
+
const quotedAt = Date.now();
|
|
464
|
+
return {
|
|
465
|
+
nativeFee: result.data.messageFee.nativeFee,
|
|
466
|
+
dstAmount: result.data.dstAmount,
|
|
467
|
+
minDstAmount: result.data.minDstAmount,
|
|
468
|
+
quotedAt,
|
|
469
|
+
expiresAt: quotedAt + crossChain_1.CROSS_CHAIN_QUOTE_TTL_MS,
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Estimate fees and expected assets for a cross-chain redeem. The returned
|
|
474
|
+
* quote carries `quotedAt`/`expiresAt`; re-quote via {@link isQuoteStale}
|
|
475
|
+
* before submitting.
|
|
476
|
+
*/
|
|
477
|
+
async function quoteCrossChainRedeem(props) {
|
|
478
|
+
const result = await buildCrossChainVaultTx({
|
|
479
|
+
ovaultSdk: props.ovaultSdk,
|
|
480
|
+
config: props.config,
|
|
481
|
+
operation: 'REDEEM',
|
|
482
|
+
amount: props.amount,
|
|
483
|
+
decimals: props.decimals,
|
|
484
|
+
userChainId: props.config.hubChainId,
|
|
485
|
+
walletAddress: props.walletAddress,
|
|
486
|
+
destinationChainId: props.destinationChainId,
|
|
487
|
+
slippageBps: props.slippageBps,
|
|
488
|
+
feeBufferPercent: props.feeBufferPercent,
|
|
489
|
+
hubChain: props.hubChain,
|
|
490
|
+
sourceChain: props.sourceChain,
|
|
491
|
+
});
|
|
492
|
+
if (!result.success || !result.data)
|
|
493
|
+
return null;
|
|
494
|
+
const quotedAt = Date.now();
|
|
495
|
+
return {
|
|
496
|
+
nativeFee: result.data.messageFee.nativeFee,
|
|
497
|
+
dstAmount: result.data.dstAmount,
|
|
498
|
+
minDstAmount: result.data.minDstAmount,
|
|
499
|
+
quotedAt,
|
|
500
|
+
expiresAt: quotedAt + crossChain_1.CROSS_CHAIN_QUOTE_TTL_MS,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
// ---------------------------------------------------------------------------
|
|
504
|
+
// Public API: Approval
|
|
505
|
+
// ---------------------------------------------------------------------------
|
|
506
|
+
/**
|
|
507
|
+
* Returns `true` when the wallet's ERC-20 allowance is insufficient for the
|
|
508
|
+
* cross-chain operation. On RPC failure also returns `true` (defensive
|
|
509
|
+
* default — better a redundant approval than a downstream revert).
|
|
510
|
+
*/
|
|
511
|
+
async function needsCrossChainApproval(tokenAddress, spenderAddress, walletAddress, amount, publicClient) {
|
|
512
|
+
try {
|
|
513
|
+
const allowance = (await publicClient.readContract({
|
|
514
|
+
address: tokenAddress,
|
|
515
|
+
abi: OFT_1.ABI_CROSS_CHAIN_ERC20,
|
|
516
|
+
functionName: 'allowance',
|
|
517
|
+
args: [walletAddress, spenderAddress],
|
|
518
|
+
}));
|
|
519
|
+
return allowance < amount;
|
|
520
|
+
}
|
|
521
|
+
catch {
|
|
522
|
+
// If we can't check allowance, assume approval is needed
|
|
523
|
+
return true;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Execute a token approval for a cross-chain operation.
|
|
528
|
+
*
|
|
529
|
+
* @returns Transaction hash of the approval
|
|
530
|
+
*/
|
|
531
|
+
async function approveCrossChain(tokenAddress, spenderAddress, amount, walletClient, publicClient) {
|
|
532
|
+
if (!walletClient.account) {
|
|
533
|
+
throw new Error('Wallet not ready — please reconnect your wallet');
|
|
534
|
+
}
|
|
535
|
+
const hash = await walletClient.writeContract({
|
|
536
|
+
account: walletClient.account,
|
|
537
|
+
address: tokenAddress,
|
|
538
|
+
abi: OFT_1.ABI_CROSS_CHAIN_ERC20,
|
|
539
|
+
functionName: 'approve',
|
|
540
|
+
args: [spenderAddress, amount],
|
|
541
|
+
dataSuffix: (0, attribution_1.getAttributionSuffix)(),
|
|
542
|
+
});
|
|
543
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
544
|
+
hash: hash,
|
|
545
|
+
});
|
|
546
|
+
if (receipt.status === 'reverted') {
|
|
547
|
+
throw new Error(`Approval transaction reverted on-chain (tx ${hash}). Check token allowance limits or that the token contract supports approve().`);
|
|
548
|
+
}
|
|
549
|
+
return hash;
|
|
550
|
+
}
|
|
551
|
+
// ---------------------------------------------------------------------------
|
|
552
|
+
// Public API: Execute
|
|
553
|
+
// ---------------------------------------------------------------------------
|
|
554
|
+
/**
|
|
555
|
+
* Execute a cross-chain deposit via LayerZero OVault. Validates the source
|
|
556
|
+
* chain, ensures approval, estimates gas with a buffer, and waits for the
|
|
557
|
+
* on-chain receipt. Defaults: `slippageBps` 100, `feeBufferPercent` 50,
|
|
558
|
+
* `gasBufferPercent` 150 — all overridable on the request.
|
|
559
|
+
*
|
|
560
|
+
* @throws Error on unsupported chain, approval revert, gas-estimation
|
|
561
|
+
* failure, or on-chain revert.
|
|
562
|
+
*/
|
|
563
|
+
async function crossChainVaultDeposit(props) {
|
|
564
|
+
if (props.userChainId !== props.config.hubChainId &&
|
|
565
|
+
!props.config.spokeChainIds.includes(props.userChainId)) {
|
|
566
|
+
throw new Error(`Chain ${props.userChainId} is not supported for deposits to this vault — switch to the hub (${props.config.hubChainId}) or a configured spoke (${props.config.spokeChainIds.join(', ')}).`);
|
|
567
|
+
}
|
|
568
|
+
const walletClient = props.walletClient;
|
|
569
|
+
const publicClient = props.publicClient;
|
|
570
|
+
// 1. Build transaction inputs
|
|
571
|
+
const result = await buildCrossChainVaultTx({
|
|
572
|
+
ovaultSdk: props.ovaultSdk,
|
|
573
|
+
config: props.config,
|
|
574
|
+
operation: 'DEPOSIT',
|
|
575
|
+
amount: props.amount,
|
|
576
|
+
decimals: props.decimals,
|
|
577
|
+
userChainId: props.userChainId,
|
|
578
|
+
walletAddress: props.walletAddress,
|
|
579
|
+
destinationChainId: props.destinationChainId,
|
|
580
|
+
slippageBps: props.slippageBps,
|
|
581
|
+
feeBufferPercent: props.feeBufferPercent,
|
|
582
|
+
hubChain: props.hubChain,
|
|
583
|
+
sourceChain: props.sourceChain,
|
|
584
|
+
});
|
|
585
|
+
if (!result.success || !result.data) {
|
|
586
|
+
throw new Error(result.error || 'Failed to build transaction');
|
|
587
|
+
}
|
|
588
|
+
const txInputs = result.data;
|
|
589
|
+
// 2. Check and execute approval if needed
|
|
590
|
+
if (!props.skipApprovalCheck) {
|
|
591
|
+
const tokenAddr = txInputs.approval?.tokenAddress ??
|
|
592
|
+
props.config.contracts.assetToken[props.userChainId];
|
|
593
|
+
const spenderAddr = txInputs.approval?.spender ?? txInputs.contractAddress;
|
|
594
|
+
const approvalAmount = txInputs.approval?.amount ??
|
|
595
|
+
BigInt((0, core_1.toNormalizedBn)(props.amount, props.decimals).raw);
|
|
596
|
+
const approvalNeeded = await needsCrossChainApproval(tokenAddr, spenderAddr, props.walletAddress, approvalAmount, publicClient);
|
|
597
|
+
if (approvalNeeded) {
|
|
598
|
+
await approveCrossChain(tokenAddr, spenderAddr, approvalAmount, walletClient, publicClient);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
// 3. Estimate gas with buffer
|
|
602
|
+
if (!walletClient.account) {
|
|
603
|
+
throw new Error('Wallet not ready — please reconnect your wallet');
|
|
604
|
+
}
|
|
605
|
+
const gasBufferPercent = props.gasBufferPercent ?? DEFAULT_DEPOSIT_GAS_BUFFER_PERCENT;
|
|
606
|
+
let gasLimit;
|
|
607
|
+
try {
|
|
608
|
+
const estimated = await publicClient.estimateContractGas({
|
|
609
|
+
account: walletClient.account,
|
|
610
|
+
address: txInputs.contractAddress,
|
|
611
|
+
abi: txInputs.abi,
|
|
612
|
+
functionName: txInputs.contractFunctionName,
|
|
613
|
+
args: txInputs.txArgs,
|
|
614
|
+
value: txInputs.messageValue,
|
|
615
|
+
});
|
|
616
|
+
gasLimit = (estimated * BigInt(gasBufferPercent)) / 100n;
|
|
617
|
+
}
|
|
618
|
+
catch (estimateErr) {
|
|
619
|
+
throw new Error(`Gas estimation failed for cross-chain deposit. Ensure you have sufficient native tokens for the LayerZero fee (${txInputs.messageFee.nativeFee.toString()} wei). Original error: ${estimateErr instanceof Error ? estimateErr.message : String(estimateErr)}`);
|
|
620
|
+
}
|
|
621
|
+
// 4. Execute the transaction
|
|
622
|
+
const hash = await walletClient.writeContract({
|
|
623
|
+
account: walletClient.account,
|
|
624
|
+
address: txInputs.contractAddress,
|
|
625
|
+
abi: txInputs.abi,
|
|
626
|
+
functionName: txInputs.contractFunctionName,
|
|
627
|
+
args: txInputs.txArgs,
|
|
628
|
+
value: txInputs.messageValue,
|
|
629
|
+
gas: gasLimit,
|
|
630
|
+
dataSuffix: (0, attribution_1.getAttributionSuffix)(props.userChainId),
|
|
631
|
+
});
|
|
632
|
+
// 5. Wait for confirmation
|
|
633
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
634
|
+
hash: hash,
|
|
635
|
+
});
|
|
636
|
+
if (receipt.status === 'reverted') {
|
|
637
|
+
throw new Error('Transaction reverted on-chain. This may be due to insufficient gas for the LayerZero compose call or a contract error.');
|
|
638
|
+
}
|
|
639
|
+
return {
|
|
640
|
+
transactionHash: hash,
|
|
641
|
+
success: true,
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Execute a cross-chain redemption via LayerZero OVault. User must be on the
|
|
646
|
+
* hub chain. Defaults: `slippageBps` 100, `feeBufferPercent` 50,
|
|
647
|
+
* `gasBufferPercent` 200 (compose calls under-estimate on mainnet),
|
|
648
|
+
* `minGasLimit` 5_000_000n — all overridable on the request.
|
|
649
|
+
*/
|
|
650
|
+
async function crossChainVaultRedeem(props) {
|
|
651
|
+
// 1. Validate: redemptions must be from hub chain
|
|
652
|
+
if (props.userChainId !== props.config.hubChainId) {
|
|
653
|
+
throw new Error(`Redemptions must be executed from hub chain (${props.config.hubChainId}). ` +
|
|
654
|
+
`Please switch to the hub chain first.`);
|
|
655
|
+
}
|
|
656
|
+
const walletClient = props.walletClient;
|
|
657
|
+
const publicClient = props.publicClient;
|
|
658
|
+
// 2. Build transaction inputs
|
|
659
|
+
const result = await buildCrossChainVaultTx({
|
|
660
|
+
ovaultSdk: props.ovaultSdk,
|
|
661
|
+
config: props.config,
|
|
662
|
+
operation: 'REDEEM',
|
|
663
|
+
amount: props.amount,
|
|
664
|
+
decimals: props.decimals,
|
|
665
|
+
userChainId: props.userChainId,
|
|
666
|
+
walletAddress: props.walletAddress,
|
|
667
|
+
destinationChainId: props.destinationChainId ?? props.userChainId,
|
|
668
|
+
slippageBps: props.slippageBps,
|
|
669
|
+
feeBufferPercent: props.feeBufferPercent,
|
|
670
|
+
hubChain: props.hubChain,
|
|
671
|
+
sourceChain: props.sourceChain,
|
|
672
|
+
});
|
|
673
|
+
if (!result.success || !result.data) {
|
|
674
|
+
throw new Error(result.error || 'Failed to build transaction');
|
|
675
|
+
}
|
|
676
|
+
const txInputs = result.data;
|
|
677
|
+
// 3. Check and execute approval if needed
|
|
678
|
+
if (!props.skipApprovalCheck && txInputs.approval) {
|
|
679
|
+
const approvalNeeded = await needsCrossChainApproval(txInputs.approval.tokenAddress, txInputs.approval.spender, props.walletAddress, txInputs.approval.amount, publicClient);
|
|
680
|
+
if (approvalNeeded) {
|
|
681
|
+
await approveCrossChain(txInputs.approval.tokenAddress, txInputs.approval.spender, txInputs.approval.amount, walletClient, publicClient);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
// 4. Estimate gas with buffer
|
|
685
|
+
if (!walletClient.account) {
|
|
686
|
+
throw new Error('Wallet account not available');
|
|
687
|
+
}
|
|
688
|
+
const gasBufferPercent = props.gasBufferPercent ?? DEFAULT_REDEEM_GAS_BUFFER_PERCENT;
|
|
689
|
+
const minGasLimit = props.minGasLimit ?? DEFAULT_MIN_REDEEM_GAS;
|
|
690
|
+
let gasLimit;
|
|
691
|
+
try {
|
|
692
|
+
const estimated = await publicClient.estimateContractGas({
|
|
693
|
+
account: walletClient.account,
|
|
694
|
+
address: txInputs.contractAddress,
|
|
695
|
+
abi: txInputs.abi,
|
|
696
|
+
functionName: txInputs.contractFunctionName,
|
|
697
|
+
args: txInputs.txArgs,
|
|
698
|
+
value: txInputs.messageValue,
|
|
699
|
+
});
|
|
700
|
+
const buffered = (estimated * BigInt(gasBufferPercent)) / 100n;
|
|
701
|
+
gasLimit = buffered > minGasLimit ? buffered : minGasLimit;
|
|
702
|
+
}
|
|
703
|
+
catch (estimateErr) {
|
|
704
|
+
throw new Error(`Gas estimation failed for cross-chain redeem. Ensure you have sufficient native tokens for the LayerZero fee (${txInputs.messageFee.nativeFee.toString()} wei). Original error: ${estimateErr instanceof Error ? estimateErr.message : String(estimateErr)}`);
|
|
705
|
+
}
|
|
706
|
+
// 5. Execute the transaction
|
|
707
|
+
const hash = await walletClient.writeContract({
|
|
708
|
+
account: walletClient.account,
|
|
709
|
+
address: txInputs.contractAddress,
|
|
710
|
+
abi: txInputs.abi,
|
|
711
|
+
functionName: txInputs.contractFunctionName,
|
|
712
|
+
args: txInputs.txArgs,
|
|
713
|
+
value: txInputs.messageValue,
|
|
714
|
+
gas: gasLimit,
|
|
715
|
+
dataSuffix: (0, attribution_1.getAttributionSuffix)(props.config.hubChainId),
|
|
716
|
+
});
|
|
717
|
+
// 6. Wait for confirmation
|
|
718
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
719
|
+
hash: hash,
|
|
720
|
+
});
|
|
721
|
+
if (receipt.status === 'reverted') {
|
|
722
|
+
throw new Error('Transaction reverted on-chain. This may be due to insufficient gas for the LayerZero compose call or a contract error.');
|
|
723
|
+
}
|
|
724
|
+
return {
|
|
725
|
+
transactionHash: hash,
|
|
726
|
+
success: true,
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
// ---------------------------------------------------------------------------
|
|
730
|
+
// Public API: Error Classification
|
|
731
|
+
// ---------------------------------------------------------------------------
|
|
732
|
+
/**
|
|
733
|
+
* Check if an error indicates a missing LayerZero peer configuration.
|
|
734
|
+
*/
|
|
735
|
+
function isNoPeerError(errStr) {
|
|
736
|
+
return errStr.includes('0xf6ff4fb7') || errStr.includes('NoPeer');
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Classify a cross-chain contract error into a known error type.
|
|
740
|
+
*/
|
|
741
|
+
function classifyCrossChainError(error) {
|
|
742
|
+
const err = error;
|
|
743
|
+
const msg = err?.message ?? String(error);
|
|
744
|
+
const lower = msg.toLowerCase();
|
|
745
|
+
if (msg.includes('0x79012fb2') || msg.includes('ERC4626ExceededMaxDeposit'))
|
|
746
|
+
return 'max-deposit';
|
|
747
|
+
if (lower.includes('nonce') || msg.includes('invalid nonce'))
|
|
748
|
+
return 'nonce';
|
|
749
|
+
if (lower.includes('user rejected') || msg.includes('User denied'))
|
|
750
|
+
return 'user-rejected';
|
|
751
|
+
if (isNoPeerError(msg))
|
|
752
|
+
return 'no-peer';
|
|
753
|
+
if (msg.includes('quoteSend') ||
|
|
754
|
+
lower.includes('layerzero') ||
|
|
755
|
+
lower.includes('lz fee') ||
|
|
756
|
+
lower.includes('oft') ||
|
|
757
|
+
lower.includes('unable to decode signature'))
|
|
758
|
+
return 'bridge-fee';
|
|
759
|
+
if (msg.includes('insufficient') || lower.includes('balance'))
|
|
760
|
+
return 'insufficient-balance';
|
|
761
|
+
if (lower.includes('gas'))
|
|
762
|
+
return 'gas';
|
|
763
|
+
if (lower.includes('network') || lower.includes('connection'))
|
|
764
|
+
return 'network';
|
|
765
|
+
return 'unknown';
|
|
766
|
+
}
|
|
767
|
+
// ---------------------------------------------------------------------------
|
|
768
|
+
// Public API: Utility helpers
|
|
769
|
+
// ---------------------------------------------------------------------------
|
|
770
|
+
/**
|
|
771
|
+
* Check if a user can deposit from their current chain.
|
|
772
|
+
*/
|
|
773
|
+
function canDepositFromChain(config, userChainId) {
|
|
774
|
+
return (userChainId === config.hubChainId ||
|
|
775
|
+
config.spokeChainIds.includes(userChainId));
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Check if a user can redeem from their current chain (must be hub).
|
|
779
|
+
*/
|
|
780
|
+
function canRedeemFromChain(config, userChainId) {
|
|
781
|
+
return userChainId === config.hubChainId;
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Check if an operation is cross-chain (needs LayerZero).
|
|
785
|
+
*
|
|
786
|
+
* An operation is cross-chain when:
|
|
787
|
+
* - The user's chain differs from the hub chain, OR
|
|
788
|
+
* - The destination chain differs from the hub chain
|
|
789
|
+
* (e.g. deposit from hub but receive shares on a spoke)
|
|
790
|
+
*/
|
|
791
|
+
function isCrossChainOperation(config, userChainId, destinationChainId) {
|
|
792
|
+
if (userChainId !== config.hubChainId)
|
|
793
|
+
return true;
|
|
794
|
+
if (destinationChainId !== undefined &&
|
|
795
|
+
destinationChainId !== config.hubChainId)
|
|
796
|
+
return true;
|
|
797
|
+
return false;
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Get all chain IDs that a vault supports (hub + all spokes).
|
|
801
|
+
*/
|
|
802
|
+
function getAvailableChains(config) {
|
|
803
|
+
return [config.hubChainId, ...config.spokeChainIds];
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Format a cross-chain error into a user-friendly message.
|
|
807
|
+
*
|
|
808
|
+
* Mirrors the `classifyCrossChainDepositError` from upshift-app but is
|
|
809
|
+
* generic enough for any SDK consumer.
|
|
810
|
+
*/
|
|
811
|
+
function formatCrossChainError(errMsg) {
|
|
812
|
+
// If the error already contains specific gas fee info, show it directly
|
|
813
|
+
if (errMsg.includes('Cross-chain fee is') ||
|
|
814
|
+
errMsg.includes('Gas estimation failed'))
|
|
815
|
+
return errMsg;
|
|
816
|
+
const lower = errMsg.toLowerCase();
|
|
817
|
+
if (lower.includes('nonce') || errMsg.includes('invalid nonce'))
|
|
818
|
+
return 'Transaction nonce conflict. Please cancel any pending transactions and try again.';
|
|
819
|
+
if (errMsg.includes('quoteSend') ||
|
|
820
|
+
lower.includes('layerzero') ||
|
|
821
|
+
lower.includes('oft') ||
|
|
822
|
+
lower.includes('unable to decode signature'))
|
|
823
|
+
return 'Cross-chain fee estimation failed. The bridge may be temporarily unavailable — please try again.';
|
|
824
|
+
if (lower.includes('user rejected'))
|
|
825
|
+
return 'Transaction cancelled by user.';
|
|
826
|
+
if (lower.includes('hub chain'))
|
|
827
|
+
return errMsg; // Already descriptive
|
|
828
|
+
if (errMsg.includes('reverted'))
|
|
829
|
+
return 'Transaction reverted on-chain. This may be due to insufficient gas or a contract error.';
|
|
830
|
+
return 'Cross-chain transaction failed. Please try again.';
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Get the LayerZero Scan URL for tracking a cross-chain message.
|
|
834
|
+
*/
|
|
835
|
+
function getLayerZeroScanUrl(txHash, testnet = false) {
|
|
836
|
+
const baseUrl = testnet
|
|
837
|
+
? 'https://testnet.layerzeroscan.com'
|
|
838
|
+
: 'https://layerzeroscan.com';
|
|
839
|
+
return `${baseUrl}/tx/${txHash}`;
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Well-known LayerZero Endpoint IDs for common chains.
|
|
843
|
+
* SDK consumers can use these when building their ICrossChainVaultConfig.
|
|
844
|
+
*/
|
|
845
|
+
exports.LAYERZERO_ENDPOINT_IDS = {
|
|
846
|
+
ETHEREUM: 30101,
|
|
847
|
+
ARBITRUM: 30110,
|
|
848
|
+
AVALANCHE: 30106,
|
|
849
|
+
BASE: 30184,
|
|
850
|
+
SONIC: 30332,
|
|
851
|
+
MONAD: 30390,
|
|
852
|
+
};
|
|
853
|
+
//# sourceMappingURL=crossChainVault.js.map
|