@augustdigital/sdk 8.16.1 → 8.19.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/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/core/analytics/constants.d.ts +1 -1
- package/lib/core/analytics/constants.js +1 -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/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/signer.d.ts +21 -0
- package/lib/core/helpers/signer.js +52 -0
- package/lib/core/helpers/web3.d.ts +152 -2
- package/lib/core/helpers/web3.js +307 -45
- package/lib/core/index.d.ts +1 -0
- package/lib/core/index.js +1 -0
- package/lib/evm/methods/crossChainVault.js +4 -0
- package/lib/modules/vaults/getters.js +6 -2
- package/lib/modules/vaults/utils.js +4 -0
- package/lib/modules/vaults/write.actions.d.ts +41 -1
- package/lib/modules/vaults/write.actions.js +302 -86
- package/lib/sdk.d.ts +461 -3
- package/lib/services/subgraph/vaults.js +85 -14
- package/lib/types/vaults.d.ts +12 -0
- package/lib/types/webserver.d.ts +11 -0
- package/package.json +1 -1
|
@@ -57,8 +57,10 @@ class EVMAdapter {
|
|
|
57
57
|
if (this.signer) {
|
|
58
58
|
return this.signer;
|
|
59
59
|
}
|
|
60
|
-
// Normalize and cache the signer
|
|
61
|
-
|
|
60
|
+
// Normalize and cache the signer. The attribution wrap is unconditional
|
|
61
|
+
// and reads the process-global config at send time, so a cached signer
|
|
62
|
+
// honors attribution enabled or reset after caching.
|
|
63
|
+
this.signer = (0, signer_1.wrapSignerWithAttribution)(await (0, signer_1.normalizeSigner)(this.rawSigner));
|
|
62
64
|
return this.signer;
|
|
63
65
|
}
|
|
64
66
|
// Core EVM operations
|
|
@@ -58,9 +58,14 @@ export declare function getHealthyServer(config: ISorobanNetworkConfig): Promise
|
|
|
58
58
|
* Exported so an operation with different semantics (e.g. an idempotency-aware
|
|
59
59
|
* submission path) can compose or replace this policy via
|
|
60
60
|
* {@link FailoverOptions.isRetryable} — extension without editing the core.
|
|
61
|
+
*
|
|
62
|
+
* Named distinctly from `core/helpers/chain-error`'s `isRetryableRpcError`
|
|
63
|
+
* (the EVM transport classifier, deliberately public) — the internal-dts
|
|
64
|
+
* leak check matches `@internal` tags by name across the whole `src.ts` tree,
|
|
65
|
+
* so a same-named pair would make it flag the public EVM one as a leak.
|
|
61
66
|
* @internal
|
|
62
67
|
*/
|
|
63
|
-
export declare function
|
|
68
|
+
export declare function isRetryableSorobanRpcError(err: unknown): boolean;
|
|
64
69
|
/**
|
|
65
70
|
* Whether a simulation *error* string is a node/infrastructure failure (which
|
|
66
71
|
* clears on another endpoint) rather than a genuine contract revert (which
|
|
@@ -76,7 +81,7 @@ export declare function isRetryableSimulationError(errorText: string): boolean;
|
|
|
76
81
|
* failover core (Open/Closed at the call site).
|
|
77
82
|
*/
|
|
78
83
|
export interface FailoverOptions {
|
|
79
|
-
/** Which thrown errors get the next endpoint. Defaults to {@link
|
|
84
|
+
/** Which thrown errors get the next endpoint. Defaults to {@link isRetryableSorobanRpcError}. */
|
|
80
85
|
isRetryable?: (err: unknown) => boolean;
|
|
81
86
|
/** Per-attempt timeout in ms. Defaults to {@link RPC_OPERATION_TIMEOUT_MS}. */
|
|
82
87
|
timeoutMs?: number;
|
|
@@ -92,7 +97,7 @@ export interface FailoverOptions {
|
|
|
92
97
|
* the health-gated server first (the fast, cached path) and, on a retryable
|
|
93
98
|
* failure, drop that cached choice and retry the op against every endpoint in
|
|
94
99
|
* priority order. Deterministic contract/domain errors short-circuit (see
|
|
95
|
-
* {@link
|
|
100
|
+
* {@link isRetryableSorobanRpcError}); each attempt is time-boxed so a hung node can't
|
|
96
101
|
* stall the loop.
|
|
97
102
|
*
|
|
98
103
|
* The `operation` is a strategy (dependency-injected), so new read/build calls
|
|
@@ -11,7 +11,7 @@ exports.resolveNetworkConfig = resolveNetworkConfig;
|
|
|
11
11
|
exports.resetHealthyServerCache = resetHealthyServerCache;
|
|
12
12
|
exports.redactRpcUrl = redactRpcUrl;
|
|
13
13
|
exports.getHealthyServer = getHealthyServer;
|
|
14
|
-
exports.
|
|
14
|
+
exports.isRetryableSorobanRpcError = isRetryableSorobanRpcError;
|
|
15
15
|
exports.isRetryableSimulationError = isRetryableSimulationError;
|
|
16
16
|
exports.withEndpointFailover = withEndpointFailover;
|
|
17
17
|
exports.toBigIntAmount = toBigIntAmount;
|
|
@@ -291,9 +291,14 @@ async function probeHealthyServer(rpcUrls, cacheKey) {
|
|
|
291
291
|
* Exported so an operation with different semantics (e.g. an idempotency-aware
|
|
292
292
|
* submission path) can compose or replace this policy via
|
|
293
293
|
* {@link FailoverOptions.isRetryable} — extension without editing the core.
|
|
294
|
+
*
|
|
295
|
+
* Named distinctly from `core/helpers/chain-error`'s `isRetryableRpcError`
|
|
296
|
+
* (the EVM transport classifier, deliberately public) — the internal-dts
|
|
297
|
+
* leak check matches `@internal` tags by name across the whole `src.ts` tree,
|
|
298
|
+
* so a same-named pair would make it flag the public EVM one as a leak.
|
|
294
299
|
* @internal
|
|
295
300
|
*/
|
|
296
|
-
function
|
|
301
|
+
function isRetryableSorobanRpcError(err) {
|
|
297
302
|
return !(err instanceof core_1.AugustValidationError || err instanceof core_1.AugustSDKError);
|
|
298
303
|
}
|
|
299
304
|
/**
|
|
@@ -334,7 +339,7 @@ function dropCachedServer(rpcUrls) {
|
|
|
334
339
|
* the health-gated server first (the fast, cached path) and, on a retryable
|
|
335
340
|
* failure, drop that cached choice and retry the op against every endpoint in
|
|
336
341
|
* priority order. Deterministic contract/domain errors short-circuit (see
|
|
337
|
-
* {@link
|
|
342
|
+
* {@link isRetryableSorobanRpcError}); each attempt is time-boxed so a hung node can't
|
|
338
343
|
* stall the loop.
|
|
339
344
|
*
|
|
340
345
|
* The `operation` is a strategy (dependency-injected), so new read/build calls
|
|
@@ -345,7 +350,7 @@ function dropCachedServer(rpcUrls) {
|
|
|
345
350
|
* @internal
|
|
346
351
|
*/
|
|
347
352
|
async function withEndpointFailover(config, method, operation, options = {}) {
|
|
348
|
-
const isRetryable = options.isRetryable ??
|
|
353
|
+
const isRetryable = options.isRetryable ?? isRetryableSorobanRpcError;
|
|
349
354
|
const timeoutMs = options.timeoutMs ?? RPC_OPERATION_TIMEOUT_MS;
|
|
350
355
|
const rpcUrls = config.rpcUrls && config.rpcUrls.length > 0
|
|
351
356
|
? config.rpcUrls
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
* Sentry DSN for SDK analytics.
|
|
3
3
|
* This is an internal constant - not exposed to SDK users.
|
|
4
4
|
*/
|
|
5
|
-
export declare const SENTRY_DSN = "https://
|
|
5
|
+
export declare const SENTRY_DSN = "https://ca39246016d0b43154f0640314cbd3b5@o4507215496609792.ingest.de.sentry.io/4511864614813776";
|
|
@@ -5,5 +5,5 @@ exports.SENTRY_DSN = void 0;
|
|
|
5
5
|
* Sentry DSN for SDK analytics.
|
|
6
6
|
* This is an internal constant - not exposed to SDK users.
|
|
7
7
|
*/
|
|
8
|
-
exports.SENTRY_DSN = 'https://
|
|
8
|
+
exports.SENTRY_DSN = 'https://ca39246016d0b43154f0640314cbd3b5@o4507215496609792.ingest.de.sentry.io/4511864614813776';
|
|
9
9
|
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ERC-8021 transaction attribution (Base Builder Codes).
|
|
3
|
+
*
|
|
4
|
+
* ERC-8021 appends an attribution suffix to transaction calldata. Contracts
|
|
5
|
+
* decode their ABI-encoded arguments normally and ignore the trailing bytes,
|
|
6
|
+
* while offchain indexers (e.g. base.dev analytics for Base Builder Codes)
|
|
7
|
+
* read the suffix to attribute the transaction to the originating app.
|
|
8
|
+
*
|
|
9
|
+
* This is a leaf module: process-global state configured once via
|
|
10
|
+
* {@link IAugustBase.attribution} in the SDK constructor and read by the
|
|
11
|
+
* EVM write paths (the normalized-signer wrap in the EVM adapter and the
|
|
12
|
+
* cross-chain vault `writeContract` calls).
|
|
13
|
+
*
|
|
14
|
+
* @module attribution
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* The 16-byte ERC-8021 suffix terminator. The last 16 bytes of an attributed
|
|
18
|
+
* transaction's calldata are always this marker; parsers read backwards from
|
|
19
|
+
* it to recover the schema ID and builder codes.
|
|
20
|
+
*/
|
|
21
|
+
export declare const ERC8021_MARKER = "80218021802180218021802180218021";
|
|
22
|
+
/**
|
|
23
|
+
* Configuration for ERC-8021 calldata-suffix attribution (Base Builder Codes).
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const sdk = new AugustSDK({
|
|
28
|
+
* appName: 'my-app',
|
|
29
|
+
* providers: { 8453: 'https://...' },
|
|
30
|
+
* keys: { august: '...' },
|
|
31
|
+
* attribution: { builderCodes: ['bc_abc123'] },
|
|
32
|
+
* });
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export interface IAttributionConfig {
|
|
36
|
+
/**
|
|
37
|
+
* Builder codes to embed in the suffix, e.g. from base.dev registration
|
|
38
|
+
* (`bc_…`). ASCII strings of 1–64 characters each; the comma-joined list
|
|
39
|
+
* must fit in 255 bytes (schema 0 length prefix is a single byte).
|
|
40
|
+
*/
|
|
41
|
+
builderCodes: string[];
|
|
42
|
+
/**
|
|
43
|
+
* EVM chain IDs to attribute. Omit to attribute writes on every EVM chain
|
|
44
|
+
* (the suffix is inert on chains without an ERC-8021 indexer and costs
|
|
45
|
+
* ~16 gas per non-zero byte). When set, writes on other chains are sent
|
|
46
|
+
* without the suffix; call sites that cannot determine their chain ID
|
|
47
|
+
* append the suffix regardless, since over-attribution is harmless and
|
|
48
|
+
* under-attribution loses data.
|
|
49
|
+
*/
|
|
50
|
+
chains?: number[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Build an ERC-8021 schema-0 attribution suffix from builder codes.
|
|
54
|
+
*
|
|
55
|
+
* Layout (appended to calldata, read back-to-front by parsers):
|
|
56
|
+
* `ascii(codes.join(','))` ∥ codesLength (1 byte) ∥ schemaId `0x00` ∥
|
|
57
|
+
* 16-byte marker `0x80218021802180218021802180218021`.
|
|
58
|
+
*
|
|
59
|
+
* @param codes Builder codes as printable-ASCII strings (e.g. `bc_abc123`),
|
|
60
|
+
* each 1–64 chars, no commas; the comma-joined list must be ≤ 255 bytes.
|
|
61
|
+
* @returns `0x`-prefixed hex suffix ready to concatenate onto calldata.
|
|
62
|
+
* @throws Error when `codes` is empty, a code contains a comma or
|
|
63
|
+
* non-printable/non-ASCII characters, or the joined list exceeds 255 bytes.
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* buildAttributionSuffix(['baseapp', 'morpho']);
|
|
67
|
+
* // '0x626173656170702c6d6f7270686f0e0080218021802180218021802180218021'
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export declare function buildAttributionSuffix(codes: string[]): string;
|
|
71
|
+
/**
|
|
72
|
+
* Set (or clear) the process-global attribution config.
|
|
73
|
+
*
|
|
74
|
+
* Called unconditionally on every `AugustSDK` construction — an instance
|
|
75
|
+
* that omits `attribution` passes `null` and RESETS the state, so a prior
|
|
76
|
+
* instance's builder codes never leak into a later instance in the same
|
|
77
|
+
* process (same semantics as `setPublicApiBaseUrl`).
|
|
78
|
+
*
|
|
79
|
+
* @param config Attribution config from the SDK constructor, or `null` to
|
|
80
|
+
* disable attribution.
|
|
81
|
+
* @throws Error when the config's builder codes fail validation — thrown
|
|
82
|
+
* synchronously from the constructor so misconfiguration is caught at
|
|
83
|
+
* init, not on the first write.
|
|
84
|
+
*/
|
|
85
|
+
export declare function setAttribution(config: IAttributionConfig | null): void;
|
|
86
|
+
/**
|
|
87
|
+
* Return the active ERC-8021 suffix for a write on the given chain, or
|
|
88
|
+
* `undefined` when attribution is off or the chain is excluded.
|
|
89
|
+
*
|
|
90
|
+
* @param chainId EVM chain ID of the transaction, when the call site knows
|
|
91
|
+
* it. When omitted and a `chains` restriction is configured, the suffix is
|
|
92
|
+
* returned anyway (over-attribution is harmless; see
|
|
93
|
+
* {@link IAttributionConfig.chains}).
|
|
94
|
+
* @returns `0x`-prefixed hex suffix, or `undefined` when nothing should be
|
|
95
|
+
* appended.
|
|
96
|
+
*/
|
|
97
|
+
export declare function getAttributionSuffix(chainId?: number): string | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* Append the active attribution suffix to calldata.
|
|
100
|
+
*
|
|
101
|
+
* No-ops (returns `data` unchanged) when attribution is off, the chain is
|
|
102
|
+
* excluded, `data` is empty/absent (plain value transfers are never
|
|
103
|
+
* attributed), or `data` already ends with the ERC-8021 marker (guards
|
|
104
|
+
* against double-appending when an upstream layer — e.g. a wagmi config
|
|
105
|
+
* `dataSuffix` — already attributed the transaction).
|
|
106
|
+
*
|
|
107
|
+
* @param data `0x`-prefixed calldata of the outgoing transaction.
|
|
108
|
+
* @param chainId EVM chain ID of the transaction, when known.
|
|
109
|
+
* @returns Calldata with the suffix appended, or the input unchanged.
|
|
110
|
+
*/
|
|
111
|
+
export declare function appendAttributionSuffix(data: string | undefined | null, chainId?: number): string | undefined | null;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ERC-8021 transaction attribution (Base Builder Codes).
|
|
4
|
+
*
|
|
5
|
+
* ERC-8021 appends an attribution suffix to transaction calldata. Contracts
|
|
6
|
+
* decode their ABI-encoded arguments normally and ignore the trailing bytes,
|
|
7
|
+
* while offchain indexers (e.g. base.dev analytics for Base Builder Codes)
|
|
8
|
+
* read the suffix to attribute the transaction to the originating app.
|
|
9
|
+
*
|
|
10
|
+
* This is a leaf module: process-global state configured once via
|
|
11
|
+
* {@link IAugustBase.attribution} in the SDK constructor and read by the
|
|
12
|
+
* EVM write paths (the normalized-signer wrap in the EVM adapter and the
|
|
13
|
+
* cross-chain vault `writeContract` calls).
|
|
14
|
+
*
|
|
15
|
+
* @module attribution
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.ERC8021_MARKER = void 0;
|
|
19
|
+
exports.buildAttributionSuffix = buildAttributionSuffix;
|
|
20
|
+
exports.setAttribution = setAttribution;
|
|
21
|
+
exports.getAttributionSuffix = getAttributionSuffix;
|
|
22
|
+
exports.appendAttributionSuffix = appendAttributionSuffix;
|
|
23
|
+
/**
|
|
24
|
+
* The 16-byte ERC-8021 suffix terminator. The last 16 bytes of an attributed
|
|
25
|
+
* transaction's calldata are always this marker; parsers read backwards from
|
|
26
|
+
* it to recover the schema ID and builder codes.
|
|
27
|
+
*/
|
|
28
|
+
exports.ERC8021_MARKER = '80218021802180218021802180218021';
|
|
29
|
+
/**
|
|
30
|
+
* Build an ERC-8021 schema-0 attribution suffix from builder codes.
|
|
31
|
+
*
|
|
32
|
+
* Layout (appended to calldata, read back-to-front by parsers):
|
|
33
|
+
* `ascii(codes.join(','))` ∥ codesLength (1 byte) ∥ schemaId `0x00` ∥
|
|
34
|
+
* 16-byte marker `0x80218021802180218021802180218021`.
|
|
35
|
+
*
|
|
36
|
+
* @param codes Builder codes as printable-ASCII strings (e.g. `bc_abc123`),
|
|
37
|
+
* each 1–64 chars, no commas; the comma-joined list must be ≤ 255 bytes.
|
|
38
|
+
* @returns `0x`-prefixed hex suffix ready to concatenate onto calldata.
|
|
39
|
+
* @throws Error when `codes` is empty, a code contains a comma or
|
|
40
|
+
* non-printable/non-ASCII characters, or the joined list exceeds 255 bytes.
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* buildAttributionSuffix(['baseapp', 'morpho']);
|
|
44
|
+
* // '0x626173656170702c6d6f7270686f0e0080218021802180218021802180218021'
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
function buildAttributionSuffix(codes) {
|
|
48
|
+
if (!Array.isArray(codes) || codes.length === 0) {
|
|
49
|
+
throw new Error('August SDK: attribution.builderCodes must be a non-empty array of builder-code strings (e.g. ["bc_abc123"]). Register at https://base.dev to obtain one.');
|
|
50
|
+
}
|
|
51
|
+
for (const code of codes) {
|
|
52
|
+
if (typeof code !== 'string' ||
|
|
53
|
+
code.length === 0 ||
|
|
54
|
+
code.length > 64 ||
|
|
55
|
+
!/^[\x21-\x7e]+$/.test(code) ||
|
|
56
|
+
code.includes(',')) {
|
|
57
|
+
throw new Error(`August SDK: invalid builder code "${code}". Codes must be 1-64 printable ASCII characters with no commas or whitespace.`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const joined = codes.join(',');
|
|
61
|
+
if (joined.length > 255) {
|
|
62
|
+
throw new Error(`August SDK: attribution builder codes exceed 255 bytes when comma-joined (got ${joined.length}). Use fewer or shorter codes.`);
|
|
63
|
+
}
|
|
64
|
+
let hex = '';
|
|
65
|
+
for (let i = 0; i < joined.length; i++) {
|
|
66
|
+
hex += joined.charCodeAt(i).toString(16).padStart(2, '0');
|
|
67
|
+
}
|
|
68
|
+
const length = joined.length.toString(16).padStart(2, '0');
|
|
69
|
+
return `0x${hex}${length}00${exports.ERC8021_MARKER}`;
|
|
70
|
+
}
|
|
71
|
+
let activeSuffix = null;
|
|
72
|
+
let activeChains = null;
|
|
73
|
+
/**
|
|
74
|
+
* Set (or clear) the process-global attribution config.
|
|
75
|
+
*
|
|
76
|
+
* Called unconditionally on every `AugustSDK` construction — an instance
|
|
77
|
+
* that omits `attribution` passes `null` and RESETS the state, so a prior
|
|
78
|
+
* instance's builder codes never leak into a later instance in the same
|
|
79
|
+
* process (same semantics as `setPublicApiBaseUrl`).
|
|
80
|
+
*
|
|
81
|
+
* @param config Attribution config from the SDK constructor, or `null` to
|
|
82
|
+
* disable attribution.
|
|
83
|
+
* @throws Error when the config's builder codes fail validation — thrown
|
|
84
|
+
* synchronously from the constructor so misconfiguration is caught at
|
|
85
|
+
* init, not on the first write.
|
|
86
|
+
*/
|
|
87
|
+
function setAttribution(config) {
|
|
88
|
+
if (!config) {
|
|
89
|
+
activeSuffix = null;
|
|
90
|
+
activeChains = null;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
activeSuffix = buildAttributionSuffix(config.builderCodes);
|
|
94
|
+
activeChains =
|
|
95
|
+
Array.isArray(config.chains) && config.chains.length > 0
|
|
96
|
+
? config.chains
|
|
97
|
+
: null;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Return the active ERC-8021 suffix for a write on the given chain, or
|
|
101
|
+
* `undefined` when attribution is off or the chain is excluded.
|
|
102
|
+
*
|
|
103
|
+
* @param chainId EVM chain ID of the transaction, when the call site knows
|
|
104
|
+
* it. When omitted and a `chains` restriction is configured, the suffix is
|
|
105
|
+
* returned anyway (over-attribution is harmless; see
|
|
106
|
+
* {@link IAttributionConfig.chains}).
|
|
107
|
+
* @returns `0x`-prefixed hex suffix, or `undefined` when nothing should be
|
|
108
|
+
* appended.
|
|
109
|
+
*/
|
|
110
|
+
function getAttributionSuffix(chainId) {
|
|
111
|
+
if (!activeSuffix)
|
|
112
|
+
return undefined;
|
|
113
|
+
if (activeChains && typeof chainId === 'number') {
|
|
114
|
+
if (!activeChains.includes(chainId))
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
return activeSuffix;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Append the active attribution suffix to calldata.
|
|
121
|
+
*
|
|
122
|
+
* No-ops (returns `data` unchanged) when attribution is off, the chain is
|
|
123
|
+
* excluded, `data` is empty/absent (plain value transfers are never
|
|
124
|
+
* attributed), or `data` already ends with the ERC-8021 marker (guards
|
|
125
|
+
* against double-appending when an upstream layer — e.g. a wagmi config
|
|
126
|
+
* `dataSuffix` — already attributed the transaction).
|
|
127
|
+
*
|
|
128
|
+
* @param data `0x`-prefixed calldata of the outgoing transaction.
|
|
129
|
+
* @param chainId EVM chain ID of the transaction, when known.
|
|
130
|
+
* @returns Calldata with the suffix appended, or the input unchanged.
|
|
131
|
+
*/
|
|
132
|
+
function appendAttributionSuffix(data, chainId) {
|
|
133
|
+
const suffix = getAttributionSuffix(chainId);
|
|
134
|
+
if (!suffix)
|
|
135
|
+
return data;
|
|
136
|
+
if (typeof data !== 'string' || data === '' || data === '0x')
|
|
137
|
+
return data;
|
|
138
|
+
if (data.toLowerCase().endsWith(exports.ERC8021_MARKER))
|
|
139
|
+
return data;
|
|
140
|
+
return data + suffix.slice(2);
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=attribution.js.map
|
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.
|
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`
|