@augustdigital/sdk 8.17.0 → 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/lib/adapters/evm/index.js +4 -2
- package/lib/adapters/stellar/soroban.d.ts +8 -3
- package/lib/adapters/stellar/soroban.js +9 -4
- package/lib/adapters/sui/constants.d.ts +1 -1
- package/lib/adapters/sui/constants.js +6 -1
- package/lib/core/analytics/constants.d.ts +1 -1
- package/lib/core/analytics/constants.js +1 -1
- package/lib/core/analytics/sentry.d.ts +7 -0
- package/lib/core/analytics/sentry.js +182 -1
- package/lib/core/analytics/version.d.ts +1 -1
- package/lib/core/analytics/version.js +1 -1
- package/lib/core/attribution.d.ts +111 -0
- package/lib/core/attribution.js +142 -0
- package/lib/core/base.class.d.ts +17 -1
- package/lib/core/base.class.js +6 -1
- package/lib/core/constants/core.js +42 -15
- package/lib/core/constants/web3.d.ts +17 -0
- package/lib/core/constants/web3.js +22 -1
- package/lib/core/fetcher.js +10 -1
- package/lib/core/helpers/chain-error.d.ts +140 -0
- package/lib/core/helpers/chain-error.js +412 -0
- package/lib/core/helpers/chain-support.d.ts +80 -0
- package/lib/core/helpers/chain-support.js +115 -0
- package/lib/core/helpers/signer.d.ts +21 -0
- package/lib/core/helpers/signer.js +52 -0
- package/lib/core/helpers/web3.d.ts +172 -3
- package/lib/core/helpers/web3.js +357 -49
- package/lib/core/index.d.ts +2 -0
- package/lib/core/index.js +2 -0
- package/lib/evm/methods/crossChainVault.js +4 -0
- package/lib/modules/vaults/getters.js +115 -23
- package/lib/modules/vaults/main.d.ts +24 -3
- package/lib/modules/vaults/main.js +32 -31
- package/lib/modules/vaults/write.actions.d.ts +41 -1
- package/lib/modules/vaults/write.actions.js +302 -86
- package/lib/sdk.d.ts +11315 -10736
- package/lib/services/subgraph/vaults.js +85 -14
- package/package.json +1 -1
package/lib/core/base.class.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ISolanaConfig, IStellarConfig, IAddress, IChainId, IEnv, IProvidersConfig, IWSMonitorHeaders } from '../types';
|
|
2
2
|
import { type IAnalyticsConfig } from './analytics';
|
|
3
3
|
import { type IVersionCheckConfig } from './version-check';
|
|
4
|
+
import { type IAttributionConfig } from './attribution';
|
|
4
5
|
interface IKeys {
|
|
5
6
|
august?: string;
|
|
6
7
|
graph?: string;
|
|
@@ -83,6 +84,21 @@ export interface IAugustBase {
|
|
|
83
84
|
* a stale cross-environment base is higher blast-radius than a stale timeout.)
|
|
84
85
|
*/
|
|
85
86
|
publicApiBaseUrl?: string;
|
|
87
|
+
/**
|
|
88
|
+
* ERC-8021 calldata-suffix attribution (Base Builder Codes). When set,
|
|
89
|
+
* every EVM write sent through the SDK — ethers vault writes and the
|
|
90
|
+
* cross-chain (OVault) viem writes — carries the attribution suffix so
|
|
91
|
+
* offchain indexers (base.dev) can credit the transaction to your app.
|
|
92
|
+
* Omit to send unattributed transactions (the default).
|
|
93
|
+
*
|
|
94
|
+
* Note: this is a process-global override applied on EVERY construction —
|
|
95
|
+
* the last `AugustSDK` instantiated is authoritative, and one that omits
|
|
96
|
+
* `attribution` RESETS it (same semantics as `publicApiBaseUrl`).
|
|
97
|
+
*
|
|
98
|
+
* @throws Throws synchronously from the constructor when the builder codes
|
|
99
|
+
* are malformed — see {@link IAttributionConfig}.
|
|
100
|
+
*/
|
|
101
|
+
attribution?: IAttributionConfig;
|
|
86
102
|
}
|
|
87
103
|
interface IActiveNetwork {
|
|
88
104
|
chainId: IChainId;
|
|
@@ -107,7 +123,7 @@ export declare class AugustBase {
|
|
|
107
123
|
* @throws If `appName` is missing, malformed, or out of the allowed
|
|
108
124
|
* length range — see {@link IAugustBase.appName}.
|
|
109
125
|
*/
|
|
110
|
-
constructor({ appName, providers, keys, monitoring, analytics, versionCheck, timeoutMs, publicApiBaseUrl, }: IAugustBase);
|
|
126
|
+
constructor({ appName, providers, keys, monitoring, analytics, versionCheck, timeoutMs, publicApiBaseUrl, attribution, }: IAugustBase);
|
|
111
127
|
/**
|
|
112
128
|
* Verify API keys and authorize SDK usage.
|
|
113
129
|
* TODO: initialize class with appropriate keys and verify august key
|
package/lib/core/base.class.js
CHANGED
|
@@ -6,6 +6,7 @@ const logger_1 = require("./logger");
|
|
|
6
6
|
const analytics_1 = require("./analytics");
|
|
7
7
|
const version_check_1 = require("./version-check");
|
|
8
8
|
const fetcher_1 = require("./fetcher");
|
|
9
|
+
const attribution_1 = require("./attribution");
|
|
9
10
|
/**
|
|
10
11
|
* Validate an appName at SDK construction time.
|
|
11
12
|
*
|
|
@@ -55,7 +56,7 @@ class AugustBase {
|
|
|
55
56
|
* @throws If `appName` is missing, malformed, or out of the allowed
|
|
56
57
|
* length range — see {@link IAugustBase.appName}.
|
|
57
58
|
*/
|
|
58
|
-
constructor({ appName, providers = {}, keys, monitoring, analytics, versionCheck, timeoutMs, publicApiBaseUrl, }) {
|
|
59
|
+
constructor({ appName, providers = {}, keys, monitoring, analytics, versionCheck, timeoutMs, publicApiBaseUrl, attribution, }) {
|
|
59
60
|
// Validate first so the failure mode is a clear, actionable error before
|
|
60
61
|
// any provider / analytics side effects run.
|
|
61
62
|
this.appName = validateAppName(appName);
|
|
@@ -95,6 +96,10 @@ class AugustBase {
|
|
|
95
96
|
// prior staging instance would leak into a later prod instance in the same
|
|
96
97
|
// process — the inverse of the isolation this option exists to provide.
|
|
97
98
|
(0, fetcher_1.setPublicApiBaseUrl)(publicApiBaseUrl ?? null);
|
|
99
|
+
// ERC-8021 attribution follows the same reset-on-omit contract as
|
|
100
|
+
// `publicApiBaseUrl`: called unconditionally so a prior instance's
|
|
101
|
+
// builder codes never leak into a later instance in the same process.
|
|
102
|
+
(0, attribution_1.setAttribution)(attribution ?? null);
|
|
98
103
|
}
|
|
99
104
|
/**
|
|
100
105
|
* Verify API keys and authorize SDK usage.
|
|
@@ -5,6 +5,33 @@
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.DEFAULT_FETCH_OPTIONS = exports.WEBSERVER_ENDPOINTS = exports.WEBSERVER_URL = exports.PRICE_SERVER_URL = exports.REQUEST_TIMEOUT_MS = void 0;
|
|
7
7
|
const ethers_1 = require("ethers");
|
|
8
|
+
/**
|
|
9
|
+
* Build the path segment for a subaccount identifier.
|
|
10
|
+
*
|
|
11
|
+
* Why this exists: subaccounts are not EVM-only. Vault operators and loan
|
|
12
|
+
* borrowers can be Solana base58 keys or Stellar `G…`/`C…` addresses, and the
|
|
13
|
+
* backend keys those records by their native string form. Calling ethers'
|
|
14
|
+
* `getAddress()` on them throws `INVALID_ARGUMENT` before the request is ever
|
|
15
|
+
* made — which is how a Solana borrower turned every
|
|
16
|
+
* `getVaultAllocations` call for a mixed-chain vault into a
|
|
17
|
+
* `TypeError: invalid address` (see `getVaultAllocations:cefi` /
|
|
18
|
+
* `:otc` in Sentry).
|
|
19
|
+
*
|
|
20
|
+
* EVM addresses are still checksummed, because the backend expects the
|
|
21
|
+
* checksummed form for those and case-normalising avoids cache misses.
|
|
22
|
+
* Everything else is passed through URL-encoded so a malformed value can never
|
|
23
|
+
* inject extra path segments.
|
|
24
|
+
*
|
|
25
|
+
* @param subaccount - Subaccount identifier: EVM hex address, Solana base58
|
|
26
|
+
* public key, or Stellar strkey. Not validated beyond the EVM branch — the
|
|
27
|
+
* backend is the authority on whether a non-EVM identifier exists.
|
|
28
|
+
* @returns A single URL path segment safe to interpolate into an endpoint.
|
|
29
|
+
*/
|
|
30
|
+
function subaccountSegment(subaccount) {
|
|
31
|
+
return (0, ethers_1.isAddress)(subaccount)
|
|
32
|
+
? (0, ethers_1.getAddress)(subaccount)
|
|
33
|
+
: encodeURIComponent(subaccount);
|
|
34
|
+
}
|
|
8
35
|
/**
|
|
9
36
|
* Request timeout in milliseconds.
|
|
10
37
|
* Set to 90 seconds to accommodate slow API responses.
|
|
@@ -34,22 +61,22 @@ exports.WEBSERVER_ENDPOINTS = {
|
|
|
34
61
|
},
|
|
35
62
|
subaccount: {
|
|
36
63
|
list: (offset = 0, limit = 100) => `/subaccount?offset=${offset}&limit=${limit}`,
|
|
37
|
-
rewards: (subaccount) => `/subaccount/${(
|
|
38
|
-
tokens: (subaccount) => `/subaccount/${(
|
|
64
|
+
rewards: (subaccount) => `/subaccount/${subaccountSegment(subaccount)}/rewards`,
|
|
65
|
+
tokens: (subaccount) => `/subaccount/${subaccountSegment(subaccount)}/tokens`,
|
|
39
66
|
twap: {
|
|
40
|
-
create: (subaccount) => `/subaccount/${(
|
|
41
|
-
stop: (subaccount, id) => `/subaccount/${(
|
|
42
|
-
fills: (subaccount, id) => `/subaccount/${(
|
|
67
|
+
create: (subaccount) => `/subaccount/${subaccountSegment(subaccount)}/twap`,
|
|
68
|
+
stop: (subaccount, id) => `/subaccount/${subaccountSegment(subaccount)}/twap/${id}/stop`,
|
|
69
|
+
fills: (subaccount, id) => `/subaccount/${subaccountSegment(subaccount)}/twap/${id}/fills`,
|
|
43
70
|
},
|
|
44
|
-
debank: (subaccount) => `/subaccount/${(
|
|
45
|
-
health_factor: (subaccount) => `/subaccount/${(
|
|
46
|
-
summary: (subaccount) => `/subaccount/${(
|
|
47
|
-
batch: (subaccount) => `/subaccount/${(
|
|
48
|
-
loans: (subaccount, side = 'BOTH', active = true) => `/subaccount/${(
|
|
49
|
-
cefi: (subaccount) => `/subaccount/${(
|
|
50
|
-
otc_positions: (subaccount) => `/subaccount/${(
|
|
71
|
+
debank: (subaccount) => `/subaccount/${subaccountSegment(subaccount)}/debank`,
|
|
72
|
+
health_factor: (subaccount) => `/subaccount/${subaccountSegment(subaccount)}/health_factor`,
|
|
73
|
+
summary: (subaccount) => `/subaccount/${subaccountSegment(subaccount)}/summary`,
|
|
74
|
+
batch: (subaccount) => `/subaccount/${subaccountSegment(subaccount)}/tx_batcher_integrations`,
|
|
75
|
+
loans: (subaccount, side = 'BOTH', active = true) => `/subaccount/${subaccountSegment(subaccount)}/loans?side=${side}&active=${active}`,
|
|
76
|
+
cefi: (subaccount) => `/subaccount/${subaccountSegment(subaccount)}/cefi_position`,
|
|
77
|
+
otc_positions: (subaccount) => `/subaccount/${subaccountSegment(subaccount)}/otc_positions`,
|
|
51
78
|
loanByAddress: (loanAddress, chainId) => `/subaccount/loans/${encodeURIComponent(loanAddress)}?chain_id=${chainId}`,
|
|
52
|
-
_: (subaccount) => `/subaccount/${(
|
|
79
|
+
_: (subaccount) => `/subaccount/${subaccountSegment(subaccount)}`,
|
|
53
80
|
},
|
|
54
81
|
transactions: {
|
|
55
82
|
v2: (subaccount, startTime, endTime) => {
|
|
@@ -108,7 +135,7 @@ exports.WEBSERVER_ENDPOINTS = {
|
|
|
108
135
|
},
|
|
109
136
|
prices: (symbol) => `/prices/${symbol}`,
|
|
110
137
|
metrics: {
|
|
111
|
-
pnl: (subaccount, startTime, endTime) => `/metrics/pnl?subaccount_address=${(
|
|
138
|
+
pnl: (subaccount, startTime, endTime) => `/metrics/pnl?subaccount_address=${subaccountSegment(subaccount)}${startTime && endTime ? `&start=${startTime}&end=${endTime}` : ''}`,
|
|
112
139
|
vaultPerformanceFees: (params) => {
|
|
113
140
|
const q = new URLSearchParams({
|
|
114
141
|
vault_address: params.vaultAddress,
|
|
@@ -126,7 +153,7 @@ exports.WEBSERVER_ENDPOINTS = {
|
|
|
126
153
|
public: {
|
|
127
154
|
integrations: {
|
|
128
155
|
morpho: {
|
|
129
|
-
apy: (subaccount, vaultAddress) => `/integrations/morpho/apy?subaccount_address=${(
|
|
156
|
+
apy: (subaccount, vaultAddress) => `/integrations/morpho/apy?subaccount_address=${subaccountSegment(subaccount)}&vault_address=${(0, ethers_1.getAddress)(vaultAddress)}`,
|
|
130
157
|
},
|
|
131
158
|
},
|
|
132
159
|
tokenizedVault: {
|
|
@@ -11,6 +11,23 @@ export declare const SPECIAL_CHAINS: {
|
|
|
11
11
|
explorer: string;
|
|
12
12
|
};
|
|
13
13
|
};
|
|
14
|
+
/**
|
|
15
|
+
* Chain ID used to address Sui vaults.
|
|
16
|
+
*
|
|
17
|
+
* Unlike Solana (`-1`) and Stellar (`-3`), Sui does not use a synthetic
|
|
18
|
+
* negative ID — `101` is the SLIP-44-derived value the Sui adapter has always
|
|
19
|
+
* used. It lives here rather than in `adapters/sui/constants.ts` so that
|
|
20
|
+
* `core/` can recognise it without importing the adapter layer (which would
|
|
21
|
+
* create a `core → adapters` back-edge); the adapter re-exports it.
|
|
22
|
+
*/
|
|
23
|
+
export declare const SUI_CHAIN_ID = 101;
|
|
24
|
+
/**
|
|
25
|
+
* Chain IDs the SDK understands that are **not** served by an EVM JSON-RPC
|
|
26
|
+
* provider. Callers may legitimately pass these to vault methods without a
|
|
27
|
+
* matching entry in the `providers` map — the request routes through the
|
|
28
|
+
* Solana / Stellar / Sui adapter instead.
|
|
29
|
+
*/
|
|
30
|
+
export declare const NON_EVM_CHAIN_IDS: ReadonlySet<number>;
|
|
14
31
|
export declare const NATIVE_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
|
|
15
32
|
/**
|
|
16
33
|
* Decimal precision of the native gas token on every EVM chain this SDK
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.FALLBACK_RPC_URLS = exports.FALLBACK_CHAINID = exports.FALLBACK_DECIMALS = exports.MULTICALL3_VERIFIED_CHAINS = exports.MULTICALL3_ADDRESS = exports.AVAILABLE_CHAINS = exports.NETWORKS = exports.ORACLE_CONTRACTS = exports.MIN_ABIS = exports.EVM_NATIVE_DECIMALS = exports.NATIVE_ADDRESS = exports.SPECIAL_CHAINS = void 0;
|
|
3
|
+
exports.FALLBACK_RPC_URLS = exports.FALLBACK_CHAINID = exports.FALLBACK_DECIMALS = exports.MULTICALL3_VERIFIED_CHAINS = exports.MULTICALL3_ADDRESS = exports.AVAILABLE_CHAINS = exports.NETWORKS = exports.ORACLE_CONTRACTS = exports.MIN_ABIS = exports.EVM_NATIVE_DECIMALS = exports.NATIVE_ADDRESS = exports.NON_EVM_CHAIN_IDS = exports.SUI_CHAIN_ID = exports.SPECIAL_CHAINS = void 0;
|
|
4
4
|
// Special Chains
|
|
5
5
|
exports.SPECIAL_CHAINS = {
|
|
6
6
|
solana: {
|
|
@@ -14,6 +14,27 @@ exports.SPECIAL_CHAINS = {
|
|
|
14
14
|
explorer: 'https://stellar.expert',
|
|
15
15
|
},
|
|
16
16
|
};
|
|
17
|
+
/**
|
|
18
|
+
* Chain ID used to address Sui vaults.
|
|
19
|
+
*
|
|
20
|
+
* Unlike Solana (`-1`) and Stellar (`-3`), Sui does not use a synthetic
|
|
21
|
+
* negative ID — `101` is the SLIP-44-derived value the Sui adapter has always
|
|
22
|
+
* used. It lives here rather than in `adapters/sui/constants.ts` so that
|
|
23
|
+
* `core/` can recognise it without importing the adapter layer (which would
|
|
24
|
+
* create a `core → adapters` back-edge); the adapter re-exports it.
|
|
25
|
+
*/
|
|
26
|
+
exports.SUI_CHAIN_ID = 101;
|
|
27
|
+
/**
|
|
28
|
+
* Chain IDs the SDK understands that are **not** served by an EVM JSON-RPC
|
|
29
|
+
* provider. Callers may legitimately pass these to vault methods without a
|
|
30
|
+
* matching entry in the `providers` map — the request routes through the
|
|
31
|
+
* Solana / Stellar / Sui adapter instead.
|
|
32
|
+
*/
|
|
33
|
+
exports.NON_EVM_CHAIN_IDS = new Set([
|
|
34
|
+
exports.SPECIAL_CHAINS.solana.chainId,
|
|
35
|
+
exports.SPECIAL_CHAINS.stellar.chainId,
|
|
36
|
+
exports.SUI_CHAIN_ID,
|
|
37
|
+
]);
|
|
17
38
|
// General
|
|
18
39
|
exports.NATIVE_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
|
|
19
40
|
/**
|
package/lib/core/fetcher.js
CHANGED
|
@@ -23,6 +23,7 @@ const ethers_1 = require("ethers");
|
|
|
23
23
|
const abis_1 = require("../abis");
|
|
24
24
|
const lru_cache_1 = require("lru-cache");
|
|
25
25
|
const chain_address_1 = require("./helpers/chain-address");
|
|
26
|
+
const chain_error_1 = require("./helpers/chain-error");
|
|
26
27
|
const fetcher_1 = require("../services/coingecko/fetcher");
|
|
27
28
|
const logger_1 = require("./logger");
|
|
28
29
|
const ethers_2 = require("ethers");
|
|
@@ -705,7 +706,15 @@ async function _fetchTokenPriceInternal(symbol, provider, coinGeckoKey, headers)
|
|
|
705
706
|
if (foundVaultLpAsset?.vault && provider && version === 'evm-2') {
|
|
706
707
|
try {
|
|
707
708
|
const vaultContract = new ethers_2.Contract((0, ethers_1.getAddress)(foundVaultLpAsset?.vault), abis_1.ABI_TOKENIZED_VAULT_V2, provider);
|
|
708
|
-
|
|
709
|
+
// Same bounded, no-fallback retry the write paths use for this selector
|
|
710
|
+
// (`getReceiptTokenAddressOrThrow` in `core/helpers/web3`). It cannot be
|
|
711
|
+
// reused here: `helpers/web3` imports `fetchTokenizedVault` from this
|
|
712
|
+
// module, so importing it back would be a circular dependency (CLAUDE.md
|
|
713
|
+
// §9). A transient empty `eth_call` response is absorbed; a vault that
|
|
714
|
+
// genuinely lacks `lpTokenAddress()` still falls into the catch below
|
|
715
|
+
// after the attempts are spent.
|
|
716
|
+
const receiptAddress = await (0, chain_error_1.retryOnTransientRpc)('fetchTokenPrice:receiptToken', () => vaultContract.lpTokenAddress(), { vault: foundVaultLpAsset.vault }, (error) => (0, chain_error_1.isRetryableRpcError)(error) ||
|
|
717
|
+
(0, chain_error_1.isEmptyViewResponse)(error, chain_error_1.LP_TOKEN_ADDRESS_SELECTOR));
|
|
709
718
|
const receiptContract = new ethers_2.Contract(receiptAddress, [web3_1.MIN_ABIS.decimals], provider);
|
|
710
719
|
const decimals = Number(await receiptContract.decimals());
|
|
711
720
|
const sharePrice = await vaultContract.getSharePrice();
|
|
@@ -46,6 +46,146 @@ export declare function isUserRejectionError(error: unknown): boolean;
|
|
|
46
46
|
*/
|
|
47
47
|
export declare function isExpectedRevertError(error: unknown): boolean;
|
|
48
48
|
export declare function isInsufficientFundsError(error: unknown): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Is this error a transient RPC **transport** failure that is safe to retry,
|
|
51
|
+
* rather than a decision the chain made?
|
|
52
|
+
*
|
|
53
|
+
* Why this exists: the SDK's write paths poll `eth_getTransactionReceipt` to
|
|
54
|
+
* confirm a broadcast transaction. When the provider hiccups mid-poll, ethers
|
|
55
|
+
* surfaces `could not coalesce error (error={ "code": -32603, … "method":
|
|
56
|
+
* "eth_getTransactionReceipt" … })`. Historically that propagated out of
|
|
57
|
+
* `safeWaitForTx` and the SDK reported the write as **failed** — even though
|
|
58
|
+
* the transaction was broadcast, its hash was known, and it mined fine. Users
|
|
59
|
+
* then retried and hit `ERC20InsufficientBalance` because the first attempt had
|
|
60
|
+
* in fact succeeded. Classifying the failure as transport-level lets callers
|
|
61
|
+
* re-poll instead of lying to the user.
|
|
62
|
+
*
|
|
63
|
+
* Matches, in order of precedence:
|
|
64
|
+
* 1. **Veto** — anything with revert evidence ({@link hasRevertEvidence}:
|
|
65
|
+
* `CALL_EXCEPTION` carrying revert `data`, an `execution reverted` message,
|
|
66
|
+
* or an attached `receipt.status === 0`) returns `false`. Nodes reuse
|
|
67
|
+
* `-32603`/`-32000` for real reverts, so the veto must come first.
|
|
68
|
+
* 2. JSON-RPC / ethers transport codes — see `RETRYABLE_RPC_CODES`.
|
|
69
|
+
* 3. HTTP `429` and any `5xx` carried on the error.
|
|
70
|
+
* 4. Transport message fragments — see `RETRYABLE_RPC_PHRASES`.
|
|
71
|
+
*
|
|
72
|
+
* Retrying is only safe for **idempotent** work: re-reading an immutable value
|
|
73
|
+
* (`decimals()`) or re-polling a receipt for a hash that is already on the
|
|
74
|
+
* wire. Never use this to re-send a transaction.
|
|
75
|
+
*
|
|
76
|
+
* @param error - The caught value, of unknown type.
|
|
77
|
+
* @returns `true` when the failure is a transient transport fault worth
|
|
78
|
+
* retrying with backoff; `false` for chain-level decisions (reverts) and for
|
|
79
|
+
* anything unrecognised — the safe default is to surface the error.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```ts
|
|
83
|
+
* try {
|
|
84
|
+
* return await provider.waitForTransaction(hash, 1, 120_000);
|
|
85
|
+
* } catch (e) {
|
|
86
|
+
* if (!isRetryableRpcError(e)) throw e; // real revert — surface it
|
|
87
|
+
* await sleep(250);
|
|
88
|
+
* return await provider.waitForTransaction(hash, 1, 120_000);
|
|
89
|
+
* }
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export declare function isRetryableRpcError(error: unknown): boolean;
|
|
93
|
+
/**
|
|
94
|
+
* `lpTokenAddress()` — `keccak256("lpTokenAddress()")[0..4]`. The August `evm-2`
|
|
95
|
+
* tokenized vault's receipt-token getter, exported so the readers that invoke it
|
|
96
|
+
* can scope {@link isEmptyViewResponse} to exactly this call instead of
|
|
97
|
+
* duplicating the literal.
|
|
98
|
+
*/
|
|
99
|
+
export declare const LP_TOKEN_ADDRESS_SELECTOR = "0xf5ae497a";
|
|
100
|
+
/**
|
|
101
|
+
* Is this error an **empty RPC response to an argument-free view call** —
|
|
102
|
+
* i.e. a transport artefact wearing a revert's clothes?
|
|
103
|
+
*
|
|
104
|
+
* Why this is separate from {@link isRetryableRpcError}: when a provider
|
|
105
|
+
* truncates or 500s a response to `eth_call`, ethers reports
|
|
106
|
+
* `missing revert data (action="call", data="0x313ce567", …)` with a `null`
|
|
107
|
+
* `data` field. That is byte-for-byte the shape of a genuine revert with no
|
|
108
|
+
* reason string, so a general "transport" predicate cannot safely claim it —
|
|
109
|
+
* doing so would retry every data-less `CALL_EXCEPTION` in the SDK. This
|
|
110
|
+
* predicate narrows the claim to the one case where the ambiguity resolves:
|
|
111
|
+
* a **deployed ERC-20's `decimals()`/`symbol()`/`name()`/`totalSupply()` cannot
|
|
112
|
+
* legitimately revert**, because it takes no arguments and returns state fixed
|
|
113
|
+
* at deployment. An empty response there is the provider's fault, full stop.
|
|
114
|
+
*
|
|
115
|
+
* A match requires all of:
|
|
116
|
+
* 1. no revert evidence ({@link hasRevertEvidence}) — anything carrying real
|
|
117
|
+
* revert `data`, an `execution reverted` message, or a failed receipt is out;
|
|
118
|
+
* 2. a `missing revert data` message;
|
|
119
|
+
* 3. an `action` of `call` or `staticCall` — a read, never a state change;
|
|
120
|
+
* 4. **when a selector is derivable** from the error, that it is
|
|
121
|
+
* `expectedSelector` (if given) or one of
|
|
122
|
+
* {@link ARGUMENT_FREE_VIEW_SELECTORS}. When no selector can be recovered,
|
|
123
|
+
* conditions 1–3 stand on their own.
|
|
124
|
+
*
|
|
125
|
+
* Note the cost of a false positive is bounded and small: the caller retries an
|
|
126
|
+
* idempotent read a couple of times before surfacing the same error. The cost
|
|
127
|
+
* of a false negative is the production flood this predicate exists to stop.
|
|
128
|
+
*
|
|
129
|
+
* @param error - The caught value, of unknown type.
|
|
130
|
+
* @param expectedSelector - Optional `0x`-prefixed 4-byte selector the caller
|
|
131
|
+
* knows it invoked (e.g. `'0x313ce567'` for `decimals()`). When supplied, the
|
|
132
|
+
* error's own selector must match it — this stops a `decimals()` retry from
|
|
133
|
+
* firing on an unrelated view call that happened to fail the same way.
|
|
134
|
+
* @returns `true` when the failure is an empty provider response to a view call
|
|
135
|
+
* that cannot revert, and is therefore safe to retry.
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```ts
|
|
139
|
+
* try { return Number(await erc20.decimals()); }
|
|
140
|
+
* catch (e) {
|
|
141
|
+
* if (!isEmptyViewResponse(e, '0x313ce567')) throw e; // real problem
|
|
142
|
+
* return Number(await erc20.decimals()); // provider blip
|
|
143
|
+
* }
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
export declare function isEmptyViewResponse(error: unknown, expectedSelector?: string): boolean;
|
|
147
|
+
/**
|
|
148
|
+
* Run an **idempotent** RPC read, retrying with exponential backoff while the
|
|
149
|
+
* failure classifies as a transient transport fault
|
|
150
|
+
* ({@link isRetryableRpcError}).
|
|
151
|
+
*
|
|
152
|
+
* Lives next to the classifiers it consumes so there is exactly one retry
|
|
153
|
+
* implementation in the SDK: both the receipt-poll fallback in the vault write
|
|
154
|
+
* paths and the cached `decimals()` reader in `core/helpers/web3.ts` call this.
|
|
155
|
+
*
|
|
156
|
+
* Only safe for operations that can be repeated without side effects: polling
|
|
157
|
+
* `eth_getTransactionReceipt` for an already-broadcast hash, or re-reading an
|
|
158
|
+
* immutable value such as `decimals()`. **Never wrap a transaction send in
|
|
159
|
+
* this.**
|
|
160
|
+
*
|
|
161
|
+
* Anything that is not a transport fault (a genuine revert, a user rejection,
|
|
162
|
+
* an insufficient-funds rejection) is rethrown on the first attempt with no
|
|
163
|
+
* delay, so real failures still fail fast.
|
|
164
|
+
*
|
|
165
|
+
* @param tag - Low-cardinality log label for the retry breadcrumb.
|
|
166
|
+
* @param operation - The idempotent async read to run.
|
|
167
|
+
* @param context - Extra structured context for the retry breadcrumb (e.g.
|
|
168
|
+
* `{ hash }`). Sanitized by the logger before transport.
|
|
169
|
+
* @param isRetryable - Predicate deciding whether a caught error warrants
|
|
170
|
+
* another attempt. Defaults to the strict transport definition
|
|
171
|
+
* ({@link isRetryableRpcError}); pass a wider one only where the call site
|
|
172
|
+
* can prove the extra shape is also a provider artefact — the only such case
|
|
173
|
+
* today is the selector-scoped {@link isEmptyViewResponse} used by
|
|
174
|
+
* `getDecimalsOrThrow`.
|
|
175
|
+
* @returns Whatever `operation` resolves to on the first successful attempt.
|
|
176
|
+
* @throws The last error thrown by `operation` once retries are exhausted, or
|
|
177
|
+
* immediately when the error is not retryable.
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```ts
|
|
181
|
+
* const receipt = await retryOnTransientRpc(
|
|
182
|
+
* 'safeWaitForTx:transport-retry',
|
|
183
|
+
* () => provider.waitForTransaction(hash, 1, 120_000),
|
|
184
|
+
* { hash },
|
|
185
|
+
* );
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
export declare function retryOnTransientRpc<T>(tag: string, operation: () => Promise<T>, context?: Record<string, unknown>, isRetryable?: (error: unknown) => boolean): Promise<T>;
|
|
49
189
|
/**
|
|
50
190
|
* Log a caught chain error at the severity its category warrants, without
|
|
51
191
|
* swallowing it. When `isBenign` is `true` the failure is recorded as a `warn`
|