augustdigital-sdk 8.20.1
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,682 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LP_TOKEN_ADDRESS_SELECTOR = void 0;
|
|
4
|
+
exports.isUserRejectionError = isUserRejectionError;
|
|
5
|
+
exports.isExpectedRevertError = isExpectedRevertError;
|
|
6
|
+
exports.isInsufficientFundsError = isInsufficientFundsError;
|
|
7
|
+
exports.isRetryableRpcError = isRetryableRpcError;
|
|
8
|
+
exports.isEmptyViewResponse = isEmptyViewResponse;
|
|
9
|
+
exports.retryOnTransientRpc = retryOnTransientRpc;
|
|
10
|
+
exports.logChainError = logChainError;
|
|
11
|
+
const logger_1 = require("../logger");
|
|
12
|
+
/**
|
|
13
|
+
* Classification of caught chain/RPC/wallet errors so the SDK can decide
|
|
14
|
+
* whether a failure is worth a standalone Sentry issue or is routine, expected
|
|
15
|
+
* noise that should ride along as a breadcrumb instead.
|
|
16
|
+
*
|
|
17
|
+
* Why this exists: most of the SDK's read/write paths `catch` and re-throw, and
|
|
18
|
+
* historically logged every caught error at `error` level — which the SDK's
|
|
19
|
+
* Sentry sink turns into a billed issue (see the severity policy on
|
|
20
|
+
* `SDKSentrySink` in `core/logger`). Two large, low-signal categories dominate
|
|
21
|
+
* that volume:
|
|
22
|
+
*
|
|
23
|
+
* 1. **User-rejected transactions** — the user clicked "reject" in their wallet.
|
|
24
|
+
* This is normal product behaviour, not an SDK fault, yet it fires on every
|
|
25
|
+
* cancelled deposit/redeem/approve.
|
|
26
|
+
* 2. **Expected on-chain read reverts** — reading a function a vault doesn't
|
|
27
|
+
* implement, or an address with no/incompatible bytecode, reverts. This is a
|
|
28
|
+
* routine outcome of probing heterogeneous vaults, not a defect.
|
|
29
|
+
* 3. **Underfunded sender accounts** — the node rejects the transaction because
|
|
30
|
+
* the sender can't cover gas (or, on rollups such as Citrea, the extra L1
|
|
31
|
+
* data-availability fee). This is a "top up your wallet" prompt, not a bug.
|
|
32
|
+
*
|
|
33
|
+
* These predicates let call sites demote exactly those cases to `warn` while
|
|
34
|
+
* leaving genuine failures at `error`. They are intentionally dependency-free
|
|
35
|
+
* (no `ethers`/Sentry imports) so they stay cheap and safe in both browser and
|
|
36
|
+
* Node, and classify purely by inspecting the error's `code`/`message` shape.
|
|
37
|
+
*
|
|
38
|
+
* @module
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* Best-effort message extraction from an unknown thrown value. Reads
|
|
42
|
+
* `error.message` for `Error` and error-like objects, passes strings through,
|
|
43
|
+
* and falls back to `String(error)` for everything else. Never throws.
|
|
44
|
+
*
|
|
45
|
+
* @param error - The caught value, of unknown type.
|
|
46
|
+
* @returns The error's message text (never `undefined`).
|
|
47
|
+
*/
|
|
48
|
+
function errorText(error) {
|
|
49
|
+
if (typeof error === 'string')
|
|
50
|
+
return error;
|
|
51
|
+
if (error instanceof Error)
|
|
52
|
+
return error.message;
|
|
53
|
+
if (error && typeof error === 'object') {
|
|
54
|
+
const maybe = error.message;
|
|
55
|
+
if (typeof maybe === 'string')
|
|
56
|
+
return maybe;
|
|
57
|
+
}
|
|
58
|
+
return String(error);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Collect the `code` fields an error-like value may carry. Wallet/provider
|
|
62
|
+
* errors stash their machine-readable code in different places depending on the
|
|
63
|
+
* stack: ethers sets a top-level string `code` (e.g. `'ACTION_REJECTED'`),
|
|
64
|
+
* EIP-1193 providers use a numeric `code` (e.g. `4001`), and some wrap the
|
|
65
|
+
* original under `error`/`info.error`/`cause`. We scan the common locations so
|
|
66
|
+
* callers needn't know which library produced the error.
|
|
67
|
+
*
|
|
68
|
+
* @param error - The caught value, of unknown type.
|
|
69
|
+
* @returns Every string/number `code` found (possibly empty).
|
|
70
|
+
*/
|
|
71
|
+
function errorCodes(error) {
|
|
72
|
+
const codes = [];
|
|
73
|
+
if (error && typeof error === 'object') {
|
|
74
|
+
const e = error;
|
|
75
|
+
const candidates = [
|
|
76
|
+
e.code,
|
|
77
|
+
e.error?.code,
|
|
78
|
+
e.info?.error?.code,
|
|
79
|
+
e.cause?.code,
|
|
80
|
+
];
|
|
81
|
+
for (const c of candidates) {
|
|
82
|
+
if (typeof c === 'string' || typeof c === 'number')
|
|
83
|
+
codes.push(c);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return codes;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Collect every human-readable message string an error-like value carries,
|
|
90
|
+
* across the nested locations providers stash them in.
|
|
91
|
+
*
|
|
92
|
+
* Why this is separate from {@link errorText}: ethers v6 flattens a rejected
|
|
93
|
+
* `eth_estimateGas`/`eth_call` to a generic *top-level* `message` (e.g.
|
|
94
|
+
* `"missing revert data"`) while preserving the node's original JSON-RPC error
|
|
95
|
+
* one level down at `info.error.message`. The real reason (an insufficient-funds
|
|
96
|
+
* report, say) is therefore invisible to a top-level `.message` read. We scan
|
|
97
|
+
* the top-level message plus `error.message`, `info.error.message`,
|
|
98
|
+
* `cause.message`, and `cause.info.error.message` so a caller catches the reason
|
|
99
|
+
* whether it holds the raw provider error or an SDK error that wrapped it as
|
|
100
|
+
* `cause`.
|
|
101
|
+
*
|
|
102
|
+
* @param error - The caught value, of unknown type.
|
|
103
|
+
* @returns Every non-empty message string found (possibly empty array).
|
|
104
|
+
*/
|
|
105
|
+
function nestedMessages(error) {
|
|
106
|
+
const out = [];
|
|
107
|
+
const push = (value) => {
|
|
108
|
+
if (typeof value === 'string' && value.length > 0)
|
|
109
|
+
out.push(value);
|
|
110
|
+
};
|
|
111
|
+
push(errorText(error));
|
|
112
|
+
if (error && typeof error === 'object') {
|
|
113
|
+
const e = error;
|
|
114
|
+
push(e.error?.message);
|
|
115
|
+
push(e.info?.error?.message);
|
|
116
|
+
push(e.cause?.message);
|
|
117
|
+
push(e.cause?.info?.error?.message);
|
|
118
|
+
}
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Is this error a wallet/user rejection of a transaction or signature request?
|
|
123
|
+
*
|
|
124
|
+
* Detects ethers v6's `ACTION_REJECTED` code, the EIP-1193 `4001`
|
|
125
|
+
* ("User rejected the request") code (top-level or nested), and the common
|
|
126
|
+
* human-readable phrasings as a fallback for providers that omit a code.
|
|
127
|
+
*
|
|
128
|
+
* @param error - The caught value, of unknown type.
|
|
129
|
+
* @returns `true` when the failure was the user declining in their wallet.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```ts
|
|
133
|
+
* try { await vault.deposit(...); }
|
|
134
|
+
* catch (e) {
|
|
135
|
+
* if (isUserRejectionError(e)) return; // user cancelled — not an error
|
|
136
|
+
* throw e;
|
|
137
|
+
* }
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
function isUserRejectionError(error) {
|
|
141
|
+
const codes = errorCodes(error);
|
|
142
|
+
if (codes.includes('ACTION_REJECTED') || codes.includes(4001))
|
|
143
|
+
return true;
|
|
144
|
+
const msg = errorText(error).toLowerCase();
|
|
145
|
+
return (msg.includes('user rejected') ||
|
|
146
|
+
msg.includes('user denied') ||
|
|
147
|
+
msg.includes('rejected the request') ||
|
|
148
|
+
msg.includes('request rejected'));
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Is this error a routine on-chain read revert rather than a real failure?
|
|
152
|
+
*
|
|
153
|
+
* Reading a function a contract doesn't implement, or an address that holds no
|
|
154
|
+
* (or incompatible) bytecode, reverts — a normal outcome when the SDK probes
|
|
155
|
+
* heterogeneous vaults. Matches ethers' `CALL_EXCEPTION` code and the
|
|
156
|
+
* message variants emitted by ethers and viem (`missing revert data`,
|
|
157
|
+
* `execution reverted`, `call revert exception`, and the bare `reverted`
|
|
158
|
+
* phrasing such as `the contract function "totalAssets" reverted`).
|
|
159
|
+
*
|
|
160
|
+
* Scope note: callers apply this on **read** paths only. A reverted *write*
|
|
161
|
+
* (a tx that failed on-chain) is a genuine error and is intentionally not
|
|
162
|
+
* demoted by this predicate's use in `write.actions`.
|
|
163
|
+
*
|
|
164
|
+
* @param error - The caught value, of unknown type.
|
|
165
|
+
* @returns `true` when the failure is an expected/benign contract revert.
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ```ts
|
|
169
|
+
* try { return await vaultContract.maxDepositAmount(); }
|
|
170
|
+
* catch (e) {
|
|
171
|
+
* logChainError('maxDeposit', e, isExpectedRevertError(e));
|
|
172
|
+
* throw e;
|
|
173
|
+
* }
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
function isExpectedRevertError(error) {
|
|
177
|
+
const codes = errorCodes(error);
|
|
178
|
+
if (codes.includes('CALL_EXCEPTION'))
|
|
179
|
+
return true;
|
|
180
|
+
const msg = errorText(error).toLowerCase();
|
|
181
|
+
return (msg.includes('call_exception') ||
|
|
182
|
+
msg.includes('missing revert data') ||
|
|
183
|
+
msg.includes('execution reverted') ||
|
|
184
|
+
msg.includes('call revert exception') ||
|
|
185
|
+
msg.includes('reverted'));
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Is this error the chain node reporting that the sender can't afford the
|
|
189
|
+
* transaction's gas/fee — i.e. the account needs topping up, not a defect?
|
|
190
|
+
*
|
|
191
|
+
* This is distinct from a contract revert, and easy to misclassify. When the
|
|
192
|
+
* node rejects `eth_estimateGas` for an underfunded account, ethers v6 discards
|
|
193
|
+
* the node's reason and surfaces a generic `CALL_EXCEPTION` / `"missing revert
|
|
194
|
+
* data"` at the top level — which {@link isExpectedRevertError} matches. The
|
|
195
|
+
* real reason survives only in the nested JSON-RPC error, so this predicate
|
|
196
|
+
* scans there (via `nestedMessages`). On a write path, check this **before**
|
|
197
|
+
* {@link isExpectedRevertError}, or a genuine funds shortfall reads as a benign
|
|
198
|
+
* revert.
|
|
199
|
+
*
|
|
200
|
+
* Matching is anchored to the node's pre-execution funds-check phrasings (see
|
|
201
|
+
* `INSUFFICIENT_FUNDS_PHRASES`), each of which carries a `for <purpose>`
|
|
202
|
+
* qualifier — `insufficient funds for gas`, `insufficient funds for transfer`,
|
|
203
|
+
* `not enough funds for L1 fee` (Citrea's data-availability fee), etc. This is
|
|
204
|
+
* deliberately narrower than a bare `"insufficient funds"` substring: a
|
|
205
|
+
* *contract revert reason* that merely contains the word "funds" (e.g.
|
|
206
|
+
* `execution reverted: insufficient funds in pool`) must stay a genuine failure,
|
|
207
|
+
* not be demoted to an ACCOUNT_NOT_FUNDED "top up gas" prompt.
|
|
208
|
+
*
|
|
209
|
+
* @param error - The caught value, of unknown type.
|
|
210
|
+
* @returns `true` when the failure is an unfunded/underfunded sender account.
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```ts
|
|
214
|
+
* try { await vault.requestRedeem(...); }
|
|
215
|
+
* catch (e) {
|
|
216
|
+
* if (isInsufficientFundsError(e))
|
|
217
|
+
* throw new AugustValidationError(
|
|
218
|
+
* 'ACCOUNT_NOT_FUNDED',
|
|
219
|
+
* 'Add gas to continue',
|
|
220
|
+
* { cause: e },
|
|
221
|
+
* );
|
|
222
|
+
* throw e;
|
|
223
|
+
* }
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
/**
|
|
227
|
+
* The node pre-execution funds-check phrasings {@link isInsufficientFundsError}
|
|
228
|
+
* recognises, lower-cased. Each keeps the `for <purpose>` qualifier so the match
|
|
229
|
+
* is anchored to a funding rejection and can't be tripped by a contract revert
|
|
230
|
+
* reason that merely mentions "funds". `insufficient funds for gas` covers the
|
|
231
|
+
* geth/reth `"... for gas * price + value"` message; `not enough funds for l1
|
|
232
|
+
* fee` is the rollup (Citrea) data-availability-fee shortfall.
|
|
233
|
+
*/
|
|
234
|
+
const INSUFFICIENT_FUNDS_PHRASES = [
|
|
235
|
+
'insufficient funds for gas',
|
|
236
|
+
'insufficient funds for transfer',
|
|
237
|
+
'insufficient funds for intrinsic transaction cost',
|
|
238
|
+
'not enough funds for l1 fee',
|
|
239
|
+
];
|
|
240
|
+
function isInsufficientFundsError(error) {
|
|
241
|
+
return nestedMessages(error).some((raw) => {
|
|
242
|
+
const msg = raw.toLowerCase();
|
|
243
|
+
return INSUFFICIENT_FUNDS_PHRASES.some((phrase) => msg.includes(phrase));
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Collect the HTTP status codes an error-like value may carry. Transport
|
|
248
|
+
* failures that reach us through ethers' `FetchRequest` keep the upstream
|
|
249
|
+
* status on the error (`status`), on the wrapped fetch response
|
|
250
|
+
* (`info.status` / `response.status`), or on a `statusCode` alias depending on
|
|
251
|
+
* which layer produced it. We scan all of them so a caller needn't know.
|
|
252
|
+
*
|
|
253
|
+
* @param error - The caught value, of unknown type.
|
|
254
|
+
* @returns Every numeric status found (possibly empty).
|
|
255
|
+
*/
|
|
256
|
+
function errorStatuses(error) {
|
|
257
|
+
const out = [];
|
|
258
|
+
const push = (value) => {
|
|
259
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
260
|
+
out.push(value);
|
|
261
|
+
};
|
|
262
|
+
if (error && typeof error === 'object') {
|
|
263
|
+
const e = error;
|
|
264
|
+
push(e.status);
|
|
265
|
+
push(e.statusCode);
|
|
266
|
+
push(e.info?.status);
|
|
267
|
+
push(e.info?.statusCode);
|
|
268
|
+
push(e.response?.status);
|
|
269
|
+
push(e.response?.statusCode);
|
|
270
|
+
}
|
|
271
|
+
return out;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Does this error carry evidence that the EVM actually executed and reverted?
|
|
275
|
+
*
|
|
276
|
+
* Used as a **veto** by {@link isRetryableRpcError}: nodes reuse the generic
|
|
277
|
+
* JSON-RPC codes (`-32603`, `-32000`) for genuine execution reverts as well as
|
|
278
|
+
* for internal/transport faults, so a positive transport match must never win
|
|
279
|
+
* over a real revert. Evidence of real execution is:
|
|
280
|
+
*
|
|
281
|
+
* - a `CALL_EXCEPTION` that carries non-empty revert `data` (the ABI-encoded
|
|
282
|
+
* custom error / `Error(string)` payload),
|
|
283
|
+
* - an `execution reverted` / `call revert exception` message anywhere in the
|
|
284
|
+
* nested error chain,
|
|
285
|
+
* - an attached receipt with `status === 0` (the tx mined and failed).
|
|
286
|
+
*
|
|
287
|
+
* Note that a bare `CALL_EXCEPTION` with **no** revert data (ethers' `missing
|
|
288
|
+
* revert data`) is deliberately *not* evidence — see {@link isRetryableRpcError}.
|
|
289
|
+
*
|
|
290
|
+
* @param error - The caught value, of unknown type.
|
|
291
|
+
* @returns `true` when the error proves on-chain execution reverted.
|
|
292
|
+
*/
|
|
293
|
+
function hasRevertEvidence(error) {
|
|
294
|
+
if (error && typeof error === 'object') {
|
|
295
|
+
const e = error;
|
|
296
|
+
const codes = errorCodes(error);
|
|
297
|
+
if (codes.includes('CALL_EXCEPTION') &&
|
|
298
|
+
typeof e.data === 'string' &&
|
|
299
|
+
e.data.length > 2) {
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
if (e.receipt?.status === 0)
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
return nestedMessages(error).some((raw) => {
|
|
306
|
+
const msg = raw.toLowerCase();
|
|
307
|
+
return (msg.includes('execution reverted') ||
|
|
308
|
+
msg.includes('call revert exception'));
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* JSON-RPC error codes that indicate the *node or its transport* failed, not
|
|
313
|
+
* that the EVM rejected the call. `-32603` is the spec's "Internal error" and
|
|
314
|
+
* `-32000` the de-facto "Server error" both Alchemy and Infura emit for
|
|
315
|
+
* transient upstream faults (including while serving
|
|
316
|
+
* `eth_getTransactionReceipt` for a freshly broadcast tx).
|
|
317
|
+
*/
|
|
318
|
+
const RETRYABLE_RPC_CODES = [
|
|
319
|
+
-32603,
|
|
320
|
+
-32000,
|
|
321
|
+
'NETWORK_ERROR',
|
|
322
|
+
'SERVER_ERROR',
|
|
323
|
+
'TIMEOUT',
|
|
324
|
+
'ETIMEDOUT',
|
|
325
|
+
'ECONNRESET',
|
|
326
|
+
'ECONNREFUSED',
|
|
327
|
+
'ENOTFOUND',
|
|
328
|
+
'EAI_AGAIN',
|
|
329
|
+
];
|
|
330
|
+
/**
|
|
331
|
+
* Message fragments (lower-cased) that identify a transient transport failure.
|
|
332
|
+
*
|
|
333
|
+
* `could not coalesce error` is ethers v6's catch-all when it cannot map a
|
|
334
|
+
* node's JSON-RPC payload onto a typed error — in practice this is what a
|
|
335
|
+
* flaky provider looks like from inside `tx.wait()`. `eth_gettransactionreceipt`
|
|
336
|
+
* is included because a failure *naming that method* is by definition a receipt
|
|
337
|
+
* poll, which is safe to repeat: the transaction is already broadcast and
|
|
338
|
+
* polling is idempotent.
|
|
339
|
+
*
|
|
340
|
+
* `missing revert data` is deliberately **absent**. It is a `CALL_EXCEPTION`,
|
|
341
|
+
* not a transport frame, and {@link isExpectedRevertError} already treats it as
|
|
342
|
+
* a revert — having this predicate disagree would make the two classifiers
|
|
343
|
+
* contradict each other. The narrow subset of `missing revert data` that really
|
|
344
|
+
* is a transport artefact (an empty response to an argument-free view call) has
|
|
345
|
+
* its own predicate: {@link isEmptyViewResponse}.
|
|
346
|
+
*/
|
|
347
|
+
const RETRYABLE_RPC_PHRASES = [
|
|
348
|
+
'could not coalesce error',
|
|
349
|
+
'eth_gettransactionreceipt',
|
|
350
|
+
'timeout',
|
|
351
|
+
'timed out',
|
|
352
|
+
'etimedout',
|
|
353
|
+
'econnreset',
|
|
354
|
+
'econnrefused',
|
|
355
|
+
'enotfound',
|
|
356
|
+
'socket hang up',
|
|
357
|
+
'network error',
|
|
358
|
+
'network request failed',
|
|
359
|
+
'failed to fetch',
|
|
360
|
+
'fetch failed',
|
|
361
|
+
'load failed',
|
|
362
|
+
'connection closed',
|
|
363
|
+
'connection reset',
|
|
364
|
+
'too many requests',
|
|
365
|
+
'rate limit',
|
|
366
|
+
'service unavailable',
|
|
367
|
+
'bad gateway',
|
|
368
|
+
'gateway timeout',
|
|
369
|
+
'internal server error',
|
|
370
|
+
'internal error',
|
|
371
|
+
'server error',
|
|
372
|
+
];
|
|
373
|
+
/**
|
|
374
|
+
* Is this error a transient RPC **transport** failure that is safe to retry,
|
|
375
|
+
* rather than a decision the chain made?
|
|
376
|
+
*
|
|
377
|
+
* Why this exists: the SDK's write paths poll `eth_getTransactionReceipt` to
|
|
378
|
+
* confirm a broadcast transaction. When the provider hiccups mid-poll, ethers
|
|
379
|
+
* surfaces `could not coalesce error (error={ "code": -32603, … "method":
|
|
380
|
+
* "eth_getTransactionReceipt" … })`. Historically that propagated out of
|
|
381
|
+
* `safeWaitForTx` and the SDK reported the write as **failed** — even though
|
|
382
|
+
* the transaction was broadcast, its hash was known, and it mined fine. Users
|
|
383
|
+
* then retried and hit `ERC20InsufficientBalance` because the first attempt had
|
|
384
|
+
* in fact succeeded. Classifying the failure as transport-level lets callers
|
|
385
|
+
* re-poll instead of lying to the user.
|
|
386
|
+
*
|
|
387
|
+
* Matches, in order of precedence:
|
|
388
|
+
* 1. **Veto** — anything with revert evidence ({@link hasRevertEvidence}:
|
|
389
|
+
* `CALL_EXCEPTION` carrying revert `data`, an `execution reverted` message,
|
|
390
|
+
* or an attached `receipt.status === 0`) returns `false`. Nodes reuse
|
|
391
|
+
* `-32603`/`-32000` for real reverts, so the veto must come first.
|
|
392
|
+
* 2. JSON-RPC / ethers transport codes — see `RETRYABLE_RPC_CODES`.
|
|
393
|
+
* 3. HTTP `429` and any `5xx` carried on the error.
|
|
394
|
+
* 4. Transport message fragments — see `RETRYABLE_RPC_PHRASES`.
|
|
395
|
+
*
|
|
396
|
+
* Retrying is only safe for **idempotent** work: re-reading an immutable value
|
|
397
|
+
* (`decimals()`) or re-polling a receipt for a hash that is already on the
|
|
398
|
+
* wire. Never use this to re-send a transaction.
|
|
399
|
+
*
|
|
400
|
+
* @param error - The caught value, of unknown type.
|
|
401
|
+
* @returns `true` when the failure is a transient transport fault worth
|
|
402
|
+
* retrying with backoff; `false` for chain-level decisions (reverts) and for
|
|
403
|
+
* anything unrecognised — the safe default is to surface the error.
|
|
404
|
+
*
|
|
405
|
+
* @example
|
|
406
|
+
* ```ts
|
|
407
|
+
* try {
|
|
408
|
+
* return await provider.waitForTransaction(hash, 1, 120_000);
|
|
409
|
+
* } catch (e) {
|
|
410
|
+
* if (!isRetryableRpcError(e)) throw e; // real revert — surface it
|
|
411
|
+
* await sleep(250);
|
|
412
|
+
* return await provider.waitForTransaction(hash, 1, 120_000);
|
|
413
|
+
* }
|
|
414
|
+
* ```
|
|
415
|
+
*/
|
|
416
|
+
function isRetryableRpcError(error) {
|
|
417
|
+
if (error === null || error === undefined)
|
|
418
|
+
return false;
|
|
419
|
+
// A chain-level decision is never a transport fault. Check first: nodes
|
|
420
|
+
// reuse -32603/-32000 for genuine execution reverts.
|
|
421
|
+
if (hasRevertEvidence(error))
|
|
422
|
+
return false;
|
|
423
|
+
const codes = errorCodes(error);
|
|
424
|
+
if (codes.some((code) => RETRYABLE_RPC_CODES.includes(code)))
|
|
425
|
+
return true;
|
|
426
|
+
// 429 (rate limited) and any 5xx are upstream faults, not our request being
|
|
427
|
+
// wrong — 4xx other than 429 means retrying would fail identically.
|
|
428
|
+
if (errorStatuses(error).some((status) => status === 429 || (status >= 500 && status < 600))) {
|
|
429
|
+
return true;
|
|
430
|
+
}
|
|
431
|
+
return nestedMessages(error).some((raw) => {
|
|
432
|
+
const msg = raw.toLowerCase();
|
|
433
|
+
return RETRYABLE_RPC_PHRASES.some((phrase) => msg.includes(phrase));
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* `lpTokenAddress()` — `keccak256("lpTokenAddress()")[0..4]`. The August `evm-2`
|
|
438
|
+
* tokenized vault's receipt-token getter, exported so the readers that invoke it
|
|
439
|
+
* can scope {@link isEmptyViewResponse} to exactly this call instead of
|
|
440
|
+
* duplicating the literal.
|
|
441
|
+
*/
|
|
442
|
+
exports.LP_TOKEN_ADDRESS_SELECTOR = '0xf5ae497a';
|
|
443
|
+
/**
|
|
444
|
+
* Four-byte selectors for the argument-free view functions that a correctly
|
|
445
|
+
* addressed contract **cannot** legitimately revert on. Each is
|
|
446
|
+
* `keccak256(signature)[0..4]`.
|
|
447
|
+
*
|
|
448
|
+
* These are the only calls for which an empty RPC response is unambiguously a
|
|
449
|
+
* provider artefact rather than a contract decision — the metadata they return
|
|
450
|
+
* is fixed at deployment and takes no arguments, so there is no input that
|
|
451
|
+
* could make them fail.
|
|
452
|
+
*
|
|
453
|
+
* **Caveat for `lpTokenAddress()`.** The four ERC-20 entries hold that property
|
|
454
|
+
* absolutely: any deployed, conforming token implements them. `lpTokenAddress()`
|
|
455
|
+
* holds it only *given correct routing* — the function exists on `evm-2` vaults
|
|
456
|
+
* and nowhere else, so a vault wrongly routed into the `evm-2` branch returns
|
|
457
|
+
* empty returndata **deterministically**, producing a byte-identical error to a
|
|
458
|
+
* provider blip. That ambiguity is resolved not by this predicate but by the
|
|
459
|
+
* caller: every reader that retries on this selector is bounded (3 attempts) and
|
|
460
|
+
* rethrows the original error once they are spent, so a deterministic misroute
|
|
461
|
+
* still surfaces unchanged — only a transient blip is absorbed. Never pair this
|
|
462
|
+
* selector with an unbounded retry or a fallback value.
|
|
463
|
+
*/
|
|
464
|
+
const ARGUMENT_FREE_VIEW_SELECTORS = new Set([
|
|
465
|
+
'0x313ce567', // decimals()
|
|
466
|
+
'0x95d89b41', // symbol()
|
|
467
|
+
'0x06fdde03', // name()
|
|
468
|
+
'0x18160ddd', // totalSupply()
|
|
469
|
+
exports.LP_TOKEN_ADDRESS_SELECTOR, // lpTokenAddress()
|
|
470
|
+
]);
|
|
471
|
+
/**
|
|
472
|
+
* Extract the 4-byte selector of the call an ethers `CALL_EXCEPTION` describes.
|
|
473
|
+
*
|
|
474
|
+
* ethers v6 attaches the attempted call as `error.transaction.data`, and also
|
|
475
|
+
* embeds it in the human-readable message as `data="0x…"`. We read the
|
|
476
|
+
* structured field first and fall back to the message so the predicate still
|
|
477
|
+
* works on an error that has been serialized and rehydrated (which is how these
|
|
478
|
+
* arrive from a logging pipeline).
|
|
479
|
+
*
|
|
480
|
+
* @param error - The caught value, of unknown type.
|
|
481
|
+
* @returns The lower-cased `0x`-prefixed 4-byte selector, or `null` when the
|
|
482
|
+
* error does not name a call.
|
|
483
|
+
*/
|
|
484
|
+
function callSelector(error) {
|
|
485
|
+
if (error && typeof error === 'object') {
|
|
486
|
+
const e = error;
|
|
487
|
+
const data = e.transaction?.data;
|
|
488
|
+
if (typeof data === 'string' && /^0x[0-9a-fA-F]{8}/.test(data)) {
|
|
489
|
+
return data.slice(0, 10).toLowerCase();
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
// ethers renders the same field two ways depending on nesting depth:
|
|
493
|
+
// `data="0x313ce567"` at the top level and `"data": "0x313ce567"` inside the
|
|
494
|
+
// serialized `transaction={…}` blob. Accept both.
|
|
495
|
+
for (const raw of nestedMessages(error)) {
|
|
496
|
+
const match = raw.match(/data"?\s*[:=]\s*"(0x[0-9a-fA-F]{8})/);
|
|
497
|
+
if (match?.[1])
|
|
498
|
+
return match[1].toLowerCase();
|
|
499
|
+
}
|
|
500
|
+
return null;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Is this error an **empty RPC response to an argument-free view call** —
|
|
504
|
+
* i.e. a transport artefact wearing a revert's clothes?
|
|
505
|
+
*
|
|
506
|
+
* Why this is separate from {@link isRetryableRpcError}: when a provider
|
|
507
|
+
* truncates or 500s a response to `eth_call`, ethers reports
|
|
508
|
+
* `missing revert data (action="call", data="0x313ce567", …)` with a `null`
|
|
509
|
+
* `data` field. That is byte-for-byte the shape of a genuine revert with no
|
|
510
|
+
* reason string, so a general "transport" predicate cannot safely claim it —
|
|
511
|
+
* doing so would retry every data-less `CALL_EXCEPTION` in the SDK. This
|
|
512
|
+
* predicate narrows the claim to the one case where the ambiguity resolves:
|
|
513
|
+
* a **deployed ERC-20's `decimals()`/`symbol()`/`name()`/`totalSupply()` cannot
|
|
514
|
+
* legitimately revert**, because it takes no arguments and returns state fixed
|
|
515
|
+
* at deployment. An empty response there is the provider's fault, full stop.
|
|
516
|
+
*
|
|
517
|
+
* A match requires all of:
|
|
518
|
+
* 1. no revert evidence ({@link hasRevertEvidence}) — anything carrying real
|
|
519
|
+
* revert `data`, an `execution reverted` message, or a failed receipt is out;
|
|
520
|
+
* 2. a `missing revert data` message;
|
|
521
|
+
* 3. an `action` of `call` or `staticCall` — a read, never a state change;
|
|
522
|
+
* 4. **when a selector is derivable** from the error, that it is
|
|
523
|
+
* `expectedSelector` (if given) or one of
|
|
524
|
+
* {@link ARGUMENT_FREE_VIEW_SELECTORS}. When no selector can be recovered,
|
|
525
|
+
* conditions 1–3 stand on their own.
|
|
526
|
+
*
|
|
527
|
+
* Note the cost of a false positive is bounded and small: the caller retries an
|
|
528
|
+
* idempotent read a couple of times before surfacing the same error. The cost
|
|
529
|
+
* of a false negative is the production flood this predicate exists to stop.
|
|
530
|
+
*
|
|
531
|
+
* @param error - The caught value, of unknown type.
|
|
532
|
+
* @param expectedSelector - Optional `0x`-prefixed 4-byte selector the caller
|
|
533
|
+
* knows it invoked (e.g. `'0x313ce567'` for `decimals()`). When supplied, the
|
|
534
|
+
* error's own selector must match it — this stops a `decimals()` retry from
|
|
535
|
+
* firing on an unrelated view call that happened to fail the same way.
|
|
536
|
+
* @returns `true` when the failure is an empty provider response to a view call
|
|
537
|
+
* that cannot revert, and is therefore safe to retry.
|
|
538
|
+
*
|
|
539
|
+
* @example
|
|
540
|
+
* ```ts
|
|
541
|
+
* try { return Number(await erc20.decimals()); }
|
|
542
|
+
* catch (e) {
|
|
543
|
+
* if (!isEmptyViewResponse(e, '0x313ce567')) throw e; // real problem
|
|
544
|
+
* return Number(await erc20.decimals()); // provider blip
|
|
545
|
+
* }
|
|
546
|
+
* ```
|
|
547
|
+
*/
|
|
548
|
+
function isEmptyViewResponse(error, expectedSelector) {
|
|
549
|
+
if (error === null || error === undefined)
|
|
550
|
+
return false;
|
|
551
|
+
if (hasRevertEvidence(error))
|
|
552
|
+
return false;
|
|
553
|
+
const messages = nestedMessages(error).map((raw) => raw.toLowerCase());
|
|
554
|
+
if (!messages.some((msg) => msg.includes('missing revert data')))
|
|
555
|
+
return false;
|
|
556
|
+
// The call must be a read. ethers exposes this as a structured `action`
|
|
557
|
+
// ('call' | 'estimateGas' | 'sendTransaction' | …) and mirrors it in the
|
|
558
|
+
// message as action="call".
|
|
559
|
+
const action = error && typeof error === 'object'
|
|
560
|
+
? error.action
|
|
561
|
+
: undefined;
|
|
562
|
+
const isRead = action === 'call' ||
|
|
563
|
+
action === 'staticCall' ||
|
|
564
|
+
messages.some((msg) => msg.includes('action="call"') || msg.includes('action="staticcall"'));
|
|
565
|
+
if (!isRead)
|
|
566
|
+
return false;
|
|
567
|
+
const selector = callSelector(error);
|
|
568
|
+
// Nothing to check against — conditions 1-3 already establish "empty response
|
|
569
|
+
// to a read", which is the signal we act on.
|
|
570
|
+
if (!selector)
|
|
571
|
+
return true;
|
|
572
|
+
if (expectedSelector)
|
|
573
|
+
return selector === expectedSelector.toLowerCase();
|
|
574
|
+
return ARGUMENT_FREE_VIEW_SELECTORS.has(selector);
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* How many times an idempotent RPC read is attempted in total (1 initial call +
|
|
578
|
+
* 2 retries) before the transport error is surfaced. Deliberately small:
|
|
579
|
+
* CLAUDE.md §4.2 — retrying harder during a provider outage amplifies load
|
|
580
|
+
* rather than recovering from it.
|
|
581
|
+
*/
|
|
582
|
+
const RPC_RETRY_ATTEMPTS = 3;
|
|
583
|
+
/**
|
|
584
|
+
* Base backoff between retry attempts, in milliseconds. Doubles per attempt
|
|
585
|
+
* (250ms, then 500ms), so a fully-failed read costs ~750ms of added latency.
|
|
586
|
+
*/
|
|
587
|
+
const RPC_RETRY_BASE_DELAY_MS = 250;
|
|
588
|
+
/**
|
|
589
|
+
* Run an **idempotent** RPC read, retrying with exponential backoff while the
|
|
590
|
+
* failure classifies as a transient transport fault
|
|
591
|
+
* ({@link isRetryableRpcError}).
|
|
592
|
+
*
|
|
593
|
+
* Lives next to the classifiers it consumes so there is exactly one retry
|
|
594
|
+
* implementation in the SDK: both the receipt-poll fallback in the vault write
|
|
595
|
+
* paths and the cached `decimals()` reader in `core/helpers/web3.ts` call this.
|
|
596
|
+
*
|
|
597
|
+
* Only safe for operations that can be repeated without side effects: polling
|
|
598
|
+
* `eth_getTransactionReceipt` for an already-broadcast hash, or re-reading an
|
|
599
|
+
* immutable value such as `decimals()`. **Never wrap a transaction send in
|
|
600
|
+
* this.**
|
|
601
|
+
*
|
|
602
|
+
* Anything that is not a transport fault (a genuine revert, a user rejection,
|
|
603
|
+
* an insufficient-funds rejection) is rethrown on the first attempt with no
|
|
604
|
+
* delay, so real failures still fail fast.
|
|
605
|
+
*
|
|
606
|
+
* @param tag - Low-cardinality log label for the retry breadcrumb.
|
|
607
|
+
* @param operation - The idempotent async read to run.
|
|
608
|
+
* @param context - Extra structured context for the retry breadcrumb (e.g.
|
|
609
|
+
* `{ hash }`). Sanitized by the logger before transport.
|
|
610
|
+
* @param isRetryable - Predicate deciding whether a caught error warrants
|
|
611
|
+
* another attempt. Defaults to the strict transport definition
|
|
612
|
+
* ({@link isRetryableRpcError}); pass a wider one only where the call site
|
|
613
|
+
* can prove the extra shape is also a provider artefact — the only such case
|
|
614
|
+
* today is the selector-scoped {@link isEmptyViewResponse} used by
|
|
615
|
+
* `getDecimalsOrThrow`.
|
|
616
|
+
* @returns Whatever `operation` resolves to on the first successful attempt.
|
|
617
|
+
* @throws The last error thrown by `operation` once retries are exhausted, or
|
|
618
|
+
* immediately when the error is not retryable.
|
|
619
|
+
*
|
|
620
|
+
* @example
|
|
621
|
+
* ```ts
|
|
622
|
+
* const receipt = await retryOnTransientRpc(
|
|
623
|
+
* 'safeWaitForTx:transport-retry',
|
|
624
|
+
* () => provider.waitForTransaction(hash, 1, 120_000),
|
|
625
|
+
* { hash },
|
|
626
|
+
* );
|
|
627
|
+
* ```
|
|
628
|
+
*/
|
|
629
|
+
async function retryOnTransientRpc(tag, operation, context = {}, isRetryable = isRetryableRpcError) {
|
|
630
|
+
let lastError;
|
|
631
|
+
for (let attempt = 1; attempt <= RPC_RETRY_ATTEMPTS; attempt += 1) {
|
|
632
|
+
try {
|
|
633
|
+
return await operation();
|
|
634
|
+
}
|
|
635
|
+
catch (error) {
|
|
636
|
+
lastError = error;
|
|
637
|
+
if (!isRetryable(error) || attempt === RPC_RETRY_ATTEMPTS) {
|
|
638
|
+
throw error;
|
|
639
|
+
}
|
|
640
|
+
const delayMs = RPC_RETRY_BASE_DELAY_MS * 2 ** (attempt - 1);
|
|
641
|
+
logger_1.Logger.log.warn(tag, 'transient RPC error; retrying', {
|
|
642
|
+
...context,
|
|
643
|
+
attempt,
|
|
644
|
+
attempts: RPC_RETRY_ATTEMPTS,
|
|
645
|
+
delayMs,
|
|
646
|
+
message: error instanceof Error ? error.message : String(error),
|
|
647
|
+
});
|
|
648
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
// Unreachable: the loop either returns or throws on its final attempt.
|
|
652
|
+
throw lastError;
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Log a caught chain error at the severity its category warrants, without
|
|
656
|
+
* swallowing it. When `isBenign` is `true` the failure is recorded as a `warn`
|
|
657
|
+
* (a Sentry breadcrumb that rides along with the next real issue, not a billed
|
|
658
|
+
* standalone issue); otherwise it is logged at `error` (a Sentry issue). The
|
|
659
|
+
* caller is still responsible for re-throwing — this only routes telemetry.
|
|
660
|
+
*
|
|
661
|
+
* Pass the benign decision explicitly (via {@link isUserRejectionError} or
|
|
662
|
+
* {@link isExpectedRevertError}) so the call site documents *why* the demotion
|
|
663
|
+
* is safe and each path opts into only the category that applies to it.
|
|
664
|
+
*
|
|
665
|
+
* @param tag - Low-cardinality call-site label (e.g. `'deposit'`), used as the
|
|
666
|
+
* Sentry breadcrumb/issue grouping key.
|
|
667
|
+
* @param error - The caught value, of unknown type.
|
|
668
|
+
* @param isBenign - `true` to demote to `warn`; `false` to keep at `error`.
|
|
669
|
+
* @param context - Optional structured context attached to the log entry. It is
|
|
670
|
+
* sanitized by the logger before transport.
|
|
671
|
+
*/
|
|
672
|
+
function logChainError(tag, error, isBenign, context) {
|
|
673
|
+
if (isBenign) {
|
|
674
|
+
// Demote to a breadcrumb: the SDK's Sentry sink forwards `warn` as a
|
|
675
|
+
// breadcrumb, so this no longer creates its own billed issue while still
|
|
676
|
+
// preserving the trail if a genuine error follows.
|
|
677
|
+
logger_1.Logger.log.warn(tag, { message: errorText(error), ...(context ?? {}) });
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
logger_1.Logger.log.error(tag, error, context);
|
|
681
|
+
}
|
|
682
|
+
//# sourceMappingURL=chain-error.js.map
|