@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
|
@@ -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;
|