@metamask/connect-multichain 0.13.0 → 0.15.0

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 (51) hide show
  1. package/CHANGELOG.md +24 -1
  2. package/README.md +20 -19
  3. package/dist/browser/es/connect-multichain.d.mts +62 -6
  4. package/dist/browser/es/connect-multichain.mjs +286 -124
  5. package/dist/browser/es/connect-multichain.mjs.map +1 -1
  6. package/dist/browser/es/metafile-esm.json +1 -1
  7. package/dist/browser/iife/connect-multichain.d.ts +62 -6
  8. package/dist/browser/iife/connect-multichain.js +304 -124
  9. package/dist/browser/iife/connect-multichain.js.map +1 -1
  10. package/dist/browser/iife/metafile-iife.json +1 -1
  11. package/dist/browser/umd/connect-multichain.d.ts +62 -6
  12. package/dist/browser/umd/connect-multichain.js +286 -124
  13. package/dist/browser/umd/connect-multichain.js.map +1 -1
  14. package/dist/browser/umd/metafile-cjs.json +1 -1
  15. package/dist/node/cjs/connect-multichain.d.ts +62 -6
  16. package/dist/node/cjs/connect-multichain.js +287 -124
  17. package/dist/node/cjs/connect-multichain.js.map +1 -1
  18. package/dist/node/cjs/metafile-cjs.json +1 -1
  19. package/dist/node/es/connect-multichain.d.mts +62 -6
  20. package/dist/node/es/connect-multichain.mjs +286 -124
  21. package/dist/node/es/connect-multichain.mjs.map +1 -1
  22. package/dist/node/es/metafile-esm.json +1 -1
  23. package/dist/react-native/es/connect-multichain.d.mts +62 -6
  24. package/dist/react-native/es/connect-multichain.mjs +286 -124
  25. package/dist/react-native/es/connect-multichain.mjs.map +1 -1
  26. package/dist/react-native/es/metafile-esm.json +1 -1
  27. package/dist/src/domain/multichain/index.d.ts +1 -1
  28. package/dist/src/domain/multichain/index.d.ts.map +1 -1
  29. package/dist/src/domain/multichain/index.js +7 -3
  30. package/dist/src/domain/multichain/index.js.map +1 -1
  31. package/dist/src/domain/multichain/types.d.ts +15 -3
  32. package/dist/src/domain/multichain/types.d.ts.map +1 -1
  33. package/dist/src/domain/utils/index.d.ts +2 -1
  34. package/dist/src/domain/utils/index.d.ts.map +1 -1
  35. package/dist/src/domain/utils/index.js +1 -1
  36. package/dist/src/domain/utils/index.js.map +1 -1
  37. package/dist/src/multichain/index.d.ts.map +1 -1
  38. package/dist/src/multichain/index.js +109 -63
  39. package/dist/src/multichain/index.js.map +1 -1
  40. package/dist/src/multichain/rpc/requestRouter.d.ts.map +1 -1
  41. package/dist/src/multichain/rpc/requestRouter.js +27 -9
  42. package/dist/src/multichain/rpc/requestRouter.js.map +1 -1
  43. package/dist/src/multichain/transports/mwp/index.d.ts.map +1 -1
  44. package/dist/src/multichain/transports/mwp/index.js +17 -5
  45. package/dist/src/multichain/transports/mwp/index.js.map +1 -1
  46. package/dist/src/multichain/utils/analytics.d.ts +82 -1
  47. package/dist/src/multichain/utils/analytics.d.ts.map +1 -1
  48. package/dist/src/multichain/utils/analytics.js +252 -17
  49. package/dist/src/multichain/utils/analytics.js.map +1 -1
  50. package/dist/types/connect-multichain.d.ts +62 -6
  51. package/package.json +2 -2
@@ -10,27 +10,257 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  /* eslint-disable @typescript-eslint/naming-convention */
11
11
  /* eslint-disable @typescript-eslint/no-unused-vars -- Scope type used in JSDoc */
12
12
  import { getDappId } from '.';
13
- import { getPlatformType } from '../../domain';
13
+ import { getPlatformType, RPCInvokeMethodErr } from '../../domain';
14
+ /**
15
+ * Maximum length of `error_message_sample` after sanitisation. Mirrors the
16
+ * `maxLength: 200` constraint declared in the analytics-api `api.spec.yml`.
17
+ */
18
+ const ERROR_MESSAGE_SAMPLE_MAX_LENGTH = 200;
19
+ /**
20
+ * Patterns scrubbed from `error_message_sample` before it leaves the SDK.
21
+ * The goal is to surface enough error context for triage in Mixpanel
22
+ * without leaking PII / wallet addresses / RPC endpoints / large numeric
23
+ * quantities, across any chain the SDK might route to.
24
+ *
25
+ * Order matters. URLs are stripped early so address-shaped path segments
26
+ * inside URLs aren't re-mangled by later passes. Specific patterns run
27
+ * before broad ones (e.g. EVM `0x{40}` before the generic long-hex pass)
28
+ * so the longer / more specific match wins. Bech32 runs before generic
29
+ * Base58 because the two alphabets partially overlap — Bech32 includes
30
+ * `0` (which Base58 excludes) and Base58 includes `o`/`i`/`b`/`l` (which
31
+ * Bech32 excludes) — so running Base58 first can chop off the tail of an
32
+ * HRP-prefixed Bech32 address at the first `l`/`i`/`o`/`b` and leave the
33
+ * suffix unscrubbed. Decimal-number scrubbing runs last so it doesn't
34
+ * fragment hex / Base58 tokens that contain digit runs.
35
+ */
36
+ const SANITISE_PATTERNS = [
37
+ // EVM-style 20-byte hex addresses (e.g. `0x` + 40 hex chars).
38
+ { pattern: /0x[a-fA-F0-9]{40}/gu, replacement: '<addr>' },
39
+ // Other long hex blobs: tx hashes, signatures, raw byte strings, large
40
+ // hex amounts. 16+ hex chars catches 32-byte hashes/signatures without
41
+ // snagging EVM method selectors (8 chars) or short hex codes.
42
+ { pattern: /(?:0x)?[a-fA-F0-9]{16,}/gu, replacement: '<hex>' },
43
+ // URLs of any scheme up to the first whitespace / quote / closing paren.
44
+ // Catches RPC endpoints, dapp deeplinks, query strings with secrets.
45
+ { pattern: /https?:\/\/[^\s"')]+/gu, replacement: '<url>' },
46
+ // Bech32 addresses: short HRP (1-10 lowercase chars) + `1` separator +
47
+ // ≥38 chars of Bech32 data alphabet `[ac-hj-np-z02-9]` (excludes the
48
+ // look-alike chars `b`, `i`, `o`, `1`). Covers Bitcoin SegWit
49
+ // (`bc1…`/`tb1…`) and Cosmos-SDK chains (`cosmos1…`, `osmo1…`,
50
+ // `juno1…`, `inj1…`, etc.) without enumerating every HRP. Runs before
51
+ // the Base58 pattern below — see header comment for why.
52
+ {
53
+ pattern: /\b[a-z]{1,10}1[ac-hj-np-z02-9]{38,}\b/gu,
54
+ replacement: '<addr>',
55
+ },
56
+ // Base58 tokens (32+ chars, Base58 alphabet `[1-9A-HJ-NP-Za-km-z]`).
57
+ // Covers Solana pubkeys (32-44 chars), Solana tx signatures (~88 chars),
58
+ // and Bitcoin Base58 addresses ≥32 chars. The 32-char floor and `\b`
59
+ // word boundary keep English words and shorter alphanumerics safe.
60
+ {
61
+ pattern: /\b[1-9A-HJ-NP-Za-km-z]{32,}\b/gu,
62
+ replacement: '<addr>',
63
+ },
64
+ // Long decimal numbers — token amounts, gas units, timestamps, lamports.
65
+ // 10+ digits catches typical chain quantities without affecting JSON-RPC
66
+ // codes (-32601, 4001, etc.) or short numeric IDs.
67
+ { pattern: /\d{10,}/gu, replacement: '<num>' },
68
+ ];
69
+ /**
70
+ * Sanitises an error message for inclusion in analytics. Strips wallet
71
+ * addresses (EVM hex, Solana / Bitcoin Base58, Bech32), long hex blobs,
72
+ * URLs, and large decimal numbers, then truncates to
73
+ * {@link ERROR_MESSAGE_SAMPLE_MAX_LENGTH} characters. Returns `undefined`
74
+ * if there's no message to sample.
75
+ *
76
+ * @param message - Raw error message
77
+ * @returns A safe-to-emit short string, or `undefined`
78
+ */
79
+ export function sanitiseErrorMessage(message) {
80
+ if (!message) {
81
+ return undefined;
82
+ }
83
+ let sanitised = message;
84
+ for (const { pattern, replacement } of SANITISE_PATTERNS) {
85
+ sanitised = sanitised.replace(pattern, replacement);
86
+ }
87
+ if (sanitised.length > ERROR_MESSAGE_SAMPLE_MAX_LENGTH) {
88
+ // Trim and mark as truncated so consumers can tell vs. naturally short
89
+ // messages. The trailing ellipsis fits inside the maxLength budget.
90
+ sanitised = `${sanitised.slice(0, ERROR_MESSAGE_SAMPLE_MAX_LENGTH - 1)}…`;
91
+ }
92
+ return sanitised;
93
+ }
94
+ /**
95
+ * Pulls the most informative `code` / `message` pair out of an error,
96
+ * unwrapping `RPCInvokeMethodErr` so the wallet-side code (e.g. 4001) is
97
+ * visible to classifiers instead of being hidden behind the SDK's static
98
+ * `code: 53`. Falls back to the outer error if there is no inner wallet code.
99
+ *
100
+ * @param error - The error object to inspect
101
+ * @returns The most relevant `{ code, message }` pair we can extract
102
+ */
103
+ function getUnwrappedErrorDetails(error) {
104
+ var _a, _b, _c, _d;
105
+ if (typeof error !== 'object' || error === null) {
106
+ return { code: undefined, message: '' };
107
+ }
108
+ if (error instanceof RPCInvokeMethodErr) {
109
+ return {
110
+ code: (_a = error.rpcCode) !== null && _a !== void 0 ? _a : error.code,
111
+ message: (_c = (_b = error.rpcMessage) !== null && _b !== void 0 ? _b : error.message) !== null && _c !== void 0 ? _c : '',
112
+ };
113
+ }
114
+ const errorObj = error;
115
+ return {
116
+ code: errorObj.code,
117
+ message: (_d = errorObj.message) !== null && _d !== void 0 ? _d : '',
118
+ };
119
+ }
14
120
  /**
15
121
  * Checks if an error represents a user rejection.
16
122
  *
123
+ * Unwraps `RPCInvokeMethodErr` so the wallet's `code: 4001` survives the
124
+ * SDK's transport-boundary wrapping (the outer error otherwise reports
125
+ * `code: 53`, which would never match the heuristics here).
126
+ *
17
127
  * @param error - The error object to check
18
128
  * @returns True if the error indicates a user rejection, false otherwise
19
129
  */
20
130
  export function isRejectionError(error) {
21
- var _a, _b;
22
131
  if (typeof error !== 'object' || error === null) {
23
132
  return false;
24
133
  }
25
- const errorObj = error;
26
- const errorCode = errorObj.code;
27
- const errorMessage = (_b = (_a = errorObj.message) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : '';
28
- return (errorCode === 4001 || // User rejected request (common EIP-1193 code)
29
- errorCode === 4100 || // Unauthorized (common rejection code)
134
+ const { code, message } = getUnwrappedErrorDetails(error);
135
+ const errorMessage = message.toLowerCase();
136
+ // EIP-1193 4001 "User Rejected Request" is the canonical rejection code.
137
+ // Note: 4100 "Unauthorized" is deliberately NOT matched here. On multichain
138
+ // sessions it's what the CAIP-25 permission layer returns when a method
139
+ // isn't in the granted scope (the layer rejects it before the method
140
+ // handler runs). That's a permission/support signal, not a user-driven
141
+ // rejection — misclassifying it as `_rejected` hides genuine permission
142
+ // issues from `_failed`.
143
+ return (code === 4001 ||
30
144
  errorMessage.includes('reject') ||
31
145
  errorMessage.includes('denied') ||
32
146
  errorMessage.includes('cancel') ||
33
- errorMessage.includes('user'));
147
+ // Narrow "user …" matches — bare "user" is too greedy (catches Account
148
+ // Abstraction errors like "user operation reverted").
149
+ errorMessage.includes('user rejected') ||
150
+ errorMessage.includes('user denied') ||
151
+ errorMessage.includes('user cancelled') ||
152
+ errorMessage.includes('user canceled'));
153
+ }
154
+ /**
155
+ * Classifies a failed wallet action / connection error into a short tag for
156
+ * the `failure_reason` analytics property. Caller is expected to have already
157
+ * established that the error is *not* a user rejection (use `isRejectionError`
158
+ * for that branching).
159
+ *
160
+ * The taxonomy is deliberately producer-side-only — the schema accepts any
161
+ * string — so we can add buckets here without an API migration. Once the
162
+ * distribution stabilises we may convert the schema field to a closed enum.
163
+ *
164
+ * @param error - The error to classify
165
+ * @returns A short, snake_case tag describing why the operation failed
166
+ */
167
+ export function classifyFailureReason(error) {
168
+ var _a, _b;
169
+ if (typeof error !== 'object' || error === null) {
170
+ return 'unknown';
171
+ }
172
+ const errorObj = error;
173
+ const errorName = (_a = errorObj.name) !== null && _a !== void 0 ? _a : '';
174
+ const errorMessageRaw = (_b = errorObj.message) !== null && _b !== void 0 ? _b : '';
175
+ const errorMessage = errorMessageRaw.toLowerCase();
176
+ // Wallet-side JSON-RPC / EIP-1193 code is the strongest signal we have —
177
+ // check it before any message-substring heuristics so a wallet error like
178
+ // `{ code: 4900, message: 'Disconnected' }` doesn't get caught by the
179
+ // transport-disconnect text match below. Unwraps `RPCInvokeMethodErr` so
180
+ // the wallet's actual error code is visible.
181
+ const { code } = getUnwrappedErrorDetails(error);
182
+ if (typeof code === 'number') {
183
+ // JSON-RPC 2.0 + EIP-1474 standard codes.
184
+ if (code === -32601) {
185
+ return 'wallet_method_unsupported';
186
+ }
187
+ if (code === -32602) {
188
+ return 'wallet_invalid_params';
189
+ }
190
+ if (code === -32603) {
191
+ return 'wallet_internal_error';
192
+ }
193
+ // Standard JSON-RPC server error range.
194
+ if (code <= -32000 && code >= -32099) {
195
+ return 'wallet_internal_error';
196
+ }
197
+ // EIP-1193 named provider codes — handled individually. Codes in the
198
+ // 1000–4999 range that aren't matched here fall through to `unknown`.
199
+ if (code === 4100) {
200
+ // Unauthorized — most commonly fires when a method isn't in the
201
+ // CAIP-25 scope's granted methods list (the multichain permission
202
+ // layer rejects it before the method handler runs). Distinct from
203
+ // a user rejection (4001) and worth tracking separately.
204
+ return 'wallet_unauthorized';
205
+ }
206
+ if (code === 4200) {
207
+ // Unsupported method — wallet handler exists but explicitly refuses.
208
+ return 'wallet_method_unsupported';
209
+ }
210
+ if (code === 4902) {
211
+ // Unrecognized chain ID — `wallet_switchEthereumChain` to a chain the
212
+ // wallet hasn't been told about. MetaMask (extension + mobile) always
213
+ // sets code 4902 on this error, so we don't need a message-substring
214
+ // fallback below.
215
+ return 'unrecognized_chain';
216
+ }
217
+ // Anything else in the EIP-1193 / EIP-1474 provider-defined range
218
+ // (1000–4999) falls through to `unknown` — we can promote specific codes
219
+ // into their own buckets later as the distribution stabilises, without a
220
+ // schema migration. Two buckets for "we don't know what this is" adds
221
+ // noise without insight.
222
+ }
223
+ // Transport-layer errors. Two shapes exist:
224
+ // - `TransportTimeoutError` from `@metamask/multichain-api-client` (used by
225
+ // MWP and the warmup paths of the default extension transport). It's a
226
+ // subclass of `TransportError` so we match on the name field rather than
227
+ // importing the symbol (the type lives in a runtime dependency that the
228
+ // analytics utils shouldn't pull in directly).
229
+ // - A plain `new Error('Request timeout')` thrown by `DefaultTransport`'s
230
+ // own setTimeout. Indistinguishable from other errors without the message.
231
+ if (errorName === 'TransportTimeoutError' ||
232
+ errorMessageRaw === 'Request timeout' ||
233
+ errorMessage.includes('timed out') ||
234
+ errorMessage.includes('timeout')) {
235
+ return 'transport_timeout';
236
+ }
237
+ // Transport disconnect. Narrowed substring set so we don't snag wallet
238
+ // error messages that happen to contain "disconnect" (e.g. EIP-1193
239
+ // `4900 Disconnected`, which the wallet-code branch above already routed
240
+ // to `unknown` per policy).
241
+ if (errorName === 'TransportError' ||
242
+ errorMessage.includes('not connected') ||
243
+ errorMessage.includes('transport disconnect') ||
244
+ errorMessage.includes('connection lost') ||
245
+ errorMessage.includes('socket closed')) {
246
+ return 'transport_disconnect';
247
+ }
248
+ return 'unknown';
249
+ }
250
+ /**
251
+ * Computes the full set of diagnostic properties to attach to a
252
+ * `mmconnect_*_failed` event from a single error. Combines
253
+ * {@link classifyFailureReason}, the unwrapped wallet code, and a
254
+ * sanitised message sample so producer call sites stay a single line.
255
+ *
256
+ * @param error - The error to inspect
257
+ * @returns Diagnostics ready to spread into the event properties
258
+ */
259
+ export function extractErrorDiagnostics(error) {
260
+ const failureReason = classifyFailureReason(error);
261
+ const { code, message } = getUnwrappedErrorDetails(error);
262
+ const messageSample = sanitiseErrorMessage(message);
263
+ return Object.assign(Object.assign({ failure_reason: failureReason }, (typeof code === 'number' ? { error_code: code } : {})), (messageSample ? { error_message_sample: messageSample } : {}));
34
264
  }
35
265
  /**
36
266
  * Gets base analytics properties that are common across all events.
@@ -60,21 +290,26 @@ export function getBaseAnalyticsProperties(options, storage) {
60
290
  * @param storage - Storage client for getting anonymous ID
61
291
  * @param invokeOptions - The invoke method options containing method and scope
62
292
  * @param transportType - The transport type to use for the analytics event
293
+ * @param extra - Optional event-specific diagnostic properties. Used by
294
+ * `mmconnect_wallet_action_failed` to attach the {@link ErrorDiagnostics}
295
+ * bundle (`failure_reason`, `error_code`, `error_message_sample`).
296
+ * @param extra.failure_reason - A short tag describing why the operation
297
+ * failed; see `classifyFailureReason` and the `FailureReason` union.
298
+ * @param extra.error_code - The raw wallet-side error code, if present.
299
+ * @param extra.error_message_sample - A sanitised, truncated sample of the
300
+ * original error message.
63
301
  * @returns Wallet action analytics properties
64
302
  */
65
- export function getWalletActionAnalyticsProperties(options, storage, invokeOptions, transportType) {
303
+ export function getWalletActionAnalyticsProperties(options, storage, invokeOptions, transportType, extra) {
66
304
  return __awaiter(this, void 0, void 0, function* () {
67
305
  var _a;
68
306
  const dappId = getDappId(options.dapp);
69
307
  const anonId = yield storage.getAnonId();
70
- return {
71
- mmconnect_versions: (_a = options.versions) !== null && _a !== void 0 ? _a : {},
72
- dapp_id: dappId,
73
- method: invokeOptions.request.method,
74
- caip_chain_id: invokeOptions.scope,
75
- anon_id: anonId,
76
- transport_type: transportType,
77
- };
308
+ return Object.assign(Object.assign(Object.assign({ mmconnect_versions: (_a = options.versions) !== null && _a !== void 0 ? _a : {}, dapp_id: dappId, method: invokeOptions.request.method, caip_chain_id: invokeOptions.scope, anon_id: anonId, transport_type: transportType }, ((extra === null || extra === void 0 ? void 0 : extra.failure_reason) ? { failure_reason: extra.failure_reason } : {})), (typeof (extra === null || extra === void 0 ? void 0 : extra.error_code) === 'number'
309
+ ? { error_code: extra.error_code }
310
+ : {})), ((extra === null || extra === void 0 ? void 0 : extra.error_message_sample)
311
+ ? { error_message_sample: extra.error_message_sample }
312
+ : {}));
78
313
  });
79
314
  }
80
315
  //# sourceMappingURL=analytics.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"analytics.js","sourceRoot":"","sources":["../../../../src/multichain/utils/analytics.ts"],"names":[],"mappings":";;;;;;;;;AAAA,yDAAyD;AACzD,kFAAkF;AAClF,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,CAAC;AAS9B,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAc;;IAC7C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAChD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,QAAQ,GAAG,KAA4C,CAAC;IAC9D,MAAM,SAAS,GAAG,QAAQ,CAAC,IAAI,CAAC;IAChC,MAAM,YAAY,GAAG,MAAA,MAAA,QAAQ,CAAC,OAAO,0CAAE,WAAW,EAAE,mCAAI,EAAE,CAAC;IAE3D,OAAO,CACL,SAAS,KAAK,IAAI,IAAI,+CAA+C;QACrE,SAAS,KAAK,IAAI,IAAI,uCAAuC;QAC7D,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC/B,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC/B,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC/B,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC,CAC9B,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAgB,0BAA0B,CAC9C,OAA0B,EAC1B,OAAoB;;;QAOpB,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACvC,MAAM,QAAQ,GAAG,eAAe,EAAE,CAAC;QACnC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,SAAS,EAAE,CAAC;QAEzC,OAAO;YACL,kBAAkB,EAAE,MAAA,OAAO,CAAC,QAAQ,mCAAI,EAAE;YAC1C,OAAO,EAAE,MAAM;YACf,QAAQ;YACR,OAAO,EAAE,MAAM;SAChB,CAAC;IACJ,CAAC;CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,UAAgB,kCAAkC,CACtD,OAA0B,EAC1B,OAAoB,EACpB,aAAkC,EAClC,aAA4B;;;QAS5B,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACvC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,SAAS,EAAE,CAAC;QAEzC,OAAO;YACL,kBAAkB,EAAE,MAAA,OAAO,CAAC,QAAQ,mCAAI,EAAE;YAC1C,OAAO,EAAE,MAAM;YACf,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,MAAM;YACpC,aAAa,EAAE,aAAa,CAAC,KAAK;YAClC,OAAO,EAAE,MAAM;YACf,cAAc,EAAE,aAAa;SAC9B,CAAC;IACJ,CAAC;CAAA"}
1
+ {"version":3,"file":"analytics.js","sourceRoot":"","sources":["../../../../src/multichain/utils/analytics.ts"],"names":[],"mappings":"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"}
@@ -406,6 +406,19 @@ type ConnectionRequest = {
406
406
  type ConnectVersions = {
407
407
  'connect-multichain': string;
408
408
  } & Partial<Record<'connect-evm' | 'connect-solana', string>>;
409
+ type AnalyticsOptions = {
410
+ /**
411
+ * Whether to enable analytics tracking. Defaults to `true` when omitted.
412
+ * Set to `false` to disable all analytics event collection.
413
+ */
414
+ enabled?: boolean;
415
+ /**
416
+ * Identifies the integration surface that instantiated the SDK (e.g. `'direct'`,
417
+ * `'wagmi'`). Recorded as the `integration_types` global analytics property.
418
+ * Defaults to `'direct'` when omitted or empty.
419
+ */
420
+ integrationType?: string;
421
+ };
409
422
  /**
410
423
  * Constructor options for creating a Multichain SDK instance.
411
424
  *
@@ -422,9 +435,7 @@ type MultichainOptions = {
422
435
  supportedNetworks: RpcUrlsMap;
423
436
  };
424
437
  /** Analytics configuration */
425
- analytics?: {
426
- integrationType: string;
427
- };
438
+ analytics?: AnalyticsOptions;
428
439
  /** Storage client for persisting SDK data */
429
440
  storage: StoreClient;
430
441
  /** UI configuration options */
@@ -464,6 +475,7 @@ type MultiChainFNOptions = Omit<MultichainOptions, 'storage' | 'ui'> & {
464
475
  * with an existing singleton.
465
476
  */
466
477
  type MergeableMultichainOptions = Omit<MultichainOptions, 'dapp' | 'analytics' | 'storage' | 'api' | 'ui' | 'transport' | 'versions'> & {
478
+ analytics?: AnalyticsOptions;
467
479
  api?: MultichainOptions['api'];
468
480
  ui?: Pick<MultichainOptions['ui'], 'headless' | 'preferExtension' | 'showInstallModal'>;
469
481
  transport?: Pick<NonNullable<MultichainOptions['transport']>, 'extensionId'>;
@@ -553,7 +565,7 @@ declare abstract class MultichainCore extends EventEmitter<SDKEvents> {
553
565
  constructor(options: MultichainOptions);
554
566
  /**
555
567
  * Merges the given options into the current instance options.
556
- * Only the mergeable keys are updated (api.supportedNetworks, versions, ui.*, mobile.*, transport.extensionId, debug).
568
+ * Only the mergeable keys are updated (api.supportedNetworks, analytics, versions, ui.*, mobile.*, transport.extensionId, debug).
557
569
  * The main thing to note is that the value for `dapp` is not merged as it does not make sense for
558
570
  * subsequent calls to `createMultichainClient` to have a different `dapp` value.
559
571
  * Used when createMultichainClient is called with an existing singleton.
@@ -679,13 +691,42 @@ declare abstract class Modal<Options, Data extends DataType = DataType> {
679
691
  set data(data: Data);
680
692
  }
681
693
 
694
+ /**
695
+ * Tag describing the cause of a failed wallet action / connection. Surfaced
696
+ * as the `failure_reason` property on `mmconnect_wallet_action_failed` and
697
+ * `mmconnect_connection_failed` events so we can distinguish e.g. a transport
698
+ * timeout from a wallet-side internal error in Mixpanel.
699
+ *
700
+ * Intentionally a string union (not a const enum) so callers stay free to
701
+ * pass through a new bucket; the schema-side property is an open string for
702
+ * the same reason.
703
+ */
704
+ type FailureReason = 'transport_timeout' | 'transport_disconnect' | 'wallet_method_unsupported' | 'wallet_invalid_params' | 'wallet_internal_error' | 'wallet_unauthorized' | 'unrecognized_chain' | 'unknown';
682
705
  /**
683
706
  * Checks if an error represents a user rejection.
684
707
  *
708
+ * Unwraps `RPCInvokeMethodErr` so the wallet's `code: 4001` survives the
709
+ * SDK's transport-boundary wrapping (the outer error otherwise reports
710
+ * `code: 53`, which would never match the heuristics here).
711
+ *
685
712
  * @param error - The error object to check
686
713
  * @returns True if the error indicates a user rejection, false otherwise
687
714
  */
688
715
  declare function isRejectionError(error: unknown): boolean;
716
+ /**
717
+ * Classifies a failed wallet action / connection error into a short tag for
718
+ * the `failure_reason` analytics property. Caller is expected to have already
719
+ * established that the error is *not* a user rejection (use `isRejectionError`
720
+ * for that branching).
721
+ *
722
+ * The taxonomy is deliberately producer-side-only — the schema accepts any
723
+ * string — so we can add buckets here without an API migration. Once the
724
+ * distribution stabilises we may convert the schema field to a closed enum.
725
+ *
726
+ * @param error - The error to classify
727
+ * @returns A short, snake_case tag describing why the operation failed
728
+ */
729
+ declare function classifyFailureReason(error: unknown): FailureReason;
689
730
  /**
690
731
  * Gets analytics properties specific to wallet action events.
691
732
  *
@@ -693,15 +734,30 @@ declare function isRejectionError(error: unknown): boolean;
693
734
  * @param storage - Storage client for getting anonymous ID
694
735
  * @param invokeOptions - The invoke method options containing method and scope
695
736
  * @param transportType - The transport type to use for the analytics event
737
+ * @param extra - Optional event-specific diagnostic properties. Used by
738
+ * `mmconnect_wallet_action_failed` to attach the {@link ErrorDiagnostics}
739
+ * bundle (`failure_reason`, `error_code`, `error_message_sample`).
740
+ * @param extra.failure_reason - A short tag describing why the operation
741
+ * failed; see `classifyFailureReason` and the `FailureReason` union.
742
+ * @param extra.error_code - The raw wallet-side error code, if present.
743
+ * @param extra.error_message_sample - A sanitised, truncated sample of the
744
+ * original error message.
696
745
  * @returns Wallet action analytics properties
697
746
  */
698
- declare function getWalletActionAnalyticsProperties(options: MultichainOptions, storage: StoreClient, invokeOptions: InvokeMethodOptions, transportType: TransportType): Promise<{
747
+ declare function getWalletActionAnalyticsProperties(options: MultichainOptions, storage: StoreClient, invokeOptions: InvokeMethodOptions, transportType: TransportType, extra?: {
748
+ failure_reason?: FailureReason;
749
+ error_code?: number;
750
+ error_message_sample?: string;
751
+ }): Promise<{
699
752
  mmconnect_versions: Record<string, string>;
700
753
  dapp_id: string;
701
754
  method: string;
702
755
  caip_chain_id: string;
703
756
  anon_id: string;
704
757
  transport_type: TransportType;
758
+ failure_reason?: FailureReason;
759
+ error_code?: number;
760
+ error_message_sample?: string;
705
761
  }>;
706
762
 
707
763
  /**
@@ -713,4 +769,4 @@ declare function getVersion(): string;
713
769
 
714
770
  declare const createMultichainClient: CreateMultichainFN;
715
771
 
716
- export { type ConnectVersions, type ConnectionRequest, type ConnectionStatus, type CreateMultichainFN, type DappSettings, type DataType, type DomainErrorCodes, type Enumerate, type ErrorCodeRange, type ErrorCodes, EventEmitter, type EventTypes, type ExtendedTransport, type InstallWidgetProps, type InvokeMethodOptions, type LoggerNameSpaces, type MergeableMultichainOptions, Modal, type ModalFactoryConnectOptions, type ModalFactoryOptions, MultichainCore, type MultichainOptions, type NotificationCallback, type OTPCode, type OTPCodeWidgetProps, PlatformType, type QRLink, type RPCAPI, type RPCErrorCodes, RPCHttpErr, RPCInvokeMethodErr, RPCReadonlyRequestErr, RPCReadonlyResponseErr, type RPCResponse, RPC_HANDLED_METHODS, type RpcMethod, type RpcUrlsMap, type SDKEvents, SDK_HANDLED_METHODS, type Scope, type StorageErrorCodes, StoreAdapter, StoreClient, type StoreOptions, TransportType, createLogger, createMultichainClient, enableDebug, getInfuraRpcUrls, getPlatformType, getTransportType, getVersion, getWalletActionAnalyticsProperties, hasExtension, infuraRpcUrls, isEnabled, isMetamaskExtensionInstalled, isRejectionError, isSecure };
772
+ export { type AnalyticsOptions, type ConnectVersions, type ConnectionRequest, type ConnectionStatus, type CreateMultichainFN, type DappSettings, type DataType, type DomainErrorCodes, type Enumerate, type ErrorCodeRange, type ErrorCodes, EventEmitter, type EventTypes, type ExtendedTransport, type FailureReason, type InstallWidgetProps, type InvokeMethodOptions, type LoggerNameSpaces, type MergeableMultichainOptions, Modal, type ModalFactoryConnectOptions, type ModalFactoryOptions, MultichainCore, type MultichainOptions, type NotificationCallback, type OTPCode, type OTPCodeWidgetProps, PlatformType, type QRLink, type RPCAPI, type RPCErrorCodes, RPCHttpErr, RPCInvokeMethodErr, RPCReadonlyRequestErr, RPCReadonlyResponseErr, type RPCResponse, RPC_HANDLED_METHODS, type RpcMethod, type RpcUrlsMap, type SDKEvents, SDK_HANDLED_METHODS, type Scope, type StorageErrorCodes, StoreAdapter, StoreClient, type StoreOptions, TransportType, classifyFailureReason, createLogger, createMultichainClient, enableDebug, getInfuraRpcUrls, getPlatformType, getTransportType, getVersion, getWalletActionAnalyticsProperties, hasExtension, infuraRpcUrls, isEnabled, isMetamaskExtensionInstalled, isRejectionError, isSecure };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask/connect-multichain",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "description": "Multichain package for MetaMask Connect",
5
5
  "keywords": [
6
6
  "MetaMask",
@@ -68,7 +68,7 @@
68
68
  "test:watch": "vitest watch"
69
69
  },
70
70
  "dependencies": {
71
- "@metamask/analytics": "^0.4.0",
71
+ "@metamask/analytics": "^0.6.0",
72
72
  "@metamask/mobile-wallet-protocol-core": "^0.4.0",
73
73
  "@metamask/mobile-wallet-protocol-dapp-client": "^0.3.0",
74
74
  "@metamask/multichain-api-client": "^0.10.1",