@formo/analytics 1.37.0 → 1.38.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/cjs/src/FormoAnalytics.d.ts +66 -0
  2. package/dist/cjs/src/FormoAnalytics.js +176 -5
  3. package/dist/cjs/src/FormoAnalyticsProvider.d.ts +8 -0
  4. package/dist/cjs/src/FormoAnalyticsProvider.js +42 -32
  5. package/dist/cjs/src/evm/EvmEventTracker.d.ts +38 -10
  6. package/dist/cjs/src/evm/EvmEventTracker.js +182 -0
  7. package/dist/cjs/src/evm/EvmProviderRegistry.js +26 -4
  8. package/dist/cjs/src/evm/EvmRequestTracker.d.ts +40 -10
  9. package/dist/cjs/src/evm/EvmRequestTracker.js +219 -59
  10. package/dist/cjs/src/provider/detection.d.ts +30 -0
  11. package/dist/cjs/src/provider/detection.js +62 -0
  12. package/dist/cjs/src/provider/index.d.ts +1 -1
  13. package/dist/cjs/src/provider/index.js +3 -1
  14. package/dist/cjs/src/types/base.d.ts +23 -0
  15. package/dist/cjs/src/types/provider.d.ts +16 -0
  16. package/dist/cjs/src/types/provider.js +17 -1
  17. package/dist/cjs/src/version.d.ts +1 -1
  18. package/dist/cjs/src/version.js +1 -1
  19. package/dist/cjs/src/wagmi/WagmiEventHandler.d.ts +81 -0
  20. package/dist/cjs/src/wagmi/WagmiEventHandler.js +478 -38
  21. package/dist/esm/src/FormoAnalytics.d.ts +66 -0
  22. package/dist/esm/src/FormoAnalytics.js +176 -5
  23. package/dist/esm/src/FormoAnalyticsProvider.d.ts +8 -0
  24. package/dist/esm/src/FormoAnalyticsProvider.js +40 -31
  25. package/dist/esm/src/evm/EvmEventTracker.d.ts +38 -10
  26. package/dist/esm/src/evm/EvmEventTracker.js +183 -1
  27. package/dist/esm/src/evm/EvmProviderRegistry.js +27 -5
  28. package/dist/esm/src/evm/EvmRequestTracker.d.ts +40 -10
  29. package/dist/esm/src/evm/EvmRequestTracker.js +218 -58
  30. package/dist/esm/src/provider/detection.d.ts +30 -0
  31. package/dist/esm/src/provider/detection.js +60 -0
  32. package/dist/esm/src/provider/index.d.ts +1 -1
  33. package/dist/esm/src/provider/index.js +1 -1
  34. package/dist/esm/src/types/base.d.ts +23 -0
  35. package/dist/esm/src/types/provider.d.ts +16 -0
  36. package/dist/esm/src/types/provider.js +16 -0
  37. package/dist/esm/src/version.d.ts +1 -1
  38. package/dist/esm/src/version.js +1 -1
  39. package/dist/esm/src/wagmi/WagmiEventHandler.d.ts +81 -0
  40. package/dist/esm/src/wagmi/WagmiEventHandler.js +478 -38
  41. package/dist/index.umd.min.js +1 -1
  42. package/package.json +2 -2
@@ -56,10 +56,20 @@ var __rest = (this && this.__rest) || function (s, e) {
56
56
  }
57
57
  return t;
58
58
  };
59
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
60
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
61
+ if (ar || !(i in from)) {
62
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
63
+ ar[i] = from[i];
64
+ }
65
+ }
66
+ return to.concat(ar || Array.prototype.slice.call(from));
67
+ };
59
68
  import { logger } from "../logger";
69
+ import { isUserRejectionError } from "../provider";
60
70
  import { parseChainId } from "../utils/chain";
61
71
  import { validateAndChecksumAddress } from "../utils/address";
62
- import { SignatureStatus, TransactionStatus, WRAPPED_REQUEST_SYMBOL, WRAPPED_REQUEST_REF_SYMBOL, } from "../types";
72
+ import { SignatureStatus, TransactionStatus, WRAPPED_REQUEST_SYMBOL, WRAPPED_REQUEST_REF_SYMBOL, WRAPPED_REQUEST_OWNER_SYMBOL, } from "../types";
63
73
  import { readBatchId, readBatchStatusCode, batchCallOutcome, batchReceiptForCall, } from "./batch";
64
74
  /**
65
75
  * Decode a hex-encoded `personal_sign` message.
@@ -97,6 +107,19 @@ function hexToUtf8(hex) {
97
107
  * probed - an SDK-issued lookup on a serialising transport can wedge the
98
108
  * user's wallet, which is never an acceptable price for a label.
99
109
  */
110
+ /**
111
+ * Providers with a dispatch already on the synchronous call stack.
112
+ *
113
+ * A page can end up with LAYERED Formo wrappers: another library wraps our
114
+ * wrapper, the SDK rebuilds, and the new instance wraps the outer function
115
+ * because the marker is not on it. Both layers route to the same newest
116
+ * live tracker, which would instrument one user request twice. Every
117
+ * dispatch issues its underlying request synchronously (deliberately -
118
+ * the wallet call always goes out first), so an inner shim reached during
119
+ * that window belongs to the SAME user request and passes straight
120
+ * through.
121
+ */
122
+ var dispatchInFlight = new WeakSet();
100
123
  var EvmRequestTracker = /** @class */ (function () {
101
124
  function EvmRequestTracker(wallet, registry, deps) {
102
125
  this.wallet = wallet;
@@ -112,12 +135,29 @@ var EvmRequestTracker = /** @class */ (function () {
112
135
  */
113
136
  this.polls = new Set();
114
137
  this.disposed = false;
138
+ /** Providers whose owner list includes this instance; pruned on cleanup. */
139
+ this.wrappedProviders = new Set();
140
+ this.creationSeq = EvmRequestTracker.nextCreationSeq++;
115
141
  }
116
142
  /** Stop every poll in flight. Terminal, like the event queue's close(). */
117
143
  EvmRequestTracker.prototype.cleanup = function () {
144
+ var _this = this;
118
145
  this.disposed = true;
119
146
  this.polls.forEach(function (timer) { return clearTimeout(timer); });
120
147
  this.polls.clear();
148
+ // Remove this instance from every provider's owner list. The lists
149
+ // live on LONG-LIVED provider objects; leaving disposed trackers in
150
+ // them retains each old instance's whole object graph across rebuilds,
151
+ // growing without bound under HMR.
152
+ this.wrappedProviders.forEach(function (provider) {
153
+ var owners = provider[WRAPPED_REQUEST_OWNER_SYMBOL];
154
+ if (Array.isArray(owners)) {
155
+ var idx = owners.indexOf(_this);
156
+ if (idx !== -1)
157
+ owners.splice(idx, 1);
158
+ }
159
+ });
160
+ this.wrappedProviders.clear();
121
161
  };
122
162
  /** Re-arm a poll, unless this tracker has been torn down. */
123
163
  EvmRequestTracker.prototype.schedulePoll = function (fn, delayMs) {
@@ -131,11 +171,24 @@ var EvmRequestTracker = /** @class */ (function () {
131
171
  this.polls.add(timer);
132
172
  };
133
173
  /**
134
- * Wrap a provider's `request`. Returns whether the wrapper is installed:
135
- * a caller that marks the provider as tracked on a false return would
136
- * never retry it, and every signature and transaction from that wallet
137
- * would be missed for the rest of the session.
174
+ * Put this tracker into an owner list at its CREATION-ORDER position.
175
+ * Registration can arrive out of order - an older instance's async wrap
176
+ * kick may resolve after a newer instance's - and dispatch picks from
177
+ * the END of the list, so append-on-registration would let resolution
178
+ * order decide which instance owns capture. Creation order is what the
179
+ * newest-live contract promises.
138
180
  */
181
+ EvmRequestTracker.prototype.insertBySeniority = function (owners) {
182
+ var _this = this;
183
+ var idx = owners.indexOf(this);
184
+ if (idx !== -1)
185
+ owners.splice(idx, 1);
186
+ var insertAt = owners.findIndex(function (o) { return o.creationSeq > _this.creationSeq; });
187
+ if (insertAt === -1)
188
+ owners.push(this);
189
+ else
190
+ owners.splice(insertAt, 0, this);
191
+ };
139
192
  EvmRequestTracker.prototype.registerRequestListeners = function (provider) {
140
193
  var _this = this;
141
194
  logger.info("registerRequestListeners");
@@ -143,19 +196,112 @@ var EvmRequestTracker = /** @class */ (function () {
143
196
  logger.error("Provider not found for request (signature, transaction) tracking");
144
197
  return false;
145
198
  }
146
- // Check if the provider is already wrapped with our SDK's wrapper
199
+ // Already wrapped: take OWNERSHIP rather than skipping. The wrapper
200
+ // survives an SDK rebuild and closes over the instance that installed
201
+ // it, whose queue is closed after cleanup - "skip" made the rebuilt
202
+ // instance report success while every request-derived event died in
203
+ // the dead instance's queue. The wrapper reads the owner slot per call.
147
204
  var currentRequest = provider.request;
148
205
  if (this.registry.isWrapped(provider, currentRequest)) {
149
- logger.info("Provider already wrapped with our SDK; skipping request wrapping.");
206
+ var owners = provider[WRAPPED_REQUEST_OWNER_SYMBOL];
207
+ if (!Array.isArray(owners)) {
208
+ // A wrapper without its owner list cannot be taken over, and
209
+ // claiming success would silence every request event.
210
+ logger.warn("wrapped without owner list; cannot rebind");
211
+ return false;
212
+ }
213
+ this.insertBySeniority(owners);
214
+ this.wrappedProviders.add(provider);
215
+ logger.info("Provider already wrapped; rebinding the wrapper to this instance.");
150
216
  return true;
151
217
  }
152
218
  var request = provider.request.bind(provider);
153
219
  var wrappedRequest = function (_a) { return __awaiter(_this, [_a], void 0, function (_b) {
220
+ var owners, liveOwner;
221
+ var method = _b.method, params = _b.params;
222
+ return __generator(this, function (_c) {
223
+ owners = provider[WRAPPED_REQUEST_OWNER_SYMBOL];
224
+ if (dispatchInFlight.has(provider)) {
225
+ // An outer Formo wrapper is already instrumenting this very
226
+ // request; this layer only forwards.
227
+ return [2 /*return*/, request({ method: method, params: params })];
228
+ }
229
+ liveOwner = Array.isArray(owners)
230
+ ? __spreadArray([], owners, true).reverse().find(function (o) { return !o.disposed; })
231
+ : undefined;
232
+ dispatchInFlight.add(provider);
233
+ try {
234
+ return [2 /*return*/, (liveOwner !== null && liveOwner !== void 0 ? liveOwner : this).dispatchWrappedRequest({ method: method, params: params }, provider, request)];
235
+ }
236
+ finally {
237
+ // Cleared as soon as the dispatch call RETURNS its promise: the
238
+ // underlying request has been issued synchronously by then, so
239
+ // the window covers exactly the nested layers of this one call
240
+ // and never a concurrent request.
241
+ dispatchInFlight.delete(provider);
242
+ }
243
+ return [2 /*return*/];
244
+ });
245
+ }); };
246
+ try {
247
+ // MERGE with any existing list rather than overwriting: a wallet that
248
+ // replaced `request` forces a re-wrap, and discarding the prior list
249
+ // would drop other live instances from ownership - the newest-live
250
+ // fallback then has nobody to fall back to.
251
+ var slot = provider;
252
+ var prior = slot[WRAPPED_REQUEST_OWNER_SYMBOL];
253
+ var owners = Array.isArray(prior) ? prior : [];
254
+ this.insertBySeniority(owners);
255
+ if (!Array.isArray(prior)) {
256
+ slot[WRAPPED_REQUEST_OWNER_SYMBOL] = owners;
257
+ }
258
+ this.wrappedProviders.add(provider);
259
+ }
260
+ catch (_a) {
261
+ /* frozen provider: the request write below fails too and aborts */
262
+ }
263
+ return this.installWrappedRequest(provider, wrappedRequest);
264
+ };
265
+ /** Install the wrapper function onto the provider; separated so the
266
+ * routing shim above stays small. */
267
+ EvmRequestTracker.prototype.installWrappedRequest = function (provider, wrappedRequest) {
268
+ // Mark the wrapper so we can detect if request is replaced externally and keep a reference on provider
269
+ wrappedRequest[WRAPPED_REQUEST_SYMBOL] = true;
270
+ // Both writes go inside the try. A frozen or non-extensible provider
271
+ // throws on the symbol assignment just as readily as on `request`, and
272
+ // that one used to sit outside the guard, so an unwrappable provider
273
+ // aborted registration instead of being skipped.
274
+ try {
275
+ provider[WRAPPED_REQUEST_REF_SYMBOL] =
276
+ wrappedRequest;
277
+ provider.request = wrappedRequest;
278
+ // Read back: an accessor or Proxy can ACCEPT the assignment without
279
+ // installing it, and success here is a promise that capture works.
280
+ if (provider.request !== wrappedRequest) {
281
+ logger.warn("request assignment swallowed; not wrapped");
282
+ return false;
283
+ }
284
+ return true;
285
+ }
286
+ catch (e) {
287
+ logger.warn("Failed to wrap provider.request; skipping", e);
288
+ return false;
289
+ }
290
+ };
291
+ /**
292
+ * The wrapper body proper: everything a request observation does, run
293
+ * against THIS instance's registry, wallet state, and event queue. Kept
294
+ * as a method so a surviving wrapper installed by a previous SDK
295
+ * instance can hand calls to the current one.
296
+ */
297
+ EvmRequestTracker.prototype.dispatchWrappedRequest = function (_a, provider_1, request_1) {
298
+ return __awaiter(this, arguments, void 0, function (_b, provider, request) {
154
299
  var generation_1, responsePromise, capturedChainId_1, response_1, error_1, rpcError, txPromise, txChainId_1, transactionHash_1, error_2, rpcError;
155
300
  var _this = this;
301
+ var _c, _d, _e, _f;
156
302
  var method = _b.method, params = _b.params;
157
- return __generator(this, function (_c) {
158
- switch (_c.label) {
303
+ return __generator(this, function (_g) {
304
+ switch (_g.label) {
159
305
  case 0:
160
306
  // Learn the chain from a call the APP was making anyway.
161
307
  //
@@ -184,6 +330,10 @@ var EvmRequestTracker = /** @class */ (function () {
184
330
  logger.debug("Signature event skipped (autocapture.signature: false)", { method: method });
185
331
  return [2 /*return*/, request({ method: method, params: params })];
186
332
  }
333
+ if ((_d = (_c = this.deps).shouldSkipRequestCapture) === null || _d === void 0 ? void 0 : _d.call(_c, method, params)) {
334
+ // A pending wagmi mutation owns this capture.
335
+ return [2 /*return*/, request({ method: method, params: params })];
336
+ }
187
337
  responsePromise = request({ method: method, params: params });
188
338
  // Attach a no-op handler now so a rejection arriving before the await
189
339
  // below is never reported as unhandled. The real handling is there.
@@ -196,7 +346,7 @@ var EvmRequestTracker = /** @class */ (function () {
196
346
  switch (_a.label) {
197
347
  case 0:
198
348
  _a.trys.push([0, 2, , 3]);
199
- return [4 /*yield*/, this.deps.signature(__assign({ status: SignatureStatus.REQUESTED }, this.buildSignatureEventPayload(method, params, undefined, capturedChainId_1, provider)))];
349
+ return [4 /*yield*/, this.deps.signature(__assign({ status: SignatureStatus.REQUESTED }, this.buildSignatureEventPayload(method, params, undefined, capturedChainId_1, provider)), this.attributionFor(provider))];
200
350
  case 1:
201
351
  _a.sent();
202
352
  return [3 /*break*/, 3];
@@ -208,12 +358,12 @@ var EvmRequestTracker = /** @class */ (function () {
208
358
  }
209
359
  });
210
360
  }); })();
211
- _c.label = 1;
361
+ _g.label = 1;
212
362
  case 1:
213
- _c.trys.push([1, 3, , 4]);
363
+ _g.trys.push([1, 3, , 4]);
214
364
  return [4 /*yield*/, responsePromise];
215
365
  case 2:
216
- response_1 = _c.sent();
366
+ response_1 = _g.sent();
217
367
  // Track signature confirmation only for truthy responses
218
368
  if (response_1) {
219
369
  (function () { return __awaiter(_this, void 0, void 0, function () {
@@ -222,7 +372,7 @@ var EvmRequestTracker = /** @class */ (function () {
222
372
  switch (_a.label) {
223
373
  case 0:
224
374
  _a.trys.push([0, 2, , 3]);
225
- return [4 /*yield*/, this.deps.signature(__assign({ status: SignatureStatus.CONFIRMED }, this.buildSignatureEventPayload(method, params, response_1, capturedChainId_1, provider)))];
375
+ return [4 /*yield*/, this.deps.signature(__assign({ status: SignatureStatus.CONFIRMED }, this.buildSignatureEventPayload(method, params, response_1, capturedChainId_1, provider)), this.attributionFor(provider))];
226
376
  case 1:
227
377
  _a.sent();
228
378
  return [3 /*break*/, 3];
@@ -237,9 +387,9 @@ var EvmRequestTracker = /** @class */ (function () {
237
387
  }
238
388
  return [2 /*return*/, response_1];
239
389
  case 3:
240
- error_1 = _c.sent();
390
+ error_1 = _g.sent();
241
391
  rpcError = error_1;
242
- if ((rpcError === null || rpcError === void 0 ? void 0 : rpcError.code) === 4001) {
392
+ if (isUserRejectionError(rpcError)) {
243
393
  // Use the already cast rpcError to avoid duplication
244
394
  (function () { return __awaiter(_this, void 0, void 0, function () {
245
395
  var e_3;
@@ -247,7 +397,7 @@ var EvmRequestTracker = /** @class */ (function () {
247
397
  switch (_a.label) {
248
398
  case 0:
249
399
  _a.trys.push([0, 2, , 3]);
250
- return [4 /*yield*/, this.deps.signature(__assign({ status: SignatureStatus.REJECTED }, this.buildSignatureEventPayload(method, params, undefined, capturedChainId_1, provider)))];
400
+ return [4 /*yield*/, this.deps.signature(__assign({ status: SignatureStatus.REJECTED }, this.buildSignatureEventPayload(method, params, undefined, capturedChainId_1, provider)), this.attributionFor(provider))];
251
401
  case 1:
252
402
  _a.sent();
253
403
  return [3 /*break*/, 3];
@@ -279,6 +429,10 @@ var EvmRequestTracker = /** @class */ (function () {
279
429
  logger.debug("Transaction event skipped (autocapture.transaction: false)", { method: method });
280
430
  return [2 /*return*/, request({ method: method, params: params })];
281
431
  }
432
+ if ((_f = (_e = this.deps).shouldSkipRequestCapture) === null || _f === void 0 ? void 0 : _f.call(_e, method, params)) {
433
+ // A pending wagmi mutation owns this capture.
434
+ return [2 /*return*/, request({ method: method, params: params })];
435
+ }
282
436
  txPromise = request({ method: method, params: params });
283
437
  txPromise.catch(function () { return undefined; });
284
438
  txChainId_1 = this.registry.resolveChainId(provider);
@@ -291,7 +445,7 @@ var EvmRequestTracker = /** @class */ (function () {
291
445
  return [4 /*yield*/, this.buildTransactionEventPayload(params, provider, txChainId_1)];
292
446
  case 1:
293
447
  payload = _a.sent();
294
- return [4 /*yield*/, this.deps.transaction(__assign({ status: TransactionStatus.STARTED }, payload))];
448
+ return [4 /*yield*/, this.deps.transaction(__assign({ status: TransactionStatus.STARTED }, payload), this.attributionFor(provider))];
295
449
  case 2:
296
450
  _a.sent();
297
451
  return [3 /*break*/, 4];
@@ -303,12 +457,12 @@ var EvmRequestTracker = /** @class */ (function () {
303
457
  }
304
458
  });
305
459
  }); })();
306
- _c.label = 5;
460
+ _g.label = 5;
307
461
  case 5:
308
- _c.trys.push([5, 7, , 8]);
462
+ _g.trys.push([5, 7, , 8]);
309
463
  return [4 /*yield*/, txPromise];
310
464
  case 6:
311
- transactionHash_1 = _c.sent();
465
+ transactionHash_1 = _g.sent();
312
466
  (function () { return __awaiter(_this, void 0, void 0, function () {
313
467
  var payload, e_5;
314
468
  return __generator(this, function (_a) {
@@ -318,7 +472,7 @@ var EvmRequestTracker = /** @class */ (function () {
318
472
  return [4 /*yield*/, this.buildTransactionEventPayload(params, provider, txChainId_1)];
319
473
  case 1:
320
474
  payload = _a.sent();
321
- return [4 /*yield*/, this.deps.transaction(__assign(__assign({ status: TransactionStatus.BROADCASTED }, payload), { transactionHash: transactionHash_1 }))];
475
+ return [4 /*yield*/, this.deps.transaction(__assign(__assign({ status: TransactionStatus.BROADCASTED }, payload), { transactionHash: transactionHash_1 }), this.attributionFor(provider))];
322
476
  case 2:
323
477
  _a.sent();
324
478
  // Start async polling for transaction receipt
@@ -334,9 +488,9 @@ var EvmRequestTracker = /** @class */ (function () {
334
488
  }); })();
335
489
  return [2 /*return*/, transactionHash_1];
336
490
  case 7:
337
- error_2 = _c.sent();
491
+ error_2 = _g.sent();
338
492
  rpcError = error_2;
339
- if ((rpcError === null || rpcError === void 0 ? void 0 : rpcError.code) === 4001) {
493
+ if (isUserRejectionError(rpcError)) {
340
494
  // Use the already cast rpcError to avoid duplication
341
495
  (function () { return __awaiter(_this, void 0, void 0, function () {
342
496
  var payload, e_6;
@@ -347,7 +501,7 @@ var EvmRequestTracker = /** @class */ (function () {
347
501
  return [4 /*yield*/, this.buildTransactionEventPayload(params, provider, txChainId_1)];
348
502
  case 1:
349
503
  payload = _a.sent();
350
- return [4 /*yield*/, this.deps.transaction(__assign({ status: TransactionStatus.REJECTED }, payload))];
504
+ return [4 /*yield*/, this.deps.transaction(__assign({ status: TransactionStatus.REJECTED }, payload), this.attributionFor(provider))];
351
505
  case 2:
352
506
  _a.sent();
353
507
  return [3 /*break*/, 4];
@@ -364,23 +518,19 @@ var EvmRequestTracker = /** @class */ (function () {
364
518
  case 8: return [2 /*return*/, request({ method: method, params: params })];
365
519
  }
366
520
  });
367
- }); };
368
- // Mark the wrapper so we can detect if request is replaced externally and keep a reference on provider
369
- wrappedRequest[WRAPPED_REQUEST_SYMBOL] = true;
370
- // Both writes go inside the try. A frozen or non-extensible provider
371
- // throws on the symbol assignment just as readily as on `request`, and
372
- // that one used to sit outside the guard, so an unwrappable provider
373
- // aborted registration instead of being skipped.
374
- try {
375
- provider[WRAPPED_REQUEST_REF_SYMBOL] =
376
- wrappedRequest;
377
- provider.request = wrappedRequest;
378
- return true;
379
- }
380
- catch (e) {
381
- logger.warn("Failed to wrap provider.request; skipping", e);
382
- return false;
383
- }
521
+ });
522
+ };
523
+ /**
524
+ * Wallet attribution for request-derived events.
525
+ *
526
+ * Live per read through the registry, so a WalletConnect session names
527
+ * its actual signer ("MetaMask Wallet", "Ledger Live") - the live-test
528
+ * rows had provider_name EMPTY on every signature and transaction, which
529
+ * made per-wallet activity unanswerable in the warehouse.
530
+ */
531
+ EvmRequestTracker.prototype.attributionFor = function (provider) {
532
+ var info = this.registry.infoFor(provider);
533
+ return { providerName: info.name, rdns: info.rdns };
384
534
  };
385
535
  EvmRequestTracker.prototype.buildSignatureEventPayload = function (method, params,
386
536
  // Intentionally not read. Kept for positional call-site arity.
@@ -476,7 +626,7 @@ var EvmRequestTracker = /** @class */ (function () {
476
626
  // status: 1 = success, 0 = reverted
477
627
  if (receipt.status === "0x1" || receipt.status === 1) {
478
628
  this.deps
479
- .transaction(__assign(__assign({ status: TransactionStatus.CONFIRMED }, payload), { transactionHash: transactionHash }))
629
+ .transaction(__assign(__assign({ status: TransactionStatus.CONFIRMED }, payload), { transactionHash: transactionHash }), this.attributionFor(provider))
480
630
  .catch(function (e) {
481
631
  return logger.error("Formo: Failed to track transaction confirmation", e);
482
632
  });
@@ -484,7 +634,7 @@ var EvmRequestTracker = /** @class */ (function () {
484
634
  }
485
635
  else if (receipt.status === "0x0" || receipt.status === 0) {
486
636
  this.deps
487
- .transaction(__assign(__assign({ status: TransactionStatus.REVERTED }, payload), { transactionHash: transactionHash }))
637
+ .transaction(__assign(__assign({ status: TransactionStatus.REVERTED }, payload), { transactionHash: transactionHash }), this.attributionFor(provider))
488
638
  .catch(function (e) {
489
639
  return logger.error("Formo: Failed to track transaction revert", e);
490
640
  });
@@ -527,10 +677,10 @@ var EvmRequestTracker = /** @class */ (function () {
527
677
  */
528
678
  EvmRequestTracker.prototype.trackBatchedCalls = function (provider, request, params) {
529
679
  return __awaiter(this, void 0, void 0, function () {
530
- var sendPromise, batch, calls, declared, chainId, address, payloads, _i, payloads_1, p, properties, rest, result, batchId, _a, payloads_2, p, properties, rest, error_3, rpcError, _b, payloads_3, p, properties, rest;
531
- var _c;
532
- return __generator(this, function (_d) {
533
- switch (_d.label) {
680
+ var sendPromise, batch, calls, declared, chainId, address, attribution, payloads, _i, payloads_1, p, properties, rest, result, batchId, _a, payloads_2, p, properties, rest, error_3, rpcError, _b, payloads_3, p, properties, rest;
681
+ var _c, _d, _e;
682
+ return __generator(this, function (_f) {
683
+ switch (_f.label) {
534
684
  case 0:
535
685
  if (!this.deps.isAutocaptureEnabled("transaction")) {
536
686
  logger.debug("Transaction event skipped (autocapture.transaction: false)", {
@@ -538,13 +688,17 @@ var EvmRequestTracker = /** @class */ (function () {
538
688
  });
539
689
  return [2 /*return*/, request({ method: "wallet_sendCalls", params: params })];
540
690
  }
691
+ if ((_d = (_c = this.deps).shouldSkipRequestCapture) === null || _d === void 0 ? void 0 : _d.call(_c, "wallet_sendCalls", params)) {
692
+ // A pending wagmi sendCalls mutation owns this capture.
693
+ return [2 /*return*/, request({ method: "wallet_sendCalls", params: params })];
694
+ }
541
695
  sendPromise = request({ method: "wallet_sendCalls", params: params });
542
696
  sendPromise.catch(function () { return undefined; });
543
697
  batch = params[0];
544
698
  calls = Array.isArray(batch === null || batch === void 0 ? void 0 : batch.calls) ? batch.calls : [];
545
699
  declared = typeof (batch === null || batch === void 0 ? void 0 : batch.chainId) === "string" ? parseChainId(batch.chainId) : undefined;
546
700
  chainId = declared || this.registry.resolveChainId(provider);
547
- address = validateAndChecksumAddress((_c = batch === null || batch === void 0 ? void 0 : batch.from) !== null && _c !== void 0 ? _c : "");
701
+ address = validateAndChecksumAddress((_e = batch === null || batch === void 0 ? void 0 : batch.from) !== null && _e !== void 0 ? _e : "");
548
702
  if (!address) {
549
703
  // Nothing can be attributed without a sender, and inventing one would
550
704
  // be worse than reporting nothing. The user's call still goes through.
@@ -563,16 +717,14 @@ var EvmRequestTracker = /** @class */ (function () {
563
717
  if (!provider || provider === this.wallet.provider || !this.wallet.provider) {
564
718
  this.wallet.backfill(address, chainId, provider);
565
719
  }
720
+ attribution = this.attributionFor(provider);
566
721
  payloads = calls.map(function (call, index) { return ({
567
722
  chainId: chainId,
568
723
  address: address,
569
724
  to: call === null || call === void 0 ? void 0 : call.to,
570
725
  value: call === null || call === void 0 ? void 0 : call.value,
571
726
  data: call === null || call === void 0 ? void 0 : call.data,
572
- properties: {
573
- batch_size: calls.length,
574
- batch_index: index,
575
- },
727
+ properties: __assign({ batch_size: calls.length, batch_index: index }, attribution),
576
728
  }); });
577
729
  // STARTED carries no batch id: the wallet has not issued one yet, exactly
578
730
  // as `eth_sendTransaction` has no hash at this point. Position within the
@@ -584,12 +736,12 @@ var EvmRequestTracker = /** @class */ (function () {
584
736
  .transaction(__assign({ status: TransactionStatus.STARTED }, rest), properties)
585
737
  .catch(function (e) { return logger.error("Formo: Failed to track batch call start", e); });
586
738
  }
587
- _d.label = 1;
739
+ _f.label = 1;
588
740
  case 1:
589
- _d.trys.push([1, 3, , 4]);
741
+ _f.trys.push([1, 3, , 4]);
590
742
  return [4 /*yield*/, sendPromise];
591
743
  case 2:
592
- result = _d.sent();
744
+ result = _f.sent();
593
745
  batchId = readBatchId(result);
594
746
  for (_a = 0, payloads_2 = payloads; _a < payloads_2.length; _a++) {
595
747
  p = payloads_2[_a];
@@ -604,9 +756,9 @@ var EvmRequestTracker = /** @class */ (function () {
604
756
  this.pollBatchStatus(provider, batchId, payloads);
605
757
  return [2 /*return*/, result];
606
758
  case 3:
607
- error_3 = _d.sent();
759
+ error_3 = _f.sent();
608
760
  rpcError = error_3;
609
- if ((rpcError === null || rpcError === void 0 ? void 0 : rpcError.code) === 4001) {
761
+ if (isUserRejectionError(rpcError)) {
610
762
  // One rejection dismisses the whole prompt, so every call in it is
611
763
  // rejected. Reporting only the first would undercount.
612
764
  for (_b = 0, payloads_3 = payloads; _b < payloads_3.length; _b++) {
@@ -704,6 +856,14 @@ var EvmRequestTracker = /** @class */ (function () {
704
856
  });
705
857
  });
706
858
  };
859
+ /**
860
+ * Wrap a provider's `request`. Returns whether the wrapper is installed:
861
+ * a caller that marks the provider as tracked on a false return would
862
+ * never retry it, and every signature and transaction from that wallet
863
+ * would be missed for the rest of the session.
864
+ */
865
+ /** Monotonic creation stamp; owner precedence is decided by it. */
866
+ EvmRequestTracker.nextCreationSeq = 0;
707
867
  return EvmRequestTracker;
708
868
  }());
709
869
  export { EvmRequestTracker };
@@ -41,6 +41,36 @@ export interface ProviderInfo {
41
41
  * ```
42
42
  */
43
43
  export declare function detectInjectedProviderInfo(provider: EIP1193Provider): ProviderInfo;
44
+ /**
45
+ * Was this error the USER declining, whatever transport delivered it?
46
+ *
47
+ * Three dialects say "the user said no":
48
+ * - EIP-1193: code 4001 (UserRejectedRequest).
49
+ * - WalletConnect sdkErrors: codes 5000-5005 (USER_REJECTED and its
50
+ * variants). A LIVE MetaMask Mobile session rejecting a transaction
51
+ * produced one of these and the SDK's 4001-only match reported nothing -
52
+ * every WalletConnect rejection was silently uncounted.
53
+ * - viem: a typed UserRejectedRequestError, sometimes without the numeric
54
+ * code surviving the wrapping.
55
+ *
56
+ * The real code often hides under `cause` (viem nests, WC wraps), so the
57
+ * chain is walked a few levels.
58
+ */
59
+ export declare function isUserRejectionError(error: unknown): boolean;
60
+ /**
61
+ * The wallet on the far side of a WalletConnect session.
62
+ *
63
+ * WalletConnect is a transport, not a wallet: the signing wallet (Ledger
64
+ * Live, MetaMask Mobile, Safe, ...) identifies itself in the session's peer
65
+ * metadata. Reporting only "WalletConnect" hides every wallet behind it -
66
+ * production showed Ledger at effectively zero while its sessions were being
67
+ * tracked under the transport's name. Reads synchronous state only; never
68
+ * issues an RPC.
69
+ */
70
+ export declare function readWalletConnectPeer(provider: EIP1193Provider): {
71
+ name: string;
72
+ url?: string;
73
+ } | undefined;
44
74
  /**
45
75
  * Validates that a provider implements the required EIP-1193 interface
46
76
  *
@@ -1,6 +1,17 @@
1
1
  /**
2
2
  * Provider detection utilities for identifying wallet providers
3
3
  */
4
+ var __assign = (this && this.__assign) || function () {
5
+ __assign = Object.assign || function(t) {
6
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
7
+ s = arguments[i];
8
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
9
+ t[p] = s[p];
10
+ }
11
+ return t;
12
+ };
13
+ return __assign.apply(this, arguments);
14
+ };
4
15
  /**
5
16
  * Default icon for providers without custom icons
6
17
  */
@@ -76,6 +87,55 @@ export function detectInjectedProviderInfo(provider) {
76
87
  icon: DEFAULT_PROVIDER_ICON,
77
88
  };
78
89
  }
90
+ /**
91
+ * Was this error the USER declining, whatever transport delivered it?
92
+ *
93
+ * Three dialects say "the user said no":
94
+ * - EIP-1193: code 4001 (UserRejectedRequest).
95
+ * - WalletConnect sdkErrors: codes 5000-5005 (USER_REJECTED and its
96
+ * variants). A LIVE MetaMask Mobile session rejecting a transaction
97
+ * produced one of these and the SDK's 4001-only match reported nothing -
98
+ * every WalletConnect rejection was silently uncounted.
99
+ * - viem: a typed UserRejectedRequestError, sometimes without the numeric
100
+ * code surviving the wrapping.
101
+ *
102
+ * The real code often hides under `cause` (viem nests, WC wraps), so the
103
+ * chain is walked a few levels.
104
+ */
105
+ export function isUserRejectionError(error) {
106
+ var cursor = error;
107
+ for (var depth = 0; cursor && depth < 5; depth++) {
108
+ var code = cursor.code;
109
+ if (code === 4001 ||
110
+ (typeof code === "number" && code >= 5000 && code <= 5005) ||
111
+ cursor.name === "UserRejectedRequestError") {
112
+ return true;
113
+ }
114
+ cursor = cursor.cause;
115
+ }
116
+ return false;
117
+ }
118
+ /**
119
+ * The wallet on the far side of a WalletConnect session.
120
+ *
121
+ * WalletConnect is a transport, not a wallet: the signing wallet (Ledger
122
+ * Live, MetaMask Mobile, Safe, ...) identifies itself in the session's peer
123
+ * metadata. Reporting only "WalletConnect" hides every wallet behind it -
124
+ * production showed Ledger at effectively zero while its sessions were being
125
+ * tracked under the transport's name. Reads synchronous state only; never
126
+ * issues an RPC.
127
+ */
128
+ export function readWalletConnectPeer(provider) {
129
+ var _a;
130
+ var session = provider.session;
131
+ var metadata = (_a = session === null || session === void 0 ? void 0 : session.peer) === null || _a === void 0 ? void 0 : _a.metadata;
132
+ if (!metadata || typeof metadata.name !== "string" || metadata.name.length === 0) {
133
+ return undefined;
134
+ }
135
+ return __assign({ name: metadata.name }, (typeof metadata.url === "string" && metadata.url.length > 0
136
+ ? { url: metadata.url }
137
+ : {}));
138
+ }
79
139
  /**
80
140
  * Validates that a provider implements the required EIP-1193 interface
81
141
  *
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Provider-related exports
3
3
  */
4
- export { detectInjectedProviderInfo, isValidProvider, DEFAULT_PROVIDER_ICON, } from './detection';
4
+ export { detectInjectedProviderInfo, isValidProvider, readWalletConnectPeer, isUserRejectionError, DEFAULT_PROVIDER_ICON, } from './detection';
5
5
  export type { WalletProviderFlags, ProviderInfo, } from './detection';
6
6
  //# sourceMappingURL=index.d.ts.map
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * Provider-related exports
3
3
  */
4
- export { detectInjectedProviderInfo, isValidProvider, DEFAULT_PROVIDER_ICON, } from './detection';
4
+ export { detectInjectedProviderInfo, isValidProvider, readWalletConnectPeer, isUserRejectionError, DEFAULT_PROVIDER_ICON, } from './detection';
5
5
  //# sourceMappingURL=index.js.map