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