@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
@@ -1,8 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LP_TOKEN_ADDRESS_SELECTOR = void 0;
3
4
  exports.isUserRejectionError = isUserRejectionError;
4
5
  exports.isExpectedRevertError = isExpectedRevertError;
5
6
  exports.isInsufficientFundsError = isInsufficientFundsError;
7
+ exports.isRetryableRpcError = isRetryableRpcError;
8
+ exports.isEmptyViewResponse = isEmptyViewResponse;
9
+ exports.retryOnTransientRpc = retryOnTransientRpc;
6
10
  exports.logChainError = logChainError;
7
11
  const logger_1 = require("../logger");
8
12
  /**
@@ -239,6 +243,414 @@ function isInsufficientFundsError(error) {
239
243
  return INSUFFICIENT_FUNDS_PHRASES.some((phrase) => msg.includes(phrase));
240
244
  });
241
245
  }
246
+ /**
247
+ * Collect the HTTP status codes an error-like value may carry. Transport
248
+ * failures that reach us through ethers' `FetchRequest` keep the upstream
249
+ * status on the error (`status`), on the wrapped fetch response
250
+ * (`info.status` / `response.status`), or on a `statusCode` alias depending on
251
+ * which layer produced it. We scan all of them so a caller needn't know.
252
+ *
253
+ * @param error - The caught value, of unknown type.
254
+ * @returns Every numeric status found (possibly empty).
255
+ */
256
+ function errorStatuses(error) {
257
+ const out = [];
258
+ const push = (value) => {
259
+ if (typeof value === 'number' && Number.isFinite(value))
260
+ out.push(value);
261
+ };
262
+ if (error && typeof error === 'object') {
263
+ const e = error;
264
+ push(e.status);
265
+ push(e.statusCode);
266
+ push(e.info?.status);
267
+ push(e.info?.statusCode);
268
+ push(e.response?.status);
269
+ push(e.response?.statusCode);
270
+ }
271
+ return out;
272
+ }
273
+ /**
274
+ * Does this error carry evidence that the EVM actually executed and reverted?
275
+ *
276
+ * Used as a **veto** by {@link isRetryableRpcError}: nodes reuse the generic
277
+ * JSON-RPC codes (`-32603`, `-32000`) for genuine execution reverts as well as
278
+ * for internal/transport faults, so a positive transport match must never win
279
+ * over a real revert. Evidence of real execution is:
280
+ *
281
+ * - a `CALL_EXCEPTION` that carries non-empty revert `data` (the ABI-encoded
282
+ * custom error / `Error(string)` payload),
283
+ * - an `execution reverted` / `call revert exception` message anywhere in the
284
+ * nested error chain,
285
+ * - an attached receipt with `status === 0` (the tx mined and failed).
286
+ *
287
+ * Note that a bare `CALL_EXCEPTION` with **no** revert data (ethers' `missing
288
+ * revert data`) is deliberately *not* evidence — see {@link isRetryableRpcError}.
289
+ *
290
+ * @param error - The caught value, of unknown type.
291
+ * @returns `true` when the error proves on-chain execution reverted.
292
+ */
293
+ function hasRevertEvidence(error) {
294
+ if (error && typeof error === 'object') {
295
+ const e = error;
296
+ const codes = errorCodes(error);
297
+ if (codes.includes('CALL_EXCEPTION') &&
298
+ typeof e.data === 'string' &&
299
+ e.data.length > 2) {
300
+ return true;
301
+ }
302
+ if (e.receipt?.status === 0)
303
+ return true;
304
+ }
305
+ return nestedMessages(error).some((raw) => {
306
+ const msg = raw.toLowerCase();
307
+ return (msg.includes('execution reverted') ||
308
+ msg.includes('call revert exception'));
309
+ });
310
+ }
311
+ /**
312
+ * JSON-RPC error codes that indicate the *node or its transport* failed, not
313
+ * that the EVM rejected the call. `-32603` is the spec's "Internal error" and
314
+ * `-32000` the de-facto "Server error" both Alchemy and Infura emit for
315
+ * transient upstream faults (including while serving
316
+ * `eth_getTransactionReceipt` for a freshly broadcast tx).
317
+ */
318
+ const RETRYABLE_RPC_CODES = [
319
+ -32603,
320
+ -32000,
321
+ 'NETWORK_ERROR',
322
+ 'SERVER_ERROR',
323
+ 'TIMEOUT',
324
+ 'ETIMEDOUT',
325
+ 'ECONNRESET',
326
+ 'ECONNREFUSED',
327
+ 'ENOTFOUND',
328
+ 'EAI_AGAIN',
329
+ ];
330
+ /**
331
+ * Message fragments (lower-cased) that identify a transient transport failure.
332
+ *
333
+ * `could not coalesce error` is ethers v6's catch-all when it cannot map a
334
+ * node's JSON-RPC payload onto a typed error — in practice this is what a
335
+ * flaky provider looks like from inside `tx.wait()`. `eth_gettransactionreceipt`
336
+ * is included because a failure *naming that method* is by definition a receipt
337
+ * poll, which is safe to repeat: the transaction is already broadcast and
338
+ * polling is idempotent.
339
+ *
340
+ * `missing revert data` is deliberately **absent**. It is a `CALL_EXCEPTION`,
341
+ * not a transport frame, and {@link isExpectedRevertError} already treats it as
342
+ * a revert — having this predicate disagree would make the two classifiers
343
+ * contradict each other. The narrow subset of `missing revert data` that really
344
+ * is a transport artefact (an empty response to an argument-free view call) has
345
+ * its own predicate: {@link isEmptyViewResponse}.
346
+ */
347
+ const RETRYABLE_RPC_PHRASES = [
348
+ 'could not coalesce error',
349
+ 'eth_gettransactionreceipt',
350
+ 'timeout',
351
+ 'timed out',
352
+ 'etimedout',
353
+ 'econnreset',
354
+ 'econnrefused',
355
+ 'enotfound',
356
+ 'socket hang up',
357
+ 'network error',
358
+ 'network request failed',
359
+ 'failed to fetch',
360
+ 'fetch failed',
361
+ 'load failed',
362
+ 'connection closed',
363
+ 'connection reset',
364
+ 'too many requests',
365
+ 'rate limit',
366
+ 'service unavailable',
367
+ 'bad gateway',
368
+ 'gateway timeout',
369
+ 'internal server error',
370
+ 'internal error',
371
+ 'server error',
372
+ ];
373
+ /**
374
+ * Is this error a transient RPC **transport** failure that is safe to retry,
375
+ * rather than a decision the chain made?
376
+ *
377
+ * Why this exists: the SDK's write paths poll `eth_getTransactionReceipt` to
378
+ * confirm a broadcast transaction. When the provider hiccups mid-poll, ethers
379
+ * surfaces `could not coalesce error (error={ "code": -32603, … "method":
380
+ * "eth_getTransactionReceipt" … })`. Historically that propagated out of
381
+ * `safeWaitForTx` and the SDK reported the write as **failed** — even though
382
+ * the transaction was broadcast, its hash was known, and it mined fine. Users
383
+ * then retried and hit `ERC20InsufficientBalance` because the first attempt had
384
+ * in fact succeeded. Classifying the failure as transport-level lets callers
385
+ * re-poll instead of lying to the user.
386
+ *
387
+ * Matches, in order of precedence:
388
+ * 1. **Veto** — anything with revert evidence ({@link hasRevertEvidence}:
389
+ * `CALL_EXCEPTION` carrying revert `data`, an `execution reverted` message,
390
+ * or an attached `receipt.status === 0`) returns `false`. Nodes reuse
391
+ * `-32603`/`-32000` for real reverts, so the veto must come first.
392
+ * 2. JSON-RPC / ethers transport codes — see `RETRYABLE_RPC_CODES`.
393
+ * 3. HTTP `429` and any `5xx` carried on the error.
394
+ * 4. Transport message fragments — see `RETRYABLE_RPC_PHRASES`.
395
+ *
396
+ * Retrying is only safe for **idempotent** work: re-reading an immutable value
397
+ * (`decimals()`) or re-polling a receipt for a hash that is already on the
398
+ * wire. Never use this to re-send a transaction.
399
+ *
400
+ * @param error - The caught value, of unknown type.
401
+ * @returns `true` when the failure is a transient transport fault worth
402
+ * retrying with backoff; `false` for chain-level decisions (reverts) and for
403
+ * anything unrecognised — the safe default is to surface the error.
404
+ *
405
+ * @example
406
+ * ```ts
407
+ * try {
408
+ * return await provider.waitForTransaction(hash, 1, 120_000);
409
+ * } catch (e) {
410
+ * if (!isRetryableRpcError(e)) throw e; // real revert — surface it
411
+ * await sleep(250);
412
+ * return await provider.waitForTransaction(hash, 1, 120_000);
413
+ * }
414
+ * ```
415
+ */
416
+ function isRetryableRpcError(error) {
417
+ if (error === null || error === undefined)
418
+ return false;
419
+ // A chain-level decision is never a transport fault. Check first: nodes
420
+ // reuse -32603/-32000 for genuine execution reverts.
421
+ if (hasRevertEvidence(error))
422
+ return false;
423
+ const codes = errorCodes(error);
424
+ if (codes.some((code) => RETRYABLE_RPC_CODES.includes(code)))
425
+ return true;
426
+ // 429 (rate limited) and any 5xx are upstream faults, not our request being
427
+ // wrong — 4xx other than 429 means retrying would fail identically.
428
+ if (errorStatuses(error).some((status) => status === 429 || (status >= 500 && status < 600))) {
429
+ return true;
430
+ }
431
+ return nestedMessages(error).some((raw) => {
432
+ const msg = raw.toLowerCase();
433
+ return RETRYABLE_RPC_PHRASES.some((phrase) => msg.includes(phrase));
434
+ });
435
+ }
436
+ /**
437
+ * `lpTokenAddress()` — `keccak256("lpTokenAddress()")[0..4]`. The August `evm-2`
438
+ * tokenized vault's receipt-token getter, exported so the readers that invoke it
439
+ * can scope {@link isEmptyViewResponse} to exactly this call instead of
440
+ * duplicating the literal.
441
+ */
442
+ exports.LP_TOKEN_ADDRESS_SELECTOR = '0xf5ae497a';
443
+ /**
444
+ * Four-byte selectors for the argument-free view functions that a correctly
445
+ * addressed contract **cannot** legitimately revert on. Each is
446
+ * `keccak256(signature)[0..4]`.
447
+ *
448
+ * These are the only calls for which an empty RPC response is unambiguously a
449
+ * provider artefact rather than a contract decision — the metadata they return
450
+ * is fixed at deployment and takes no arguments, so there is no input that
451
+ * could make them fail.
452
+ *
453
+ * **Caveat for `lpTokenAddress()`.** The four ERC-20 entries hold that property
454
+ * absolutely: any deployed, conforming token implements them. `lpTokenAddress()`
455
+ * holds it only *given correct routing* — the function exists on `evm-2` vaults
456
+ * and nowhere else, so a vault wrongly routed into the `evm-2` branch returns
457
+ * empty returndata **deterministically**, producing a byte-identical error to a
458
+ * provider blip. That ambiguity is resolved not by this predicate but by the
459
+ * caller: every reader that retries on this selector is bounded (3 attempts) and
460
+ * rethrows the original error once they are spent, so a deterministic misroute
461
+ * still surfaces unchanged — only a transient blip is absorbed. Never pair this
462
+ * selector with an unbounded retry or a fallback value.
463
+ */
464
+ const ARGUMENT_FREE_VIEW_SELECTORS = new Set([
465
+ '0x313ce567', // decimals()
466
+ '0x95d89b41', // symbol()
467
+ '0x06fdde03', // name()
468
+ '0x18160ddd', // totalSupply()
469
+ exports.LP_TOKEN_ADDRESS_SELECTOR, // lpTokenAddress()
470
+ ]);
471
+ /**
472
+ * Extract the 4-byte selector of the call an ethers `CALL_EXCEPTION` describes.
473
+ *
474
+ * ethers v6 attaches the attempted call as `error.transaction.data`, and also
475
+ * embeds it in the human-readable message as `data="0x…"`. We read the
476
+ * structured field first and fall back to the message so the predicate still
477
+ * works on an error that has been serialized and rehydrated (which is how these
478
+ * arrive from a logging pipeline).
479
+ *
480
+ * @param error - The caught value, of unknown type.
481
+ * @returns The lower-cased `0x`-prefixed 4-byte selector, or `null` when the
482
+ * error does not name a call.
483
+ */
484
+ function callSelector(error) {
485
+ if (error && typeof error === 'object') {
486
+ const e = error;
487
+ const data = e.transaction?.data;
488
+ if (typeof data === 'string' && /^0x[0-9a-fA-F]{8}/.test(data)) {
489
+ return data.slice(0, 10).toLowerCase();
490
+ }
491
+ }
492
+ // ethers renders the same field two ways depending on nesting depth:
493
+ // `data="0x313ce567"` at the top level and `"data": "0x313ce567"` inside the
494
+ // serialized `transaction={…}` blob. Accept both.
495
+ for (const raw of nestedMessages(error)) {
496
+ const match = raw.match(/data"?\s*[:=]\s*"(0x[0-9a-fA-F]{8})/);
497
+ if (match?.[1])
498
+ return match[1].toLowerCase();
499
+ }
500
+ return null;
501
+ }
502
+ /**
503
+ * Is this error an **empty RPC response to an argument-free view call** —
504
+ * i.e. a transport artefact wearing a revert's clothes?
505
+ *
506
+ * Why this is separate from {@link isRetryableRpcError}: when a provider
507
+ * truncates or 500s a response to `eth_call`, ethers reports
508
+ * `missing revert data (action="call", data="0x313ce567", …)` with a `null`
509
+ * `data` field. That is byte-for-byte the shape of a genuine revert with no
510
+ * reason string, so a general "transport" predicate cannot safely claim it —
511
+ * doing so would retry every data-less `CALL_EXCEPTION` in the SDK. This
512
+ * predicate narrows the claim to the one case where the ambiguity resolves:
513
+ * a **deployed ERC-20's `decimals()`/`symbol()`/`name()`/`totalSupply()` cannot
514
+ * legitimately revert**, because it takes no arguments and returns state fixed
515
+ * at deployment. An empty response there is the provider's fault, full stop.
516
+ *
517
+ * A match requires all of:
518
+ * 1. no revert evidence ({@link hasRevertEvidence}) — anything carrying real
519
+ * revert `data`, an `execution reverted` message, or a failed receipt is out;
520
+ * 2. a `missing revert data` message;
521
+ * 3. an `action` of `call` or `staticCall` — a read, never a state change;
522
+ * 4. **when a selector is derivable** from the error, that it is
523
+ * `expectedSelector` (if given) or one of
524
+ * {@link ARGUMENT_FREE_VIEW_SELECTORS}. When no selector can be recovered,
525
+ * conditions 1–3 stand on their own.
526
+ *
527
+ * Note the cost of a false positive is bounded and small: the caller retries an
528
+ * idempotent read a couple of times before surfacing the same error. The cost
529
+ * of a false negative is the production flood this predicate exists to stop.
530
+ *
531
+ * @param error - The caught value, of unknown type.
532
+ * @param expectedSelector - Optional `0x`-prefixed 4-byte selector the caller
533
+ * knows it invoked (e.g. `'0x313ce567'` for `decimals()`). When supplied, the
534
+ * error's own selector must match it — this stops a `decimals()` retry from
535
+ * firing on an unrelated view call that happened to fail the same way.
536
+ * @returns `true` when the failure is an empty provider response to a view call
537
+ * that cannot revert, and is therefore safe to retry.
538
+ *
539
+ * @example
540
+ * ```ts
541
+ * try { return Number(await erc20.decimals()); }
542
+ * catch (e) {
543
+ * if (!isEmptyViewResponse(e, '0x313ce567')) throw e; // real problem
544
+ * return Number(await erc20.decimals()); // provider blip
545
+ * }
546
+ * ```
547
+ */
548
+ function isEmptyViewResponse(error, expectedSelector) {
549
+ if (error === null || error === undefined)
550
+ return false;
551
+ if (hasRevertEvidence(error))
552
+ return false;
553
+ const messages = nestedMessages(error).map((raw) => raw.toLowerCase());
554
+ if (!messages.some((msg) => msg.includes('missing revert data')))
555
+ return false;
556
+ // The call must be a read. ethers exposes this as a structured `action`
557
+ // ('call' | 'estimateGas' | 'sendTransaction' | …) and mirrors it in the
558
+ // message as action="call".
559
+ const action = error && typeof error === 'object'
560
+ ? error.action
561
+ : undefined;
562
+ const isRead = action === 'call' ||
563
+ action === 'staticCall' ||
564
+ messages.some((msg) => msg.includes('action="call"') || msg.includes('action="staticcall"'));
565
+ if (!isRead)
566
+ return false;
567
+ const selector = callSelector(error);
568
+ // Nothing to check against — conditions 1-3 already establish "empty response
569
+ // to a read", which is the signal we act on.
570
+ if (!selector)
571
+ return true;
572
+ if (expectedSelector)
573
+ return selector === expectedSelector.toLowerCase();
574
+ return ARGUMENT_FREE_VIEW_SELECTORS.has(selector);
575
+ }
576
+ /**
577
+ * How many times an idempotent RPC read is attempted in total (1 initial call +
578
+ * 2 retries) before the transport error is surfaced. Deliberately small:
579
+ * CLAUDE.md §4.2 — retrying harder during a provider outage amplifies load
580
+ * rather than recovering from it.
581
+ */
582
+ const RPC_RETRY_ATTEMPTS = 3;
583
+ /**
584
+ * Base backoff between retry attempts, in milliseconds. Doubles per attempt
585
+ * (250ms, then 500ms), so a fully-failed read costs ~750ms of added latency.
586
+ */
587
+ const RPC_RETRY_BASE_DELAY_MS = 250;
588
+ /**
589
+ * Run an **idempotent** RPC read, retrying with exponential backoff while the
590
+ * failure classifies as a transient transport fault
591
+ * ({@link isRetryableRpcError}).
592
+ *
593
+ * Lives next to the classifiers it consumes so there is exactly one retry
594
+ * implementation in the SDK: both the receipt-poll fallback in the vault write
595
+ * paths and the cached `decimals()` reader in `core/helpers/web3.ts` call this.
596
+ *
597
+ * Only safe for operations that can be repeated without side effects: polling
598
+ * `eth_getTransactionReceipt` for an already-broadcast hash, or re-reading an
599
+ * immutable value such as `decimals()`. **Never wrap a transaction send in
600
+ * this.**
601
+ *
602
+ * Anything that is not a transport fault (a genuine revert, a user rejection,
603
+ * an insufficient-funds rejection) is rethrown on the first attempt with no
604
+ * delay, so real failures still fail fast.
605
+ *
606
+ * @param tag - Low-cardinality log label for the retry breadcrumb.
607
+ * @param operation - The idempotent async read to run.
608
+ * @param context - Extra structured context for the retry breadcrumb (e.g.
609
+ * `{ hash }`). Sanitized by the logger before transport.
610
+ * @param isRetryable - Predicate deciding whether a caught error warrants
611
+ * another attempt. Defaults to the strict transport definition
612
+ * ({@link isRetryableRpcError}); pass a wider one only where the call site
613
+ * can prove the extra shape is also a provider artefact — the only such case
614
+ * today is the selector-scoped {@link isEmptyViewResponse} used by
615
+ * `getDecimalsOrThrow`.
616
+ * @returns Whatever `operation` resolves to on the first successful attempt.
617
+ * @throws The last error thrown by `operation` once retries are exhausted, or
618
+ * immediately when the error is not retryable.
619
+ *
620
+ * @example
621
+ * ```ts
622
+ * const receipt = await retryOnTransientRpc(
623
+ * 'safeWaitForTx:transport-retry',
624
+ * () => provider.waitForTransaction(hash, 1, 120_000),
625
+ * { hash },
626
+ * );
627
+ * ```
628
+ */
629
+ async function retryOnTransientRpc(tag, operation, context = {}, isRetryable = isRetryableRpcError) {
630
+ let lastError;
631
+ for (let attempt = 1; attempt <= RPC_RETRY_ATTEMPTS; attempt += 1) {
632
+ try {
633
+ return await operation();
634
+ }
635
+ catch (error) {
636
+ lastError = error;
637
+ if (!isRetryable(error) || attempt === RPC_RETRY_ATTEMPTS) {
638
+ throw error;
639
+ }
640
+ const delayMs = RPC_RETRY_BASE_DELAY_MS * 2 ** (attempt - 1);
641
+ logger_1.Logger.log.warn(tag, 'transient RPC error; retrying', {
642
+ ...context,
643
+ attempt,
644
+ attempts: RPC_RETRY_ATTEMPTS,
645
+ delayMs,
646
+ message: error instanceof Error ? error.message : String(error),
647
+ });
648
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
649
+ }
650
+ }
651
+ // Unreachable: the loop either returns or throws on its final attempt.
652
+ throw lastError;
653
+ }
242
654
  /**
243
655
  * Log a caught chain error at the severity its category warrants, without
244
656
  * swallowing it. When `isBenign` is `true` the failure is recorded as a `warn`
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Chain-ID validation for public SDK entry points.
3
+ *
4
+ * Two distinct failure modes are covered here, and they are deliberately kept
5
+ * separate because they have different remediations:
6
+ *
7
+ * 1. **Unknown chain ID** — the caller passed an ID the SDK has no concept of
8
+ * (a typo, a stale constant, an off-by-one on a non-EVM synthetic ID). No
9
+ * configuration can make this work; the call site is wrong.
10
+ * 2. **Known EVM chain, no RPC configured** — the ID is valid but the SDK was
11
+ * constructed without a provider for it. Remediated by passing an RPC URL.
12
+ *
13
+ * Both used to be logged-and-ignored, letting execution continue with an
14
+ * `undefined` RPC URL. That produced a cascade of misleading downstream
15
+ * failures (`connect ECONNREFUSED 127.0.0.1:8545` from ethers' localhost
16
+ * default, `missing revert data` from `decimals()` reads issued against the
17
+ * wrong chain, and `TypeError: Cannot read properties of undefined`). Failing
18
+ * fast at the boundary replaces that cascade with one actionable error.
19
+ *
20
+ * @module core/helpers/chain-support
21
+ */
22
+ /**
23
+ * Whether `chainId` is an EVM chain the SDK knows about (i.e. has an entry in
24
+ * {@link NETWORKS}).
25
+ *
26
+ * @param chainId - Numeric chain ID to test.
27
+ * @returns `true` for supported EVM chains, `false` for non-EVM synthetic IDs
28
+ * (Solana, Stellar, Sui) and for anything unrecognised.
29
+ */
30
+ export declare function isEvmChainId(chainId: number): boolean;
31
+ /**
32
+ * Whether `chainId` is recognised by the SDK at all — either a supported EVM
33
+ * chain or one of the non-EVM chain IDs in {@link NON_EVM_CHAIN_IDS}.
34
+ *
35
+ * @param chainId - Numeric chain ID to test.
36
+ * @returns `true` when the ID maps to a chain the SDK can route to.
37
+ */
38
+ export declare function isKnownChainId(chainId: number): boolean;
39
+ /**
40
+ * Throw when `chainId` is not a chain the SDK recognises.
41
+ *
42
+ * Called at public vault entry points so an unroutable ID surfaces as one
43
+ * typed error instead of an opaque downstream RPC failure.
44
+ *
45
+ * @param chainId - Chain ID supplied by the caller. `undefined` is a no-op —
46
+ * omitting the chain is valid and falls back to the SDK's active network.
47
+ * @param providers - The SDK's configured `chainId → RPC URL` map. A chain
48
+ * with a caller-configured provider is routable even if it is missing from
49
+ * {@link NETWORKS} (the SDK ships fallback RPCs/oracles for some EVM chains
50
+ * ahead of adding them to `NETWORKS`), so it is accepted here too.
51
+ * @param method - Public method name, used to make the message actionable.
52
+ * @throws {@link AugustValidationError} with code `INVALID_CHAIN` when the ID
53
+ * is not in {@link NETWORKS} or {@link NON_EVM_CHAIN_IDS}, and has no
54
+ * caller-configured provider.
55
+ * @example
56
+ * ```ts
57
+ * assertKnownChainId(-2, undefined, 'getVault');
58
+ * // AugustValidationError: getVault: unknown chainId -2.
59
+ * ```
60
+ */
61
+ export declare function assertKnownChainId(chainId: number | undefined, providers: Partial<Record<number, string>> | undefined, method: string): void;
62
+ /**
63
+ * Throw when a known EVM `chainId` has no RPC URL configured on the SDK.
64
+ *
65
+ * Non-EVM chain IDs are exempt: Solana, Stellar and Sui vaults route through
66
+ * their adapters and never read from the `providers` map, so a missing entry
67
+ * is expected rather than a misconfiguration.
68
+ *
69
+ * @param chainId - Chain ID supplied by the caller. `undefined` is a no-op.
70
+ * @param providers - The SDK's configured `chainId → RPC URL` map.
71
+ * @param method - Public method name, used to make the message actionable.
72
+ * @throws {@link AugustValidationError} with code `INVALID_CHAIN` when
73
+ * `chainId` is an EVM chain with no configured provider.
74
+ * @example
75
+ * ```ts
76
+ * assertEvmProviderConfigured(143, {}, 'getVault');
77
+ * // AugustValidationError: getVault: no RPC URL configured for chainId 143 (Monad).
78
+ * ```
79
+ */
80
+ export declare function assertEvmProviderConfigured(chainId: number | undefined, providers: Partial<Record<number, string>> | undefined, method: string): void;
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ /**
3
+ * Chain-ID validation for public SDK entry points.
4
+ *
5
+ * Two distinct failure modes are covered here, and they are deliberately kept
6
+ * separate because they have different remediations:
7
+ *
8
+ * 1. **Unknown chain ID** — the caller passed an ID the SDK has no concept of
9
+ * (a typo, a stale constant, an off-by-one on a non-EVM synthetic ID). No
10
+ * configuration can make this work; the call site is wrong.
11
+ * 2. **Known EVM chain, no RPC configured** — the ID is valid but the SDK was
12
+ * constructed without a provider for it. Remediated by passing an RPC URL.
13
+ *
14
+ * Both used to be logged-and-ignored, letting execution continue with an
15
+ * `undefined` RPC URL. That produced a cascade of misleading downstream
16
+ * failures (`connect ECONNREFUSED 127.0.0.1:8545` from ethers' localhost
17
+ * default, `missing revert data` from `decimals()` reads issued against the
18
+ * wrong chain, and `TypeError: Cannot read properties of undefined`). Failing
19
+ * fast at the boundary replaces that cascade with one actionable error.
20
+ *
21
+ * @module core/helpers/chain-support
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.isEvmChainId = isEvmChainId;
25
+ exports.isKnownChainId = isKnownChainId;
26
+ exports.assertKnownChainId = assertKnownChainId;
27
+ exports.assertEvmProviderConfigured = assertEvmProviderConfigured;
28
+ const web3_1 = require("../constants/web3");
29
+ const errors_1 = require("../errors");
30
+ /**
31
+ * Whether `chainId` is an EVM chain the SDK knows about (i.e. has an entry in
32
+ * {@link NETWORKS}).
33
+ *
34
+ * @param chainId - Numeric chain ID to test.
35
+ * @returns `true` for supported EVM chains, `false` for non-EVM synthetic IDs
36
+ * (Solana, Stellar, Sui) and for anything unrecognised.
37
+ */
38
+ function isEvmChainId(chainId) {
39
+ return Object.hasOwn(web3_1.NETWORKS, chainId);
40
+ }
41
+ /**
42
+ * Whether `chainId` is recognised by the SDK at all — either a supported EVM
43
+ * chain or one of the non-EVM chain IDs in {@link NON_EVM_CHAIN_IDS}.
44
+ *
45
+ * @param chainId - Numeric chain ID to test.
46
+ * @returns `true` when the ID maps to a chain the SDK can route to.
47
+ */
48
+ function isKnownChainId(chainId) {
49
+ return isEvmChainId(chainId) || web3_1.NON_EVM_CHAIN_IDS.has(chainId);
50
+ }
51
+ /**
52
+ * Throw when `chainId` is not a chain the SDK recognises.
53
+ *
54
+ * Called at public vault entry points so an unroutable ID surfaces as one
55
+ * typed error instead of an opaque downstream RPC failure.
56
+ *
57
+ * @param chainId - Chain ID supplied by the caller. `undefined` is a no-op —
58
+ * omitting the chain is valid and falls back to the SDK's active network.
59
+ * @param providers - The SDK's configured `chainId → RPC URL` map. A chain
60
+ * with a caller-configured provider is routable even if it is missing from
61
+ * {@link NETWORKS} (the SDK ships fallback RPCs/oracles for some EVM chains
62
+ * ahead of adding them to `NETWORKS`), so it is accepted here too.
63
+ * @param method - Public method name, used to make the message actionable.
64
+ * @throws {@link AugustValidationError} with code `INVALID_CHAIN` when the ID
65
+ * is not in {@link NETWORKS} or {@link NON_EVM_CHAIN_IDS}, and has no
66
+ * caller-configured provider.
67
+ * @example
68
+ * ```ts
69
+ * assertKnownChainId(-2, undefined, 'getVault');
70
+ * // AugustValidationError: getVault: unknown chainId -2.
71
+ * ```
72
+ */
73
+ function assertKnownChainId(chainId, providers, method) {
74
+ if (typeof chainId === 'undefined')
75
+ return;
76
+ if (isKnownChainId(chainId))
77
+ return;
78
+ if (providers?.[chainId])
79
+ return;
80
+ throw new errors_1.AugustValidationError('INVALID_CHAIN', `${method}: unknown chainId ${chainId}. ` +
81
+ 'Pass a supported EVM chain ID (see NETWORKS) or a non-EVM chain ID ' +
82
+ '(-1 Solana, -3 Stellar, 101 Sui).', { context: { method, chainId } });
83
+ }
84
+ /**
85
+ * Throw when a known EVM `chainId` has no RPC URL configured on the SDK.
86
+ *
87
+ * Non-EVM chain IDs are exempt: Solana, Stellar and Sui vaults route through
88
+ * their adapters and never read from the `providers` map, so a missing entry
89
+ * is expected rather than a misconfiguration.
90
+ *
91
+ * @param chainId - Chain ID supplied by the caller. `undefined` is a no-op.
92
+ * @param providers - The SDK's configured `chainId → RPC URL` map.
93
+ * @param method - Public method name, used to make the message actionable.
94
+ * @throws {@link AugustValidationError} with code `INVALID_CHAIN` when
95
+ * `chainId` is an EVM chain with no configured provider.
96
+ * @example
97
+ * ```ts
98
+ * assertEvmProviderConfigured(143, {}, 'getVault');
99
+ * // AugustValidationError: getVault: no RPC URL configured for chainId 143 (Monad).
100
+ * ```
101
+ */
102
+ function assertEvmProviderConfigured(chainId, providers, method) {
103
+ if (typeof chainId === 'undefined')
104
+ return;
105
+ if (!isEvmChainId(chainId))
106
+ return;
107
+ if (providers?.[chainId])
108
+ return;
109
+ const name = web3_1.NETWORKS[chainId]?.name;
110
+ throw new errors_1.AugustValidationError('INVALID_CHAIN', `${method}: no RPC URL configured for chainId ${chainId}` +
111
+ `${name ? ` (${name})` : ''}. ` +
112
+ 'Pass one when constructing AugustSDK: ' +
113
+ `new AugustSDK({ providers: { ${chainId}: '<rpc-url>' } }).`, { context: { method, chainId } });
114
+ }
115
+ //# sourceMappingURL=chain-support.js.map
@@ -29,3 +29,24 @@ export declare function normalizeSigner(signer: Signer | Wallet | any): Promise<
29
29
  * Type that represents either an ethers signer or a viem wallet client
30
30
  */
31
31
  export type CompatibleSigner = Signer | Wallet | any;
32
+ /**
33
+ * Wrap an ethers signer so every transaction it sends carries the active
34
+ * ERC-8021 attribution suffix (Base Builder Codes).
35
+ *
36
+ * Every ethers write in the SDK funnels through the wrapped signer's
37
+ * `sendTransaction`, so this single wrap attributes all vault writes. The
38
+ * wrapper is a Proxy — the caller's signer object is never mutated, so a
39
+ * signer reused outside the SDK sends unattributed transactions. Attribution
40
+ * state is read at send time, not wrap time; when no attribution is
41
+ * configured (see `IAugustBase.attribution`) the wrapper is a pass-through.
42
+ *
43
+ * Plain value transfers (no calldata) are never attributed, and calldata
44
+ * already ending in the ERC-8021 marker is left untouched. When the
45
+ * configured `chains` list requires a chain check and the transaction does
46
+ * not carry a `chainId`, the signer's provider network is consulted (one
47
+ * cached RPC call).
48
+ *
49
+ * @param signer Normalized ethers Signer or Wallet.
50
+ * @returns A proxied signer with an attribution-aware `sendTransaction`.
51
+ */
52
+ export declare function wrapSignerWithAttribution<T extends Signer | Wallet>(signer: T): T;