@augustdigital/sdk 8.21.1 → 8.24.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/lib/core/analytics/sentry.js +26 -2
- package/lib/core/analytics/version.d.ts +1 -1
- package/lib/core/analytics/version.js +1 -1
- package/lib/core/attribution.d.ts +24 -8
- package/lib/core/attribution.js +21 -5
- package/lib/core/constants/vaults.d.ts +1 -1
- package/lib/core/constants/vaults.js +1 -1
- package/lib/core/helpers/signer.d.ts +2 -1
- package/lib/core/helpers/signer.js +7 -5
- package/lib/core/helpers/web3.d.ts +10 -4
- package/lib/core/helpers/web3.js +31 -5
- package/lib/core/logger/slack.d.ts +35 -2
- package/lib/core/logger/slack.js +59 -5
- package/lib/evm/methods/crossChainVault.d.ts +5 -1
- package/lib/evm/methods/crossChainVault.js +8 -4
- package/lib/modules/vaults/getters.js +120 -4
- package/lib/sdk.d.ts +82 -17
- package/lib/services/subgraph/vaults.js +7 -7
- package/package.json +2 -1
|
@@ -384,12 +384,16 @@ function initializeSentry(config, environment, walletAddress, apiKey, appName) {
|
|
|
384
384
|
? config.tracesSampleRate
|
|
385
385
|
: 0.1; // Phase 2 default — verified partners can opt into 1.0 explicitly
|
|
386
386
|
const tracesSampleRate = Math.max(0, Math.min(1, rawSampleRate));
|
|
387
|
+
// Hoisted so `release`, `august.sdk_version`, and the deprecated
|
|
388
|
+
// `sdk.version` are provably the same value rather than three separate
|
|
389
|
+
// reads of `./version`.
|
|
390
|
+
const sdkVersion = getSDKVersion();
|
|
387
391
|
sdk.init({
|
|
388
392
|
dsn: constants_1.SENTRY_DSN,
|
|
389
393
|
tracesSampleRate,
|
|
390
394
|
enableTracing: true,
|
|
391
395
|
environment: environment.toLowerCase(),
|
|
392
|
-
release: `august-sdk@${
|
|
396
|
+
release: `august-sdk@${sdkVersion}`,
|
|
393
397
|
sendDefaultPii: true,
|
|
394
398
|
// SDK errors route through Logger.setSentrySink → captureException directly,
|
|
395
399
|
// so captureConsoleIntegration is redundant here. Omitting it also prevents
|
|
@@ -445,7 +449,27 @@ function initializeSentry(config, environment, walletAddress, apiKey, appName) {
|
|
|
445
449
|
updateUser(walletAddress, environment);
|
|
446
450
|
// Set global SDK tags
|
|
447
451
|
safeSetTag('sdk', 'august-digital');
|
|
448
|
-
|
|
452
|
+
// `august.sdk_version` is the queryable dimension for "which SDK build is
|
|
453
|
+
// this partner on" — the question that decides whether an error is a live
|
|
454
|
+
// defect or an already-shipped fix the consumer hasn't picked up.
|
|
455
|
+
//
|
|
456
|
+
// It exists because `sdk.version` (set below, kept for back-compat) is a
|
|
457
|
+
// RESERVED Sentry field: Sentry populates it with the version of its own
|
|
458
|
+
// client (`@sentry/core`). The custom tag is still stored, but a bare
|
|
459
|
+
// `sdk.version` in Discover resolves to Sentry's value, so the natural
|
|
460
|
+
// query silently answers the wrong question — uniform across every
|
|
461
|
+
// consumer, which makes it look like a working answer. Reaching the real
|
|
462
|
+
// value requires `tags[sdk.version]`, which nobody remembers to type.
|
|
463
|
+
//
|
|
464
|
+
// Both are low-cardinality version strings, so carrying the pair costs
|
|
465
|
+
// nothing against the tag budget (CLAUDE.md §8.4).
|
|
466
|
+
safeSetTag('august.sdk_version', sdkVersion);
|
|
467
|
+
/**
|
|
468
|
+
* @deprecated Shadowed by Sentry's reserved `sdk.version` field. Query
|
|
469
|
+
* `august.sdk_version` instead. Retained so existing saved queries,
|
|
470
|
+
* dashboards, and alert rules keep resolving; remove in the next major.
|
|
471
|
+
*/
|
|
472
|
+
safeSetTag('sdk.version', sdkVersion);
|
|
449
473
|
safeSetTag('sdk.runtime', (0, sentry_runtime_1.getSentryRuntime)());
|
|
450
474
|
// `app.name` is the dimension we filter on in Sentry to attribute
|
|
451
475
|
// events to a specific consuming application — see SDK quickstart docs.
|
|
@@ -42,10 +42,15 @@ export interface IAttributionConfig {
|
|
|
42
42
|
/**
|
|
43
43
|
* EVM chain IDs to attribute. Omit to attribute writes on every EVM chain
|
|
44
44
|
* (the suffix is inert on chains without an ERC-8021 indexer and costs
|
|
45
|
-
* ~16 gas per non-zero byte). When set,
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
45
|
+
* ~16 gas per non-zero byte). When set, gating is fail-closed: writes on
|
|
46
|
+
* other chains — and writes whose chain ID cannot be determined — are sent
|
|
47
|
+
* without the suffix.
|
|
48
|
+
*
|
|
49
|
+
* Over-attribution is not harmless. The suffix makes calldata longer than
|
|
50
|
+
* the ABI encoding of the call, which breaks clear-signing on hardware
|
|
51
|
+
* wallets: a Ledger rejects an over-long ERC-20 `approve` with
|
|
52
|
+
* `EthAppCommandError: Invalid data 6a80`, so an unattributed chain that
|
|
53
|
+
* receives the suffix anyway cannot be transacted on from a Ledger at all.
|
|
49
54
|
*/
|
|
50
55
|
chains?: number[];
|
|
51
56
|
}
|
|
@@ -83,13 +88,23 @@ export declare function buildAttributionSuffix(codes: string[]): string;
|
|
|
83
88
|
* init, not on the first write.
|
|
84
89
|
*/
|
|
85
90
|
export declare function setAttribution(config: IAttributionConfig | null): void;
|
|
91
|
+
/**
|
|
92
|
+
* Whether attribution is configured at all, independent of any chain gate.
|
|
93
|
+
*
|
|
94
|
+
* Call sites that need to know whether to resolve a chain ID before asking
|
|
95
|
+
* for the suffix use this; {@link getAttributionSuffix} applies the gate.
|
|
96
|
+
*
|
|
97
|
+
* @returns `true` when builder codes are configured.
|
|
98
|
+
*/
|
|
99
|
+
export declare function isAttributionEnabled(): boolean;
|
|
86
100
|
/**
|
|
87
101
|
* Return the active ERC-8021 suffix for a write on the given chain, or
|
|
88
|
-
* `undefined` when attribution is off or the chain is
|
|
102
|
+
* `undefined` when attribution is off or the chain is not attributed.
|
|
89
103
|
*
|
|
90
104
|
* @param chainId EVM chain ID of the transaction, when the call site knows
|
|
91
|
-
* it. When
|
|
92
|
-
*
|
|
105
|
+
* it. When a `chains` restriction is configured, gating is fail-closed: an
|
|
106
|
+
* omitted chain ID yields no suffix, since a suffix on an unattributed
|
|
107
|
+
* chain buys nothing and breaks hardware-wallet clear-signing (see
|
|
93
108
|
* {@link IAttributionConfig.chains}).
|
|
94
109
|
* @returns `0x`-prefixed hex suffix, or `undefined` when nothing should be
|
|
95
110
|
* appended.
|
|
@@ -99,7 +114,8 @@ export declare function getAttributionSuffix(chainId?: number): string | undefin
|
|
|
99
114
|
* Append the active attribution suffix to calldata.
|
|
100
115
|
*
|
|
101
116
|
* No-ops (returns `data` unchanged) when attribution is off, the chain is
|
|
102
|
-
*
|
|
117
|
+
* not attributed (including an unknown chain under a `chains` restriction),
|
|
118
|
+
* `data` is empty/absent (plain value transfers are never
|
|
103
119
|
* attributed), or `data` already ends with the ERC-8021 marker (guards
|
|
104
120
|
* against double-appending when an upstream layer — e.g. a wagmi config
|
|
105
121
|
* `dataSuffix` — already attributed the transaction).
|
package/lib/core/attribution.js
CHANGED
|
@@ -18,6 +18,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
18
18
|
exports.ERC8021_MARKER = void 0;
|
|
19
19
|
exports.buildAttributionSuffix = buildAttributionSuffix;
|
|
20
20
|
exports.setAttribution = setAttribution;
|
|
21
|
+
exports.isAttributionEnabled = isAttributionEnabled;
|
|
21
22
|
exports.getAttributionSuffix = getAttributionSuffix;
|
|
22
23
|
exports.appendAttributionSuffix = appendAttributionSuffix;
|
|
23
24
|
/**
|
|
@@ -96,13 +97,25 @@ function setAttribution(config) {
|
|
|
96
97
|
? config.chains
|
|
97
98
|
: null;
|
|
98
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Whether attribution is configured at all, independent of any chain gate.
|
|
102
|
+
*
|
|
103
|
+
* Call sites that need to know whether to resolve a chain ID before asking
|
|
104
|
+
* for the suffix use this; {@link getAttributionSuffix} applies the gate.
|
|
105
|
+
*
|
|
106
|
+
* @returns `true` when builder codes are configured.
|
|
107
|
+
*/
|
|
108
|
+
function isAttributionEnabled() {
|
|
109
|
+
return activeSuffix !== null;
|
|
110
|
+
}
|
|
99
111
|
/**
|
|
100
112
|
* Return the active ERC-8021 suffix for a write on the given chain, or
|
|
101
|
-
* `undefined` when attribution is off or the chain is
|
|
113
|
+
* `undefined` when attribution is off or the chain is not attributed.
|
|
102
114
|
*
|
|
103
115
|
* @param chainId EVM chain ID of the transaction, when the call site knows
|
|
104
|
-
* it. When
|
|
105
|
-
*
|
|
116
|
+
* it. When a `chains` restriction is configured, gating is fail-closed: an
|
|
117
|
+
* omitted chain ID yields no suffix, since a suffix on an unattributed
|
|
118
|
+
* chain buys nothing and breaks hardware-wallet clear-signing (see
|
|
106
119
|
* {@link IAttributionConfig.chains}).
|
|
107
120
|
* @returns `0x`-prefixed hex suffix, or `undefined` when nothing should be
|
|
108
121
|
* appended.
|
|
@@ -110,7 +123,9 @@ function setAttribution(config) {
|
|
|
110
123
|
function getAttributionSuffix(chainId) {
|
|
111
124
|
if (!activeSuffix)
|
|
112
125
|
return undefined;
|
|
113
|
-
if (activeChains
|
|
126
|
+
if (activeChains) {
|
|
127
|
+
if (typeof chainId !== 'number')
|
|
128
|
+
return undefined;
|
|
114
129
|
if (!activeChains.includes(chainId))
|
|
115
130
|
return undefined;
|
|
116
131
|
}
|
|
@@ -120,7 +135,8 @@ function getAttributionSuffix(chainId) {
|
|
|
120
135
|
* Append the active attribution suffix to calldata.
|
|
121
136
|
*
|
|
122
137
|
* No-ops (returns `data` unchanged) when attribution is off, the chain is
|
|
123
|
-
*
|
|
138
|
+
* not attributed (including an unknown chain under a `chains` restriction),
|
|
139
|
+
* `data` is empty/absent (plain value transfers are never
|
|
124
140
|
* attributed), or `data` already ends with the ERC-8021 marker (guards
|
|
125
141
|
* against double-appending when an upstream layer — e.g. a wagmi config
|
|
126
142
|
* `dataSuffix` — already attributed the transaction).
|
|
@@ -69,7 +69,7 @@ export declare const NEMO_VAULT_ADDRESS = "0xa422c3018c46ba90a14acd14f96cb60616f
|
|
|
69
69
|
* Subgraph base URL
|
|
70
70
|
* @deprecated use getVaultMetadata to fetch subgraph URLs from the backend
|
|
71
71
|
*/
|
|
72
|
-
export declare const GOLDSKY_BASE_URL = "https://api.goldsky.com/api/
|
|
72
|
+
export declare const GOLDSKY_BASE_URL = "https://api.goldsky.com/api/public/project_cm9g0xy3o4j6v01vd34r3hvv9/subgraphs";
|
|
73
73
|
/**
|
|
74
74
|
* Subgraph URLs
|
|
75
75
|
* @deprecated use getVaultMetadata to fetch subgraph URLs from the backend
|
|
@@ -131,7 +131,7 @@ exports.NEMO_VAULT_ADDRESS = '0xa422c3018c46ba90a14acd14f96cb60616f5c91b';
|
|
|
131
131
|
* Subgraph base URL
|
|
132
132
|
* @deprecated use getVaultMetadata to fetch subgraph URLs from the backend
|
|
133
133
|
*/
|
|
134
|
-
exports.GOLDSKY_BASE_URL = 'https://api.goldsky.com/api/
|
|
134
|
+
exports.GOLDSKY_BASE_URL = 'https://api.goldsky.com/api/public/project_cm9g0xy3o4j6v01vd34r3hvv9/subgraphs';
|
|
135
135
|
/**
|
|
136
136
|
* Subgraph URLs
|
|
137
137
|
* @deprecated use getVaultMetadata to fetch subgraph URLs from the backend
|
|
@@ -44,7 +44,8 @@ export type CompatibleSigner = Signer | Wallet | any;
|
|
|
44
44
|
* already ending in the ERC-8021 marker is left untouched. When the
|
|
45
45
|
* configured `chains` list requires a chain check and the transaction does
|
|
46
46
|
* not carry a `chainId`, the signer's provider network is consulted (one
|
|
47
|
-
* cached RPC call)
|
|
47
|
+
* cached RPC call); if that lookup fails the transaction is sent
|
|
48
|
+
* unattributed.
|
|
48
49
|
*
|
|
49
50
|
* @param signer Normalized ethers Signer or Wallet.
|
|
50
51
|
* @returns A proxied signer with an attribution-aware `sendTransaction`.
|
|
@@ -107,7 +107,8 @@ async function normalizeSigner(signer) {
|
|
|
107
107
|
* already ending in the ERC-8021 marker is left untouched. When the
|
|
108
108
|
* configured `chains` list requires a chain check and the transaction does
|
|
109
109
|
* not carry a `chainId`, the signer's provider network is consulted (one
|
|
110
|
-
* cached RPC call)
|
|
110
|
+
* cached RPC call); if that lookup fails the transaction is sent
|
|
111
|
+
* unattributed.
|
|
111
112
|
*
|
|
112
113
|
* @param signer Normalized ethers Signer or Wallet.
|
|
113
114
|
* @returns A proxied signer with an attribution-aware `sendTransaction`.
|
|
@@ -117,7 +118,7 @@ function wrapSignerWithAttribution(signer) {
|
|
|
117
118
|
get(target, prop) {
|
|
118
119
|
if (prop === 'sendTransaction') {
|
|
119
120
|
return async (tx) => {
|
|
120
|
-
if (!(0, attribution_1.
|
|
121
|
+
if (!(0, attribution_1.isAttributionEnabled)() || typeof tx?.data !== 'string') {
|
|
121
122
|
return target.sendTransaction(tx);
|
|
122
123
|
}
|
|
123
124
|
let chainId = tx.chainId != null ? Number(tx.chainId) : undefined;
|
|
@@ -126,9 +127,10 @@ function wrapSignerWithAttribution(signer) {
|
|
|
126
127
|
chainId = Number((await target.provider.getNetwork()).chainId);
|
|
127
128
|
}
|
|
128
129
|
catch {
|
|
129
|
-
// Unknown chain: fall through with chainId undefined
|
|
130
|
-
//
|
|
131
|
-
//
|
|
130
|
+
// Unknown chain: fall through with chainId undefined. Under a
|
|
131
|
+
// `chains` restriction that means no suffix — attributing a
|
|
132
|
+
// chain we cannot identify risks breaking hardware-wallet
|
|
133
|
+
// clear-signing for a gain we cannot confirm.
|
|
132
134
|
}
|
|
133
135
|
}
|
|
134
136
|
const data = (0, attribution_1.appendAttributionSuffix)(tx.data, chainId);
|
|
@@ -95,10 +95,16 @@ export { explorerLink } from './explorer-link';
|
|
|
95
95
|
* Fetch token decimals from contract or Solana mint.
|
|
96
96
|
* Results are cached to minimize RPC calls.
|
|
97
97
|
*
|
|
98
|
-
* **Never throws** — a failed read
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
98
|
+
* **Never throws** — a failed read resolves `undefined`. Callers that must not
|
|
99
|
+
* silently proceed on an unknown scale (any path that encodes an amount) should
|
|
100
|
+
* use {@link getDecimalsOrThrow} instead: feeding `undefined` into
|
|
101
|
+
* `toNormalizedBn` silently defaults to 18 decimals.
|
|
102
|
+
*
|
|
103
|
+
* Transient transport faults (provider rate limits, socket resets, an empty
|
|
104
|
+
* response to `decimals()`) are logged at `warn` — a breadcrumb, not a
|
|
105
|
+
* standalone Sentry issue — since the caller sees the same `undefined` either
|
|
106
|
+
* way. Everything else is logged at `error`. The read itself is **not**
|
|
107
|
+
* retried here; that is opt-in via {@link getDecimalsOrThrow}.
|
|
102
108
|
*
|
|
103
109
|
* @param provider Web3 provider
|
|
104
110
|
* @param address Token contract address or Solana mint
|
package/lib/core/helpers/web3.js
CHANGED
|
@@ -395,10 +395,16 @@ async function fetchDecimals(runner, address, isVault) {
|
|
|
395
395
|
* Fetch token decimals from contract or Solana mint.
|
|
396
396
|
* Results are cached to minimize RPC calls.
|
|
397
397
|
*
|
|
398
|
-
* **Never throws** — a failed read
|
|
399
|
-
*
|
|
400
|
-
*
|
|
401
|
-
*
|
|
398
|
+
* **Never throws** — a failed read resolves `undefined`. Callers that must not
|
|
399
|
+
* silently proceed on an unknown scale (any path that encodes an amount) should
|
|
400
|
+
* use {@link getDecimalsOrThrow} instead: feeding `undefined` into
|
|
401
|
+
* `toNormalizedBn` silently defaults to 18 decimals.
|
|
402
|
+
*
|
|
403
|
+
* Transient transport faults (provider rate limits, socket resets, an empty
|
|
404
|
+
* response to `decimals()`) are logged at `warn` — a breadcrumb, not a
|
|
405
|
+
* standalone Sentry issue — since the caller sees the same `undefined` either
|
|
406
|
+
* way. Everything else is logged at `error`. The read itself is **not**
|
|
407
|
+
* retried here; that is opt-in via {@link getDecimalsOrThrow}.
|
|
402
408
|
*
|
|
403
409
|
* @param provider Web3 provider
|
|
404
410
|
* @param address Token contract address or Solana mint
|
|
@@ -422,7 +428,27 @@ const getDecimals = async (provider, address, isVault = true) => {
|
|
|
422
428
|
return await sharedDecimalsRequest(key, () => fetchDecimals(provider, address, isVault));
|
|
423
429
|
}
|
|
424
430
|
catch (e) {
|
|
425
|
-
|
|
431
|
+
// Severity split. A provider rate-limit or socket fault is not a defect in
|
|
432
|
+
// the SDK or the caller's input — the read resolves `undefined` either way,
|
|
433
|
+
// exactly as documented, and logging it at error level made a QuickNode
|
|
434
|
+
// `50/second request limit reached` burst the fourth highest-volume issue
|
|
435
|
+
// in Sentry with nothing actionable in it. Genuine failures (a non-ERC-20
|
|
436
|
+
// at this address, a real revert) stay at error.
|
|
437
|
+
//
|
|
438
|
+
// Note this only changes the log level. The no-retry contract of this
|
|
439
|
+
// lenient reader is deliberate and unchanged — callers that must not
|
|
440
|
+
// proceed on an unknown scale use `getDecimalsOrThrow`, which retries.
|
|
441
|
+
const isTransient = (0, chain_error_1.isRetryableRpcError)(e) || (0, chain_error_1.isEmptyViewResponse)(e, DECIMALS_SELECTOR);
|
|
442
|
+
if (isTransient) {
|
|
443
|
+
logger_1.Logger.log.warn('getDecimals', {
|
|
444
|
+
address,
|
|
445
|
+
reason: 'transient RPC failure',
|
|
446
|
+
message: e instanceof Error ? e.message : String(e),
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
logger_1.Logger.log.error('getDecimals', `${address}::${e}`);
|
|
451
|
+
}
|
|
426
452
|
return undefined;
|
|
427
453
|
}
|
|
428
454
|
};
|
|
@@ -1,7 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Environment variable holding the Slack incoming-webhook to post SDK alerts
|
|
3
|
+
* to. Accepts either the full `https://hooks.slack.com/services/T…/B…/x…` URL
|
|
4
|
+
* or the bare `T…/B…/x…` path suffix.
|
|
3
5
|
*/
|
|
4
|
-
export declare const
|
|
6
|
+
export declare const SLACK_WEBHOOK_ENV_VAR = "AUGUST_SDK_SLACK_WEBHOOK_URL";
|
|
7
|
+
/**
|
|
8
|
+
* Resolve the Slack webhook to alert through.
|
|
9
|
+
*
|
|
10
|
+
* Why this replaced a hardcoded constant: a webhook path is a **bearer
|
|
11
|
+
* credential** — anyone holding it can post to the channel. The previous
|
|
12
|
+
* default embedded August's own webhook as a string literal, which shipped in
|
|
13
|
+
* every published tarball and in the generated `.d.ts`, handing every consumer
|
|
14
|
+
* of the SDK write access to an internal Slack channel (CLAUDE.md §5: no
|
|
15
|
+
* secrets in client-reachable code). It also meant a consumer's alerts went to
|
|
16
|
+
* August's channel rather than their own, with no way to redirect them.
|
|
17
|
+
*
|
|
18
|
+
* Resolution order:
|
|
19
|
+
* 1. `explicit` — passed by the caller, wins outright;
|
|
20
|
+
* 2. `AUGUST_SDK_SLACK_WEBHOOK_URL` in the environment;
|
|
21
|
+
* 3. none — alerting is disabled and the call becomes a no-op.
|
|
22
|
+
*
|
|
23
|
+
* Reading the env var per call (rather than once at module load) means a
|
|
24
|
+
* consumer configuring it after import still gets alerts, and a test can set
|
|
25
|
+
* and unset it without re-importing the module.
|
|
26
|
+
*
|
|
27
|
+
* @param explicit - Caller-supplied webhook, full URL or bare path suffix.
|
|
28
|
+
* @returns The bare `T…/B…/x…` path suffix, or `''` when unconfigured.
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveSlackWebhookUrl(explicit?: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* @deprecated Was a hardcoded webhook credential baked into the published
|
|
33
|
+
* bundle. It now resolves from {@link SLACK_WEBHOOK_ENV_VAR} and is `''` when
|
|
34
|
+
* unset. Call {@link resolveSlackWebhookUrl} instead — this export exists only
|
|
35
|
+
* so existing imports keep compiling, and is removed in the next major.
|
|
36
|
+
*/
|
|
37
|
+
export declare const DEFAULT_SLACK_WEBHOOK_URL = "";
|
|
5
38
|
declare function error(options: {
|
|
6
39
|
title: string;
|
|
7
40
|
error: string;
|
package/lib/core/logger/slack.js
CHANGED
|
@@ -1,16 +1,70 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SLACK = exports.DEFAULT_SLACK_WEBHOOK_URL = void 0;
|
|
3
|
+
exports.SLACK = exports.DEFAULT_SLACK_WEBHOOK_URL = exports.SLACK_WEBHOOK_ENV_VAR = void 0;
|
|
4
|
+
exports.resolveSlackWebhookUrl = resolveSlackWebhookUrl;
|
|
4
5
|
const core_1 = require("../helpers/core");
|
|
5
6
|
const explorer_link_1 = require("../helpers/explorer-link");
|
|
6
7
|
const sanitize_1 = require("../analytics/sanitize");
|
|
8
|
+
const env_1 = require("../analytics/env");
|
|
7
9
|
/**
|
|
8
|
-
*
|
|
10
|
+
* Environment variable holding the Slack incoming-webhook to post SDK alerts
|
|
11
|
+
* to. Accepts either the full `https://hooks.slack.com/services/T…/B…/x…` URL
|
|
12
|
+
* or the bare `T…/B…/x…` path suffix.
|
|
9
13
|
*/
|
|
10
|
-
exports.
|
|
14
|
+
exports.SLACK_WEBHOOK_ENV_VAR = 'AUGUST_SDK_SLACK_WEBHOOK_URL';
|
|
15
|
+
/** `https://hooks.slack.com/services/` — stripped so both forms are accepted. */
|
|
16
|
+
const SLACK_WEBHOOK_PREFIX = 'https://hooks.slack.com/services/';
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the Slack webhook to alert through.
|
|
19
|
+
*
|
|
20
|
+
* Why this replaced a hardcoded constant: a webhook path is a **bearer
|
|
21
|
+
* credential** — anyone holding it can post to the channel. The previous
|
|
22
|
+
* default embedded August's own webhook as a string literal, which shipped in
|
|
23
|
+
* every published tarball and in the generated `.d.ts`, handing every consumer
|
|
24
|
+
* of the SDK write access to an internal Slack channel (CLAUDE.md §5: no
|
|
25
|
+
* secrets in client-reachable code). It also meant a consumer's alerts went to
|
|
26
|
+
* August's channel rather than their own, with no way to redirect them.
|
|
27
|
+
*
|
|
28
|
+
* Resolution order:
|
|
29
|
+
* 1. `explicit` — passed by the caller, wins outright;
|
|
30
|
+
* 2. `AUGUST_SDK_SLACK_WEBHOOK_URL` in the environment;
|
|
31
|
+
* 3. none — alerting is disabled and the call becomes a no-op.
|
|
32
|
+
*
|
|
33
|
+
* Reading the env var per call (rather than once at module load) means a
|
|
34
|
+
* consumer configuring it after import still gets alerts, and a test can set
|
|
35
|
+
* and unset it without re-importing the module.
|
|
36
|
+
*
|
|
37
|
+
* @param explicit - Caller-supplied webhook, full URL or bare path suffix.
|
|
38
|
+
* @returns The bare `T…/B…/x…` path suffix, or `''` when unconfigured.
|
|
39
|
+
*/
|
|
40
|
+
function resolveSlackWebhookUrl(explicit) {
|
|
41
|
+
const raw = explicit?.trim() || (0, env_1.readEnv)(exports.SLACK_WEBHOOK_ENV_VAR)?.trim() || '';
|
|
42
|
+
return raw.startsWith(SLACK_WEBHOOK_PREFIX)
|
|
43
|
+
? raw.slice(SLACK_WEBHOOK_PREFIX.length)
|
|
44
|
+
: raw;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* @deprecated Was a hardcoded webhook credential baked into the published
|
|
48
|
+
* bundle. It now resolves from {@link SLACK_WEBHOOK_ENV_VAR} and is `''` when
|
|
49
|
+
* unset. Call {@link resolveSlackWebhookUrl} instead — this export exists only
|
|
50
|
+
* so existing imports keep compiling, and is removed in the next major.
|
|
51
|
+
*/
|
|
52
|
+
exports.DEFAULT_SLACK_WEBHOOK_URL = '';
|
|
53
|
+
/** One-shot guard so an unconfigured webhook warns once, not per alert. */
|
|
54
|
+
let warnedUnconfigured = false;
|
|
11
55
|
function error(options) {
|
|
12
56
|
const { title, error, poolAddress, chainId, slackWebookUrl, address } = options;
|
|
13
|
-
|
|
57
|
+
// Falls back to the environment so a caller that passes nothing still
|
|
58
|
+
// alerts, provided the deployment configured a webhook of its own.
|
|
59
|
+
const webhookPath = resolveSlackWebhookUrl(slackWebookUrl);
|
|
60
|
+
if (!webhookPath) {
|
|
61
|
+
// Silence here used to be the hardcoded-default's job. Say it once, so a
|
|
62
|
+
// deployment that expected alerts learns they are off instead of watching
|
|
63
|
+
// an empty channel. Never log the webhook value itself.
|
|
64
|
+
if (!warnedUnconfigured) {
|
|
65
|
+
warnedUnconfigured = true;
|
|
66
|
+
console.warn(`#Slack.error: no webhook configured — set ${exports.SLACK_WEBHOOK_ENV_VAR} to enable Slack alerts. Alerts are disabled.`);
|
|
67
|
+
}
|
|
14
68
|
return;
|
|
15
69
|
}
|
|
16
70
|
if (!error) {
|
|
@@ -34,7 +88,7 @@ function error(options) {
|
|
|
34
88
|
return;
|
|
35
89
|
}
|
|
36
90
|
(async () => {
|
|
37
|
-
const webhookUrl =
|
|
91
|
+
const webhookUrl = `${SLACK_WEBHOOK_PREFIX}${webhookPath}`;
|
|
38
92
|
const safeError = (0, sanitize_1.sanitizeString)(String(error));
|
|
39
93
|
const safeTitle = (0, sanitize_1.sanitizeString)(String(title));
|
|
40
94
|
const data = {
|
|
@@ -43,6 +43,10 @@ export declare function needsCrossChainApproval(tokenAddress: IAddress, spenderA
|
|
|
43
43
|
/**
|
|
44
44
|
* Execute a token approval for a cross-chain operation.
|
|
45
45
|
*
|
|
46
|
+
* @param chainId Chain the approval is sent on, used to gate ERC-8021
|
|
47
|
+
* attribution. Omitted means unattributed under a `chains` restriction —
|
|
48
|
+
* the suffix would lengthen `approve` calldata past its ABI encoding and
|
|
49
|
+
* break Ledger clear-signing.
|
|
46
50
|
* @returns Transaction hash of the approval
|
|
47
51
|
*/
|
|
48
52
|
export declare function approveCrossChain(tokenAddress: IAddress, spenderAddress: IAddress, amount: bigint, walletClient: {
|
|
@@ -56,7 +60,7 @@ export declare function approveCrossChain(tokenAddress: IAddress, spenderAddress
|
|
|
56
60
|
}) => Promise<{
|
|
57
61
|
status: string;
|
|
58
62
|
}>;
|
|
59
|
-
}): Promise<string>;
|
|
63
|
+
}, chainId?: number): Promise<string>;
|
|
60
64
|
/**
|
|
61
65
|
* Execute a cross-chain deposit via LayerZero OVault. Validates the source
|
|
62
66
|
* chain, ensures approval, estimates gas with a buffer, and waits for the
|
|
@@ -526,9 +526,13 @@ async function needsCrossChainApproval(tokenAddress, spenderAddress, walletAddre
|
|
|
526
526
|
/**
|
|
527
527
|
* Execute a token approval for a cross-chain operation.
|
|
528
528
|
*
|
|
529
|
+
* @param chainId Chain the approval is sent on, used to gate ERC-8021
|
|
530
|
+
* attribution. Omitted means unattributed under a `chains` restriction —
|
|
531
|
+
* the suffix would lengthen `approve` calldata past its ABI encoding and
|
|
532
|
+
* break Ledger clear-signing.
|
|
529
533
|
* @returns Transaction hash of the approval
|
|
530
534
|
*/
|
|
531
|
-
async function approveCrossChain(tokenAddress, spenderAddress, amount, walletClient, publicClient) {
|
|
535
|
+
async function approveCrossChain(tokenAddress, spenderAddress, amount, walletClient, publicClient, chainId) {
|
|
532
536
|
if (!walletClient.account) {
|
|
533
537
|
throw new Error('Wallet not ready — please reconnect your wallet');
|
|
534
538
|
}
|
|
@@ -538,7 +542,7 @@ async function approveCrossChain(tokenAddress, spenderAddress, amount, walletCli
|
|
|
538
542
|
abi: OFT_1.ABI_CROSS_CHAIN_ERC20,
|
|
539
543
|
functionName: 'approve',
|
|
540
544
|
args: [spenderAddress, amount],
|
|
541
|
-
dataSuffix: (0, attribution_1.getAttributionSuffix)(),
|
|
545
|
+
dataSuffix: (0, attribution_1.getAttributionSuffix)(chainId),
|
|
542
546
|
});
|
|
543
547
|
const receipt = await publicClient.waitForTransactionReceipt({
|
|
544
548
|
hash: hash,
|
|
@@ -595,7 +599,7 @@ async function crossChainVaultDeposit(props) {
|
|
|
595
599
|
BigInt((0, core_1.toNormalizedBn)(props.amount, props.decimals).raw);
|
|
596
600
|
const approvalNeeded = await needsCrossChainApproval(tokenAddr, spenderAddr, props.walletAddress, approvalAmount, publicClient);
|
|
597
601
|
if (approvalNeeded) {
|
|
598
|
-
await approveCrossChain(tokenAddr, spenderAddr, approvalAmount, walletClient, publicClient);
|
|
602
|
+
await approveCrossChain(tokenAddr, spenderAddr, approvalAmount, walletClient, publicClient, props.userChainId);
|
|
599
603
|
}
|
|
600
604
|
}
|
|
601
605
|
// 3. Estimate gas with buffer
|
|
@@ -678,7 +682,7 @@ async function crossChainVaultRedeem(props) {
|
|
|
678
682
|
if (!props.skipApprovalCheck && txInputs.approval) {
|
|
679
683
|
const approvalNeeded = await needsCrossChainApproval(txInputs.approval.tokenAddress, txInputs.approval.spender, props.walletAddress, txInputs.approval.amount, publicClient);
|
|
680
684
|
if (approvalNeeded) {
|
|
681
|
-
await approveCrossChain(txInputs.approval.tokenAddress, txInputs.approval.spender, txInputs.approval.amount, walletClient, publicClient);
|
|
685
|
+
await approveCrossChain(txInputs.approval.tokenAddress, txInputs.approval.spender, txInputs.approval.amount, walletClient, publicClient, props.config.hubChainId);
|
|
682
686
|
}
|
|
683
687
|
}
|
|
684
688
|
// 4. Estimate gas with buffer
|
|
@@ -68,6 +68,7 @@ const abis_1 = require("../../abis");
|
|
|
68
68
|
const types_1 = require("../../types");
|
|
69
69
|
const core_1 = require("../../core");
|
|
70
70
|
const ethers_1 = require("ethers");
|
|
71
|
+
const cache_1 = require("../../core/cache");
|
|
71
72
|
const utils_1 = require("./utils");
|
|
72
73
|
const subgraph_1 = require("../../services/subgraph");
|
|
73
74
|
const vaults_1 = require("../../services/subgraph/vaults");
|
|
@@ -356,8 +357,30 @@ async function getVaultSubaccountLoans(vault, options) {
|
|
|
356
357
|
* HTTP statuses that mean "no such record for this subaccount", not "something
|
|
357
358
|
* broke". The backend returns 404 for a subaccount with no CeFi/OTC position,
|
|
358
359
|
* which is the normal case for most vault borrowers.
|
|
360
|
+
*
|
|
361
|
+
* `400` is here for the same reason. The CeFi and OTC endpoints answer `400`
|
|
362
|
+
* for a borrower they do not track — a well-formed, checksummed EVM address
|
|
363
|
+
* that simply has no record on that side — and every `getVaultAllocations`
|
|
364
|
+
* call re-asks for the same untracked borrowers on every render. In Sentry
|
|
365
|
+
* that produced ~2.7k `AugustServerError: Request failed: 400` events across
|
|
366
|
+
* two issues in two days, all from the same handful of borrowers, none of them
|
|
367
|
+
* actionable. Both fetches are strictly best-effort enrichment: the vault's
|
|
368
|
+
* allocations are returned either way, so a `400` here can never mean the
|
|
369
|
+
* caller's request was malformed.
|
|
370
|
+
*
|
|
371
|
+
* This demotes the *log severity* only — nothing about the response handling
|
|
372
|
+
* changes, and any other status (401, 5xx, transport failures) is still an
|
|
373
|
+
* error-level Sentry issue.
|
|
374
|
+
*
|
|
375
|
+
* **Maintainer note on the `400` entry specifically.** Unlike `204`/`404`, a
|
|
376
|
+
* `400` is semantically "the client sent something wrong", so this demotion is
|
|
377
|
+
* safe only for as long as these two endpoints use `400` to mean "unknown
|
|
378
|
+
* borrower". If either ever starts returning `400` for a genuinely malformed
|
|
379
|
+
* request — a real defect in how the SDK builds the URL or body — this set
|
|
380
|
+
* would silence it. Should that contract change, drop `400` from here and let
|
|
381
|
+
* the backend distinguish the two cases with a status of its own.
|
|
359
382
|
*/
|
|
360
|
-
const EXPECTED_SUBACCOUNT_FETCH_STATUSES = new Set([204, 404]);
|
|
383
|
+
const EXPECTED_SUBACCOUNT_FETCH_STATUSES = new Set([204, 400, 404]);
|
|
361
384
|
/**
|
|
362
385
|
* Log a per-subaccount enrichment fetch failure at the right severity.
|
|
363
386
|
*
|
|
@@ -628,6 +651,87 @@ function subgraphAmountToBigInt(value) {
|
|
|
628
651
|
return BigInt(0);
|
|
629
652
|
}
|
|
630
653
|
}
|
|
654
|
+
/**
|
|
655
|
+
* Read `lagDuration()` off a vault, tolerating vaults that do not implement it.
|
|
656
|
+
*
|
|
657
|
+
* Why this exists: `getVaultAvailableRedemptions` binds every vault — v1 and
|
|
658
|
+
* v2 — to `ABI_LENDING_POOL_V2` because that ABI carries the superset of
|
|
659
|
+
* methods the scan needs. `lagDuration()` is not in that superset for every
|
|
660
|
+
* deployed pool. Calling it on a pool without the function reaches the
|
|
661
|
+
* fallback-less contract, which returns empty calldata, and ethers reports it
|
|
662
|
+
* as `missing revert data (action="call", data=null, …, code=CALL_EXCEPTION)`.
|
|
663
|
+
* That threw out of the whole redemption scan and became the single
|
|
664
|
+
* highest-volume error in production Sentry (~5.5k events over two days from
|
|
665
|
+
* three server deployments), while also returning an empty redemption list to
|
|
666
|
+
* every caller for the affected vaults.
|
|
667
|
+
*
|
|
668
|
+
* A missing `lagDuration()` means "this pool has no claim lag", which is what
|
|
669
|
+
* `0` encodes — the same value the v1 branch of the caller assumes.
|
|
670
|
+
*
|
|
671
|
+
* The empty response is **retried before it is believed**, because a provider
|
|
672
|
+
* that truncates an `eth_call` produces the identical shape: falling straight
|
|
673
|
+
* to `0` for a vault that really does have a lag would shift every computed
|
|
674
|
+
* claimable date. Only after the retries agree is the function treated as
|
|
675
|
+
* absent. A genuine revert carrying revert data still propagates, so real
|
|
676
|
+
* breakage stays loud.
|
|
677
|
+
*
|
|
678
|
+
* **The absence is memoized, the value is not.** Paying the retry budget (3
|
|
679
|
+
* `eth_call`s plus ~750ms of backoff) on every call for a vault that will never
|
|
680
|
+
* implement `lagDuration()` would trade an error flood for a latency and
|
|
681
|
+
* RPC-volume regression on exactly the vaults this path targets — a dashboard
|
|
682
|
+
* re-rendering redemptions pays it per render (CLAUDE.md §4.1, §4.2). Whether a
|
|
683
|
+
* deployed pool implements the function is fixed by its bytecode, so a
|
|
684
|
+
* confirmed absence is cacheable forever; the lag *value* is operator-settable
|
|
685
|
+
* and is deliberately never cached.
|
|
686
|
+
*
|
|
687
|
+
* Caveat on "fixed by its bytecode": an **upgradeable proxy** could gain a
|
|
688
|
+
* `lagDuration()` in a later implementation, and a process holding the memo
|
|
689
|
+
* would keep answering `0` for it. Bounded, not unbounded — `CACHE` carries a
|
|
690
|
+
* 24h TTL and `allowStale: true`, which serves an expired entry exactly once
|
|
691
|
+
* before evicting it, so the vault is re-read on the following call. Vaults
|
|
692
|
+
* behind an upgradeable proxy that add the function mid-process therefore
|
|
693
|
+
* misreport a zero lag for at most that window. Accepted deliberately: no
|
|
694
|
+
* deployed pool does this today, and the alternative is re-paying the retry
|
|
695
|
+
* budget forever for the vaults this path exists to serve.
|
|
696
|
+
*
|
|
697
|
+
* @param vaultContract - Anything exposing the vault's `lagDuration()` view;
|
|
698
|
+
* structurally typed so both `ethers.Contract` and the ABI-typed contract
|
|
699
|
+
* returned by `createContract` satisfy it.
|
|
700
|
+
* @param vault - Vault address, for the breadcrumb only.
|
|
701
|
+
* @param scope - Cache scope for the absence memo — the RPC endpoint the read
|
|
702
|
+
* went through. Scoping by endpoint (rather than globally by address) keeps
|
|
703
|
+
* the same address on two chains in separate entries.
|
|
704
|
+
* @returns The lag in seconds, or `0` when the vault has no `lagDuration()`.
|
|
705
|
+
* @throws The original error for any failure that is not an empty response.
|
|
706
|
+
*/
|
|
707
|
+
async function readLagDuration(vaultContract, vault, scope) {
|
|
708
|
+
const absenceKey = `lag-absent-${scope}-${vault.toLowerCase()}`;
|
|
709
|
+
// A vault proven not to implement the function cannot start implementing it.
|
|
710
|
+
if (cache_1.CACHE.get(absenceKey) === true)
|
|
711
|
+
return 0;
|
|
712
|
+
try {
|
|
713
|
+
return Number(await (0, core_1.retryOnTransientRpc)('getVaultAvailableRedemptions:lagDuration', () => vaultContract.lagDuration(), { vault }, (error) => (0, core_1.isRetryableRpcError)(error) ||
|
|
714
|
+
(0, core_1.isEmptyViewResponse)(error, LAG_DURATION_SELECTOR)));
|
|
715
|
+
}
|
|
716
|
+
catch (error) {
|
|
717
|
+
if (!(0, core_1.isEmptyViewResponse)(error, LAG_DURATION_SELECTOR))
|
|
718
|
+
throw error;
|
|
719
|
+
// Retries agreed the response is empty — record the absence so the next
|
|
720
|
+
// call short-circuits instead of re-paying the full retry budget.
|
|
721
|
+
cache_1.CACHE.set(absenceKey, true);
|
|
722
|
+
core_1.Logger.log.warn('getVaultAvailableRedemptions', {
|
|
723
|
+
vault,
|
|
724
|
+
reason: 'vault does not implement lagDuration(); assuming no claim lag',
|
|
725
|
+
});
|
|
726
|
+
return 0;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* `lagDuration()` — `keccak256("lagDuration()")[0..4]`. Scopes the
|
|
731
|
+
* empty-response tolerance in {@link readLagDuration} to exactly that call, so
|
|
732
|
+
* an empty response to any *other* view still surfaces as an error.
|
|
733
|
+
*/
|
|
734
|
+
const LAG_DURATION_SELECTOR = '0x24e86d67';
|
|
631
735
|
async function getVaultAvailableRedemptions({ vault, wallet, options, prefetchedReads, }) {
|
|
632
736
|
try {
|
|
633
737
|
// Stellar vaults don't support on-chain redemptions yet
|
|
@@ -674,7 +778,7 @@ async function getVaultAvailableRedemptions({ vault, wallet, options, prefetched
|
|
|
674
778
|
}
|
|
675
779
|
const lagDuration = typeof prefetchedReads?.lagDuration === 'number'
|
|
676
780
|
? prefetchedReads.lagDuration
|
|
677
|
-
:
|
|
781
|
+
: await readLagDuration(vaultContract, vault, options.rpcUrl);
|
|
678
782
|
const { withdrawalRequesteds, withdrawalProcesseds } = await (0, subgraph_1.getSubgraphAllWithdrawals)(vault, provider);
|
|
679
783
|
// format
|
|
680
784
|
const availableRedemptions = [];
|
|
@@ -954,7 +1058,13 @@ async function getVaultRedemptionHistory({ vault, wallet, lookbackBlocks, option
|
|
|
954
1058
|
const currentBlock = await provider.getBlockNumber();
|
|
955
1059
|
const blockSkip = (0, core_1.determineBlockSkipInternal)(chainId);
|
|
956
1060
|
const cutoffBlock = currentBlock - (lookbackBlocks ?? (0, core_1.determineBlockCutoff)(chainId));
|
|
957
|
-
|
|
1061
|
+
// Max concurrent `eth_getLogs` requests per batch. Kept well under the
|
|
1062
|
+
// per-second cap of the smallest provider plan we run against (QuickNode:
|
|
1063
|
+
// 50/s) because this scan is not the only caller on the connection — a
|
|
1064
|
+
// page rendering several vaults runs several of these concurrently. At 20
|
|
1065
|
+
// the batches reliably tripped `-32007 50/second request limit reached`,
|
|
1066
|
+
// which the retry below now absorbs and this ceiling mostly avoids.
|
|
1067
|
+
const BATCH_SIZE = 8;
|
|
958
1068
|
core_1.Logger.log.info('getVaultRedemptionHistory', {
|
|
959
1069
|
vault,
|
|
960
1070
|
version,
|
|
@@ -987,7 +1097,13 @@ async function getVaultRedemptionHistory({ vault, wallet, lookbackBlocks, option
|
|
|
987
1097
|
const logs = [];
|
|
988
1098
|
for (let i = 0; i < ranges.length; i += BATCH_SIZE) {
|
|
989
1099
|
const batch = ranges.slice(i, i + BATCH_SIZE);
|
|
990
|
-
const batchResults = await Promise.allSettled(batch.map((r) =>
|
|
1100
|
+
const batchResults = await Promise.allSettled(batch.map((r) =>
|
|
1101
|
+
// `eth_getLogs` over a fixed block range is idempotent, so a chunk
|
|
1102
|
+
// rejected by a rate limiter or a flaky socket is safe to repeat.
|
|
1103
|
+
// Without this a single `-32007` burst aborted the whole scan and
|
|
1104
|
+
// the caller lost the redemption history entirely — the same failure
|
|
1105
|
+
// mode the throw below was added to make visible.
|
|
1106
|
+
(0, core_1.retryOnTransientRpc)('getVaultRedemptionHistory:chunk', () => poolContract.queryFilter('WithdrawalProcessed', BigInt(r.from), BigInt(r.to)), { vault, fromBlock: r.from, toBlock: r.to })));
|
|
991
1107
|
const failed = batchResults.filter((r) => r.status === 'rejected');
|
|
992
1108
|
if (failed.length > 0) {
|
|
993
1109
|
const firstReason = failed[0].reason;
|
package/lib/sdk.d.ts
CHANGED
|
@@ -15020,7 +15020,8 @@ export declare function allowance(signer: IContractRunner, options: IAllowanceOp
|
|
|
15020
15020
|
* Append the active attribution suffix to calldata.
|
|
15021
15021
|
*
|
|
15022
15022
|
* No-ops (returns `data` unchanged) when attribution is off, the chain is
|
|
15023
|
-
*
|
|
15023
|
+
* not attributed (including an unknown chain under a `chains` restriction),
|
|
15024
|
+
* `data` is empty/absent (plain value transfers are never
|
|
15024
15025
|
* attributed), or `data` already ends with the ERC-8021 marker (guards
|
|
15025
15026
|
* against double-appending when an upstream layer — e.g. a wagmi config
|
|
15026
15027
|
* `dataSuffix` — already attributed the transaction).
|
|
@@ -15065,6 +15066,10 @@ export declare function approve(signer: Signer | Wallet, options: IContractWrite
|
|
|
15065
15066
|
/**
|
|
15066
15067
|
* Execute a token approval for a cross-chain operation.
|
|
15067
15068
|
*
|
|
15069
|
+
* @param chainId Chain the approval is sent on, used to gate ERC-8021
|
|
15070
|
+
* attribution. Omitted means unattributed under a `chains` restriction —
|
|
15071
|
+
* the suffix would lengthen `approve` calldata past its ABI encoding and
|
|
15072
|
+
* break Ledger clear-signing.
|
|
15068
15073
|
* @returns Transaction hash of the approval
|
|
15069
15074
|
*/
|
|
15070
15075
|
export declare function approveCrossChain(tokenAddress: IAddress, spenderAddress: IAddress, amount: bigint, walletClient: {
|
|
@@ -15078,7 +15083,7 @@ export declare function approveCrossChain(tokenAddress: IAddress, spenderAddress
|
|
|
15078
15083
|
}) => Promise<{
|
|
15079
15084
|
status: string;
|
|
15080
15085
|
}>;
|
|
15081
|
-
}): Promise<string>;
|
|
15086
|
+
}, chainId?: number): Promise<string>;
|
|
15082
15087
|
|
|
15083
15088
|
/**
|
|
15084
15089
|
* Discriminated union returned by {@link approve}. Lets callers tell apart
|
|
@@ -17419,9 +17424,12 @@ declare type AsArray<T> = T extends readonly unknown[] ? T : never;
|
|
|
17419
17424
|
};
|
|
17420
17425
|
|
|
17421
17426
|
/**
|
|
17422
|
-
*
|
|
17427
|
+
* @deprecated Was a hardcoded webhook credential baked into the published
|
|
17428
|
+
* bundle. It now resolves from {@link SLACK_WEBHOOK_ENV_VAR} and is `''` when
|
|
17429
|
+
* unset. Call {@link resolveSlackWebhookUrl} instead — this export exists only
|
|
17430
|
+
* so existing imports keep compiling, and is removed in the next major.
|
|
17423
17431
|
*/
|
|
17424
|
-
declare const DEFAULT_SLACK_WEBHOOK_URL = "
|
|
17432
|
+
declare const DEFAULT_SLACK_WEBHOOK_URL = "";
|
|
17425
17433
|
|
|
17426
17434
|
/**
|
|
17427
17435
|
* Deposit a native token (ETH / AVAX / etc.) into a vault via the
|
|
@@ -18274,11 +18282,12 @@ declare type AsArray<T> = T extends readonly unknown[] ? T : never;
|
|
|
18274
18282
|
|
|
18275
18283
|
/**
|
|
18276
18284
|
* Return the active ERC-8021 suffix for a write on the given chain, or
|
|
18277
|
-
* `undefined` when attribution is off or the chain is
|
|
18285
|
+
* `undefined` when attribution is off or the chain is not attributed.
|
|
18278
18286
|
*
|
|
18279
18287
|
* @param chainId EVM chain ID of the transaction, when the call site knows
|
|
18280
|
-
* it. When
|
|
18281
|
-
*
|
|
18288
|
+
* it. When a `chains` restriction is configured, gating is fail-closed: an
|
|
18289
|
+
* omitted chain ID yields no suffix, since a suffix on an unattributed
|
|
18290
|
+
* chain buys nothing and breaks hardware-wallet clear-signing (see
|
|
18282
18291
|
* {@link IAttributionConfig.chains}).
|
|
18283
18292
|
* @returns `0x`-prefixed hex suffix, or `undefined` when nothing should be
|
|
18284
18293
|
* appended.
|
|
@@ -18323,10 +18332,16 @@ declare type AsArray<T> = T extends readonly unknown[] ? T : never;
|
|
|
18323
18332
|
* Fetch token decimals from contract or Solana mint.
|
|
18324
18333
|
* Results are cached to minimize RPC calls.
|
|
18325
18334
|
*
|
|
18326
|
-
* **Never throws** — a failed read
|
|
18327
|
-
*
|
|
18328
|
-
*
|
|
18329
|
-
*
|
|
18335
|
+
* **Never throws** — a failed read resolves `undefined`. Callers that must not
|
|
18336
|
+
* silently proceed on an unknown scale (any path that encodes an amount) should
|
|
18337
|
+
* use {@link getDecimalsOrThrow} instead: feeding `undefined` into
|
|
18338
|
+
* `toNormalizedBn` silently defaults to 18 decimals.
|
|
18339
|
+
*
|
|
18340
|
+
* Transient transport faults (provider rate limits, socket resets, an empty
|
|
18341
|
+
* response to `decimals()`) are logged at `warn` — a breadcrumb, not a
|
|
18342
|
+
* standalone Sentry issue — since the caller sees the same `undefined` either
|
|
18343
|
+
* way. Everything else is logged at `error`. The read itself is **not**
|
|
18344
|
+
* retried here; that is opt-in via {@link getDecimalsOrThrow}.
|
|
18330
18345
|
*
|
|
18331
18346
|
* @param provider Web3 provider
|
|
18332
18347
|
* @param address Token contract address or Solana mint
|
|
@@ -19430,7 +19445,7 @@ declare type AsArray<T> = T extends readonly unknown[] ? T : never;
|
|
|
19430
19445
|
* Subgraph base URL
|
|
19431
19446
|
* @deprecated use getVaultMetadata to fetch subgraph URLs from the backend
|
|
19432
19447
|
*/
|
|
19433
|
-
export declare const GOLDSKY_BASE_URL = "https://api.goldsky.com/api/
|
|
19448
|
+
export declare const GOLDSKY_BASE_URL = "https://api.goldsky.com/api/public/project_cm9g0xy3o4j6v01vd34r3hvv9/subgraphs";
|
|
19434
19449
|
|
|
19435
19450
|
/**
|
|
19436
19451
|
* Deposit funds into a Solana August vault and mint share tokens.
|
|
@@ -19640,10 +19655,15 @@ declare type AsArray<T> = T extends readonly unknown[] ? T : never;
|
|
|
19640
19655
|
/**
|
|
19641
19656
|
* EVM chain IDs to attribute. Omit to attribute writes on every EVM chain
|
|
19642
19657
|
* (the suffix is inert on chains without an ERC-8021 indexer and costs
|
|
19643
|
-
* ~16 gas per non-zero byte). When set,
|
|
19644
|
-
*
|
|
19645
|
-
*
|
|
19646
|
-
*
|
|
19658
|
+
* ~16 gas per non-zero byte). When set, gating is fail-closed: writes on
|
|
19659
|
+
* other chains — and writes whose chain ID cannot be determined — are sent
|
|
19660
|
+
* without the suffix.
|
|
19661
|
+
*
|
|
19662
|
+
* Over-attribution is not harmless. The suffix makes calldata longer than
|
|
19663
|
+
* the ABI encoding of the call, which breaks clear-signing on hardware
|
|
19664
|
+
* wallets: a Ledger rejects an over-long ERC-20 `approve` with
|
|
19665
|
+
* `EthAppCommandError: Invalid data 6a80`, so an unattributed chain that
|
|
19666
|
+
* receives the suffix anyway cannot be transacted on from a Ledger at all.
|
|
19647
19667
|
*/
|
|
19648
19668
|
chains?: number[];
|
|
19649
19669
|
}
|
|
@@ -21057,6 +21077,16 @@ declare type AsArray<T> = T extends readonly unknown[] ? T : never;
|
|
|
21057
21077
|
*/
|
|
21058
21078
|
export declare function isAnalyticsForcedOnViaEnv(): boolean;
|
|
21059
21079
|
|
|
21080
|
+
/**
|
|
21081
|
+
* Whether attribution is configured at all, independent of any chain gate.
|
|
21082
|
+
*
|
|
21083
|
+
* Call sites that need to know whether to resolve a chain ID before asking
|
|
21084
|
+
* for the suffix use this; {@link getAttributionSuffix} applies the gate.
|
|
21085
|
+
*
|
|
21086
|
+
* @returns `true` when builder codes are configured.
|
|
21087
|
+
*/
|
|
21088
|
+
export declare function isAttributionEnabled(): boolean;
|
|
21089
|
+
|
|
21060
21090
|
/** Type guard. Works across realms (e.g. Web Worker / VM contexts). */
|
|
21061
21091
|
export declare function isAugustSDKError(err: unknown): err is AugustSDKError;
|
|
21062
21092
|
|
|
@@ -24059,6 +24089,31 @@ declare type AsArray<T> = T extends readonly unknown[] ? T : never;
|
|
|
24059
24089
|
*/
|
|
24060
24090
|
export declare function resolveOriginCode(provided?: `0x${string}`): `0x${string}`;
|
|
24061
24091
|
|
|
24092
|
+
/**
|
|
24093
|
+
* Resolve the Slack webhook to alert through.
|
|
24094
|
+
*
|
|
24095
|
+
* Why this replaced a hardcoded constant: a webhook path is a **bearer
|
|
24096
|
+
* credential** — anyone holding it can post to the channel. The previous
|
|
24097
|
+
* default embedded August's own webhook as a string literal, which shipped in
|
|
24098
|
+
* every published tarball and in the generated `.d.ts`, handing every consumer
|
|
24099
|
+
* of the SDK write access to an internal Slack channel (CLAUDE.md §5: no
|
|
24100
|
+
* secrets in client-reachable code). It also meant a consumer's alerts went to
|
|
24101
|
+
* August's channel rather than their own, with no way to redirect them.
|
|
24102
|
+
*
|
|
24103
|
+
* Resolution order:
|
|
24104
|
+
* 1. `explicit` — passed by the caller, wins outright;
|
|
24105
|
+
* 2. `AUGUST_SDK_SLACK_WEBHOOK_URL` in the environment;
|
|
24106
|
+
* 3. none — alerting is disabled and the call becomes a no-op.
|
|
24107
|
+
*
|
|
24108
|
+
* Reading the env var per call (rather than once at module load) means a
|
|
24109
|
+
* consumer configuring it after import still gets alerts, and a test can set
|
|
24110
|
+
* and unset it without re-importing the module.
|
|
24111
|
+
*
|
|
24112
|
+
* @param explicit - Caller-supplied webhook, full URL or bare path suffix.
|
|
24113
|
+
* @returns The bare `T…/B…/x…` path suffix, or `''` when unconfigured.
|
|
24114
|
+
*/
|
|
24115
|
+
declare function resolveSlackWebhookUrl(explicit?: string): string;
|
|
24116
|
+
|
|
24062
24117
|
/* Excluded from this release type: resolveSpender */
|
|
24063
24118
|
|
|
24064
24119
|
/**
|
|
@@ -25089,12 +25144,21 @@ declare type AsArray<T> = T extends readonly unknown[] ? T : never;
|
|
|
25089
25144
|
|
|
25090
25145
|
declare namespace Slack {
|
|
25091
25146
|
export {
|
|
25147
|
+
resolveSlackWebhookUrl,
|
|
25148
|
+
SLACK_WEBHOOK_ENV_VAR,
|
|
25092
25149
|
DEFAULT_SLACK_WEBHOOK_URL,
|
|
25093
25150
|
SLACK
|
|
25094
25151
|
}
|
|
25095
25152
|
}
|
|
25096
25153
|
export { Slack }
|
|
25097
25154
|
|
|
25155
|
+
/**
|
|
25156
|
+
* Environment variable holding the Slack incoming-webhook to post SDK alerts
|
|
25157
|
+
* to. Accepts either the full `https://hooks.slack.com/services/T…/B…/x…` URL
|
|
25158
|
+
* or the bare `T…/B…/x…` path suffix.
|
|
25159
|
+
*/
|
|
25160
|
+
declare const SLACK_WEBHOOK_ENV_VAR = "AUGUST_SDK_SLACK_WEBHOOK_URL";
|
|
25161
|
+
|
|
25098
25162
|
export declare const Solana: {
|
|
25099
25163
|
utils: {
|
|
25100
25164
|
getExplorerLink: ({ signature, type, network, }: {
|
|
@@ -26791,7 +26855,8 @@ declare type AsArray<T> = T extends readonly unknown[] ? T : never;
|
|
|
26791
26855
|
* already ending in the ERC-8021 marker is left untouched. When the
|
|
26792
26856
|
* configured `chains` list requires a chain check and the transaction does
|
|
26793
26857
|
* not carry a `chainId`, the signer's provider network is consulted (one
|
|
26794
|
-
* cached RPC call)
|
|
26858
|
+
* cached RPC call); if that lookup fails the transaction is sent
|
|
26859
|
+
* unattributed.
|
|
26795
26860
|
*
|
|
26796
26861
|
* @param signer Normalized ethers Signer or Wallet.
|
|
26797
26862
|
* @returns A proxied signer with an attribution-aware `sendTransaction`.
|
|
@@ -16,7 +16,7 @@ const fetcher_2 = require("../../modules/vaults/fetcher");
|
|
|
16
16
|
/**
|
|
17
17
|
* Utils
|
|
18
18
|
*/
|
|
19
|
-
const GOLDSKY_API_KEY = '
|
|
19
|
+
const GOLDSKY_API_KEY = '';
|
|
20
20
|
// Dedupe window for "Missing Subgraph" alerts (per pool).
|
|
21
21
|
const MISSING_SUBGRAPH_ALERT_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
22
22
|
/**
|
|
@@ -371,7 +371,7 @@ const TRANSFER_QUERY_PROPS = `
|
|
|
371
371
|
* @param provider
|
|
372
372
|
* @returns
|
|
373
373
|
*/
|
|
374
|
-
async function getSubgraphWithdrawRequests(pool, provider, slackWebookUrl = slack_1.
|
|
374
|
+
async function getSubgraphWithdrawRequests(pool, provider, slackWebookUrl = (0, slack_1.resolveSlackWebhookUrl)()) {
|
|
375
375
|
// setup
|
|
376
376
|
let vaultSymbol = await (0, core_1.getVaultSymbol)(pool, provider);
|
|
377
377
|
if (vaultSymbol === undefined) {
|
|
@@ -443,7 +443,7 @@ async function getSubgraphWithdrawRequests(pool, provider, slackWebookUrl = slac
|
|
|
443
443
|
requests.push(...(json?.data?.withdrawalRequesteds || []));
|
|
444
444
|
return requests;
|
|
445
445
|
}
|
|
446
|
-
async function getSubgraphWithdrawProccessed(pool, provider, slackWebookUrl = slack_1.
|
|
446
|
+
async function getSubgraphWithdrawProccessed(pool, provider, slackWebookUrl = (0, slack_1.resolveSlackWebhookUrl)()) {
|
|
447
447
|
try {
|
|
448
448
|
// setup
|
|
449
449
|
let vaultSymbol = await (0, core_1.getVaultSymbol)(pool, provider);
|
|
@@ -526,7 +526,7 @@ async function getSubgraphWithdrawProccessed(pool, provider, slackWebookUrl = sl
|
|
|
526
526
|
* @param provider
|
|
527
527
|
* @returns
|
|
528
528
|
*/
|
|
529
|
-
async function getSubgraphAllWithdrawals(pool, provider, slackWebookUrl = slack_1.
|
|
529
|
+
async function getSubgraphAllWithdrawals(pool, provider, slackWebookUrl = (0, slack_1.resolveSlackWebhookUrl)()) {
|
|
530
530
|
let query = '';
|
|
531
531
|
try {
|
|
532
532
|
// setup
|
|
@@ -695,7 +695,7 @@ async function getArchivedVaultUserHistory(user, provider, pool) {
|
|
|
695
695
|
// chronology. The backend returns rows in arbitrary order, so re-sort here.
|
|
696
696
|
.sort((a, b) => Number(a.timestamp_) - Number(b.timestamp_)));
|
|
697
697
|
}
|
|
698
|
-
async function getSubgraphUserHistory(user, provider, pool, slackWebookUrl = slack_1.
|
|
698
|
+
async function getSubgraphUserHistory(user, provider, pool, slackWebookUrl = (0, slack_1.resolveSlackWebhookUrl)()) {
|
|
699
699
|
try {
|
|
700
700
|
// This path is EVM-only: it reads chain id and network from an EVM
|
|
701
701
|
// JsonRpcProvider (eth_chainId / getNetwork) and queries an EVM subgraph.
|
|
@@ -847,7 +847,7 @@ async function getSubgraphUserHistory(user, provider, pool, slackWebookUrl = sla
|
|
|
847
847
|
return [];
|
|
848
848
|
}
|
|
849
849
|
}
|
|
850
|
-
async function getSubgraphVaultHistory(provider, pool, slackWebookUrl = slack_1.
|
|
850
|
+
async function getSubgraphVaultHistory(provider, pool, slackWebookUrl = (0, slack_1.resolveSlackWebhookUrl)(), opts = {}) {
|
|
851
851
|
try {
|
|
852
852
|
// setup
|
|
853
853
|
const requests = [];
|
|
@@ -1008,7 +1008,7 @@ async function getSubgraphVaultHistory(provider, pool, slackWebookUrl = slack_1.
|
|
|
1008
1008
|
return [];
|
|
1009
1009
|
}
|
|
1010
1010
|
}
|
|
1011
|
-
async function getSubgraphUserTransfers(user, provider, pool, slackWebookUrl = slack_1.
|
|
1011
|
+
async function getSubgraphUserTransfers(user, provider, pool, slackWebookUrl = (0, slack_1.resolveSlackWebhookUrl)()) {
|
|
1012
1012
|
try {
|
|
1013
1013
|
// setup
|
|
1014
1014
|
let amountOfTransfers = 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@augustdigital/sdk",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.24.0",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"types": "lib/sdk.d.ts",
|
|
6
6
|
"keywords": [
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
"test:jest:watch": "jest --config jest.config.unit.js --watch",
|
|
67
67
|
"test:jest:coverage": "jest --config jest.config.unit.js --coverage",
|
|
68
68
|
"test:forknet": "node tests/forknet/run.mjs",
|
|
69
|
+
"test:solana-localnet": "node tests/solana-localnet/run.mjs",
|
|
69
70
|
"test:solana-idl": "jest --config jest.config.idl-drift.js",
|
|
70
71
|
"clean": "rm -rf ./lib",
|
|
71
72
|
"format": "biome check --write .",
|