@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.
Files changed (38) hide show
  1. package/lib/adapters/evm/index.js +4 -2
  2. package/lib/adapters/stellar/soroban.d.ts +8 -3
  3. package/lib/adapters/stellar/soroban.js +9 -4
  4. package/lib/adapters/sui/constants.d.ts +1 -1
  5. package/lib/adapters/sui/constants.js +6 -1
  6. package/lib/core/analytics/constants.d.ts +1 -1
  7. package/lib/core/analytics/constants.js +1 -1
  8. package/lib/core/analytics/sentry.d.ts +7 -0
  9. package/lib/core/analytics/sentry.js +182 -1
  10. package/lib/core/analytics/version.d.ts +1 -1
  11. package/lib/core/analytics/version.js +1 -1
  12. package/lib/core/attribution.d.ts +111 -0
  13. package/lib/core/attribution.js +142 -0
  14. package/lib/core/base.class.d.ts +17 -1
  15. package/lib/core/base.class.js +6 -1
  16. package/lib/core/constants/core.js +42 -15
  17. package/lib/core/constants/web3.d.ts +17 -0
  18. package/lib/core/constants/web3.js +22 -1
  19. package/lib/core/fetcher.js +10 -1
  20. package/lib/core/helpers/chain-error.d.ts +140 -0
  21. package/lib/core/helpers/chain-error.js +412 -0
  22. package/lib/core/helpers/chain-support.d.ts +80 -0
  23. package/lib/core/helpers/chain-support.js +115 -0
  24. package/lib/core/helpers/signer.d.ts +21 -0
  25. package/lib/core/helpers/signer.js +52 -0
  26. package/lib/core/helpers/web3.d.ts +172 -3
  27. package/lib/core/helpers/web3.js +357 -49
  28. package/lib/core/index.d.ts +2 -0
  29. package/lib/core/index.js +2 -0
  30. package/lib/evm/methods/crossChainVault.js +4 -0
  31. package/lib/modules/vaults/getters.js +115 -23
  32. package/lib/modules/vaults/main.d.ts +24 -3
  33. package/lib/modules/vaults/main.js +32 -31
  34. package/lib/modules/vaults/write.actions.d.ts +41 -1
  35. package/lib/modules/vaults/write.actions.js +302 -86
  36. package/lib/sdk.d.ts +11315 -10736
  37. package/lib/services/subgraph/vaults.js +85 -14
  38. 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
- this.signer = await (0, signer_1.normalizeSigner)(this.rawSigner);
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 isRetryableRpcError(err: unknown): boolean;
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 isRetryableRpcError}. */
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 isRetryableRpcError}); each attempt is time-boxed so a hung node can't
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.isRetryableRpcError = isRetryableRpcError;
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 isRetryableRpcError(err) {
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 isRetryableRpcError}); each attempt is time-boxed so a hung node can't
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 ?? isRetryableRpcError;
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,4 +1,4 @@
1
- export declare const SUI_CHAIN_ID = 101;
1
+ export { SUI_CHAIN_ID } from '../../core/constants/web3';
2
2
  export declare const EMBER_API_BASE_URL = "https://vaults.api.sui-prod.bluefin.io/api/v1/vaults";
3
3
  export declare const EMBER_ENDPOINTS: {
4
4
  readonly VAULTS: "/";
@@ -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
- exports.SUI_CHAIN_ID = 101;
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://d73b6a85fe14960b8dac8ac61a743a94@o4507215496609792.ingest.de.sentry.io/4510699327389776";
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://d73b6a85fe14960b8dac8ac61a743a94@o4507215496609792.ingest.de.sentry.io/4510699327389776';
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
- const normalized = error instanceof Error ? error : new Error(String(error));
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
@@ -3,4 +3,4 @@
3
3
  * Generated during publish from package.json version
4
4
  * This file is gitignored and created at publish time
5
5
  */
6
- export declare const SDK_VERSION = "8.17.0";
6
+ export declare const SDK_VERSION = "8.20.1";
@@ -6,5 +6,5 @@ exports.SDK_VERSION = void 0;
6
6
  * Generated during publish from package.json version
7
7
  * This file is gitignored and created at publish time
8
8
  */
9
- exports.SDK_VERSION = '8.17.0';
9
+ exports.SDK_VERSION = '8.20.1';
10
10
  //# sourceMappingURL=version.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