@valve-tech/viem-errors 0.3.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/CHANGELOG.md ADDED
@@ -0,0 +1,36 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@valve-tech/viem-errors` are documented in
4
+ this file.
5
+
6
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
7
+ and this project adheres to [Semantic Versioning](https://semver.org/).
8
+
9
+ ## [Unreleased]
10
+
11
+ ### Added
12
+
13
+ - Initial implementation. Functions extracted from a real-world dapp
14
+ (Provex) where they had been re-derived inline; this is the first
15
+ upstream packaging for general use.
16
+ - `walkErrorCause(error, options?)` — generator over the viem
17
+ cause chain. Default `maxDepth: 8`.
18
+ - `isUserRejectionError(error)` — three-signal check across the
19
+ cause chain (EIP-1193 `code === 4001`, class name
20
+ `UserRejectedRequestError`, message regex fallback).
21
+ - `USER_REJECTION_MESSAGE` — default rejection copy.
22
+ - `extractContractErrorName(error)` — finds decoded custom
23
+ Solidity error names on `data.errorName` anywhere in the cause
24
+ chain.
25
+ - `extractContractErrorNameFromMessage(raw)` — scrape fallback for
26
+ flattened messages.
27
+ - `getUserFriendlyErrorMessage(error, options?)` — pipeline:
28
+ rejection → decoded custom error → consumer patterns → default
29
+ patterns → fallback.
30
+ - `DEFAULT_ERROR_PATTERNS` — protocol-agnostic patterns
31
+ (wallet / gas / replacement / network / rate-limit / revert).
32
+ - `handleWalletError(error, options)` — wagmi `onError`-shape
33
+ sink with separate rejection vs real-error paths.
34
+ - 49 unit tests covering cause-chain traversal, rejection detection,
35
+ custom-error extraction, message mapping, override precedence, and
36
+ sink routing.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Valve Tech
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,128 @@
1
+ # `@valve-tech/viem-errors`
2
+
3
+ Cause-chain-aware error utilities for viem-based dapps. Detect EIP-1193
4
+ user rejections, extract decoded custom Solidity error names from
5
+ anywhere in viem's nested error chain, map raw RPC/wallet/contract
6
+ errors to short user-friendly messages with overridable patterns, and
7
+ route wagmi-style `onError` sinks through one helper.
8
+
9
+ Pure functions, no runtime dependencies. viem is a peer dependency
10
+ (used only for the `Hex` type — the runtime is plain JS).
11
+
12
+ ## Why
13
+
14
+ Every dapp re-implements wallet error handling, and most get it
15
+ slightly wrong:
16
+
17
+ - **Top-level message matching misses real rejections.** viem nests
18
+ errors several layers deep (`ContractFunctionExecutionError` →
19
+ `RpcRequestError` → `UserRejectedRequestError`). The wrapper's
20
+ `.message` reads `"Failed to send transaction"` — looks like a
21
+ generic failure, but the cause is actually a user rejection that
22
+ should reset the UI to idle, not show a red error toast.
23
+ - **Decoded custom Solidity errors get hidden.** The wrapping message
24
+ contains `"execution reverted"` so dapps short-circuit to a generic
25
+ "transaction failed" message, ignoring the actual `data.errorName`
26
+ (`HashMismatch`, `InsufficientLiquidity`, etc.) that viem decoded
27
+ for them.
28
+ - **Generic copy is the same everywhere.** "Insufficient funds for
29
+ gas", "previous transaction is still pending", "rate limited" — all
30
+ re-derived per project from raw RPC strings.
31
+
32
+ This package solves all three at the primitive layer. Extend the
33
+ default error map with your protocol's custom errors; everything else
34
+ just works.
35
+
36
+ ## Install
37
+
38
+ ```sh
39
+ npm install @valve-tech/viem-errors viem
40
+ # or
41
+ yarn add @valve-tech/viem-errors viem
42
+ ```
43
+
44
+ ## Quick start
45
+
46
+ ### Detect a wallet rejection
47
+
48
+ ```ts
49
+ import { isUserRejectionError } from '@valve-tech/viem-errors'
50
+
51
+ try {
52
+ await wallet.sendTransaction(tx)
53
+ } catch (err) {
54
+ if (isUserRejectionError(err)) {
55
+ // Reset to idle. Do NOT show a "transaction failed" error.
56
+ return
57
+ }
58
+ throw err
59
+ }
60
+ ```
61
+
62
+ ### Centralised error handling for wagmi `onError`
63
+
64
+ ```ts
65
+ import { handleWalletError } from '@valve-tech/viem-errors'
66
+
67
+ const { writeAsync } = useContractWrite({
68
+ // ...
69
+ onError: (err) => handleWalletError(err, {
70
+ setStatus,
71
+ setErrorMessage: setError,
72
+ toast,
73
+ onError: (e) => analytics.track('write.error', { message: e.message }),
74
+ customErrors: {
75
+ HashMismatch: 'The proof did not match the deposit.',
76
+ InsufficientLiquidity: 'Not enough liquidity for this trade.',
77
+ },
78
+ }),
79
+ })
80
+ ```
81
+
82
+ ### Extract a decoded custom error
83
+
84
+ ```ts
85
+ import { extractContractErrorName } from '@valve-tech/viem-errors'
86
+
87
+ catch (err) {
88
+ const name = extractContractErrorName(err)
89
+ if (name === 'IntentExpired') {
90
+ promptUserToRefresh()
91
+ return
92
+ }
93
+ // ...
94
+ }
95
+ ```
96
+
97
+ ## API
98
+
99
+ | Export | Shape |
100
+ | --- | --- |
101
+ | `walkErrorCause(error, opts?)` | Generator yielding `error` then each link in its `.cause` chain (default `maxDepth: 8`). |
102
+ | `isUserRejectionError(error)` | `true` if any link has EIP-1193 `code === 4001`, name `UserRejectedRequestError`, or matches the rejection-message regex. |
103
+ | `USER_REJECTION_MESSAGE` | Toast-friendly rejection copy. |
104
+ | `extractContractErrorName(error)` | First valid Solidity error name from `data.errorName` in the cause chain, else `null`. |
105
+ | `extractContractErrorNameFromMessage(raw)` | Scrape viem's `"reverted with the following reason:\n<Name>"` format from a flattened message string. |
106
+ | `getUserFriendlyErrorMessage(error, opts?)` | Short user-facing message. Pipeline: rejection → decoded custom error → consumer patterns → default patterns → fallback. |
107
+ | `DEFAULT_ERROR_PATTERNS` | Protocol-agnostic patterns covering wallet/gas/network/RPC/rate-limit/revert. |
108
+ | `handleWalletError(error, opts)` | Apply `getUserFriendlyErrorMessage` + sinks (`setStatus`, `setErrorMessage`, `toast`, `onError`). |
109
+
110
+ ## Design notes
111
+
112
+ - **`walkErrorCause` is a generator.** Consumers can break early without
113
+ scanning the whole chain. Default depth of 8 caps a circular cause
114
+ reference instead of looping forever.
115
+ - **`isUserRejectionError` checks three signals at every link.** Any one of
116
+ `code === 4001` / class name / message regex is sufficient. Three
117
+ signals exist because no single one is reliable across every wallet
118
+ + version on the wire.
119
+ - **`getUserFriendlyErrorMessage` puts rejection detection FIRST.** A 4001
120
+ buried in a wrapper whose top-level message contains "execution
121
+ reverted" must still produce the cancelled-by-user copy.
122
+ - **Default patterns are deliberately protocol-agnostic.** Custom-error
123
+ copy (`HashMismatch` → "Proof did not match the deposit") belongs
124
+ in your dapp's `customErrors` map, not in this package.
125
+
126
+ ## License
127
+
128
+ MIT
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @fileoverview Extract the decoded custom Solidity error name from a viem
3
+ * revert error.
4
+ *
5
+ * viem's `ContractFunctionRevertedError` exposes
6
+ * `data: { errorName, args, ... }` for decoded custom errors when the ABI
7
+ * is supplied. The decoded name is what the UI actually wants to surface
8
+ * ("HashMismatch" or "InsufficientDepositLiquidity") — much more useful
9
+ * than the wrapper's generic "execution reverted" text.
10
+ *
11
+ * The hard part is that viem's `data` field can sit several layers deep
12
+ * in the cause chain — sometimes flattened across an RPC boundary. We
13
+ * provide both a structured walk (`extractContractErrorName`) and a
14
+ * message-scraping fallback (`extractContractErrorNameFromMessage`) for
15
+ * the case where the cause chain has been collapsed.
16
+ */
17
+ /**
18
+ * Walk the cause chain and return the first valid Solidity error name found
19
+ * on `data.errorName`. Returns null if no such name is present.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * const name = extractContractErrorName(error)
24
+ * if (name) {
25
+ * showFriendlyMessage(name)
26
+ * }
27
+ * ```
28
+ */
29
+ export declare function extractContractErrorName(error: unknown): string | null;
30
+ /**
31
+ * Fall back to scraping viem's stringified message when the cause chain has
32
+ * been flattened (e.g. across an RPC boundary or after JSON round-tripping).
33
+ * Matches the literal "reverted with the following reason:\n<ErrorName>"
34
+ * shape viem produces.
35
+ */
36
+ export declare function extractContractErrorNameFromMessage(raw: string): string | null;
37
+ //# sourceMappingURL=contract-error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-error.d.ts","sourceRoot":"","sources":["../src/contract-error.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAYH;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAWtE;AAED;;;;;GAKG;AACH,wBAAgB,mCAAmC,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI9E"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * @fileoverview Extract the decoded custom Solidity error name from a viem
3
+ * revert error.
4
+ *
5
+ * viem's `ContractFunctionRevertedError` exposes
6
+ * `data: { errorName, args, ... }` for decoded custom errors when the ABI
7
+ * is supplied. The decoded name is what the UI actually wants to surface
8
+ * ("HashMismatch" or "InsufficientDepositLiquidity") — much more useful
9
+ * than the wrapper's generic "execution reverted" text.
10
+ *
11
+ * The hard part is that viem's `data` field can sit several layers deep
12
+ * in the cause chain — sometimes flattened across an RPC boundary. We
13
+ * provide both a structured walk (`extractContractErrorName`) and a
14
+ * message-scraping fallback (`extractContractErrorNameFromMessage`) for
15
+ * the case where the cause chain has been collapsed.
16
+ */
17
+ import { walkErrorCause } from './walk.js';
18
+ /**
19
+ * Solidity custom error names start with an uppercase ASCII letter and
20
+ * contain only ASCII letters, digits, and underscores. Anything outside
21
+ * that pattern is rejected to avoid false positives — `data` fields named
22
+ * `errorName` exist on a few non-revert error shapes too.
23
+ */
24
+ const ERROR_NAME_PATTERN = /^[A-Z][A-Za-z0-9_]*$/;
25
+ /**
26
+ * Walk the cause chain and return the first valid Solidity error name found
27
+ * on `data.errorName`. Returns null if no such name is present.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * const name = extractContractErrorName(error)
32
+ * if (name) {
33
+ * showFriendlyMessage(name)
34
+ * }
35
+ * ```
36
+ */
37
+ export function extractContractErrorName(error) {
38
+ for (const link of walkErrorCause(error)) {
39
+ if (link === null || link === undefined || typeof link !== 'object')
40
+ continue;
41
+ const data = link.data;
42
+ if (data === null || data === undefined || typeof data !== 'object')
43
+ continue;
44
+ const name = data.errorName;
45
+ if (typeof name !== 'string')
46
+ continue;
47
+ if (!ERROR_NAME_PATTERN.test(name))
48
+ continue;
49
+ return name;
50
+ }
51
+ return null;
52
+ }
53
+ /**
54
+ * Fall back to scraping viem's stringified message when the cause chain has
55
+ * been flattened (e.g. across an RPC boundary or after JSON round-tripping).
56
+ * Matches the literal "reverted with the following reason:\n<ErrorName>"
57
+ * shape viem produces.
58
+ */
59
+ export function extractContractErrorNameFromMessage(raw) {
60
+ if (raw.length === 0)
61
+ return null;
62
+ const match = raw.match(/reverted with the following reason:\s*\n?\s*([A-Z][A-Za-z0-9_]*)\(?/);
63
+ return match?.[1] ?? null;
64
+ }
65
+ //# sourceMappingURL=contract-error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-error.js","sourceRoot":"","sources":["../src/contract-error.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAE1C;;;;;GAKG;AACH,MAAM,kBAAkB,GAAG,sBAAsB,CAAA;AAEjD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,wBAAwB,CAAC,KAAc;IACrD,KAAK,MAAM,IAAI,IAAI,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;QACzC,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,SAAS,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,SAAQ;QAC7E,MAAM,IAAI,GAAI,IAA2B,CAAC,IAAI,CAAA;QAC9C,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,SAAS,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,SAAQ;QAC7E,MAAM,IAAI,GAAI,IAAgC,CAAC,SAAS,CAAA;QACxD,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,SAAQ;QACtC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAQ;QAC5C,OAAO,IAAI,CAAA;IACb,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mCAAmC,CAAC,GAAW;IAC7D,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IACjC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,qEAAqE,CAAC,CAAA;IAC9F,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAA;AAC3B,CAAC"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @fileoverview Centralised error handler for wagmi / viem write paths.
3
+ *
4
+ * Encapsulates the rejection-detection + friendly-message + status-reset
5
+ * pattern so every catch block / `onError` callback is a single line. The
6
+ * downstream payoff: a dapp's wallet UX stays consistent across every
7
+ * write site instead of each one re-implementing rejection-vs-failure
8
+ * detection (and getting it slightly wrong).
9
+ */
10
+ import { type ErrorPattern } from './messages.js';
11
+ /**
12
+ * Sinks the helper writes into when classifying an error.
13
+ */
14
+ export interface HandleWalletErrorOptions {
15
+ /** Callback to set a UI status — `idle` on rejection, `error` on real failure. */
16
+ setStatus?: (status: 'idle' | 'error') => void;
17
+ /** Callback to store the user-facing message — `null` on rejection, friendly text on failure. */
18
+ setErrorMessage?: (message: string | null) => void;
19
+ /** Toast bridge (e.g. sonner). Rejection → `info`, real error → `error`. */
20
+ toast?: {
21
+ error: (message: string) => void;
22
+ info: (message: string) => void;
23
+ };
24
+ /** Always called with the underlying error (coerced to `Error`). */
25
+ onError?: (error: Error) => void;
26
+ /** Forwarded to `getUserFriendlyErrorMessage`. */
27
+ customErrors?: Record<string, string>;
28
+ /** Forwarded to `getUserFriendlyErrorMessage`. */
29
+ patterns?: readonly ErrorPattern[];
30
+ /** Forwarded to `getUserFriendlyErrorMessage`. */
31
+ fallback?: string;
32
+ }
33
+ /**
34
+ * Classify a thrown value as either a user rejection or a real failure, then
35
+ * route the configured sinks accordingly.
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * // wagmi onError shape
40
+ * onError: (err) => handleWalletError(err, {
41
+ * setStatus: setTxStatus,
42
+ * setErrorMessage: setError,
43
+ * toast,
44
+ * customErrors: { HashMismatch: 'Proof did not match.' },
45
+ * })
46
+ *
47
+ * // catch block shape
48
+ * try {
49
+ * await writeAsync(args)
50
+ * } catch (err) {
51
+ * handleWalletError(err, { setStatus, setErrorMessage, toast })
52
+ * }
53
+ * ```
54
+ */
55
+ export declare function handleWalletError(error: unknown, options?: HandleWalletErrorOptions): void;
56
+ //# sourceMappingURL=handle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handle.d.ts","sourceRoot":"","sources":["../src/handle.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,KAAK,YAAY,EAA+B,MAAM,eAAe,CAAA;AAE9E;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,kFAAkF;IAClF,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,KAAK,IAAI,CAAA;IAC9C,iGAAiG;IACjG,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAClD,4EAA4E;IAC5E,KAAK,CAAC,EAAE;QAAE,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAA;IAC7E,oEAAoE;IACpE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IAChC,kDAAkD;IAClD,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACrC,kDAAkD;IAClD,QAAQ,CAAC,EAAE,SAAS,YAAY,EAAE,CAAA;IAClC,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,wBAA6B,GACrC,IAAI,CAeN"}
package/dist/handle.js ADDED
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @fileoverview Centralised error handler for wagmi / viem write paths.
3
+ *
4
+ * Encapsulates the rejection-detection + friendly-message + status-reset
5
+ * pattern so every catch block / `onError` callback is a single line. The
6
+ * downstream payoff: a dapp's wallet UX stays consistent across every
7
+ * write site instead of each one re-implementing rejection-vs-failure
8
+ * detection (and getting it slightly wrong).
9
+ */
10
+ import { USER_REJECTION_MESSAGE, isUserRejectionError } from './rejection.js';
11
+ import { getUserFriendlyErrorMessage } from './messages.js';
12
+ /**
13
+ * Classify a thrown value as either a user rejection or a real failure, then
14
+ * route the configured sinks accordingly.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * // wagmi onError shape
19
+ * onError: (err) => handleWalletError(err, {
20
+ * setStatus: setTxStatus,
21
+ * setErrorMessage: setError,
22
+ * toast,
23
+ * customErrors: { HashMismatch: 'Proof did not match.' },
24
+ * })
25
+ *
26
+ * // catch block shape
27
+ * try {
28
+ * await writeAsync(args)
29
+ * } catch (err) {
30
+ * handleWalletError(err, { setStatus, setErrorMessage, toast })
31
+ * }
32
+ * ```
33
+ */
34
+ export function handleWalletError(error, options = {}) {
35
+ const { setStatus, setErrorMessage, toast, onError, customErrors, patterns, fallback } = options;
36
+ if (isUserRejectionError(error)) {
37
+ setStatus?.('idle');
38
+ setErrorMessage?.(null);
39
+ toast?.info(USER_REJECTION_MESSAGE);
40
+ }
41
+ else {
42
+ const message = getUserFriendlyErrorMessage(error, { customErrors, patterns, fallback });
43
+ setStatus?.('error');
44
+ setErrorMessage?.(message);
45
+ toast?.error(message);
46
+ }
47
+ onError?.(error instanceof Error ? error : new Error(String(error)));
48
+ }
49
+ //# sourceMappingURL=handle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handle.js","sourceRoot":"","sources":["../src/handle.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAC7E,OAAO,EAAqB,2BAA2B,EAAE,MAAM,eAAe,CAAA;AAsB9E;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAAc,EACd,UAAoC,EAAE;IAEtC,MAAM,EAAE,SAAS,EAAE,eAAe,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAA;IAEhG,IAAI,oBAAoB,CAAC,KAAK,CAAC,EAAE,CAAC;QAChC,SAAS,EAAE,CAAC,MAAM,CAAC,CAAA;QACnB,eAAe,EAAE,CAAC,IAAI,CAAC,CAAA;QACvB,KAAK,EAAE,IAAI,CAAC,sBAAsB,CAAC,CAAA;IACrC,CAAC;SAAM,CAAC;QACN,MAAM,OAAO,GAAG,2BAA2B,CAAC,KAAK,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAA;QACxF,SAAS,EAAE,CAAC,OAAO,CAAC,CAAA;QACpB,eAAe,EAAE,CAAC,OAAO,CAAC,CAAA;QAC1B,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,CAAA;IACvB,CAAC;IAED,OAAO,EAAE,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AACtE,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @fileoverview Public API of `@valve-tech/viem-errors`.
3
+ */
4
+ export { walkErrorCause } from './walk.js';
5
+ export { isUserRejectionError, USER_REJECTION_MESSAGE } from './rejection.js';
6
+ export { extractContractErrorName, extractContractErrorNameFromMessage } from './contract-error.js';
7
+ export { getUserFriendlyErrorMessage, DEFAULT_ERROR_PATTERNS, type ErrorPattern, } from './messages.js';
8
+ export { handleWalletError, type HandleWalletErrorOptions } from './handle.js';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAC1C,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AAC7E,OAAO,EAAE,wBAAwB,EAAE,mCAAmC,EAAE,MAAM,qBAAqB,CAAA;AACnG,OAAO,EACL,2BAA2B,EAC3B,sBAAsB,EACtB,KAAK,YAAY,GAClB,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,iBAAiB,EAAE,KAAK,wBAAwB,EAAE,MAAM,aAAa,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @fileoverview Public API of `@valve-tech/viem-errors`.
3
+ */
4
+ export { walkErrorCause } from './walk.js';
5
+ export { isUserRejectionError, USER_REJECTION_MESSAGE } from './rejection.js';
6
+ export { extractContractErrorName, extractContractErrorNameFromMessage } from './contract-error.js';
7
+ export { getUserFriendlyErrorMessage, DEFAULT_ERROR_PATTERNS, } from './messages.js';
8
+ export { handleWalletError } from './handle.js';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAC1C,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AAC7E,OAAO,EAAE,wBAAwB,EAAE,mCAAmC,EAAE,MAAM,qBAAqB,CAAA;AACnG,OAAO,EACL,2BAA2B,EAC3B,sBAAsB,GAEvB,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,iBAAiB,EAAiC,MAAM,aAAa,CAAA"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @fileoverview Map raw viem / wagmi / wallet errors to short user-friendly
3
+ * messages.
4
+ *
5
+ * The default `DEFAULT_ERROR_PATTERNS` is deliberately protocol-agnostic —
6
+ * wallet rejection, gas/funds, replacement-tx, network/RPC, rate-limit,
7
+ * generic revert. Consumers add their protocol-specific custom-Solidity-error
8
+ * messages via the `customErrors` option on `getUserFriendlyErrorMessage`,
9
+ * and can prepend additional patterns via the `patterns` option without
10
+ * editing the default list.
11
+ *
12
+ * Match order at runtime:
13
+ * 1. Wallet rejection — surfaced first so a 4001 buried inside a wrapper
14
+ * whose top-level message contains "execution reverted" still produces
15
+ * the cancelled-by-user copy instead of the generic on-chain fallback.
16
+ * 2. Decoded custom-error name from `data.errorName` anywhere in the
17
+ * cause chain — best signal when the ABI is wired through viem.
18
+ * 3. Custom-error name extracted from the wrapper's stringified message —
19
+ * a flattened-cause-chain fallback.
20
+ * 4. Caller-supplied additional patterns (checked BEFORE defaults so a
21
+ * consumer can override generic copy).
22
+ * 5. Default protocol-agnostic patterns.
23
+ * 6. The configured `fallback`, or a generic message.
24
+ */
25
+ /** A pattern → message pair used by `getUserFriendlyErrorMessage`. */
26
+ export interface ErrorPattern {
27
+ pattern: RegExp;
28
+ message: string;
29
+ }
30
+ /**
31
+ * Default protocol-agnostic patterns. Consumers can prepend their own
32
+ * via the `patterns` option; the consumer entries win.
33
+ */
34
+ export declare const DEFAULT_ERROR_PATTERNS: readonly ErrorPattern[];
35
+ /**
36
+ * Convert a raw error into a short, user-friendly message safe to display.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * try {
41
+ * await client.signalIntent(params)
42
+ * } catch (error) {
43
+ * const message = getUserFriendlyErrorMessage(error, {
44
+ * customErrors: { HashMismatch: 'The proof did not match the deposit.' },
45
+ * })
46
+ * showToast(message)
47
+ * }
48
+ * ```
49
+ */
50
+ export declare function getUserFriendlyErrorMessage(error: unknown, options?: {
51
+ customErrors?: Record<string, string>;
52
+ patterns?: readonly ErrorPattern[];
53
+ fallback?: string;
54
+ }): string;
55
+ //# sourceMappingURL=messages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAKH,sEAAsE;AACtE,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;GAGG;AACH,eAAO,MAAM,sBAAsB,EAAE,SAAS,YAAY,EA4BzD,CAAA;AAKD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,OAAO,EACd,OAAO,GAAE;IACP,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACrC,QAAQ,CAAC,EAAE,SAAS,YAAY,EAAE,CAAA;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAA;CACb,GACL,MAAM,CAqBR"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * @fileoverview Map raw viem / wagmi / wallet errors to short user-friendly
3
+ * messages.
4
+ *
5
+ * The default `DEFAULT_ERROR_PATTERNS` is deliberately protocol-agnostic —
6
+ * wallet rejection, gas/funds, replacement-tx, network/RPC, rate-limit,
7
+ * generic revert. Consumers add their protocol-specific custom-Solidity-error
8
+ * messages via the `customErrors` option on `getUserFriendlyErrorMessage`,
9
+ * and can prepend additional patterns via the `patterns` option without
10
+ * editing the default list.
11
+ *
12
+ * Match order at runtime:
13
+ * 1. Wallet rejection — surfaced first so a 4001 buried inside a wrapper
14
+ * whose top-level message contains "execution reverted" still produces
15
+ * the cancelled-by-user copy instead of the generic on-chain fallback.
16
+ * 2. Decoded custom-error name from `data.errorName` anywhere in the
17
+ * cause chain — best signal when the ABI is wired through viem.
18
+ * 3. Custom-error name extracted from the wrapper's stringified message —
19
+ * a flattened-cause-chain fallback.
20
+ * 4. Caller-supplied additional patterns (checked BEFORE defaults so a
21
+ * consumer can override generic copy).
22
+ * 5. Default protocol-agnostic patterns.
23
+ * 6. The configured `fallback`, or a generic message.
24
+ */
25
+ import { extractContractErrorName, extractContractErrorNameFromMessage } from './contract-error.js';
26
+ import { isUserRejectionError } from './rejection.js';
27
+ /**
28
+ * Default protocol-agnostic patterns. Consumers can prepend their own
29
+ * via the `patterns` option; the consumer entries win.
30
+ */
31
+ export const DEFAULT_ERROR_PATTERNS = [
32
+ // Gas / balance — should match before generic "execution reverted" because
33
+ // these are more specific.
34
+ { pattern: /insufficient funds/i, message: 'Insufficient funds for gas fees.' },
35
+ { pattern: /gas required exceeds/i, message: 'Transaction requires more gas than allowed.' },
36
+ // Replacement / nonce — single message because the user fix is identical
37
+ // (wait or speed up the prior pending tx).
38
+ {
39
+ pattern: /could not replace existing tx|replacement transaction underpriced|nonce too low/i,
40
+ message: 'A previous transaction is still pending. Please wait for it to confirm or speed it up in your wallet.',
41
+ },
42
+ // Network / RPC connectivity. "disconnected" before "could not detect" so
43
+ // the more user-actionable copy wins.
44
+ { pattern: /disconnected|not connected/i, message: 'Wallet disconnected. Please reconnect and try again.' },
45
+ { pattern: /could not detect network|network changed/i, message: 'Network connection issue. Please check your wallet network.' },
46
+ { pattern: /timeout|etimedout/i, message: 'Request timed out. Please try again.' },
47
+ { pattern: /fetch failed|econnrefused|enotfound/i, message: 'Unable to reach the server. Please check your connection.' },
48
+ // Rate limiting / service availability.
49
+ { pattern: /\b429\b|too many requests/i, message: 'Too many requests. Please wait a moment and try again.' },
50
+ { pattern: /\b503\b|service unavailable/i, message: 'Service temporarily unavailable. Please try again shortly.' },
51
+ // Generic on-chain revert — last resort. Specific custom-error extraction
52
+ // happens earlier in the pipeline, so reaching here means the revert
53
+ // wasn't decoded.
54
+ { pattern: /execution reverted/i, message: 'Transaction failed on-chain.' },
55
+ ];
56
+ const DEFAULT_FALLBACK = 'Something went wrong. Please try again.';
57
+ const DEFAULT_REJECTION_COPY = 'Transaction was cancelled.';
58
+ /**
59
+ * Convert a raw error into a short, user-friendly message safe to display.
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * try {
64
+ * await client.signalIntent(params)
65
+ * } catch (error) {
66
+ * const message = getUserFriendlyErrorMessage(error, {
67
+ * customErrors: { HashMismatch: 'The proof did not match the deposit.' },
68
+ * })
69
+ * showToast(message)
70
+ * }
71
+ * ```
72
+ */
73
+ export function getUserFriendlyErrorMessage(error, options = {}) {
74
+ if (isUserRejectionError(error))
75
+ return DEFAULT_REJECTION_COPY;
76
+ const raw = stringifyError(error);
77
+ const errorName = extractContractErrorName(error) ?? extractContractErrorNameFromMessage(raw);
78
+ if (errorName !== null) {
79
+ const friendly = options.customErrors?.[errorName];
80
+ return friendly !== undefined
81
+ ? `${friendly} (${errorName})`
82
+ : `Transaction failed: ${humaniseErrorName(errorName)}.`;
83
+ }
84
+ const consumerPatterns = options.patterns ?? [];
85
+ for (const { pattern, message } of consumerPatterns) {
86
+ if (pattern.test(raw))
87
+ return message;
88
+ }
89
+ for (const { pattern, message } of DEFAULT_ERROR_PATTERNS) {
90
+ if (pattern.test(raw))
91
+ return message;
92
+ }
93
+ return options.fallback ?? DEFAULT_FALLBACK;
94
+ }
95
+ /** "PaymentVerificationFailed" → "Payment Verification Failed" */
96
+ function humaniseErrorName(name) {
97
+ return name.replace(/([A-Z])/g, ' $1').trim();
98
+ }
99
+ /** Normalise an unknown thrown value into a string for pattern matching. */
100
+ function stringifyError(error) {
101
+ if (error === null || error === undefined)
102
+ return '';
103
+ if (error instanceof Error)
104
+ return error.message;
105
+ if (typeof error === 'string')
106
+ return error;
107
+ try {
108
+ return JSON.stringify(error);
109
+ }
110
+ catch {
111
+ return String(error);
112
+ }
113
+ }
114
+ //# sourceMappingURL=messages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.js","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @fileoverview Detect a user-initiated wallet rejection.
3
+ *
4
+ * Wallet UX needs to distinguish *I cancelled* from *something failed*.
5
+ * The two surfaces look superficially similar — both throw, both reach
6
+ * `catch` blocks — but they call for different UI: rejection → reset to
7
+ * idle, real failure → show error message and offer retry.
8
+ *
9
+ * The challenge: rejection text varies by wallet, locale, and error
10
+ * version (`User rejected the request`, `user cancelled`, `User denied
11
+ * transaction signature`, `User disapproved`, etc.). Top-level
12
+ * `.message` matching alone is fragile because viem nests the actual
13
+ * rejection several layers deep — the wrapper's message reads
14
+ * `Failed to send transaction`, which would otherwise look like a
15
+ * generic failure and get the wrong UI.
16
+ */
17
+ /** Toast-friendly message shown when the user rejects a wallet prompt. */
18
+ export declare const USER_REJECTION_MESSAGE = "Transaction was rejected in wallet.";
19
+ /**
20
+ * Check whether an error represents a user-initiated wallet rejection.
21
+ *
22
+ * Walks the cause chain (viem nests rejections several layers deep —
23
+ * typically `ContractFunctionExecutionError` → `RpcRequestError` →
24
+ * `UserRejectedRequestError`) and checks three independent signals at
25
+ * each level:
26
+ * 1. **EIP-1193 `code === 4001`** — the spec-mandated rejection code,
27
+ * language-independent. The most reliable signal when present.
28
+ * 2. **Error name `UserRejectedRequestError`** — viem's class name.
29
+ * 3. **Message text regex** — fallback for wallets that bypass the
30
+ * standard error class.
31
+ *
32
+ * Any one of those three is sufficient at any level in the chain.
33
+ */
34
+ export declare function isUserRejectionError(error: unknown): boolean;
35
+ //# sourceMappingURL=rejection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rejection.d.ts","sourceRoot":"","sources":["../src/rejection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,0EAA0E;AAC1E,eAAO,MAAM,sBAAsB,wCAAwC,CAAA;AAS3E;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAe5D"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @fileoverview Detect a user-initiated wallet rejection.
3
+ *
4
+ * Wallet UX needs to distinguish *I cancelled* from *something failed*.
5
+ * The two surfaces look superficially similar — both throw, both reach
6
+ * `catch` blocks — but they call for different UI: rejection → reset to
7
+ * idle, real failure → show error message and offer retry.
8
+ *
9
+ * The challenge: rejection text varies by wallet, locale, and error
10
+ * version (`User rejected the request`, `user cancelled`, `User denied
11
+ * transaction signature`, `User disapproved`, etc.). Top-level
12
+ * `.message` matching alone is fragile because viem nests the actual
13
+ * rejection several layers deep — the wrapper's message reads
14
+ * `Failed to send transaction`, which would otherwise look like a
15
+ * generic failure and get the wrong UI.
16
+ */
17
+ import { walkErrorCause } from './walk.js';
18
+ /** Toast-friendly message shown when the user rejects a wallet prompt. */
19
+ export const USER_REJECTION_MESSAGE = 'Transaction was rejected in wallet.';
20
+ /**
21
+ * Wallet-rejection text varies by wallet/locale ("User rejected the request",
22
+ * "user cancelled", "User denied transaction signature", …) so message
23
+ * matching alone is fragile. Order: most-common viem/MetaMask phrasing first.
24
+ */
25
+ const REJECTION_MESSAGE_PATTERN = /user rejected|user denied|rejected the request|user cancelled|user canceled|user disapproved/i;
26
+ /**
27
+ * Check whether an error represents a user-initiated wallet rejection.
28
+ *
29
+ * Walks the cause chain (viem nests rejections several layers deep —
30
+ * typically `ContractFunctionExecutionError` → `RpcRequestError` →
31
+ * `UserRejectedRequestError`) and checks three independent signals at
32
+ * each level:
33
+ * 1. **EIP-1193 `code === 4001`** — the spec-mandated rejection code,
34
+ * language-independent. The most reliable signal when present.
35
+ * 2. **Error name `UserRejectedRequestError`** — viem's class name.
36
+ * 3. **Message text regex** — fallback for wallets that bypass the
37
+ * standard error class.
38
+ *
39
+ * Any one of those three is sufficient at any level in the chain.
40
+ */
41
+ export function isUserRejectionError(error) {
42
+ for (const link of walkErrorCause(error)) {
43
+ if (link === null || link === undefined)
44
+ continue;
45
+ if (typeof link === 'string') {
46
+ if (REJECTION_MESSAGE_PATTERN.test(link))
47
+ return true;
48
+ continue;
49
+ }
50
+ if (typeof link !== 'object')
51
+ continue;
52
+ const node = link;
53
+ if (node.code === 4001)
54
+ return true;
55
+ if (typeof node.name === 'string' && node.name === 'UserRejectedRequestError')
56
+ return true;
57
+ if (typeof node.message === 'string' && REJECTION_MESSAGE_PATTERN.test(node.message))
58
+ return true;
59
+ }
60
+ return false;
61
+ }
62
+ //# sourceMappingURL=rejection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rejection.js","sourceRoot":"","sources":["../src/rejection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAE1C,0EAA0E;AAC1E,MAAM,CAAC,MAAM,sBAAsB,GAAG,qCAAqC,CAAA;AAE3E;;;;GAIG;AACH,MAAM,yBAAyB,GAAG,+FAA+F,CAAA;AAEjI;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAc;IACjD,KAAK,MAAM,IAAI,IAAI,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;QACzC,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,SAAS;YAAE,SAAQ;QACjD,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7B,IAAI,yBAAyB,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,OAAO,IAAI,CAAA;YACrD,SAAQ;QACV,CAAC;QACD,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,SAAQ;QAEtC,MAAM,IAAI,GAAG,IAA6D,CAAA;QAC1E,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI;YAAE,OAAO,IAAI,CAAA;QACnC,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,KAAK,0BAA0B;YAAE,OAAO,IAAI,CAAA;QAC1F,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,IAAI,yBAAyB,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,OAAO,IAAI,CAAA;IACnG,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC"}
package/dist/walk.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @fileoverview Walk an error's `cause` chain.
3
+ *
4
+ * viem nests errors several levels deep — `ContractFunctionExecutionError`
5
+ * wraps `RpcRequestError` wraps `UserRejectedRequestError`, etc. — so checks
6
+ * that only inspect the top-level error miss the actual underlying cause.
7
+ * This generator yields each link in order so callers can decide what to look
8
+ * for at each depth.
9
+ */
10
+ /**
11
+ * Walk an error and its `cause` chain, yielding each link in order, starting
12
+ * with the error itself. Stops at `null`/`undefined`, when the chain hits a
13
+ * primitive that has no `cause`, or when `maxDepth` is reached. Default depth
14
+ * is 8 — enough for every viem stack we've seen in the wild without risking
15
+ * a runaway loop on a circular chain.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * for (const link of walkErrorCause(error)) {
20
+ * if (typeof link === 'object' && link && 'code' in link && link.code === 4001) {
21
+ * return true
22
+ * }
23
+ * }
24
+ * ```
25
+ */
26
+ export declare function walkErrorCause(error: unknown, options?: {
27
+ maxDepth?: number;
28
+ }): Generator<unknown, void, unknown>;
29
+ //# sourceMappingURL=walk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"walk.d.ts","sourceRoot":"","sources":["../src/walk.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;;;;;;;;;;;GAeG;AACH,wBAAiB,cAAc,CAC7B,KAAK,EAAE,OAAO,EACd,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,CASnC"}
package/dist/walk.js ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @fileoverview Walk an error's `cause` chain.
3
+ *
4
+ * viem nests errors several levels deep — `ContractFunctionExecutionError`
5
+ * wraps `RpcRequestError` wraps `UserRejectedRequestError`, etc. — so checks
6
+ * that only inspect the top-level error miss the actual underlying cause.
7
+ * This generator yields each link in order so callers can decide what to look
8
+ * for at each depth.
9
+ */
10
+ /**
11
+ * Walk an error and its `cause` chain, yielding each link in order, starting
12
+ * with the error itself. Stops at `null`/`undefined`, when the chain hits a
13
+ * primitive that has no `cause`, or when `maxDepth` is reached. Default depth
14
+ * is 8 — enough for every viem stack we've seen in the wild without risking
15
+ * a runaway loop on a circular chain.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * for (const link of walkErrorCause(error)) {
20
+ * if (typeof link === 'object' && link && 'code' in link && link.code === 4001) {
21
+ * return true
22
+ * }
23
+ * }
24
+ * ```
25
+ */
26
+ export function* walkErrorCause(error, options = {}) {
27
+ const maxDepth = options.maxDepth ?? 8;
28
+ let current = error;
29
+ for (let depth = 0; depth < maxDepth; depth++) {
30
+ if (current === null || current === undefined)
31
+ return;
32
+ yield current;
33
+ if (typeof current !== 'object')
34
+ return;
35
+ current = current.cause;
36
+ }
37
+ }
38
+ //# sourceMappingURL=walk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"walk.js","sourceRoot":"","sources":["../src/walk.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,SAAS,CAAC,CAAC,cAAc,CAC7B,KAAc,EACd,UAAiC,EAAE;IAEnC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,CAAC,CAAA;IACtC,IAAI,OAAO,GAAY,KAAK,CAAA;IAC5B,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,QAAQ,EAAE,KAAK,EAAE,EAAE,CAAC;QAC9C,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,SAAS;YAAE,OAAM;QACrD,MAAM,OAAO,CAAA;QACb,IAAI,OAAO,OAAO,KAAK,QAAQ;YAAE,OAAM;QACvC,OAAO,GAAI,OAA+B,CAAC,KAAK,CAAA;IAClD,CAAC;AACH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@valve-tech/viem-errors",
3
+ "version": "0.3.1",
4
+ "description": "Cause-chain-aware error utilities for viem-based dapps. Detect EIP-1193 user rejections (4001 / class name / message regex), extract decoded custom Solidity error names from anywhere in viem's nested cause chain, map raw RPC/wallet/contract errors to short user-friendly messages with overridable patterns, and route wagmi-style onError sinks through one helper. Pure functions, no runtime dependencies. Part of the valve-tech/evm-toolkit synchronized release line.",
5
+ "license": "MIT",
6
+ "homepage": "https://github.com/valve-tech/evm-toolkit/tree/main/packages/viem-errors#readme",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/valve-tech/evm-toolkit.git",
10
+ "directory": "packages/viem-errors"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/valve-tech/evm-toolkit/issues"
14
+ },
15
+ "keywords": [
16
+ "ethereum",
17
+ "evm",
18
+ "viem",
19
+ "wagmi",
20
+ "errors",
21
+ "user-rejection",
22
+ "eip-1193",
23
+ "custom-error",
24
+ "wallet"
25
+ ],
26
+ "type": "module",
27
+ "main": "dist/index.js",
28
+ "types": "dist/index.d.ts",
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "import": "./dist/index.js"
33
+ }
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "README.md",
38
+ "CHANGELOG.md",
39
+ "LICENSE"
40
+ ],
41
+ "scripts": {
42
+ "build": "tsc -p .",
43
+ "typecheck": "tsc -p . --noEmit",
44
+ "lint": "eslint src",
45
+ "test": "vitest run",
46
+ "test:coverage": "vitest run --coverage",
47
+ "prepare": "yarn build"
48
+ },
49
+ "peerDependencies": {
50
+ "viem": "^2.0.0"
51
+ }
52
+ }