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,1749 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.safeBigInt = safeBigInt;
|
|
4
|
+
exports.resolveDepositTokenDecimals = resolveDepositTokenDecimals;
|
|
5
|
+
exports.resolveSpender = resolveSpender;
|
|
6
|
+
exports.validateAmountPrecision = validateAmountPrecision;
|
|
7
|
+
exports.isNonceParsing = isNonceParsing;
|
|
8
|
+
exports.safeWaitForTx = safeWaitForTx;
|
|
9
|
+
exports.safeSendTx = safeSendTx;
|
|
10
|
+
exports.tryRecoverTxHash = tryRecoverTxHash;
|
|
11
|
+
exports.vaultApprove = vaultApprove;
|
|
12
|
+
exports.approve = approve;
|
|
13
|
+
exports.vaultDeposit = vaultDeposit;
|
|
14
|
+
exports.vaultRequestRedeem = vaultRequestRedeem;
|
|
15
|
+
exports.vaultRedeem = vaultRedeem;
|
|
16
|
+
exports.depositNative = depositNative;
|
|
17
|
+
exports.rwaRedeemAsset = rwaRedeemAsset;
|
|
18
|
+
exports.swapAndDeposit = swapAndDeposit;
|
|
19
|
+
exports.depositViaSwapRouter = depositViaSwapRouter;
|
|
20
|
+
exports.depositNativeViaSwapRouter = depositNativeViaSwapRouter;
|
|
21
|
+
exports.swapRouterDeposit = swapRouterDeposit;
|
|
22
|
+
const ethers_1 = require("ethers");
|
|
23
|
+
const abis_1 = require("../../abis");
|
|
24
|
+
const core_1 = require("../../core");
|
|
25
|
+
const swap_quotes_1 = require("../../services/swap-quotes");
|
|
26
|
+
const adapter_helpers_1 = require("./adapter.helpers");
|
|
27
|
+
const utils_1 = require("./utils");
|
|
28
|
+
// Treat unparseable RPC payloads (bare "0x", etc.) as 0n with a warn log —
|
|
29
|
+
// the defensive default is "approve again," and the log surfaces flaky RPCs
|
|
30
|
+
// before they cause silent gas waste from spurious approvals.
|
|
31
|
+
/** @internal */
|
|
32
|
+
function safeBigInt(value, context = 'safeBigInt') {
|
|
33
|
+
if (value === null || value === undefined)
|
|
34
|
+
return 0n;
|
|
35
|
+
const s = String(value);
|
|
36
|
+
if (s === '' || s === '0x') {
|
|
37
|
+
if (s === '0x') {
|
|
38
|
+
core_1.Logger.log.warn(context, 'RPC returned bare "0x"; treating as 0', {
|
|
39
|
+
raw: s,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return 0n;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
return BigInt(s);
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
core_1.Logger.log.warn(context, 'Could not parse RPC value as BigInt; treating as 0', { raw: s, error: String(err) });
|
|
49
|
+
return 0n;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// EVM-0/1 single-asset underlying deposits reuse vaultDecimals (vault and
|
|
53
|
+
// underlying decimals match under ERC-4626 inheritance), saving one RPC.
|
|
54
|
+
// Multi-asset / adapter / non-underlying paths must read from the deposit
|
|
55
|
+
// asset's own ERC-20 — using share decimals there silently misencodes amounts.
|
|
56
|
+
/** @internal */
|
|
57
|
+
async function resolveDepositTokenDecimals(args) {
|
|
58
|
+
const { actualDepositAsset, underlyingAsset, isNativeToken, isMultiAssetVault, vaultDecimals, readErc20Decimals, } = args;
|
|
59
|
+
if (isNativeToken)
|
|
60
|
+
return 18;
|
|
61
|
+
const isUnderlying = actualDepositAsset.toLowerCase() === underlyingAsset.toLowerCase();
|
|
62
|
+
if (isUnderlying && !isMultiAssetVault)
|
|
63
|
+
return vaultDecimals;
|
|
64
|
+
return readErc20Decimals(actualDepositAsset);
|
|
65
|
+
}
|
|
66
|
+
/** @internal */
|
|
67
|
+
function resolveSpender(args) {
|
|
68
|
+
if (args.isMultiAssetVault)
|
|
69
|
+
return args.target;
|
|
70
|
+
if (args.isAdapterDeposit && args.adapterWrapperAddress)
|
|
71
|
+
return args.adapterWrapperAddress;
|
|
72
|
+
return args.target;
|
|
73
|
+
}
|
|
74
|
+
// Reject JS-number amounts past Number.MAX_SAFE_INTEGER — any larger
|
|
75
|
+
// integer the caller passes is already wrong before we hit toNormalizedBn.
|
|
76
|
+
/** @internal */
|
|
77
|
+
function validateAmountPrecision(amount) {
|
|
78
|
+
if (typeof amount === 'string' || typeof amount === 'bigint')
|
|
79
|
+
return;
|
|
80
|
+
if (!Number.isFinite(amount)) {
|
|
81
|
+
throw new core_1.AugustValidationError('INVALID_INPUT', `amount must be finite, got ${amount}`);
|
|
82
|
+
}
|
|
83
|
+
if (amount < 0) {
|
|
84
|
+
throw new core_1.AugustValidationError('INVALID_INPUT', `amount must be non-negative, got ${amount}`);
|
|
85
|
+
}
|
|
86
|
+
if (Number.isInteger(amount) && !Number.isSafeInteger(amount)) {
|
|
87
|
+
throw new core_1.AugustValidationError('INVALID_INPUT', `amount ${amount} exceeds Number.MAX_SAFE_INTEGER; pass a string or bigint to preserve precision`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Tries tx.wait() first (preserves replacement detection & revert checks), then falls back to
|
|
91
|
+
// provider.waitForTransaction() for RPCs (e.g. Monad) that return malformed pending tx fields.
|
|
92
|
+
const SAFE_WAIT_TIMEOUT_MS = 120_000;
|
|
93
|
+
/** @internal */
|
|
94
|
+
function isNonceParsing(error) {
|
|
95
|
+
const code = error?.code;
|
|
96
|
+
const msg = String(error).toLowerCase();
|
|
97
|
+
return ((code === 'INVALID_ARGUMENT' || code === 'BAD_DATA') &&
|
|
98
|
+
msg.includes('nonce'));
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Own-properties stamped onto an error describing a transaction that already
|
|
102
|
+
* reached the network. Prefixed to avoid colliding with the `hash` /
|
|
103
|
+
* `transaction` / `receipt` fields ethers puts on its own errors.
|
|
104
|
+
*
|
|
105
|
+
* These are an internal wire format between the tx-wait helpers and the write
|
|
106
|
+
* paths' catch blocks — consumers read the *sanitized* form
|
|
107
|
+
* (`txHash` / `broadcast` / `confirmationUnknown`) off `AugustSDKError.context`,
|
|
108
|
+
* never these.
|
|
109
|
+
* @internal
|
|
110
|
+
*/
|
|
111
|
+
const BROADCAST_HASH_KEY = 'augustBroadcastTxHash';
|
|
112
|
+
/** @internal */
|
|
113
|
+
const BROADCAST_UNKNOWN_KEY = 'augustConfirmationUnknown';
|
|
114
|
+
/**
|
|
115
|
+
* Stamp "this transaction was broadcast" onto an error, in place.
|
|
116
|
+
*
|
|
117
|
+
* Mutating rather than wrapping is deliberate: callers up the stack compare
|
|
118
|
+
* error identity (and ethers' own typed fields matter for downstream
|
|
119
|
+
* classification), so replacing the error object would lose information.
|
|
120
|
+
*
|
|
121
|
+
* Annotating is strictly best-effort. A frozen or sealed error, or one with a
|
|
122
|
+
* getter-only property of the same name, must never turn a *recoverable
|
|
123
|
+
* transport failure* into a `TypeError` thrown from inside the error handler —
|
|
124
|
+
* that would be a strictly worse outcome than losing the hash. Each write is
|
|
125
|
+
* therefore attempted independently and failures are swallowed; the caller then
|
|
126
|
+
* simply reports the transaction as un-annotated (no `txHash` in the error
|
|
127
|
+
* context), which is the same conservative state as "never broadcast".
|
|
128
|
+
*
|
|
129
|
+
* @param error - The error about to be thrown. Non-objects pass through
|
|
130
|
+
* untouched — there is nowhere to put the marker.
|
|
131
|
+
* @param txHash - Hash of the transaction that reached the network.
|
|
132
|
+
* @param confirmationUnknown - `true` when the transaction is on the wire but
|
|
133
|
+
* its outcome could not be determined (transport retries exhausted, or the
|
|
134
|
+
* wait timed out). `false` when the chain gave a definitive answer, e.g. a
|
|
135
|
+
* receipt with `status === 0`.
|
|
136
|
+
* @returns The same error instance (identity preserved), for
|
|
137
|
+
* `throw markBroadcast(...)`.
|
|
138
|
+
* @internal
|
|
139
|
+
*/
|
|
140
|
+
function markBroadcast(error, txHash, confirmationUnknown) {
|
|
141
|
+
if (!error || typeof error !== 'object')
|
|
142
|
+
return error;
|
|
143
|
+
// `defineProperty` with configurable/writable keeps the marker invisible to
|
|
144
|
+
// JSON.stringify and to spreads of the error, so it can't leak into a
|
|
145
|
+
// serialized crash report as a mystery field.
|
|
146
|
+
const stamp = (key, value) => {
|
|
147
|
+
try {
|
|
148
|
+
Object.defineProperty(error, key, {
|
|
149
|
+
value,
|
|
150
|
+
enumerable: false,
|
|
151
|
+
writable: true,
|
|
152
|
+
configurable: true,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// Frozen/sealed error, or a non-configurable same-named property. The
|
|
157
|
+
// hash is a nice-to-have; never let annotation failure mask the real
|
|
158
|
+
// transport error we are in the middle of reporting.
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
stamp(BROADCAST_HASH_KEY, txHash);
|
|
162
|
+
stamp(BROADCAST_UNKNOWN_KEY, confirmationUnknown);
|
|
163
|
+
return error;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Read back the markers {@link markBroadcast} stamped, if any.
|
|
167
|
+
*
|
|
168
|
+
* @param error - The caught value, of unknown type.
|
|
169
|
+
* @returns The recovered hash and confirmation state; both `undefined` when the
|
|
170
|
+
* error never passed through a tx-wait helper.
|
|
171
|
+
* @internal
|
|
172
|
+
*/
|
|
173
|
+
function readBroadcastMarkers(error) {
|
|
174
|
+
if (!error || typeof error !== 'object')
|
|
175
|
+
return {};
|
|
176
|
+
const e = error;
|
|
177
|
+
const txHash = e[BROADCAST_HASH_KEY];
|
|
178
|
+
const confirmationUnknown = e[BROADCAST_UNKNOWN_KEY];
|
|
179
|
+
return {
|
|
180
|
+
txHash: typeof txHash === 'string' ? txHash : undefined,
|
|
181
|
+
confirmationUnknown: typeof confirmationUnknown === 'boolean'
|
|
182
|
+
? confirmationUnknown
|
|
183
|
+
: undefined,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Build the broadcast half of a failed write's structured error context, for a
|
|
188
|
+
* path that holds its main transaction in a local variable.
|
|
189
|
+
*
|
|
190
|
+
* The distinction this encodes, and which consumers branch on:
|
|
191
|
+
*
|
|
192
|
+
* - **no `txHash` key at all** — the write never reached the network. Nothing
|
|
193
|
+
* is pending; it is safe to tell the user it failed and safe to retry.
|
|
194
|
+
* - **`txHash` + `broadcast: true` + `confirmationUnknown: true`** — the
|
|
195
|
+
* transaction *is* on-chain (or in the mempool) and only the confirmation was
|
|
196
|
+
* lost. Render a pending state against `txHash`; **do not** prompt a retry,
|
|
197
|
+
* that is exactly how users double-spend a redeem.
|
|
198
|
+
* - **`txHash` + `broadcast: true` + `confirmationUnknown: false`** — the
|
|
199
|
+
* transaction reached the chain and definitively failed (reverted). A real
|
|
200
|
+
* error, with a hash the user can inspect on an explorer.
|
|
201
|
+
*
|
|
202
|
+
* `tx` is the gate on purpose. Several write paths broadcast an inner approval
|
|
203
|
+
* before the main call; if that approval's wait fails, the caught error carries
|
|
204
|
+
* *its* marker. Falling back to the error's marker here would make
|
|
205
|
+
* "Request redeem failed" report the approve hash and claim the redeem is
|
|
206
|
+
* pending when it was never sent.
|
|
207
|
+
*
|
|
208
|
+
* @param tx - The path's main transaction, or `undefined`/`null` when it was
|
|
209
|
+
* never sent.
|
|
210
|
+
* @param error - The caught value, used only for the confirmation state.
|
|
211
|
+
* @returns A context fragment to spread into the thrown error's `context`.
|
|
212
|
+
* Empty when nothing was broadcast.
|
|
213
|
+
* @internal
|
|
214
|
+
*/
|
|
215
|
+
function localTxBroadcastContext(tx, error) {
|
|
216
|
+
const txHash = tx?.hash;
|
|
217
|
+
if (!txHash)
|
|
218
|
+
return {};
|
|
219
|
+
const markers = readBroadcastMarkers(error);
|
|
220
|
+
return {
|
|
221
|
+
txHash,
|
|
222
|
+
broadcast: true,
|
|
223
|
+
// Only trust the marker when it describes *this* transaction.
|
|
224
|
+
confirmationUnknown: markers.txHash === txHash && markers.confirmationUnknown === true,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Same contract as {@link localTxBroadcastContext}, for write paths whose only
|
|
229
|
+
* transaction is sent through {@link safeSendTx} — there the hash never comes
|
|
230
|
+
* back to the caller on the failure path, so the error's own marker is the only
|
|
231
|
+
* source. Safe precisely because those paths send exactly one transaction, so
|
|
232
|
+
* the marker cannot belong to a different one.
|
|
233
|
+
*
|
|
234
|
+
* @param error - The caught value, of unknown type.
|
|
235
|
+
* @returns A context fragment to spread into the thrown error's `context`.
|
|
236
|
+
* Empty when nothing was broadcast.
|
|
237
|
+
* @internal
|
|
238
|
+
*/
|
|
239
|
+
function errorTxBroadcastContext(error) {
|
|
240
|
+
const { txHash, confirmationUnknown } = readBroadcastMarkers(error);
|
|
241
|
+
if (!txHash)
|
|
242
|
+
return {};
|
|
243
|
+
return {
|
|
244
|
+
txHash,
|
|
245
|
+
broadcast: true,
|
|
246
|
+
confirmationUnknown: confirmationUnknown === true,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Await a broadcast transaction's receipt, surviving both malformed-nonce RPCs
|
|
251
|
+
* and transient transport faults while still reporting genuine on-chain
|
|
252
|
+
* failures.
|
|
253
|
+
*
|
|
254
|
+
* `tx.wait()` is tried first because it preserves ethers' replacement-tx
|
|
255
|
+
* detection and revert checks. Two recoverable failure modes are handled:
|
|
256
|
+
*
|
|
257
|
+
* 1. **Malformed nonce** ({@link isNonceParsing}) — Monad-style RPCs return
|
|
258
|
+
* `nonce: "undefined"` on the pending tx and ethers throws while parsing.
|
|
259
|
+
* One direct `provider.waitForTransaction()` recovers it (unchanged
|
|
260
|
+
* behaviour).
|
|
261
|
+
* 2. **Transient transport fault** ({@link isRetryableRpcError}) — the provider
|
|
262
|
+
* hiccups while polling `eth_getTransactionReceipt` and ethers surfaces
|
|
263
|
+
* `could not coalesce error (… "code": -32603 …)`. This used to propagate
|
|
264
|
+
* and make every write path report a **successfully mined** transaction as
|
|
265
|
+
* failed, driving users to retry into `ERC20InsufficientBalance`. The hash
|
|
266
|
+
* is already on the wire and receipt polling is idempotent, so we re-poll
|
|
267
|
+
* with bounded exponential backoff instead of rethrowing.
|
|
268
|
+
*
|
|
269
|
+
* Everything else — notably a real revert — is rethrown untouched.
|
|
270
|
+
*
|
|
271
|
+
* Every error this function throws is stamped by {@link markBroadcast}: by the
|
|
272
|
+
* time `tx.wait()` can fail, the transaction is already on the wire, so the
|
|
273
|
+
* caller must never report it as "never sent". See
|
|
274
|
+
* {@link localTxBroadcastContext} for how that reaches
|
|
275
|
+
* `AugustSDKError.context`.
|
|
276
|
+
*
|
|
277
|
+
* @param tx - The broadcast transaction response to await.
|
|
278
|
+
* @returns The mined receipt (never `null` on the fallback paths; `tx.wait()`
|
|
279
|
+
* itself may return `null` when the caller configured 0 confirmations).
|
|
280
|
+
* @throws {Error} `Transaction <hash> reverted on-chain` when the receipt
|
|
281
|
+
* reports `status === 0`. Marked `confirmationUnknown: false` — the chain
|
|
282
|
+
* answered.
|
|
283
|
+
* @throws {Error} `Transaction <hash> was not confirmed within <n>s` when the
|
|
284
|
+
* overall wait times out with no receipt. Marked
|
|
285
|
+
* `confirmationUnknown: true`.
|
|
286
|
+
* @throws The underlying transport error once receipt-poll retries are
|
|
287
|
+
* exhausted, marked `confirmationUnknown: true`.
|
|
288
|
+
* @internal
|
|
289
|
+
*/
|
|
290
|
+
async function safeWaitForTx(tx) {
|
|
291
|
+
try {
|
|
292
|
+
return await tx.wait();
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
const nonceParse = isNonceParsing(error);
|
|
296
|
+
// Not a shape we know how to recover from — surface it unchanged, but the
|
|
297
|
+
// tx is still on-chain (ethers reports a revert here), so keep the hash.
|
|
298
|
+
if (!nonceParse && !(0, core_1.isRetryableRpcError)(error)) {
|
|
299
|
+
throw markBroadcast(error, tx.hash, false);
|
|
300
|
+
}
|
|
301
|
+
const msg = error instanceof Error ? error.message : '';
|
|
302
|
+
core_1.Logger.log.warn(nonceParse
|
|
303
|
+
? 'safeWaitForTx:fallback'
|
|
304
|
+
: 'safeWaitForTx:transport-fallback', msg, { hash: tx.hash });
|
|
305
|
+
// The nonce path keeps its original single-shot behaviour: the RPC is
|
|
306
|
+
// responsive, it just mis-serialized the pending tx. The transport path
|
|
307
|
+
// re-polls, because the provider itself is the thing that failed.
|
|
308
|
+
let receipt;
|
|
309
|
+
try {
|
|
310
|
+
receipt = nonceParse
|
|
311
|
+
? await tx.provider.waitForTransaction(tx.hash, 1, SAFE_WAIT_TIMEOUT_MS)
|
|
312
|
+
: await (0, core_1.retryOnTransientRpc)('safeWaitForTx:transport-retry', () => tx.provider.waitForTransaction(tx.hash, 1, SAFE_WAIT_TIMEOUT_MS), { hash: tx.hash });
|
|
313
|
+
}
|
|
314
|
+
catch (pollError) {
|
|
315
|
+
// Retries exhausted. The transaction is on the wire and very likely
|
|
316
|
+
// mined; we simply could not read its receipt.
|
|
317
|
+
throw markBroadcast(pollError, tx.hash, true);
|
|
318
|
+
}
|
|
319
|
+
if (!receipt) {
|
|
320
|
+
throw markBroadcast(new Error(`Transaction ${tx.hash} was not confirmed within ${SAFE_WAIT_TIMEOUT_MS / 1000}s`), tx.hash, true);
|
|
321
|
+
}
|
|
322
|
+
if (receipt.status === 0) {
|
|
323
|
+
// Definitive: the chain executed it and it failed. Not pending.
|
|
324
|
+
throw markBroadcast(new Error(`Transaction ${tx.hash} reverted on-chain`), tx.hash, false);
|
|
325
|
+
}
|
|
326
|
+
return receipt;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Wraps an ethers contract call that returns a TransactionResponse.
|
|
331
|
+
* Some RPCs (e.g. Monad) return malformed pending tx fields like `nonce: "undefined"`,
|
|
332
|
+
* which causes ethers.js to throw during response parsing before safeWaitForTx runs.
|
|
333
|
+
* This wrapper catches that error, extracts the tx hash from the error context, and
|
|
334
|
+
* falls back to provider.waitForTransaction().
|
|
335
|
+
*/
|
|
336
|
+
/** @internal */
|
|
337
|
+
async function safeSendTx(contractCall, provider, shouldWait) {
|
|
338
|
+
try {
|
|
339
|
+
const tx = await contractCall();
|
|
340
|
+
if (shouldWait)
|
|
341
|
+
await safeWaitForTx(tx);
|
|
342
|
+
return { tx, hash: tx.hash };
|
|
343
|
+
}
|
|
344
|
+
catch (error) {
|
|
345
|
+
if (!isNonceParsing(error))
|
|
346
|
+
throw error;
|
|
347
|
+
// The tx was sent but ethers couldn't parse the response.
|
|
348
|
+
// Try to extract the hash from the error payload.
|
|
349
|
+
const errStr = String(error);
|
|
350
|
+
const hashMatch = errStr.match(/"hash"\s*:\s*"(0x[0-9a-fA-F]{64})"/);
|
|
351
|
+
if (!hashMatch?.[1])
|
|
352
|
+
throw error;
|
|
353
|
+
const txHash = hashMatch[1];
|
|
354
|
+
core_1.Logger.log.warn('safeSendTx:nonce-fallback', { txHash });
|
|
355
|
+
if (shouldWait) {
|
|
356
|
+
const signerProvider = 'provider' in provider ? provider.provider : provider;
|
|
357
|
+
const receipt = await signerProvider.waitForTransaction(txHash, 1, SAFE_WAIT_TIMEOUT_MS);
|
|
358
|
+
if (!receipt) {
|
|
359
|
+
throw markBroadcast(new Error(`Transaction ${txHash} was not confirmed within ${SAFE_WAIT_TIMEOUT_MS / 1000}s`), txHash, true);
|
|
360
|
+
}
|
|
361
|
+
if (receipt.status === 0) {
|
|
362
|
+
throw markBroadcast(new Error(`Transaction ${txHash} reverted on-chain`), txHash, false);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return { tx: null, hash: txHash };
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Shared nonce-recovery helper for catch blocks. When an RPC returns a
|
|
370
|
+
* malformed nonce and ethers throws, try to extract the tx hash from the
|
|
371
|
+
* error payload. Optionally waits for confirmation (with status check).
|
|
372
|
+
*
|
|
373
|
+
* Returns the recovered hash, or null if this error is not a nonce-parse error
|
|
374
|
+
* or if no hash could be extracted (caller should re-throw the original error).
|
|
375
|
+
*/
|
|
376
|
+
/** @internal */
|
|
377
|
+
async function tryRecoverTxHash(error, signer, wait) {
|
|
378
|
+
if (!isNonceParsing(error))
|
|
379
|
+
return null;
|
|
380
|
+
const hashMatch = String(error).match(/"hash"\s*:\s*"(0x[0-9a-fA-F]{64})"/);
|
|
381
|
+
if (!hashMatch?.[1])
|
|
382
|
+
return null;
|
|
383
|
+
const txHash = hashMatch[1];
|
|
384
|
+
if (wait) {
|
|
385
|
+
const provider = 'provider' in signer ? signer.provider : signer;
|
|
386
|
+
const receipt = await provider.waitForTransaction(txHash, 1, SAFE_WAIT_TIMEOUT_MS);
|
|
387
|
+
if (!receipt) {
|
|
388
|
+
throw markBroadcast(new Error(`Transaction ${txHash} was not confirmed within ${SAFE_WAIT_TIMEOUT_MS / 1000}s`), txHash, true);
|
|
389
|
+
}
|
|
390
|
+
if (receipt.status === 0) {
|
|
391
|
+
throw markBroadcast(new Error(`Transaction ${txHash} reverted on-chain`), txHash, false);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return txHash;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Rethrow a caught write-path error as a typed {@link AugustValidationError}
|
|
398
|
+
* when it is the chain node reporting the sender can't afford the transaction's
|
|
399
|
+
* gas/fee (see {@link isInsufficientFundsError}).
|
|
400
|
+
*
|
|
401
|
+
* Why: for an underfunded account ethers surfaces an opaque `CALL_EXCEPTION` /
|
|
402
|
+
* "missing revert data" that gives the caller no stable way to tell "user needs
|
|
403
|
+
* gas" apart from a genuine on-chain failure. Converting it to the
|
|
404
|
+
* `ACCOUNT_NOT_FUNDED` code lets a consuming UI branch on `err.code` (e.g. show
|
|
405
|
+
* a "top up to cover network fees" prompt) instead of string-matching. Every
|
|
406
|
+
* write path shares this so the classification is identical across
|
|
407
|
+
* deposit/redeem/approve rather than drifting per call site.
|
|
408
|
+
*
|
|
409
|
+
* A no-op when `isInsufficient` is `false`, so callers fall through to their
|
|
410
|
+
* existing handling unchanged. The caller passes the already-computed
|
|
411
|
+
* classification (it also feeds the telemetry-demotion decision) so
|
|
412
|
+
* {@link isInsufficientFundsError} runs once per catch, not twice.
|
|
413
|
+
*
|
|
414
|
+
* @param isInsufficient - Result of {@link isInsufficientFundsError} for
|
|
415
|
+
* `error`, computed once by the caller and shared with the benign-log flag.
|
|
416
|
+
* @param operation - Human-readable label for the attempted write (e.g.
|
|
417
|
+
* `'Request redeem'`), used verbatim in the thrown error's message.
|
|
418
|
+
* @param error - The caught value, of unknown type, preserved as the thrown
|
|
419
|
+
* error's `cause`.
|
|
420
|
+
* @param context - Structured context attached to the thrown error for logging.
|
|
421
|
+
* @throws {AugustValidationError} with code `ACCOUNT_NOT_FUNDED` when
|
|
422
|
+
* `isInsufficient` is `true`.
|
|
423
|
+
*/
|
|
424
|
+
function throwIfInsufficientFunds(isInsufficient, operation, error, context) {
|
|
425
|
+
if (!isInsufficient)
|
|
426
|
+
return;
|
|
427
|
+
throw new core_1.AugustValidationError('ACCOUNT_NOT_FUNDED', `${operation} failed: insufficient native balance to cover gas/network fees`, { cause: error, context });
|
|
428
|
+
}
|
|
429
|
+
async function approveCore(signer, options) {
|
|
430
|
+
const { wallet, target, wait, amount, depositAsset } = options;
|
|
431
|
+
const [goodWallet, goodPool] = [
|
|
432
|
+
(0, core_1.checkAddress)(wallet, console, 'wallet'),
|
|
433
|
+
(0, core_1.checkAddress)(target, console),
|
|
434
|
+
];
|
|
435
|
+
if (!goodWallet || !goodPool) {
|
|
436
|
+
throw new core_1.AugustValidationError('INVALID_ADDRESS', `vaultApprove: invalid ${!goodWallet ? 'wallet' : 'target'} address`);
|
|
437
|
+
}
|
|
438
|
+
if (amount === undefined || amount === null) {
|
|
439
|
+
throw new core_1.AugustValidationError('INVALID_INPUT', 'vaultApprove: amount is required');
|
|
440
|
+
}
|
|
441
|
+
validateAmountPrecision(amount);
|
|
442
|
+
try {
|
|
443
|
+
const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(target))?.[0];
|
|
444
|
+
const vaultVersion = (0, core_1.getVaultVersionV2)(tokenizedVault);
|
|
445
|
+
const adapterConfig = (0, core_1.getVaultAdapterConfig)(target);
|
|
446
|
+
const isMultiAssetVault = vaultVersion === 'evm-2';
|
|
447
|
+
const poolContract = isMultiAssetVault
|
|
448
|
+
? (0, core_1.createContract)({
|
|
449
|
+
address: target,
|
|
450
|
+
provider: signer,
|
|
451
|
+
abi: abis_1.ABI_TOKENIZED_VAULT_V2,
|
|
452
|
+
})
|
|
453
|
+
: (0, core_1.createContract)({
|
|
454
|
+
address: target,
|
|
455
|
+
provider: signer,
|
|
456
|
+
abi: abis_1.ABI_LENDING_POOLS,
|
|
457
|
+
});
|
|
458
|
+
let underlyingAsset;
|
|
459
|
+
let vaultDecimals;
|
|
460
|
+
if (isMultiAssetVault) {
|
|
461
|
+
const [asset, receiptTokenAddr] = await Promise.all([
|
|
462
|
+
poolContract.asset(),
|
|
463
|
+
// Retried on transport blips only; a misroute still throws. See
|
|
464
|
+
// getReceiptTokenAddressOrThrow.
|
|
465
|
+
(0, core_1.getReceiptTokenAddressOrThrow)(signer, target, 'vaultApprove:receiptToken'),
|
|
466
|
+
]);
|
|
467
|
+
underlyingAsset = asset;
|
|
468
|
+
vaultDecimals = await (0, core_1.getDecimalsOrThrow)(signer, receiptTokenAddr, 'vaultApprove:receiptDecimals');
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
const [asset, rawDecimals] = await Promise.all([
|
|
472
|
+
poolContract.asset(),
|
|
473
|
+
(0, core_1.getDecimalsOrThrow)(signer, target, 'vaultApprove:poolDecimals'),
|
|
474
|
+
]);
|
|
475
|
+
underlyingAsset = asset;
|
|
476
|
+
vaultDecimals = rawDecimals;
|
|
477
|
+
}
|
|
478
|
+
const actualDepositAsset = (depositAsset || underlyingAsset);
|
|
479
|
+
const isNativeToken = actualDepositAsset === ethers_1.ZeroAddress ||
|
|
480
|
+
actualDepositAsset === '0x0000000000000000000000000000000000000000';
|
|
481
|
+
// Native tokens travel as msg.value — no ERC-20 allowance to grant.
|
|
482
|
+
if (isNativeToken)
|
|
483
|
+
return { kind: 'native' };
|
|
484
|
+
const isAdapterDeposit = actualDepositAsset.toLowerCase() !== underlyingAsset.toLowerCase();
|
|
485
|
+
const spenderAddress = resolveSpender({
|
|
486
|
+
target,
|
|
487
|
+
isMultiAssetVault,
|
|
488
|
+
isAdapterDeposit,
|
|
489
|
+
adapterWrapperAddress: adapterConfig?.wrapperAddress,
|
|
490
|
+
});
|
|
491
|
+
const depositTokenDecimals = await resolveDepositTokenDecimals({
|
|
492
|
+
actualDepositAsset,
|
|
493
|
+
underlyingAsset,
|
|
494
|
+
isNativeToken: false,
|
|
495
|
+
isMultiAssetVault,
|
|
496
|
+
vaultDecimals,
|
|
497
|
+
readErc20Decimals: async (addr) => (0, core_1.getDecimalsOrThrow)(signer, addr, 'vaultApprove:depositTokenDecimals'),
|
|
498
|
+
});
|
|
499
|
+
const normalizedAmt = (0, core_1.toNormalizedBn)(amount, depositTokenDecimals);
|
|
500
|
+
const needed = BigInt(normalizedAmt.raw);
|
|
501
|
+
const tokenContract = (0, core_1.createContract)({
|
|
502
|
+
address: actualDepositAsset,
|
|
503
|
+
abi: abis_1.ABI_ERC20,
|
|
504
|
+
provider: signer,
|
|
505
|
+
});
|
|
506
|
+
const currentAllowance = await tokenContract.allowance(wallet, spenderAddress);
|
|
507
|
+
const existing = safeBigInt(currentAllowance, 'vaultApprove:allowance');
|
|
508
|
+
if (existing >= needed) {
|
|
509
|
+
core_1.Logger.log.info('approve:allowance-sufficient', {
|
|
510
|
+
target,
|
|
511
|
+
spender: spenderAddress,
|
|
512
|
+
amount,
|
|
513
|
+
});
|
|
514
|
+
return { kind: 'sufficient', existing };
|
|
515
|
+
}
|
|
516
|
+
const { hash: approvalHash } = await safeSendTx(() => tokenContract
|
|
517
|
+
.connect(signer)
|
|
518
|
+
.approve(spenderAddress, normalizedAmt.raw), signer, wait);
|
|
519
|
+
core_1.Logger.log.info('approve:tx_hash', approvalHash);
|
|
520
|
+
return { kind: 'sent', hash: approvalHash };
|
|
521
|
+
}
|
|
522
|
+
catch (e) {
|
|
523
|
+
if (e instanceof core_1.AugustSDKError)
|
|
524
|
+
throw e;
|
|
525
|
+
// Only one tx is sent on this path, so any broadcast marker on the error
|
|
526
|
+
// necessarily describes the approval itself.
|
|
527
|
+
const broadcast = errorTxBroadcastContext(e);
|
|
528
|
+
// A user declining the approval in their wallet is product behaviour, not
|
|
529
|
+
// an SDK fault — demote it to a breadcrumb so it doesn't bill as an issue.
|
|
530
|
+
const insufficientFunds = (0, core_1.isInsufficientFundsError)(e);
|
|
531
|
+
(0, core_1.logChainError)('vaultApprove', e, (0, core_1.isUserRejectionError)(e) || insufficientFunds, {
|
|
532
|
+
target,
|
|
533
|
+
amount,
|
|
534
|
+
...broadcast,
|
|
535
|
+
});
|
|
536
|
+
throwIfInsufficientFunds(insufficientFunds, 'Approval', e, {
|
|
537
|
+
target,
|
|
538
|
+
amount,
|
|
539
|
+
...broadcast,
|
|
540
|
+
});
|
|
541
|
+
throw new core_1.AugustSDKError('UNKNOWN', `Approval failed: ${e instanceof Error ? e.message : 'Unknown error'}`, { cause: e, context: { target, amount, ...broadcast } });
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Approve a vault (or the appropriate adapter wrapper) to spend a token
|
|
546
|
+
* ahead of a separate deposit call. Spender routing mirrors
|
|
547
|
+
* {@link vaultDeposit} (multi-asset → vault, adapter → wrapper, standard →
|
|
548
|
+
* vault). Returns `undefined` when the existing allowance already covers
|
|
549
|
+
* `amount` **or** when the deposit asset is native.
|
|
550
|
+
*
|
|
551
|
+
* @returns Transaction hash when an approval was sent, `undefined` when
|
|
552
|
+
* the existing allowance already covers `amount` or the deposit asset is
|
|
553
|
+
* native (no ERC-20 allowance applies). Use {@link approve} for a
|
|
554
|
+
* discriminated `ApproveResult` if you need to tell those two cases apart.
|
|
555
|
+
* @throws AugustValidationError on invalid wallet/target/amount.
|
|
556
|
+
*
|
|
557
|
+
* @example
|
|
558
|
+
* ```ts
|
|
559
|
+
* const hash = await augustSdk.evm.vaultApprove({
|
|
560
|
+
* target: '0x...', // vault contract address
|
|
561
|
+
* wallet: walletAddress,
|
|
562
|
+
* amount: '100', // 100 of the deposit token (UI units)
|
|
563
|
+
* wait: true, // wait for the approval to confirm
|
|
564
|
+
* });
|
|
565
|
+
* if (hash) console.log('approve tx', hash);
|
|
566
|
+
* // hash === undefined means the on-chain allowance already covers the
|
|
567
|
+
* // amount, or the deposit asset is native (msg.value, no allowance).
|
|
568
|
+
* ```
|
|
569
|
+
*/
|
|
570
|
+
async function vaultApprove(signer, options) {
|
|
571
|
+
const result = await approveCore(signer, options);
|
|
572
|
+
return result.kind === 'sent' ? result.hash : undefined;
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Same approval logic as {@link vaultApprove} but returns a discriminated
|
|
576
|
+
* union so callers can tell `sent` apart from `sufficient` and `native`
|
|
577
|
+
* without re-reading on-chain state. Preferred shape for new integrations.
|
|
578
|
+
*
|
|
579
|
+
* @example
|
|
580
|
+
* ```ts
|
|
581
|
+
* const r = await augustSdk.evm.approve({
|
|
582
|
+
* target: vaultAddress,
|
|
583
|
+
* wallet: walletAddress,
|
|
584
|
+
* amount: '100',
|
|
585
|
+
* wait: true,
|
|
586
|
+
* });
|
|
587
|
+
* switch (r.kind) {
|
|
588
|
+
* case 'sent':
|
|
589
|
+
* showToast('Approval submitted', r.hash);
|
|
590
|
+
* break;
|
|
591
|
+
* case 'sufficient':
|
|
592
|
+
* // r.existing is the existing on-chain allowance (raw bigint)
|
|
593
|
+
* break;
|
|
594
|
+
* case 'native':
|
|
595
|
+
* // No allowance needed; native token will travel as msg.value
|
|
596
|
+
* break;
|
|
597
|
+
* }
|
|
598
|
+
* ```
|
|
599
|
+
*
|
|
600
|
+
* @throws AugustValidationError on invalid wallet/target/amount.
|
|
601
|
+
* @throws AugustSDKError when the approval submission fails.
|
|
602
|
+
*/
|
|
603
|
+
async function approve(signer, options) {
|
|
604
|
+
return approveCore(signer, options);
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Deposit underlying token into the specified vault. Auto-routes to the
|
|
608
|
+
* correct contract path (single-asset ERC-4626, EVM-2 multi-asset,
|
|
609
|
+
* Paraswap adapter, native-token wrapper, depositWithPermit) based on the
|
|
610
|
+
* vault version and `depositAsset`.
|
|
611
|
+
*
|
|
612
|
+
* Amounts are encoded against the **deposit token's** decimals, not the
|
|
613
|
+
* vault's share decimals. The SDK reads the appropriate decimals via the
|
|
614
|
+
* vault metadata and the asset's ERC-20.
|
|
615
|
+
*
|
|
616
|
+
* Approval is handled automatically: if the existing allowance doesn't
|
|
617
|
+
* cover `amount`, an `approve` is sent and **always waited for** (regardless
|
|
618
|
+
* of the caller's `wait` flag) so the deposit tx can't be re-ordered ahead
|
|
619
|
+
* of the approval on Monad-style RPCs.
|
|
620
|
+
*
|
|
621
|
+
* @param signer ethers Signer / Wallet (or viem WalletClient via {@link setSigner})
|
|
622
|
+
* @param options Vault address, wallet, amount, optional `depositAsset` for
|
|
623
|
+
* adapter routes, optional `wait` to wait for the deposit receipt.
|
|
624
|
+
* @returns Deposit transaction hash.
|
|
625
|
+
* @throws AugustValidationError on invalid wallet/target/amount or missing
|
|
626
|
+
* adapter config when `depositAsset` differs from the vault underlying.
|
|
627
|
+
* @throws AugustSDKError when the deposit submission or on-chain call fails.
|
|
628
|
+
*
|
|
629
|
+
* @example
|
|
630
|
+
* ```ts
|
|
631
|
+
* const hash = await augustSdk.evm.vaultDeposit({
|
|
632
|
+
* target: '0xE9B725010A9E419412ed67d0fA5f3A5f40159D32', // vault
|
|
633
|
+
* wallet: walletAddress,
|
|
634
|
+
* amount: '100', // 100 USDC (UI units)
|
|
635
|
+
* wait: true, // wait for the deposit receipt
|
|
636
|
+
* });
|
|
637
|
+
* ```
|
|
638
|
+
*
|
|
639
|
+
* @example Adapter deposit (different deposit asset than the vault's underlying)
|
|
640
|
+
* ```ts
|
|
641
|
+
* await augustSdk.evm.vaultDeposit({
|
|
642
|
+
* target: vaultAddress,
|
|
643
|
+
* wallet: walletAddress,
|
|
644
|
+
* amount: '0.5',
|
|
645
|
+
* depositAsset: WETH_ADDRESS, // swap WETH -> vault underlying via the adapter
|
|
646
|
+
* chainId: 1,
|
|
647
|
+
* });
|
|
648
|
+
* ```
|
|
649
|
+
*/
|
|
650
|
+
async function vaultDeposit(signer, options) {
|
|
651
|
+
const { wallet, target, wait, amount, depositAsset, chainId, poolName } = options;
|
|
652
|
+
const [goodWallet, goodPool] = [
|
|
653
|
+
(0, core_1.checkAddress)(wallet, console, 'wallet'),
|
|
654
|
+
(0, core_1.checkAddress)(target, console),
|
|
655
|
+
];
|
|
656
|
+
if (!goodWallet || !goodPool) {
|
|
657
|
+
throw new core_1.AugustValidationError('INVALID_ADDRESS', `vaultDeposit: invalid ${!goodWallet ? 'wallet' : 'target'} address`);
|
|
658
|
+
}
|
|
659
|
+
if (amount === undefined || amount === null) {
|
|
660
|
+
throw new core_1.AugustValidationError('INVALID_INPUT', 'vaultDeposit: amount is required');
|
|
661
|
+
}
|
|
662
|
+
validateAmountPrecision(amount);
|
|
663
|
+
// Hoisted out of the try so the catch can tell "the deposit was broadcast and
|
|
664
|
+
// we lost the confirmation" from "the deposit never left". Any inner approval
|
|
665
|
+
// tx is deliberately NOT tracked here — see localTxBroadcastContext.
|
|
666
|
+
let depositTx;
|
|
667
|
+
try {
|
|
668
|
+
const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(target))?.[0];
|
|
669
|
+
const vaultVersion = (0, core_1.getVaultVersionV2)(tokenizedVault);
|
|
670
|
+
const adapterConfig = (0, core_1.getVaultAdapterConfig)(target);
|
|
671
|
+
let poolContract;
|
|
672
|
+
let underlyingAsset;
|
|
673
|
+
let vaultDecimals;
|
|
674
|
+
if (vaultVersion === 'evm-2') {
|
|
675
|
+
poolContract = (0, core_1.createContract)({
|
|
676
|
+
address: target,
|
|
677
|
+
provider: signer,
|
|
678
|
+
abi: options?.isDepositWithPermit
|
|
679
|
+
? abis_1.ABI_TOKENIZED_VAULT_V2_DEPOSIT_WITH_PERMIT
|
|
680
|
+
: abis_1.ABI_TOKENIZED_VAULT_V2,
|
|
681
|
+
});
|
|
682
|
+
const [asset, receiptTokenAddr] = await Promise.all([
|
|
683
|
+
poolContract.asset(),
|
|
684
|
+
// Retried on transport blips only; a misroute still throws. See
|
|
685
|
+
// getReceiptTokenAddressOrThrow.
|
|
686
|
+
(0, core_1.getReceiptTokenAddressOrThrow)(signer, target, 'vaultDeposit:receiptToken'),
|
|
687
|
+
]);
|
|
688
|
+
underlyingAsset = asset;
|
|
689
|
+
vaultDecimals = await (0, core_1.getDecimalsOrThrow)(signer, receiptTokenAddr, 'vaultDeposit:receiptDecimals');
|
|
690
|
+
}
|
|
691
|
+
else {
|
|
692
|
+
poolContract = (0, core_1.createContract)({
|
|
693
|
+
address: target,
|
|
694
|
+
provider: signer,
|
|
695
|
+
abi: abis_1.ABI_LENDING_POOLS,
|
|
696
|
+
});
|
|
697
|
+
const [fetchedUnderlyingAsset, rawDecimals] = await Promise.all([
|
|
698
|
+
poolContract.asset(),
|
|
699
|
+
(0, core_1.getDecimalsOrThrow)(signer, target, 'vaultDeposit:poolDecimals'),
|
|
700
|
+
]);
|
|
701
|
+
vaultDecimals = rawDecimals;
|
|
702
|
+
underlyingAsset = fetchedUnderlyingAsset;
|
|
703
|
+
}
|
|
704
|
+
const actualDepositAsset = depositAsset || underlyingAsset;
|
|
705
|
+
const isNativeToken = actualDepositAsset === ethers_1.ZeroAddress ||
|
|
706
|
+
actualDepositAsset === '0x0000000000000000000000000000000000000000';
|
|
707
|
+
const isAdapterDeposit = actualDepositAsset.toLowerCase() !== underlyingAsset.toLowerCase();
|
|
708
|
+
const isMultiAssetVault = vaultVersion === 'evm-2';
|
|
709
|
+
// vaultDeposit is the NATIVE path only — it never routes through the
|
|
710
|
+
// SwapRouter. Any-token swap deposits are the explicit, opt-in job of
|
|
711
|
+
// {@link swapRouterDeposit}; keeping them out of here is what stops a
|
|
712
|
+
// natively-accepted asset from being silently swapped.
|
|
713
|
+
if (isAdapterDeposit && !isMultiAssetVault && !adapterConfig) {
|
|
714
|
+
throw new core_1.AugustValidationError('INVALID_INPUT', `vaultDeposit: depositAsset ${actualDepositAsset} differs from vault underlying ${underlyingAsset} but no adapter is configured for vault ${target}. For an any-token deposit into a SwapRouter-eligible vault, use swapRouterDeposit instead.`);
|
|
715
|
+
}
|
|
716
|
+
const depositTokenDecimals = await resolveDepositTokenDecimals({
|
|
717
|
+
actualDepositAsset,
|
|
718
|
+
underlyingAsset,
|
|
719
|
+
isNativeToken,
|
|
720
|
+
isMultiAssetVault,
|
|
721
|
+
vaultDecimals,
|
|
722
|
+
readErc20Decimals: async (addr) => (0, core_1.getDecimalsOrThrow)(signer, addr, 'vaultDeposit:depositTokenDecimals'),
|
|
723
|
+
});
|
|
724
|
+
const normalizedAmt = (0, core_1.toNormalizedBn)(amount, depositTokenDecimals);
|
|
725
|
+
if (!isNativeToken) {
|
|
726
|
+
const spenderAddress = resolveSpender({
|
|
727
|
+
target,
|
|
728
|
+
isMultiAssetVault,
|
|
729
|
+
isAdapterDeposit,
|
|
730
|
+
adapterWrapperAddress: adapterConfig?.wrapperAddress,
|
|
731
|
+
});
|
|
732
|
+
const depositTokenContract = (0, core_1.createContract)({
|
|
733
|
+
address: actualDepositAsset,
|
|
734
|
+
provider: signer,
|
|
735
|
+
abi: abis_1.ABI_ERC20,
|
|
736
|
+
});
|
|
737
|
+
const currentAllowance = await depositTokenContract.allowance(wallet, spenderAddress);
|
|
738
|
+
if (safeBigInt(currentAllowance, 'vaultDeposit:allowance') <
|
|
739
|
+
BigInt(normalizedAmt.raw)) {
|
|
740
|
+
const { hash: approveHash } = await safeSendTx(() => depositTokenContract
|
|
741
|
+
.connect(signer)
|
|
742
|
+
.approve(spenderAddress, normalizedAmt.raw), signer, true);
|
|
743
|
+
core_1.Logger.log.info('approve:tx_hash', approveHash);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
// Route to appropriate deposit method
|
|
747
|
+
if (isMultiAssetVault) {
|
|
748
|
+
if (options?.isDepositWithPermit) {
|
|
749
|
+
// depositWithPermit flow
|
|
750
|
+
// build structure hash of permit off chain
|
|
751
|
+
const deadline = (0, utils_1.createDeadline)();
|
|
752
|
+
//sign the hash with private key of depositor
|
|
753
|
+
const { v, r, s } = await (0, utils_1.generatePermitSignature)(actualDepositAsset, wallet, await poolContract.getAddress(), normalizedAmt.raw, deadline, signer, tokenizedVault.chain);
|
|
754
|
+
//actually call depositWithPermit func
|
|
755
|
+
const { hash: permitHash } = await safeSendTx(() => poolContract
|
|
756
|
+
.connect(signer)
|
|
757
|
+
.depositWithPermit(actualDepositAsset, normalizedAmt.raw, wallet, deadline, r, s, v), signer, wait);
|
|
758
|
+
core_1.Logger.log.info('deposit:tx_hash', permitHash);
|
|
759
|
+
return permitHash;
|
|
760
|
+
}
|
|
761
|
+
// Multi-asset vault deposit (EVM-2)
|
|
762
|
+
depositTx = await poolContract
|
|
763
|
+
.connect(signer)
|
|
764
|
+
.deposit(actualDepositAsset, normalizedAmt.raw, wallet);
|
|
765
|
+
}
|
|
766
|
+
else if (isAdapterDeposit && adapterConfig) {
|
|
767
|
+
// Adapter deposit (swap or native wrapper)
|
|
768
|
+
if (adapterConfig.bridgeId === 2) {
|
|
769
|
+
// Paraswap bridge - build swap and deposit transaction
|
|
770
|
+
const adapterContract = await (0, adapter_helpers_1.buildSwapAndDepositTransaction)({
|
|
771
|
+
signer,
|
|
772
|
+
srcToken: actualDepositAsset,
|
|
773
|
+
srcDecimals: depositTokenDecimals,
|
|
774
|
+
destToken: underlyingAsset,
|
|
775
|
+
destDecimals: vaultDecimals,
|
|
776
|
+
amount: normalizedAmt.raw.toString(),
|
|
777
|
+
network: chainId?.toString() || '1',
|
|
778
|
+
adapterConfig,
|
|
779
|
+
userAddress: wallet,
|
|
780
|
+
});
|
|
781
|
+
const swapAndDepositParams = {
|
|
782
|
+
amountIn: normalizedAmt.raw,
|
|
783
|
+
minAmountOut: 0,
|
|
784
|
+
srcToken: actualDepositAsset,
|
|
785
|
+
dstToken: underlyingAsset,
|
|
786
|
+
bridgeId: adapterConfig.bridgeId,
|
|
787
|
+
quoteData: '0x', // This will be populated by buildSwapAndDepositTransaction
|
|
788
|
+
};
|
|
789
|
+
depositTx = await adapterContract
|
|
790
|
+
.connect(signer)
|
|
791
|
+
.swapAndDeposit(swapAndDepositParams);
|
|
792
|
+
}
|
|
793
|
+
else {
|
|
794
|
+
// Native/wrapped token deposit via wrapper contract
|
|
795
|
+
const nativeDepositConfig = await (0, adapter_helpers_1.buildNativeDepositTransaction)({
|
|
796
|
+
signer,
|
|
797
|
+
srcToken: actualDepositAsset,
|
|
798
|
+
amount: normalizedAmt.raw.toString(),
|
|
799
|
+
poolName,
|
|
800
|
+
adapterConfig,
|
|
801
|
+
userAddress: wallet,
|
|
802
|
+
});
|
|
803
|
+
depositTx = await nativeDepositConfig.contract
|
|
804
|
+
.connect(signer)[nativeDepositConfig.functionName](...nativeDepositConfig.args, nativeDepositConfig.value
|
|
805
|
+
? { value: nativeDepositConfig.value }
|
|
806
|
+
: {});
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
else {
|
|
810
|
+
// Standard deposit
|
|
811
|
+
if (isNativeToken) {
|
|
812
|
+
// Native ETH/AVAX deposit
|
|
813
|
+
depositTx = await poolContract
|
|
814
|
+
.connect(signer)
|
|
815
|
+
.deposit(normalizedAmt.raw, wallet, { value: normalizedAmt.raw });
|
|
816
|
+
}
|
|
817
|
+
else {
|
|
818
|
+
// Standard ERC20 deposit
|
|
819
|
+
depositTx = await poolContract
|
|
820
|
+
.connect(signer)
|
|
821
|
+
.deposit(normalizedAmt.raw, wallet);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
if (wait)
|
|
825
|
+
await safeWaitForTx(depositTx);
|
|
826
|
+
core_1.Logger.log.info('deposit:tx_hash', depositTx?.hash);
|
|
827
|
+
return depositTx?.hash;
|
|
828
|
+
}
|
|
829
|
+
catch (e) {
|
|
830
|
+
const recovered = await tryRecoverTxHash(e, signer, wait);
|
|
831
|
+
if (recovered) {
|
|
832
|
+
core_1.Logger.log.warn('deposit:nonce-fallback', { hash: recovered });
|
|
833
|
+
return recovered;
|
|
834
|
+
}
|
|
835
|
+
if (e instanceof core_1.AugustSDKError)
|
|
836
|
+
throw e;
|
|
837
|
+
// depositTx is only ever unset here when the depositWithPermit sub-path
|
|
838
|
+
// threw — that branch sends its single main tx through safeSendTx and
|
|
839
|
+
// never assigns depositTx, so the error's own markBroadcast marker is the
|
|
840
|
+
// only place the broadcast hash survives. Every other sub-path assigns
|
|
841
|
+
// depositTx before this catch can run.
|
|
842
|
+
const broadcast = depositTx
|
|
843
|
+
? localTxBroadcastContext(depositTx, e)
|
|
844
|
+
: errorTxBroadcastContext(e);
|
|
845
|
+
// User-cancelled deposits are normal; only genuine failures stay at error.
|
|
846
|
+
const insufficientFunds = (0, core_1.isInsufficientFundsError)(e);
|
|
847
|
+
(0, core_1.logChainError)('deposit', e, (0, core_1.isUserRejectionError)(e) || insufficientFunds, {
|
|
848
|
+
target,
|
|
849
|
+
amount,
|
|
850
|
+
depositAsset,
|
|
851
|
+
...broadcast,
|
|
852
|
+
});
|
|
853
|
+
throwIfInsufficientFunds(insufficientFunds, 'Deposit', e, {
|
|
854
|
+
target,
|
|
855
|
+
amount,
|
|
856
|
+
depositAsset,
|
|
857
|
+
...broadcast,
|
|
858
|
+
});
|
|
859
|
+
throw new core_1.AugustSDKError('UNKNOWN', `Deposit failed: ${e instanceof Error ? e.message : 'Unknown error'}`, { cause: e, context: { target, amount, depositAsset, ...broadcast } });
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Request to redeem vault shares. For EVM-1 vaults this queues a standard
|
|
864
|
+
* redemption (claimable later via `vaultRedeem`); for EVM-2 vaults this
|
|
865
|
+
* also handles the receipt-token allowance automatically. Pass
|
|
866
|
+
* `isInstantRedeem: true` for vaults that support instant settlement.
|
|
867
|
+
*
|
|
868
|
+
* Approval of the receipt token (EVM-2) is always waited on before the
|
|
869
|
+
* redeem call, regardless of the caller's `wait` flag.
|
|
870
|
+
*
|
|
871
|
+
* @param signer ethers Signer / Wallet (or viem WalletClient via {@link setSigner})
|
|
872
|
+
* @param options Vault address, wallet, amount, optional `isInstantRedeem`,
|
|
873
|
+
* optional `wait` to wait for the redeem receipt.
|
|
874
|
+
* @returns Redeem transaction hash.
|
|
875
|
+
* @throws AugustValidationError on invalid wallet/target/amount.
|
|
876
|
+
* @throws AugustSDKError when the redeem submission or on-chain call fails.
|
|
877
|
+
*
|
|
878
|
+
* @example
|
|
879
|
+
* ```ts
|
|
880
|
+
* const hash = await augustSdk.evm.vaultRequestRedeem({
|
|
881
|
+
* target: vaultAddress,
|
|
882
|
+
* wallet: walletAddress,
|
|
883
|
+
* amount: '10', // 10 shares (UI units)
|
|
884
|
+
* wait: true,
|
|
885
|
+
* });
|
|
886
|
+
* ```
|
|
887
|
+
*
|
|
888
|
+
* @example Instant redemption
|
|
889
|
+
* ```ts
|
|
890
|
+
* await augustSdk.evm.vaultRequestRedeem({
|
|
891
|
+
* target: vaultAddress,
|
|
892
|
+
* wallet: walletAddress,
|
|
893
|
+
* amount: '10',
|
|
894
|
+
* isInstantRedeem: true,
|
|
895
|
+
* });
|
|
896
|
+
* ```
|
|
897
|
+
*/
|
|
898
|
+
async function vaultRequestRedeem(signer, options) {
|
|
899
|
+
const { wallet, target, wait, amount } = options;
|
|
900
|
+
const [goodWallet, goodPool] = [
|
|
901
|
+
(0, core_1.checkAddress)(wallet, console, 'wallet'),
|
|
902
|
+
(0, core_1.checkAddress)(target, console),
|
|
903
|
+
];
|
|
904
|
+
if (!goodWallet || !goodPool) {
|
|
905
|
+
throw new core_1.AugustValidationError('INVALID_ADDRESS', `vaultRequestRedeem: invalid ${!goodWallet ? 'wallet' : 'target'} address`);
|
|
906
|
+
}
|
|
907
|
+
if (amount === undefined || amount === null) {
|
|
908
|
+
throw new core_1.AugustValidationError('INVALID_INPUT', 'vaultRequestRedeem: amount is required');
|
|
909
|
+
}
|
|
910
|
+
validateAmountPrecision(amount);
|
|
911
|
+
// Hoisted out of the try so the catch can tell "the redeem was broadcast and
|
|
912
|
+
// we lost the confirmation" from "the redeem never left". The EVM-2 receipt
|
|
913
|
+
// token approval sent earlier in the try is deliberately NOT tracked here:
|
|
914
|
+
// a failed approval means the redeem itself was never sent.
|
|
915
|
+
let requestRedeemTx;
|
|
916
|
+
try {
|
|
917
|
+
// Get vault version to determine correct ABI
|
|
918
|
+
const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(target))?.[0];
|
|
919
|
+
const vaultVersion = (0, core_1.getVaultVersionV2)(tokenizedVault);
|
|
920
|
+
let poolContract;
|
|
921
|
+
let decimals;
|
|
922
|
+
// Handle different vault versions
|
|
923
|
+
if (vaultVersion === 'evm-2') {
|
|
924
|
+
// For evm-2, always use ABI_TOKENIZED_VAULT_V2 to get lpTokenAddress
|
|
925
|
+
// even for instant redeem, we need lpTokenAddress for receipt token decimals
|
|
926
|
+
const vaultContract = (0, core_1.createContract)({
|
|
927
|
+
address: target,
|
|
928
|
+
provider: signer,
|
|
929
|
+
abi: abis_1.ABI_TOKENIZED_VAULT_V2,
|
|
930
|
+
});
|
|
931
|
+
// Get receipt token for decimals. Retried on transport blips only; a
|
|
932
|
+
// misroute (a non-evm-2 vault reaching this branch) still throws the
|
|
933
|
+
// original error. See getReceiptTokenAddressOrThrow.
|
|
934
|
+
const receiptTokenAddr = await (0, core_1.getReceiptTokenAddressOrThrow)(signer, target, 'vaultRequestRedeem:receiptToken');
|
|
935
|
+
const receiptContract = (0, core_1.createContract)({
|
|
936
|
+
address: receiptTokenAddr,
|
|
937
|
+
provider: signer,
|
|
938
|
+
abi: abis_1.ABI_ERC20,
|
|
939
|
+
});
|
|
940
|
+
decimals = await (0, core_1.getDecimalsOrThrow)(signer, receiptTokenAddr, 'vaultRequestRedeem:receiptDecimals');
|
|
941
|
+
// Convert amount to bignumber
|
|
942
|
+
const normalizedAmt = (0, core_1.toNormalizedBn)(amount, decimals);
|
|
943
|
+
// EVM-2 lp/receipt token is a separate ERC-20: vault.transferFrom(lp)
|
|
944
|
+
// needs explicit allowance even on self-redeem (msg.sender on the lp
|
|
945
|
+
// token is the vault, not the user — no ERC-4626 owner-shortcut).
|
|
946
|
+
const allowance = await receiptContract.allowance(wallet, target);
|
|
947
|
+
if (safeBigInt(allowance, 'vaultRequestRedeem:allowance') <
|
|
948
|
+
BigInt(normalizedAmt.raw)) {
|
|
949
|
+
const { hash: approveHash } = await safeSendTx(() => receiptContract.connect(signer).approve(target, normalizedAmt.raw), signer, true);
|
|
950
|
+
core_1.Logger.log.info('approve:receipt_token:tx_hash', approveHash);
|
|
951
|
+
}
|
|
952
|
+
// For evm-2, use vaultContract for both instant and standard redeem
|
|
953
|
+
// ABI_TOKENIZED_VAULT_V2 has both instantRedeem (2 params) and requestRedeem
|
|
954
|
+
poolContract = vaultContract;
|
|
955
|
+
}
|
|
956
|
+
else {
|
|
957
|
+
poolContract = options.isInstantRedeem
|
|
958
|
+
? (0, core_1.createContract)({
|
|
959
|
+
address: target,
|
|
960
|
+
provider: signer,
|
|
961
|
+
abi: abis_1.ABI_LENDING_POOL_V3,
|
|
962
|
+
})
|
|
963
|
+
: (0, core_1.createContract)({
|
|
964
|
+
address: target,
|
|
965
|
+
provider: signer,
|
|
966
|
+
abi: abis_1.ABI_LENDING_POOLS,
|
|
967
|
+
});
|
|
968
|
+
decimals = await (0, core_1.getDecimalsOrThrow)(signer, target, 'vaultRequestRedeem:poolDecimals');
|
|
969
|
+
}
|
|
970
|
+
// Convert amount to bignumber (for non-evm-2 vaults)
|
|
971
|
+
const normalizedAmt = (0, core_1.toNormalizedBn)(amount, decimals);
|
|
972
|
+
// Withdraw from vault - EVM-2 uses 2 params, others use 3 params
|
|
973
|
+
if (options.isInstantRedeem) {
|
|
974
|
+
if (vaultVersion === 'evm-2') {
|
|
975
|
+
// EVM-2: instantRedeem(uint256 shares, address receiverAddr) - 2 params
|
|
976
|
+
requestRedeemTx = await poolContract
|
|
977
|
+
.connect(signer)
|
|
978
|
+
.instantRedeem(BigInt(normalizedAmt.raw), wallet);
|
|
979
|
+
}
|
|
980
|
+
else {
|
|
981
|
+
// EVM-0/EVM-1: instantRedeem(uint256 shares, address receiverAddr, address holderAddr) - 3 params
|
|
982
|
+
requestRedeemTx = await poolContract
|
|
983
|
+
.connect(signer)
|
|
984
|
+
.instantRedeem(BigInt(normalizedAmt.raw), wallet, wallet);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
else if (vaultVersion === 'evm-2') {
|
|
988
|
+
// EVM-2: requestRedeem(uint256 shares, address receiverAddr)
|
|
989
|
+
requestRedeemTx = await poolContract
|
|
990
|
+
.connect(signer)
|
|
991
|
+
.requestRedeem(BigInt(normalizedAmt.raw), wallet);
|
|
992
|
+
}
|
|
993
|
+
else {
|
|
994
|
+
// EVM-0/EVM-1: requestRedeem(uint256 shares, address receiver, address owner)
|
|
995
|
+
requestRedeemTx = await poolContract
|
|
996
|
+
.connect(signer)
|
|
997
|
+
.requestRedeem(BigInt(normalizedAmt.raw), wallet, wallet);
|
|
998
|
+
}
|
|
999
|
+
if (wait)
|
|
1000
|
+
await safeWaitForTx(requestRedeemTx);
|
|
1001
|
+
core_1.Logger.log.info('requestRedeem:tx_hash', requestRedeemTx?.hash);
|
|
1002
|
+
return requestRedeemTx?.hash;
|
|
1003
|
+
}
|
|
1004
|
+
catch (e) {
|
|
1005
|
+
const recovered = await tryRecoverTxHash(e, signer, wait);
|
|
1006
|
+
if (recovered) {
|
|
1007
|
+
core_1.Logger.log.warn('requestRedeem:nonce-fallback', { hash: recovered });
|
|
1008
|
+
return recovered;
|
|
1009
|
+
}
|
|
1010
|
+
if (e instanceof core_1.AugustSDKError)
|
|
1011
|
+
throw e;
|
|
1012
|
+
const broadcast = localTxBroadcastContext(requestRedeemTx, e);
|
|
1013
|
+
// User-cancelled redeem requests are normal; only genuine failures stay at error.
|
|
1014
|
+
const insufficientFunds = (0, core_1.isInsufficientFundsError)(e);
|
|
1015
|
+
(0, core_1.logChainError)('requestRedeem', e, (0, core_1.isUserRejectionError)(e) || insufficientFunds, {
|
|
1016
|
+
target,
|
|
1017
|
+
amount,
|
|
1018
|
+
...broadcast,
|
|
1019
|
+
});
|
|
1020
|
+
throwIfInsufficientFunds(insufficientFunds, 'Request redeem', e, {
|
|
1021
|
+
target,
|
|
1022
|
+
amount,
|
|
1023
|
+
...broadcast,
|
|
1024
|
+
});
|
|
1025
|
+
throw new core_1.AugustSDKError('UNKNOWN', `Request redeem failed: ${e instanceof Error ? e.message : 'Unknown error'}`, { cause: e, context: { target, amount, ...broadcast } });
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* @TODO
|
|
1030
|
+
* @description withdraw funds from the specified pool including accrued rewards
|
|
1031
|
+
* @param signer signer / wallet object
|
|
1032
|
+
* @param options object including pool contract address, user wallet address, and more
|
|
1033
|
+
* @returns claim tx hash
|
|
1034
|
+
*/
|
|
1035
|
+
async function vaultRedeem(signer, options) {
|
|
1036
|
+
const { wallet, target, wait, year, day, month, receiverIndex } = options;
|
|
1037
|
+
const [goodWallet, goodPool] = [
|
|
1038
|
+
(0, core_1.checkAddress)(wallet, console, 'wallet'),
|
|
1039
|
+
(0, core_1.checkAddress)(target, console),
|
|
1040
|
+
];
|
|
1041
|
+
if (!goodWallet || !goodPool) {
|
|
1042
|
+
throw new core_1.AugustValidationError('INVALID_ADDRESS', `vaultRedeem: invalid ${!goodWallet ? 'wallet' : 'target'} address`);
|
|
1043
|
+
}
|
|
1044
|
+
if (!year || !month || !day || !receiverIndex) {
|
|
1045
|
+
throw new core_1.AugustValidationError('INVALID_INPUT', 'vaultRedeem: year, month, day, and receiverIndex are required');
|
|
1046
|
+
}
|
|
1047
|
+
try {
|
|
1048
|
+
const poolContract = (0, core_1.createContract)({
|
|
1049
|
+
address: target,
|
|
1050
|
+
provider: signer,
|
|
1051
|
+
abi: abis_1.ABI_LENDING_POOLS,
|
|
1052
|
+
});
|
|
1053
|
+
const { hash: redeemHash } = await safeSendTx(() => poolContract
|
|
1054
|
+
.connect(signer)
|
|
1055
|
+
.redeem(year, month, day, receiverIndex, wallet), signer, wait);
|
|
1056
|
+
core_1.Logger.log.info('redeem:tx_hash', redeemHash);
|
|
1057
|
+
return redeemHash;
|
|
1058
|
+
}
|
|
1059
|
+
catch (e) {
|
|
1060
|
+
const recovered = await tryRecoverTxHash(e, signer, wait);
|
|
1061
|
+
if (recovered) {
|
|
1062
|
+
core_1.Logger.log.warn('redeem:nonce-fallback', { hash: recovered });
|
|
1063
|
+
return recovered;
|
|
1064
|
+
}
|
|
1065
|
+
if (e instanceof core_1.AugustSDKError)
|
|
1066
|
+
throw e;
|
|
1067
|
+
// Single tx on this path, so the error's own marker can only be the redeem.
|
|
1068
|
+
const broadcast = errorTxBroadcastContext(e);
|
|
1069
|
+
// User-cancelled redeems are normal; only genuine failures stay at error.
|
|
1070
|
+
const insufficientFunds = (0, core_1.isInsufficientFundsError)(e);
|
|
1071
|
+
(0, core_1.logChainError)('redeem', e, (0, core_1.isUserRejectionError)(e) || insufficientFunds, {
|
|
1072
|
+
target,
|
|
1073
|
+
year,
|
|
1074
|
+
month,
|
|
1075
|
+
day,
|
|
1076
|
+
receiverIndex,
|
|
1077
|
+
...broadcast,
|
|
1078
|
+
});
|
|
1079
|
+
throwIfInsufficientFunds(insufficientFunds, 'Redeem', e, {
|
|
1080
|
+
target,
|
|
1081
|
+
year,
|
|
1082
|
+
month,
|
|
1083
|
+
day,
|
|
1084
|
+
receiverIndex,
|
|
1085
|
+
...broadcast,
|
|
1086
|
+
});
|
|
1087
|
+
throw new core_1.AugustSDKError('UNKNOWN', `Redeem failed: ${e instanceof Error ? e.message : 'Unknown error'}`, {
|
|
1088
|
+
cause: e,
|
|
1089
|
+
context: { target, year, month, day, receiverIndex, ...broadcast },
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Deposit a native token (ETH / AVAX / etc.) into a vault via the
|
|
1095
|
+
* `MultiAssetNativeDepositWrapper`. The wrapper forwards `msg.value` to
|
|
1096
|
+
* the vault as the deposit underlying — no allowance is required.
|
|
1097
|
+
*
|
|
1098
|
+
* `amount` is the **raw on-chain value in wei-equivalent** (18 decimals
|
|
1099
|
+
* for ETH-family natives). If you have a UI-shaped amount in ether, scale
|
|
1100
|
+
* it first.
|
|
1101
|
+
*
|
|
1102
|
+
* @param signer ethers Signer / Wallet
|
|
1103
|
+
* @param options `wrapperAddress` is the deployed wrapper contract;
|
|
1104
|
+
* `receiver` defaults to the signer's address; `amount` is the native
|
|
1105
|
+
* amount in wei; `wait` waits for the deposit receipt.
|
|
1106
|
+
* @returns Deposit transaction hash.
|
|
1107
|
+
* @throws AugustValidationError on invalid wrapper / receiver / amount.
|
|
1108
|
+
* @throws AugustSDKError when the deposit submission or on-chain call fails.
|
|
1109
|
+
*
|
|
1110
|
+
* @example
|
|
1111
|
+
* ```ts
|
|
1112
|
+
* import { parseEther } from 'ethers';
|
|
1113
|
+
*
|
|
1114
|
+
* await augustSdk.evm.depositNative({
|
|
1115
|
+
* wrapperAddress: nativeWrapperAddress,
|
|
1116
|
+
* amount: parseEther('0.5'), // 0.5 ETH in wei
|
|
1117
|
+
* wait: true,
|
|
1118
|
+
* });
|
|
1119
|
+
* ```
|
|
1120
|
+
*/
|
|
1121
|
+
async function depositNative(signer, options) {
|
|
1122
|
+
const { wrapperAddress, receiver, amount, wait } = options;
|
|
1123
|
+
const goodWrapper = (0, core_1.checkAddress)(wrapperAddress, console, 'contract');
|
|
1124
|
+
if (!goodWrapper) {
|
|
1125
|
+
throw new core_1.AugustValidationError('INVALID_ADDRESS', 'depositNative: invalid wrapper address');
|
|
1126
|
+
}
|
|
1127
|
+
if (amount === undefined || amount === null) {
|
|
1128
|
+
throw new core_1.AugustValidationError('INVALID_INPUT', 'depositNative: amount is required');
|
|
1129
|
+
}
|
|
1130
|
+
validateAmountPrecision(amount);
|
|
1131
|
+
// Hoisted so the catch can distinguish "broadcast, confirmation lost" from
|
|
1132
|
+
// "never sent" — see localTxBroadcastContext.
|
|
1133
|
+
let depositTx;
|
|
1134
|
+
try {
|
|
1135
|
+
// Create wrapper contract instance
|
|
1136
|
+
const wrapperContract = (0, core_1.createContract)({
|
|
1137
|
+
address: wrapperAddress,
|
|
1138
|
+
provider: signer,
|
|
1139
|
+
abi: abis_1.ABI_MULTI_ASSET_NATIVE_DEPOSIT_WRAPPER,
|
|
1140
|
+
}); // TODO: provide typed interface
|
|
1141
|
+
// Convert amount to bigint (native tokens use 18 decimals typically)
|
|
1142
|
+
// For native deposits, amount should be in wei/smallest unit
|
|
1143
|
+
const amountBigInt = typeof amount === 'bigint'
|
|
1144
|
+
? amount
|
|
1145
|
+
: typeof amount === 'string'
|
|
1146
|
+
? BigInt(amount)
|
|
1147
|
+
: BigInt(amount);
|
|
1148
|
+
// Connect the contract first, then get the function to disambiguate between overloads
|
|
1149
|
+
const connectedContract = wrapperContract.connect(signer);
|
|
1150
|
+
// Call depositNative with or without receiver parameter
|
|
1151
|
+
if (receiver) {
|
|
1152
|
+
const goodReceiver = (0, core_1.checkAddress)(receiver, console, 'wallet');
|
|
1153
|
+
if (!goodReceiver) {
|
|
1154
|
+
throw new core_1.AugustValidationError('INVALID_ADDRESS', 'depositNative: invalid receiver address');
|
|
1155
|
+
}
|
|
1156
|
+
// depositNative(address receiver) - with receiver
|
|
1157
|
+
const depositNativeWithReceiver = connectedContract.getFunction('depositNative(address)');
|
|
1158
|
+
depositTx = await depositNativeWithReceiver(receiver, {
|
|
1159
|
+
value: amountBigInt,
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
else {
|
|
1163
|
+
// depositNative() - without receiver (uses msg.sender as receiver)
|
|
1164
|
+
const depositNativeNoParams = connectedContract.getFunction('depositNative()');
|
|
1165
|
+
depositTx = await depositNativeNoParams({
|
|
1166
|
+
value: amountBigInt,
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
if (wait)
|
|
1170
|
+
await safeWaitForTx(depositTx);
|
|
1171
|
+
core_1.Logger.log.info('depositNative:tx_hash', depositTx?.hash);
|
|
1172
|
+
return depositTx?.hash;
|
|
1173
|
+
}
|
|
1174
|
+
catch (e) {
|
|
1175
|
+
const recovered = await tryRecoverTxHash(e, signer, wait);
|
|
1176
|
+
if (recovered) {
|
|
1177
|
+
core_1.Logger.log.warn('depositNative:nonce-fallback', { hash: recovered });
|
|
1178
|
+
return recovered;
|
|
1179
|
+
}
|
|
1180
|
+
if (e instanceof core_1.AugustSDKError)
|
|
1181
|
+
throw e;
|
|
1182
|
+
const broadcast = localTxBroadcastContext(depositTx, e);
|
|
1183
|
+
// User-cancelled native deposits are normal; only genuine failures stay at error.
|
|
1184
|
+
const insufficientFunds = (0, core_1.isInsufficientFundsError)(e);
|
|
1185
|
+
(0, core_1.logChainError)('depositNative', e, (0, core_1.isUserRejectionError)(e) || insufficientFunds, {
|
|
1186
|
+
wrapperAddress,
|
|
1187
|
+
receiver,
|
|
1188
|
+
amount,
|
|
1189
|
+
...broadcast,
|
|
1190
|
+
});
|
|
1191
|
+
throwIfInsufficientFunds(insufficientFunds, 'Deposit native', e, {
|
|
1192
|
+
wrapperAddress,
|
|
1193
|
+
receiver,
|
|
1194
|
+
amount,
|
|
1195
|
+
...broadcast,
|
|
1196
|
+
});
|
|
1197
|
+
throw new core_1.AugustSDKError('UNKNOWN', `Deposit native failed: ${e instanceof Error ? e.message : 'Unknown error'}`, { cause: e, context: { wrapperAddress, receiver, amount, ...broadcast } });
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Redeem vault shares for an underlying asset via the RwaRedeemSubaccount.
|
|
1202
|
+
* Caller must approve the **vault share token** (not the output asset) to
|
|
1203
|
+
* the subaccount beforehand — it pulls shares via `transferFrom`.
|
|
1204
|
+
*
|
|
1205
|
+
* Pass `minOut` to enforce slippage protection (in `outputDecimals` units).
|
|
1206
|
+
* The SDK does not currently compute `minOut` from a slippage percentage —
|
|
1207
|
+
* the caller is responsible for choosing a safe lower bound.
|
|
1208
|
+
*
|
|
1209
|
+
* @throws AugustValidationError on invalid addresses or amount inputs.
|
|
1210
|
+
* @throws AugustSDKError when the redeem submission or on-chain call fails.
|
|
1211
|
+
*
|
|
1212
|
+
* @example
|
|
1213
|
+
* ```ts
|
|
1214
|
+
* // 1. Approve the vault share token to the subaccount first.
|
|
1215
|
+
* await augustSdk.evm.vaultApprove({
|
|
1216
|
+
* target: subaccountAddress,
|
|
1217
|
+
* wallet: walletAddress,
|
|
1218
|
+
* amount: '10',
|
|
1219
|
+
* wait: true,
|
|
1220
|
+
* });
|
|
1221
|
+
*
|
|
1222
|
+
* // 2. Redeem 10 shares for USDC, requiring at least 9.5 USDC out.
|
|
1223
|
+
* await augustSdk.evm.rwaRedeemAsset({
|
|
1224
|
+
* target: subaccountAddress,
|
|
1225
|
+
* wallet: walletAddress,
|
|
1226
|
+
* asset: USDC_ADDRESS,
|
|
1227
|
+
* amount: '10',
|
|
1228
|
+
* minOut: '9.5',
|
|
1229
|
+
* decimals: 18, // vault share decimals
|
|
1230
|
+
* outputDecimals: 6, // USDC decimals
|
|
1231
|
+
* wait: true,
|
|
1232
|
+
* });
|
|
1233
|
+
* ```
|
|
1234
|
+
*/
|
|
1235
|
+
async function rwaRedeemAsset(signer, options) {
|
|
1236
|
+
const { target, wallet, asset, amount, minOut, decimals, outputDecimals, wait, } = options;
|
|
1237
|
+
const goodTarget = (0, core_1.checkAddress)(target, console, 'contract');
|
|
1238
|
+
const goodAsset = (0, core_1.checkAddress)(asset, console, 'contract');
|
|
1239
|
+
const goodWallet = (0, core_1.checkAddress)(wallet, console, 'wallet');
|
|
1240
|
+
if (!goodTarget || !goodAsset || !goodWallet) {
|
|
1241
|
+
throw new core_1.AugustValidationError('INVALID_ADDRESS', `rwaRedeemAsset: invalid ${!goodTarget ? 'target' : !goodAsset ? 'asset' : 'wallet'} address`);
|
|
1242
|
+
}
|
|
1243
|
+
validateAmountPrecision(amount);
|
|
1244
|
+
validateAmountPrecision(minOut);
|
|
1245
|
+
try {
|
|
1246
|
+
const subaccountContract = (0, core_1.createContract)({
|
|
1247
|
+
address: target,
|
|
1248
|
+
provider: signer,
|
|
1249
|
+
abi: abis_1.RWA_REDEEM_SUBACCOUNT,
|
|
1250
|
+
});
|
|
1251
|
+
if (!subaccountContract) {
|
|
1252
|
+
throw new Error(`rwaRedeemAsset: failed to instantiate subaccount contract at ${target}`);
|
|
1253
|
+
}
|
|
1254
|
+
const normalizedAmt = (0, core_1.toNormalizedBn)(amount, decimals);
|
|
1255
|
+
const normalizedMinOut = (0, core_1.toNormalizedBn)(minOut, outputDecimals);
|
|
1256
|
+
const { hash: redeemHash } = await safeSendTx(() => subaccountContract
|
|
1257
|
+
.connect(signer)
|
|
1258
|
+
.redeemAsset(asset, BigInt(normalizedAmt.raw), BigInt(normalizedMinOut.raw)), signer, wait);
|
|
1259
|
+
core_1.Logger.log.info('rwaRedeemAsset:tx_hash', redeemHash);
|
|
1260
|
+
return redeemHash;
|
|
1261
|
+
}
|
|
1262
|
+
catch (e) {
|
|
1263
|
+
const recovered = await tryRecoverTxHash(e, signer, wait);
|
|
1264
|
+
if (recovered) {
|
|
1265
|
+
core_1.Logger.log.warn('rwaRedeemAsset:nonce-fallback', { hash: recovered });
|
|
1266
|
+
return recovered;
|
|
1267
|
+
}
|
|
1268
|
+
if (e instanceof core_1.AugustSDKError)
|
|
1269
|
+
throw e;
|
|
1270
|
+
// Single tx on this path, so the error's own marker can only be the redeem.
|
|
1271
|
+
const broadcast = errorTxBroadcastContext(e);
|
|
1272
|
+
// User-cancelled RWA redeems are normal; only genuine failures stay at error.
|
|
1273
|
+
const insufficientFunds = (0, core_1.isInsufficientFundsError)(e);
|
|
1274
|
+
(0, core_1.logChainError)('rwaRedeemAsset', e, (0, core_1.isUserRejectionError)(e) || insufficientFunds, {
|
|
1275
|
+
target,
|
|
1276
|
+
asset,
|
|
1277
|
+
amount,
|
|
1278
|
+
minOut,
|
|
1279
|
+
...broadcast,
|
|
1280
|
+
});
|
|
1281
|
+
throwIfInsufficientFunds(insufficientFunds, 'RWA redeem', e, {
|
|
1282
|
+
target,
|
|
1283
|
+
asset,
|
|
1284
|
+
amount,
|
|
1285
|
+
minOut,
|
|
1286
|
+
...broadcast,
|
|
1287
|
+
});
|
|
1288
|
+
throw new core_1.AugustSDKError('UNKNOWN', `RWA redeem failed: ${e instanceof Error ? e.message : 'Unknown error'}`, { cause: e, context: { target, asset, amount, minOut, ...broadcast } });
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
/** @internal */
|
|
1292
|
+
async function dispatchViaSwapRouter(args) {
|
|
1293
|
+
const amountRaw = BigInt(args.normalizedAmt.raw);
|
|
1294
|
+
const sharesReceiver = args.receiver ?? args.wallet;
|
|
1295
|
+
if (args.isNativeToken) {
|
|
1296
|
+
// SwapRouter native path requires underlying === wrapped native.
|
|
1297
|
+
const wrappedNative = core_1.SWAP_ROUTER_WRAPPED_NATIVE[args.chainId];
|
|
1298
|
+
if (!wrappedNative ||
|
|
1299
|
+
args.underlyingAsset.toLowerCase() !== wrappedNative.toLowerCase()) {
|
|
1300
|
+
throw new core_1.AugustValidationError('INVALID_INPUT', `vaultDeposit (SwapRouter): native deposit is only supported when the vault's reference asset is the chain's wrapped-native token. Vault ${args.target} underlying ${args.underlyingAsset} is not. Wrap to ${wrappedNative ?? 'WETH'} first and call again with depositAsset = the wrapped token.`, {
|
|
1301
|
+
context: {
|
|
1302
|
+
vault: args.target,
|
|
1303
|
+
underlying: args.underlyingAsset,
|
|
1304
|
+
chainId: args.chainId,
|
|
1305
|
+
wrappedNative,
|
|
1306
|
+
},
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1309
|
+
return depositNativeViaSwapRouter(args.signer, {
|
|
1310
|
+
chainId: args.chainId,
|
|
1311
|
+
vault: args.target,
|
|
1312
|
+
receiver: sharesReceiver,
|
|
1313
|
+
amount: amountRaw,
|
|
1314
|
+
wait: args.wait,
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
const isUnderlying = args.actualDepositAsset.toLowerCase() ===
|
|
1318
|
+
args.underlyingAsset.toLowerCase();
|
|
1319
|
+
if (isUnderlying) {
|
|
1320
|
+
return depositViaSwapRouter(args.signer, {
|
|
1321
|
+
chainId: args.chainId,
|
|
1322
|
+
vault: args.target,
|
|
1323
|
+
receiver: sharesReceiver,
|
|
1324
|
+
asset: args.actualDepositAsset,
|
|
1325
|
+
amount: amountRaw,
|
|
1326
|
+
wait: args.wait,
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
// Vault share decimals can diverge from the underlying asset decimals
|
|
1330
|
+
// (e.g. an 18-decimal share token over an 8-decimal WBTC underlying on
|
|
1331
|
+
// a multi-asset v2 vault). The swap quote must use the underlying's
|
|
1332
|
+
// on-chain decimals so Paraswap's /prices interprets the route correctly.
|
|
1333
|
+
const underlyingDecimals = await (0, core_1.getDecimalsOrThrow)(args.signer, args.underlyingAsset, 'dispatchViaSwapRouter:underlyingDecimals');
|
|
1334
|
+
// The on-chain SwapRouter only authorizes a specific (router, selector) pair
|
|
1335
|
+
// per chain (see SWAP_ROUTER_DEX_AGGREGATOR). Pin the aggregator to that
|
|
1336
|
+
// router's single generic method so the calldata selector is deterministic,
|
|
1337
|
+
// then reject any quote that would not match — otherwise the deposit reverts
|
|
1338
|
+
// on-chain with InvalidRouter() / InvalidNotWhitelisted() after the user has
|
|
1339
|
+
// already paid gas.
|
|
1340
|
+
const aggregator = core_1.SWAP_ROUTER_DEX_AGGREGATOR[args.chainId];
|
|
1341
|
+
if (!aggregator) {
|
|
1342
|
+
throw new core_1.AugustValidationError('INVALID_CHAIN', `vaultDeposit (SwapRouter): no DEX aggregator is configured for chainId ${args.chainId}, but a swap is required because the deposit asset (${args.actualDepositAsset}) differs from the vault's reference asset (${args.underlyingAsset}). Deposit the reference asset directly, or add a SWAP_ROUTER_DEX_AGGREGATOR entry once the router is whitelisted on-chain.`, {
|
|
1343
|
+
context: {
|
|
1344
|
+
chainId: args.chainId,
|
|
1345
|
+
depositAsset: args.actualDepositAsset,
|
|
1346
|
+
underlyingAsset: args.underlyingAsset,
|
|
1347
|
+
},
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
const quote = await (0, swap_quotes_1.fetchSwapQuote)({
|
|
1351
|
+
chainId: args.chainId,
|
|
1352
|
+
srcToken: args.actualDepositAsset,
|
|
1353
|
+
srcDecimals: args.depositTokenDecimals,
|
|
1354
|
+
destToken: args.underlyingAsset,
|
|
1355
|
+
destDecimals: underlyingDecimals,
|
|
1356
|
+
amount: amountRaw,
|
|
1357
|
+
receiver: args.routerAddress,
|
|
1358
|
+
slippageBps: args.slippageBps,
|
|
1359
|
+
contractMethod: aggregator.contractMethod,
|
|
1360
|
+
});
|
|
1361
|
+
// Fail closed on off-chain/on-chain config drift: if the aggregator's quote
|
|
1362
|
+
// targets a router or selector the deployed SwapRouter has not whitelisted,
|
|
1363
|
+
// the swapAndDeposit call would revert. Surfacing it here turns a wasted gas
|
|
1364
|
+
// burn into an actionable SDK error.
|
|
1365
|
+
const quotedSelector = quote.payload.slice(0, 10).toLowerCase();
|
|
1366
|
+
if (quote.router.toLowerCase() !== aggregator.router.toLowerCase() ||
|
|
1367
|
+
quotedSelector !== aggregator.selector.toLowerCase()) {
|
|
1368
|
+
throw new core_1.AugustValidationError('INVALID_INPUT', `vaultDeposit (SwapRouter): the aggregator quote (router ${quote.router}, selector ${quotedSelector}) does not match the router whitelisted on chain ${args.chainId} (router ${aggregator.router}, selector ${aggregator.selector}); the on-chain SwapRouter would revert. This signals drift between the SDK's SWAP_ROUTER_DEX_AGGREGATOR and the deployed router's enableRouter config.`, {
|
|
1369
|
+
context: {
|
|
1370
|
+
chainId: args.chainId,
|
|
1371
|
+
quotedRouter: quote.router,
|
|
1372
|
+
quotedSelector,
|
|
1373
|
+
expectedRouter: aggregator.router,
|
|
1374
|
+
expectedSelector: aggregator.selector,
|
|
1375
|
+
},
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
return swapAndDeposit(args.signer, {
|
|
1379
|
+
chainId: args.chainId,
|
|
1380
|
+
vault: args.target,
|
|
1381
|
+
receiver: sharesReceiver,
|
|
1382
|
+
swaps: [
|
|
1383
|
+
{
|
|
1384
|
+
tokenIn: args.actualDepositAsset,
|
|
1385
|
+
tokenOut: args.underlyingAsset,
|
|
1386
|
+
amountIn: amountRaw,
|
|
1387
|
+
minAmountOut: quote.minAmountOut,
|
|
1388
|
+
router: quote.router,
|
|
1389
|
+
payload: quote.payload,
|
|
1390
|
+
},
|
|
1391
|
+
],
|
|
1392
|
+
wait: args.wait,
|
|
1393
|
+
});
|
|
1394
|
+
}
|
|
1395
|
+
/** @internal */
|
|
1396
|
+
function resolveSwapRouterOrThrow(chainId) {
|
|
1397
|
+
const router = (0, core_1.getSwapRouterAddress)(chainId);
|
|
1398
|
+
if (!router) {
|
|
1399
|
+
throw new core_1.AugustValidationError('INVALID_CHAIN', `SwapRouter: no deployment registered for chainId ${chainId}`, { context: { chainId } });
|
|
1400
|
+
}
|
|
1401
|
+
return router;
|
|
1402
|
+
}
|
|
1403
|
+
/** @internal */
|
|
1404
|
+
async function ensureAllowance(signer, token, owner, spender, amount) {
|
|
1405
|
+
const tokenContract = (0, core_1.createContract)({
|
|
1406
|
+
address: token,
|
|
1407
|
+
provider: signer,
|
|
1408
|
+
abi: abis_1.ABI_ERC20,
|
|
1409
|
+
});
|
|
1410
|
+
const current = safeBigInt(await tokenContract.allowance(owner, spender), 'swapRouter:allowance');
|
|
1411
|
+
if (current >= amount)
|
|
1412
|
+
return;
|
|
1413
|
+
const { hash } = await safeSendTx(() => tokenContract.connect(signer).approve(spender, amount), signer, true);
|
|
1414
|
+
core_1.Logger.log.info('swapRouter:approve:tx_hash', hash);
|
|
1415
|
+
}
|
|
1416
|
+
/** @internal */
|
|
1417
|
+
function ensureSwapsValid(swaps) {
|
|
1418
|
+
if (swaps.length === 0) {
|
|
1419
|
+
throw new core_1.AugustValidationError('INVALID_INPUT', 'swapAndDeposit: at least one swap leg is required');
|
|
1420
|
+
}
|
|
1421
|
+
if (swaps.length > core_1.SWAP_ROUTER_MAX_SWAPS) {
|
|
1422
|
+
throw new core_1.AugustValidationError('INVALID_INPUT', `swapAndDeposit: too many swaps (${swaps.length} > ${core_1.SWAP_ROUTER_MAX_SWAPS})`, { context: { count: swaps.length, max: core_1.SWAP_ROUTER_MAX_SWAPS } });
|
|
1423
|
+
}
|
|
1424
|
+
for (let i = 0; i < swaps.length; i += 1) {
|
|
1425
|
+
validateSwapParams(swaps[i], i);
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
/** @internal */
|
|
1429
|
+
function validateSwapParams(swap, index) {
|
|
1430
|
+
const where = `swapAndDeposit: swaps[${index}]`;
|
|
1431
|
+
if (!(0, core_1.checkAddress)(swap.tokenIn, console, 'contract')) {
|
|
1432
|
+
throw new core_1.AugustValidationError('INVALID_ADDRESS', `${where}.tokenIn is not a valid address`);
|
|
1433
|
+
}
|
|
1434
|
+
if (!(0, core_1.checkAddress)(swap.tokenOut, console, 'contract')) {
|
|
1435
|
+
throw new core_1.AugustValidationError('INVALID_ADDRESS', `${where}.tokenOut is not a valid address`);
|
|
1436
|
+
}
|
|
1437
|
+
if (!(0, core_1.checkAddress)(swap.router, console, 'contract')) {
|
|
1438
|
+
throw new core_1.AugustValidationError('INVALID_ADDRESS', `${where}.router is not a valid address`);
|
|
1439
|
+
}
|
|
1440
|
+
if (swap.tokenIn.toLowerCase() === swap.tokenOut.toLowerCase()) {
|
|
1441
|
+
throw new core_1.AugustValidationError('INVALID_INPUT', `${where}: tokenIn and tokenOut are identical — no swap needed`);
|
|
1442
|
+
}
|
|
1443
|
+
if (swap.amountIn <= 0n) {
|
|
1444
|
+
throw new core_1.AugustValidationError('INVALID_INPUT', `${where}.amountIn must be greater than zero`);
|
|
1445
|
+
}
|
|
1446
|
+
if (swap.minAmountOut <= 0n) {
|
|
1447
|
+
throw new core_1.AugustValidationError('INVALID_INPUT', `${where}.minAmountOut must be greater than zero — slippage protection cannot be disabled`);
|
|
1448
|
+
}
|
|
1449
|
+
if (!/^0x[0-9a-fA-F]+$/.test(swap.payload) || swap.payload.length < 10) {
|
|
1450
|
+
throw new core_1.AugustValidationError('INVALID_INPUT', `${where}.payload must be ABI-encoded calldata (got "${swap.payload.slice(0, 12)}…")`);
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
/** @internal */
|
|
1454
|
+
function totalInputPerToken(swaps) {
|
|
1455
|
+
const totals = new Map();
|
|
1456
|
+
for (const swap of swaps) {
|
|
1457
|
+
const key = swap.tokenIn.toLowerCase();
|
|
1458
|
+
totals.set(key, (totals.get(key) ?? 0n) + swap.amountIn);
|
|
1459
|
+
}
|
|
1460
|
+
return totals;
|
|
1461
|
+
}
|
|
1462
|
+
// Allowance owner must equal msg.sender (contract uses transferFrom(msg.sender, ...));
|
|
1463
|
+
// smart-account wallets need explicit handling outside the SDK.
|
|
1464
|
+
/** @internal */
|
|
1465
|
+
async function resolveSignerEOA(signer, callerContext) {
|
|
1466
|
+
const getAddress = signer.getAddress;
|
|
1467
|
+
if (typeof getAddress !== 'function') {
|
|
1468
|
+
throw new core_1.AugustValidationError('INVALID_INPUT', `${callerContext}: signer must implement getAddress(); got ${typeof getAddress}`);
|
|
1469
|
+
}
|
|
1470
|
+
const eoa = await getAddress.call(signer);
|
|
1471
|
+
if (!eoa || !(0, core_1.checkAddress)(eoa, console, 'wallet')) {
|
|
1472
|
+
throw new core_1.AugustValidationError('INVALID_ADDRESS', `${callerContext}: signer.getAddress() returned an invalid address: ${String(eoa)}`);
|
|
1473
|
+
}
|
|
1474
|
+
return eoa;
|
|
1475
|
+
}
|
|
1476
|
+
/**
|
|
1477
|
+
* Swap one or more whitelisted ERC-20s into a vault's reference asset via
|
|
1478
|
+
* the on-chain SwapRouter, then deposit the proceeds into the vault.
|
|
1479
|
+
*
|
|
1480
|
+
* Approval is sent automatically when the caller's allowance on each
|
|
1481
|
+
* `swaps[i].tokenIn` against the SwapRouter is below `amountIn`.
|
|
1482
|
+
*
|
|
1483
|
+
* @param signer - ethers `Signer` or `Wallet` with the EOA that holds the input tokens.
|
|
1484
|
+
* @param options - {@link ISwapAndDepositOptions}.
|
|
1485
|
+
* @returns The transaction hash of the `swapAndDeposit` call.
|
|
1486
|
+
*
|
|
1487
|
+
* @throws {@link AugustValidationError} for unsupported chains, invalid
|
|
1488
|
+
* addresses, empty/oversized swap arrays.
|
|
1489
|
+
* @throws {@link AugustSDKError} on unrecoverable contract or RPC failure.
|
|
1490
|
+
*
|
|
1491
|
+
* @example
|
|
1492
|
+
* ```ts
|
|
1493
|
+
* const quote = await fetchSwapQuote({
|
|
1494
|
+
* chainId: 1,
|
|
1495
|
+
* srcToken: WBTC,
|
|
1496
|
+
* srcDecimals: 8,
|
|
1497
|
+
* destToken: USDC,
|
|
1498
|
+
* destDecimals: 6,
|
|
1499
|
+
* amount: 100_000_000n,
|
|
1500
|
+
* receiver: SWAP_ROUTER_ADDRESSES[1]!,
|
|
1501
|
+
* });
|
|
1502
|
+
* const hash = await swapAndDeposit(signer, {
|
|
1503
|
+
* chainId: 1,
|
|
1504
|
+
* vault: '0x8AcA0841993ef4C87244d519166e767f49362C21',
|
|
1505
|
+
* receiver: wallet,
|
|
1506
|
+
* swaps: [{
|
|
1507
|
+
* tokenIn: WBTC,
|
|
1508
|
+
* tokenOut: USDC,
|
|
1509
|
+
* amountIn: 100_000_000n,
|
|
1510
|
+
* minAmountOut: quote.minAmountOut,
|
|
1511
|
+
* router: quote.router,
|
|
1512
|
+
* payload: quote.payload,
|
|
1513
|
+
* }],
|
|
1514
|
+
* });
|
|
1515
|
+
* ```
|
|
1516
|
+
*/
|
|
1517
|
+
async function swapAndDeposit(signer, options) {
|
|
1518
|
+
const { chainId, vault, receiver, swaps, originCode, wait } = options;
|
|
1519
|
+
const goodVault = (0, core_1.checkAddress)(vault, console, 'contract');
|
|
1520
|
+
const goodReceiver = (0, core_1.checkAddress)(receiver, console, 'wallet');
|
|
1521
|
+
if (!goodVault || !goodReceiver) {
|
|
1522
|
+
throw new core_1.AugustValidationError('INVALID_ADDRESS', `swapAndDeposit: invalid ${!goodVault ? 'vault' : 'receiver'} address`);
|
|
1523
|
+
}
|
|
1524
|
+
ensureSwapsValid(swaps);
|
|
1525
|
+
const routerAddress = resolveSwapRouterOrThrow(chainId);
|
|
1526
|
+
const eoa = await resolveSignerEOA(signer, 'swapAndDeposit');
|
|
1527
|
+
for (const [tokenIn, totalIn] of totalInputPerToken(swaps)) {
|
|
1528
|
+
await ensureAllowance(signer, tokenIn, eoa, routerAddress, totalIn);
|
|
1529
|
+
}
|
|
1530
|
+
const routerContract = (0, core_1.createContract)({
|
|
1531
|
+
address: routerAddress,
|
|
1532
|
+
provider: signer,
|
|
1533
|
+
abi: abis_1.ABI_SWAP_ROUTER,
|
|
1534
|
+
});
|
|
1535
|
+
const { hash } = await safeSendTx(() => routerContract
|
|
1536
|
+
.connect(signer)
|
|
1537
|
+
.swapAndDeposit((0, core_1.resolveOriginCode)(originCode), vault, receiver, swaps), signer, wait);
|
|
1538
|
+
core_1.Logger.log.info('swapAndDeposit:tx_hash', hash);
|
|
1539
|
+
return hash;
|
|
1540
|
+
}
|
|
1541
|
+
/**
|
|
1542
|
+
* Deposit a vault's reference asset directly through the SwapRouter — no
|
|
1543
|
+
* swap. Useful when the caller already holds the reference asset but wants
|
|
1544
|
+
* the origin/referral fee accounting that only the SwapRouter path provides.
|
|
1545
|
+
*
|
|
1546
|
+
* @param signer - ethers `Signer` or `Wallet`.
|
|
1547
|
+
* @param options - {@link ISwapRouterDirectDepositOptions}.
|
|
1548
|
+
* @returns The transaction hash of the `deposit` call.
|
|
1549
|
+
*
|
|
1550
|
+
* @throws {@link AugustValidationError} for unsupported chains, invalid
|
|
1551
|
+
* addresses, or zero amount.
|
|
1552
|
+
*/
|
|
1553
|
+
async function depositViaSwapRouter(signer, options) {
|
|
1554
|
+
const { chainId, vault, receiver, asset, amount, originCode, wait } = options;
|
|
1555
|
+
const goodVault = (0, core_1.checkAddress)(vault, console, 'contract');
|
|
1556
|
+
const goodReceiver = (0, core_1.checkAddress)(receiver, console, 'wallet');
|
|
1557
|
+
const goodAsset = (0, core_1.checkAddress)(asset, console, 'contract');
|
|
1558
|
+
if (!goodVault || !goodReceiver || !goodAsset) {
|
|
1559
|
+
throw new core_1.AugustValidationError('INVALID_ADDRESS', 'depositViaSwapRouter: invalid address');
|
|
1560
|
+
}
|
|
1561
|
+
if (amount === 0n) {
|
|
1562
|
+
throw new core_1.AugustValidationError('INVALID_INPUT', 'depositViaSwapRouter: amount must be greater than zero');
|
|
1563
|
+
}
|
|
1564
|
+
const routerAddress = resolveSwapRouterOrThrow(chainId);
|
|
1565
|
+
const eoa = await resolveSignerEOA(signer, 'depositViaSwapRouter');
|
|
1566
|
+
await ensureAllowance(signer, asset, eoa, routerAddress, amount);
|
|
1567
|
+
const routerContract = (0, core_1.createContract)({
|
|
1568
|
+
address: routerAddress,
|
|
1569
|
+
provider: signer,
|
|
1570
|
+
abi: abis_1.ABI_SWAP_ROUTER,
|
|
1571
|
+
});
|
|
1572
|
+
const { hash } = await safeSendTx(() => routerContract
|
|
1573
|
+
.connect(signer)
|
|
1574
|
+
.deposit((0, core_1.resolveOriginCode)(originCode), amount, vault, asset, receiver), signer, wait);
|
|
1575
|
+
core_1.Logger.log.info('depositViaSwapRouter:tx_hash', hash);
|
|
1576
|
+
return hash;
|
|
1577
|
+
}
|
|
1578
|
+
/**
|
|
1579
|
+
* Deposit native ETH (or chain-native equivalent) into a vault via the
|
|
1580
|
+
* SwapRouter. The router wraps `amount` to the chain's wrapped-native token
|
|
1581
|
+
* before depositing — only valid when the vault's reference asset equals
|
|
1582
|
+
* the wrapped-native token.
|
|
1583
|
+
*
|
|
1584
|
+
* No ERC-20 approval is needed; `amount` travels as `msg.value`.
|
|
1585
|
+
*
|
|
1586
|
+
* @param signer - ethers `Signer` or `Wallet`.
|
|
1587
|
+
* @param options - {@link ISwapRouterNativeDepositOptions}.
|
|
1588
|
+
* @returns The transaction hash of the `depositNativeToken` call.
|
|
1589
|
+
*
|
|
1590
|
+
* @throws {@link AugustValidationError} for unsupported chains, invalid
|
|
1591
|
+
* addresses, or zero amount.
|
|
1592
|
+
*/
|
|
1593
|
+
async function depositNativeViaSwapRouter(signer, options) {
|
|
1594
|
+
const { chainId, vault, receiver, amount, originCode, wait } = options;
|
|
1595
|
+
const goodVault = (0, core_1.checkAddress)(vault, console, 'contract');
|
|
1596
|
+
const goodReceiver = (0, core_1.checkAddress)(receiver, console, 'wallet');
|
|
1597
|
+
if (!goodVault || !goodReceiver) {
|
|
1598
|
+
throw new core_1.AugustValidationError('INVALID_ADDRESS', 'depositNativeViaSwapRouter: invalid address');
|
|
1599
|
+
}
|
|
1600
|
+
if (amount === 0n) {
|
|
1601
|
+
throw new core_1.AugustValidationError('INVALID_INPUT', 'depositNativeViaSwapRouter: amount must be greater than zero');
|
|
1602
|
+
}
|
|
1603
|
+
const routerAddress = resolveSwapRouterOrThrow(chainId);
|
|
1604
|
+
const routerContract = (0, core_1.createContract)({
|
|
1605
|
+
address: routerAddress,
|
|
1606
|
+
provider: signer,
|
|
1607
|
+
abi: abis_1.ABI_SWAP_ROUTER,
|
|
1608
|
+
});
|
|
1609
|
+
const { hash } = await safeSendTx(() => routerContract
|
|
1610
|
+
.connect(signer)
|
|
1611
|
+
.depositNativeToken((0, core_1.resolveOriginCode)(originCode), vault, receiver, {
|
|
1612
|
+
value: amount,
|
|
1613
|
+
}), signer, wait);
|
|
1614
|
+
core_1.Logger.log.info('depositNativeViaSwapRouter:tx_hash', hash);
|
|
1615
|
+
return hash;
|
|
1616
|
+
}
|
|
1617
|
+
/**
|
|
1618
|
+
* Deposit into an August vault through the on-chain `SwapRouter`, selecting the
|
|
1619
|
+
* correct router path from the deposit asset. This is the high-level, explicit
|
|
1620
|
+
* counterpart to the SwapRouter branch inside {@link vaultDeposit}.
|
|
1621
|
+
*
|
|
1622
|
+
* Where `vaultDeposit` decides whether to use the SwapRouter from the
|
|
1623
|
+
* `VAULTS_USING_SWAP_ROUTER` allowlist, this function always routes through the
|
|
1624
|
+
* SwapRouter because the caller asked it to. Making the intent explicit is the
|
|
1625
|
+
* point: a native-deposit vault (e.g. a multi-asset Tokenized Vault V2 whose
|
|
1626
|
+
* non-reference assets are accepted directly) is never accidentally swapped —
|
|
1627
|
+
* the regression that implicit, allowlist-driven routing caused. Use
|
|
1628
|
+
* `vaultDeposit` for native / adapter deposits and this for SwapRouter deposits.
|
|
1629
|
+
*
|
|
1630
|
+
* The vault's reference asset and its decimals are read on-chain (never trusted
|
|
1631
|
+
* from the caller — a wrong reference asset would mis-route the swap). The path
|
|
1632
|
+
* is then chosen from `depositAsset`:
|
|
1633
|
+
* - equals the reference asset → direct router deposit, no swap
|
|
1634
|
+
* ({@link depositViaSwapRouter});
|
|
1635
|
+
* - the zero address → native-token deposit ({@link depositNativeViaSwapRouter}),
|
|
1636
|
+
* valid only when the reference asset is the chain's wrapped-native token;
|
|
1637
|
+
* - any other ERC-20 → a swap to the reference asset (Paraswap quote pinned to
|
|
1638
|
+
* the chain's whitelisted aggregator, fail-closed on router/selector drift)
|
|
1639
|
+
* bundled into {@link swapAndDeposit}.
|
|
1640
|
+
*
|
|
1641
|
+
* Side effects: reads tokenized-vault metadata, the vault's reference asset and
|
|
1642
|
+
* decimals, and (on the swap path) one Paraswap quote; sends one ERC-20
|
|
1643
|
+
* `approve` to the SwapRouter when allowance is short, then the deposit tx.
|
|
1644
|
+
*
|
|
1645
|
+
* @param signer - ethers `Signer` or `Wallet` that signs the approval + deposit.
|
|
1646
|
+
* @param options - {@link ISwapRouterDepositOptions}.
|
|
1647
|
+
* @returns The transaction hash of the router deposit call.
|
|
1648
|
+
* @throws {@link AugustValidationError} for an unsupported chain (no SwapRouter
|
|
1649
|
+
* deployment), an invalid vault or deposit-asset address, a missing or zero
|
|
1650
|
+
* amount, a native deposit into a non-wrapped-native vault, or drift between
|
|
1651
|
+
* the aggregator quote and the on-chain router whitelist.
|
|
1652
|
+
* @worstCaseRpcCalls 5 on the swap path — tokenized-vault metadata, reference
|
|
1653
|
+
* asset + decimals, deposit-token decimals, the quote, and the allowance read.
|
|
1654
|
+
* @example
|
|
1655
|
+
* ```ts
|
|
1656
|
+
* // USDT deposited into a USDC-reference vault → swapped to USDC en route.
|
|
1657
|
+
* const hash = await augustSdk.evm.swapRouterDeposit({
|
|
1658
|
+
* chainId: 1,
|
|
1659
|
+
* vault: '0xe9b725010a9e419412ed67d0fa5f3a5f40159d32',
|
|
1660
|
+
* depositAsset: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
|
|
1661
|
+
* amount: '100',
|
|
1662
|
+
* slippageBps: 50,
|
|
1663
|
+
* });
|
|
1664
|
+
* ```
|
|
1665
|
+
*/
|
|
1666
|
+
async function swapRouterDeposit(signer, options) {
|
|
1667
|
+
const { chainId, vault, depositAsset, amount, receiver, slippageBps, wait } = options;
|
|
1668
|
+
if (!(0, core_1.checkAddress)(vault, console, 'contract')) {
|
|
1669
|
+
throw new core_1.AugustValidationError('INVALID_ADDRESS', `swapRouterDeposit: invalid vault address "${vault}"`);
|
|
1670
|
+
}
|
|
1671
|
+
if (!(0, core_1.checkAddress)(depositAsset, console, 'contract')) {
|
|
1672
|
+
throw new core_1.AugustValidationError('INVALID_ADDRESS', `swapRouterDeposit: invalid depositAsset address "${depositAsset}"`);
|
|
1673
|
+
}
|
|
1674
|
+
if (amount === undefined || amount === null) {
|
|
1675
|
+
throw new core_1.AugustValidationError('INVALID_INPUT', 'swapRouterDeposit: amount is required');
|
|
1676
|
+
}
|
|
1677
|
+
validateAmountPrecision(amount);
|
|
1678
|
+
// Explicit opt-in: resolve the router straight from chainId rather than
|
|
1679
|
+
// consulting VAULTS_USING_SWAP_ROUTER, so this path never depends on the
|
|
1680
|
+
// implicit allowlist that could route a native-deposit vault through a swap.
|
|
1681
|
+
const routerAddress = resolveSwapRouterOrThrow(chainId);
|
|
1682
|
+
const eoa = await resolveSignerEOA(signer, 'swapRouterDeposit');
|
|
1683
|
+
// Reference asset + decimals from the vault on-chain. Mirrors vaultDeposit's
|
|
1684
|
+
// metadata resolution so the deposit-token decimals (and therefore the raw
|
|
1685
|
+
// amount) match exactly; kept separate to leave the vaultDeposit money path
|
|
1686
|
+
// untouched.
|
|
1687
|
+
const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(vault))?.[0];
|
|
1688
|
+
const isMultiAssetVault = (0, core_1.getVaultVersionV2)(tokenizedVault) === 'evm-2';
|
|
1689
|
+
let underlyingAsset;
|
|
1690
|
+
let vaultDecimals;
|
|
1691
|
+
if (isMultiAssetVault) {
|
|
1692
|
+
const poolContract = (0, core_1.createContract)({
|
|
1693
|
+
address: vault,
|
|
1694
|
+
provider: signer,
|
|
1695
|
+
abi: abis_1.ABI_TOKENIZED_VAULT_V2,
|
|
1696
|
+
});
|
|
1697
|
+
const [asset, receiptTokenAddr] = await Promise.all([
|
|
1698
|
+
poolContract.asset(),
|
|
1699
|
+
// Retried on transport blips only; a misroute still throws. See
|
|
1700
|
+
// getReceiptTokenAddressOrThrow.
|
|
1701
|
+
(0, core_1.getReceiptTokenAddressOrThrow)(signer, vault, 'swapRouterDeposit:receiptToken'),
|
|
1702
|
+
]);
|
|
1703
|
+
underlyingAsset = asset;
|
|
1704
|
+
vaultDecimals = await (0, core_1.getDecimalsOrThrow)(signer, receiptTokenAddr, 'swapRouterDeposit:receiptDecimals');
|
|
1705
|
+
}
|
|
1706
|
+
else {
|
|
1707
|
+
const poolContract = (0, core_1.createContract)({
|
|
1708
|
+
address: vault,
|
|
1709
|
+
provider: signer,
|
|
1710
|
+
abi: abis_1.ABI_LENDING_POOLS,
|
|
1711
|
+
});
|
|
1712
|
+
const [asset, rawDecimals] = await Promise.all([
|
|
1713
|
+
poolContract.asset(),
|
|
1714
|
+
(0, core_1.getDecimalsOrThrow)(signer, vault, 'swapRouterDeposit:poolDecimals'),
|
|
1715
|
+
]);
|
|
1716
|
+
underlyingAsset = asset;
|
|
1717
|
+
vaultDecimals = rawDecimals;
|
|
1718
|
+
}
|
|
1719
|
+
const isNativeToken = depositAsset === ethers_1.ZeroAddress ||
|
|
1720
|
+
depositAsset === '0x0000000000000000000000000000000000000000';
|
|
1721
|
+
const depositTokenDecimals = await resolveDepositTokenDecimals({
|
|
1722
|
+
actualDepositAsset: depositAsset,
|
|
1723
|
+
underlyingAsset,
|
|
1724
|
+
isNativeToken,
|
|
1725
|
+
isMultiAssetVault,
|
|
1726
|
+
vaultDecimals,
|
|
1727
|
+
readErc20Decimals: async (addr) => (0, core_1.getDecimalsOrThrow)(signer, addr, 'swapRouterDeposit:depositTokenDecimals'),
|
|
1728
|
+
});
|
|
1729
|
+
const normalizedAmt = (0, core_1.toNormalizedBn)(amount, depositTokenDecimals);
|
|
1730
|
+
if (BigInt(normalizedAmt.raw) === 0n) {
|
|
1731
|
+
throw new core_1.AugustValidationError('INVALID_INPUT', 'swapRouterDeposit: amount must be greater than zero');
|
|
1732
|
+
}
|
|
1733
|
+
return dispatchViaSwapRouter({
|
|
1734
|
+
signer,
|
|
1735
|
+
routerAddress,
|
|
1736
|
+
chainId,
|
|
1737
|
+
target: vault,
|
|
1738
|
+
wallet: eoa,
|
|
1739
|
+
receiver,
|
|
1740
|
+
actualDepositAsset: depositAsset,
|
|
1741
|
+
underlyingAsset,
|
|
1742
|
+
isNativeToken,
|
|
1743
|
+
depositTokenDecimals,
|
|
1744
|
+
normalizedAmt,
|
|
1745
|
+
slippageBps,
|
|
1746
|
+
wait,
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
//# sourceMappingURL=write.actions.js.map
|