@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
|
@@ -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
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ALLOWED_SUI_VAULT_ADDRESSES = exports.EMBER_DEFAULTS = exports.EMBER_ENDPOINTS = exports.EMBER_API_BASE_URL = exports.SUI_CHAIN_ID = void 0;
|
|
4
|
-
|
|
4
|
+
// Canonical definition lives in `core/constants/web3.ts` so `core/` can
|
|
5
|
+
// recognise the Sui chain ID without importing the adapter layer. Re-exported
|
|
6
|
+
// here to keep `adapters/sui/constants` import sites (and the published API)
|
|
7
|
+
// unchanged.
|
|
8
|
+
var web3_1 = require("../../core/constants/web3");
|
|
9
|
+
Object.defineProperty(exports, "SUI_CHAIN_ID", { enumerable: true, get: function () { return web3_1.SUI_CHAIN_ID; } });
|
|
5
10
|
exports.EMBER_API_BASE_URL = 'https://vaults.api.sui-prod.bluefin.io/api/v1/vaults';
|
|
6
11
|
exports.EMBER_ENDPOINTS = {
|
|
7
12
|
VAULTS: '/',
|
|
@@ -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
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type * as Sentry from '@sentry/browser';
|
|
2
2
|
import type { IEnv } from '../../types';
|
|
3
3
|
import type { IAnalyticsConfig } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* Clear the error dedupe bookkeeping. Test-only — the rate limiter is process
|
|
6
|
+
* lifetime state and would otherwise leak between test cases.
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export declare function resetErrorDedupe(): void;
|
|
4
11
|
/**
|
|
5
12
|
* Initialize Sentry with SDK-specific configuration. Idempotent: re-calls
|
|
6
13
|
* only refresh user identity and the cached API-key hash.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resetErrorDedupe = resetErrorDedupe;
|
|
3
4
|
exports.initializeSentry = initializeSentry;
|
|
4
5
|
exports.updateUser = updateUser;
|
|
5
6
|
exports.clearUser = clearUser;
|
|
@@ -78,6 +79,164 @@ function safeSetUser(user) {
|
|
|
78
79
|
// Silently fail - analytics should never break SDK
|
|
79
80
|
}
|
|
80
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Chains whose errors are dropped before reaching Sentry.
|
|
84
|
+
*
|
|
85
|
+
* Mezo is muted by request: its public dRPC endpoint is unreliable enough that
|
|
86
|
+
* its failures dominated the error stream without ever describing an SDK
|
|
87
|
+
* defect. This is a deliberate blind spot — Mezo bugs (bad vault data, reverts,
|
|
88
|
+
* decode failures) are silenced along with the transport noise. Remove the
|
|
89
|
+
* entry to restore reporting; nothing else needs to change.
|
|
90
|
+
*
|
|
91
|
+
* Keyed by both the `chainIdToTagValue` slug and the numeric chain ID, because
|
|
92
|
+
* the two arrive on different events.
|
|
93
|
+
*/
|
|
94
|
+
const MUTED_CHAIN_SLUGS = new Set(['mezo']);
|
|
95
|
+
/** Numeric chain IDs matching {@link MUTED_CHAIN_SLUGS}. */
|
|
96
|
+
const MUTED_CHAIN_IDS = new Set(['31612']);
|
|
97
|
+
/**
|
|
98
|
+
* Substrings that identify an event as Mezo-related regardless of tags.
|
|
99
|
+
*
|
|
100
|
+
* Tag-based matching alone is not enough: `sdk.chain` / `sdk.last_chainId`
|
|
101
|
+
* record the last *instrumented method's* chain, not the chain of the RPC that
|
|
102
|
+
* actually failed — the production Mezo batch-rejection events are tagged
|
|
103
|
+
* `sdk.chain: ethereum` while carrying `"requestUrl": "https://mezo.drpc.org"`
|
|
104
|
+
* in the message. Matching the message body catches those.
|
|
105
|
+
*/
|
|
106
|
+
const MUTED_CHAIN_MESSAGE_PATTERNS = [/mezo/i];
|
|
107
|
+
/**
|
|
108
|
+
* Whether an event belongs to a muted chain and must not be sent.
|
|
109
|
+
*
|
|
110
|
+
* Checks, in order: the chain tags, then the event message and every exception
|
|
111
|
+
* value/type in the chain (an RPC failure's identifying detail often lives in a
|
|
112
|
+
* wrapped inner error rather than the top-level message).
|
|
113
|
+
*
|
|
114
|
+
* @param event - The Sentry event about to be sent.
|
|
115
|
+
* @returns `true` when the event should be dropped.
|
|
116
|
+
*/
|
|
117
|
+
function isMutedChainEvent(event) {
|
|
118
|
+
const chainTag = event.tags?.['sdk.chain'];
|
|
119
|
+
if (typeof chainTag === 'string' && MUTED_CHAIN_SLUGS.has(chainTag)) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
const chainIdTag = event.tags?.['sdk.last_chainId'];
|
|
123
|
+
if (typeof chainIdTag === 'string' && MUTED_CHAIN_IDS.has(chainIdTag)) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
const haystacks = [];
|
|
127
|
+
if (typeof event.message === 'string')
|
|
128
|
+
haystacks.push(event.message);
|
|
129
|
+
for (const exception of event.exception?.values ?? []) {
|
|
130
|
+
if (typeof exception.value === 'string')
|
|
131
|
+
haystacks.push(exception.value);
|
|
132
|
+
if (typeof exception.type === 'string')
|
|
133
|
+
haystacks.push(exception.type);
|
|
134
|
+
}
|
|
135
|
+
return haystacks.some((text) => MUTED_CHAIN_MESSAGE_PATTERNS.some((pattern) => pattern.test(text)));
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Rolling window, in milliseconds, over which identical errors are counted.
|
|
139
|
+
*/
|
|
140
|
+
const ERROR_DEDUPE_WINDOW_MS = 60_000;
|
|
141
|
+
/**
|
|
142
|
+
* Maximum captures of one error signature per {@link ERROR_DEDUPE_WINDOW_MS}.
|
|
143
|
+
* Chosen so a genuine, ongoing failure is still visible (and its rate
|
|
144
|
+
* observable via `sdk.suppressed_since_last`) while a hot loop hammering a
|
|
145
|
+
* broken RPC or a 404-per-borrower fan-out cannot bill thousands of identical
|
|
146
|
+
* events. Beyond this, further captures in the window are dropped.
|
|
147
|
+
*/
|
|
148
|
+
const ERROR_DEDUPE_MAX_PER_WINDOW = 3;
|
|
149
|
+
/** Hard cap on tracked signatures, so the map cannot grow without bound. */
|
|
150
|
+
const ERROR_DEDUPE_MAX_KEYS = 500;
|
|
151
|
+
/**
|
|
152
|
+
* Per-signature capture bookkeeping for the dedupe window.
|
|
153
|
+
*
|
|
154
|
+
* Deliberately process-global, matching the Sentry client it feeds: the client
|
|
155
|
+
* is a singleton, so every `AugustSDK` instance in the process shares one
|
|
156
|
+
* event stream and one quota. The signature intentionally omits partner/app
|
|
157
|
+
* identity — a host serving several partners reports an identical upstream
|
|
158
|
+
* failure ≤{@link ERROR_DEDUPE_MAX_PER_WINDOW} times per window in aggregate,
|
|
159
|
+
* not per partner, because the failure is one event regardless of how many
|
|
160
|
+
* tenants observed it. Per-tenant attribution comes from the `partner.id` tag
|
|
161
|
+
* on the events that are sent, and `sdk.suppressed_since_last` preserves the
|
|
162
|
+
* true rate. `resetAnalytics()` clears this map for the whole process.
|
|
163
|
+
*/
|
|
164
|
+
const errorDedupeState = new Map();
|
|
165
|
+
/**
|
|
166
|
+
* Build a low-cardinality signature for an error.
|
|
167
|
+
*
|
|
168
|
+
* Message tails carry the variable parts (addresses, block ranges, response
|
|
169
|
+
* bodies), so only the leading 120 characters participate — enough to separate
|
|
170
|
+
* distinct failure modes without splitting one failure mode into thousands of
|
|
171
|
+
* unique keys.
|
|
172
|
+
*
|
|
173
|
+
* @param error - The normalized error being captured.
|
|
174
|
+
* @param origin - The `sdk.origin` call-site tag, when present.
|
|
175
|
+
* @returns A stable key identifying this class of failure.
|
|
176
|
+
*/
|
|
177
|
+
function errorSignature(error, origin) {
|
|
178
|
+
const tag = typeof origin === 'string' ? origin : '';
|
|
179
|
+
return `${tag}|${error.name}|${(error.message || '').slice(0, 120)}`;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Decide whether an error should be sent to Sentry, applying a per-signature
|
|
183
|
+
* rolling rate limit.
|
|
184
|
+
*
|
|
185
|
+
* Why this exists: the SDK is called from server-rendered pages that fan out
|
|
186
|
+
* per vault and per borrower, so a single upstream condition (a 502 from the
|
|
187
|
+
* backend, an RPC rejecting a batch) reproduces on every iteration of every
|
|
188
|
+
* request. Those are the same event, and paying Sentry per copy buys no extra
|
|
189
|
+
* signal.
|
|
190
|
+
*
|
|
191
|
+
* Side effect: mutates {@link errorDedupeState}.
|
|
192
|
+
*
|
|
193
|
+
* @param error - The normalized error about to be captured.
|
|
194
|
+
* @param origin - The `sdk.origin` call-site tag, when present.
|
|
195
|
+
* @returns `send: false` to drop the event. When `send: true`, `suppressed` is
|
|
196
|
+
* the number of copies dropped since the last event that was sent, and is
|
|
197
|
+
* attached to the outgoing event so the true rate stays recoverable.
|
|
198
|
+
*/
|
|
199
|
+
function admitError(error, origin) {
|
|
200
|
+
const key = errorSignature(error, origin);
|
|
201
|
+
const now = Date.now();
|
|
202
|
+
const entry = errorDedupeState.get(key);
|
|
203
|
+
// Touch the key on every access so iteration order is true
|
|
204
|
+
// least-recently-used, not first-inserted: a Map's `set` on an existing key
|
|
205
|
+
// does NOT reorder it, so without the delete a signature that keeps
|
|
206
|
+
// recurring would hold its original position and be evicted ahead of a stale
|
|
207
|
+
// one-shot signature registered after it — silently handing the hot
|
|
208
|
+
// signature a fresh allowance every time it churned out of the map.
|
|
209
|
+
errorDedupeState.delete(key);
|
|
210
|
+
if (!entry || now - entry.windowStart >= ERROR_DEDUPE_WINDOW_MS) {
|
|
211
|
+
// Bound the map: drop the least-recently-used signature before inserting.
|
|
212
|
+
if (errorDedupeState.size >= ERROR_DEDUPE_MAX_KEYS) {
|
|
213
|
+
const oldest = errorDedupeState.keys().next();
|
|
214
|
+
if (!oldest.done)
|
|
215
|
+
errorDedupeState.delete(oldest.value);
|
|
216
|
+
}
|
|
217
|
+
const suppressed = entry?.suppressed ?? 0;
|
|
218
|
+
errorDedupeState.set(key, { windowStart: now, captured: 1, suppressed: 0 });
|
|
219
|
+
return { send: true, suppressed };
|
|
220
|
+
}
|
|
221
|
+
errorDedupeState.set(key, entry);
|
|
222
|
+
if (entry.captured < ERROR_DEDUPE_MAX_PER_WINDOW) {
|
|
223
|
+
// `suppressed` can only be non-zero once the allowance is exhausted, so
|
|
224
|
+
// inside the allowance there is never a carried count to report.
|
|
225
|
+
entry.captured += 1;
|
|
226
|
+
return { send: true, suppressed: 0 };
|
|
227
|
+
}
|
|
228
|
+
entry.suppressed += 1;
|
|
229
|
+
return { send: false, suppressed: entry.suppressed };
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Clear the error dedupe bookkeeping. Test-only — the rate limiter is process
|
|
233
|
+
* lifetime state and would otherwise leak between test cases.
|
|
234
|
+
*
|
|
235
|
+
* @internal
|
|
236
|
+
*/
|
|
237
|
+
function resetErrorDedupe() {
|
|
238
|
+
errorDedupeState.clear();
|
|
239
|
+
}
|
|
81
240
|
/**
|
|
82
241
|
* Build the SDK's internal Sentry sink — the bridge that forwards
|
|
83
242
|
* `Logger.log.*` output to the resolved Sentry SDK. Errors are captured as
|
|
@@ -95,6 +254,12 @@ function createSentrySink() {
|
|
|
95
254
|
const sdk = (0, sentry_runtime_1.getSentrySDK)();
|
|
96
255
|
if (!sdk)
|
|
97
256
|
return;
|
|
257
|
+
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
258
|
+
// Rate-limit identical failures before touching Sentry at all — the
|
|
259
|
+
// scope work and the event payload are both wasted if it is dropped.
|
|
260
|
+
const admission = admitError(normalized, context?.tag);
|
|
261
|
+
if (!admission.send)
|
|
262
|
+
return;
|
|
98
263
|
sdk.withScope((scope) => {
|
|
99
264
|
// `beforeSend` drops any event not tagged as ours; the tag is global
|
|
100
265
|
// already, but set it on the scope defensively in case a future
|
|
@@ -115,7 +280,10 @@ function createSentrySink() {
|
|
|
115
280
|
}
|
|
116
281
|
}
|
|
117
282
|
}
|
|
118
|
-
|
|
283
|
+
// Preserve the true failure rate even though copies were dropped.
|
|
284
|
+
if (admission.suppressed > 0) {
|
|
285
|
+
scope.setExtra('sdk.suppressed_since_last', admission.suppressed);
|
|
286
|
+
}
|
|
119
287
|
sdk.captureException(normalized);
|
|
120
288
|
});
|
|
121
289
|
}
|
|
@@ -243,12 +411,22 @@ function initializeSentry(config, environment, walletAddress, apiKey, appName) {
|
|
|
243
411
|
// Local dev/test node leaking into a prod DSN: :8545 is the canonical
|
|
244
412
|
// Anvil/Hardhat port and is never a production RPC endpoint.
|
|
245
413
|
/ECONNREFUSED .*:8545/,
|
|
414
|
+
// AugustHistoryUnavailableError is a designed, documented outcome:
|
|
415
|
+
// the getter refuses to return a fabricated number when the indexer
|
|
416
|
+
// has no deposit/withdrawal history. It is thrown to the caller, who
|
|
417
|
+
// renders "temporarily unavailable" — the SDK is behaving correctly,
|
|
418
|
+
// so it is not an SDK issue. Indexer health is monitored separately.
|
|
419
|
+
/transaction history unavailable/i,
|
|
246
420
|
],
|
|
247
421
|
beforeSend(event) {
|
|
248
422
|
// Only send events tagged as SDK-related
|
|
249
423
|
if (event.tags?.sdk !== 'august-digital') {
|
|
250
424
|
return null;
|
|
251
425
|
}
|
|
426
|
+
// Muted chains (see MUTED_CHAIN_SLUGS) are dropped wholesale.
|
|
427
|
+
if (isMutedChainEvent(event)) {
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
252
430
|
return event;
|
|
253
431
|
},
|
|
254
432
|
beforeSendTransaction(transaction) {
|
|
@@ -383,6 +561,9 @@ function resetAnalytics() {
|
|
|
383
561
|
cachedApiKey = undefined;
|
|
384
562
|
logger_1.Logger.setSentrySink(null);
|
|
385
563
|
(0, sentry_runtime_1.resetSentryRuntime)();
|
|
564
|
+
// The error rate limiter is process-lifetime state; a reset analytics layer
|
|
565
|
+
// must not inherit a half-full dedupe window from the previous one.
|
|
566
|
+
resetErrorDedupe();
|
|
386
567
|
}
|
|
387
568
|
/**
|
|
388
569
|
* Capture an exception into Sentry with SDK-specific context. Use from
|
|
@@ -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
|