@formo/analytics 1.36.0 → 1.38.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 (44) hide show
  1. package/dist/cjs/src/FormoAnalytics.d.ts +64 -0
  2. package/dist/cjs/src/FormoAnalytics.js +155 -5
  3. package/dist/cjs/src/FormoAnalyticsProvider.js +1 -0
  4. package/dist/cjs/src/evm/EvmEventTracker.d.ts +38 -10
  5. package/dist/cjs/src/evm/EvmEventTracker.js +182 -0
  6. package/dist/cjs/src/evm/EvmProviderRegistry.js +26 -4
  7. package/dist/cjs/src/evm/EvmRequestTracker.d.ts +36 -40
  8. package/dist/cjs/src/evm/EvmRequestTracker.js +216 -115
  9. package/dist/cjs/src/evm/batch.d.ts +94 -0
  10. package/dist/cjs/src/evm/batch.js +130 -0
  11. package/dist/cjs/src/provider/detection.d.ts +30 -0
  12. package/dist/cjs/src/provider/detection.js +62 -0
  13. package/dist/cjs/src/provider/index.d.ts +1 -1
  14. package/dist/cjs/src/provider/index.js +3 -1
  15. package/dist/cjs/src/types/base.d.ts +23 -0
  16. package/dist/cjs/src/types/provider.d.ts +16 -0
  17. package/dist/cjs/src/types/provider.js +17 -1
  18. package/dist/cjs/src/version.d.ts +1 -1
  19. package/dist/cjs/src/version.js +1 -1
  20. package/dist/cjs/src/wagmi/WagmiEventHandler.d.ts +87 -0
  21. package/dist/cjs/src/wagmi/WagmiEventHandler.js +536 -13
  22. package/dist/esm/src/FormoAnalytics.d.ts +64 -0
  23. package/dist/esm/src/FormoAnalytics.js +155 -5
  24. package/dist/esm/src/FormoAnalyticsProvider.js +1 -0
  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 +36 -40
  29. package/dist/esm/src/evm/EvmRequestTracker.js +215 -114
  30. package/dist/esm/src/evm/batch.d.ts +94 -0
  31. package/dist/esm/src/evm/batch.js +123 -0
  32. package/dist/esm/src/provider/detection.d.ts +30 -0
  33. package/dist/esm/src/provider/detection.js +60 -0
  34. package/dist/esm/src/provider/index.d.ts +1 -1
  35. package/dist/esm/src/provider/index.js +1 -1
  36. package/dist/esm/src/types/base.d.ts +23 -0
  37. package/dist/esm/src/types/provider.d.ts +16 -0
  38. package/dist/esm/src/types/provider.js +16 -0
  39. package/dist/esm/src/version.d.ts +1 -1
  40. package/dist/esm/src/version.js +1 -1
  41. package/dist/esm/src/wagmi/WagmiEventHandler.d.ts +87 -0
  42. package/dist/esm/src/wagmi/WagmiEventHandler.js +536 -13
  43. package/dist/index.umd.min.js +1 -1
  44. package/package.json +4 -3
@@ -57,12 +57,23 @@ 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");
76
+ var batch_1 = require("./batch");
66
77
  /**
67
78
  * Decode a hex-encoded `personal_sign` message.
68
79
  *
@@ -99,6 +110,19 @@ function hexToUtf8(hex) {
99
110
  * probed - an SDK-issued lookup on a serialising transport can wedge the
100
111
  * user's wallet, which is never an acceptable price for a label.
101
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();
102
126
  var EvmRequestTracker = /** @class */ (function () {
103
127
  function EvmRequestTracker(wallet, registry, deps) {
104
128
  this.wallet = wallet;
@@ -114,12 +138,28 @@ var EvmRequestTracker = /** @class */ (function () {
114
138
  */
115
139
  this.polls = new Set();
116
140
  this.disposed = false;
141
+ /** Providers whose owner list includes this instance; pruned on cleanup. */
142
+ this.wrappedProviders = new Set();
117
143
  }
118
144
  /** Stop every poll in flight. Terminal, like the event queue's close(). */
119
145
  EvmRequestTracker.prototype.cleanup = function () {
146
+ var _this = this;
120
147
  this.disposed = true;
121
148
  this.polls.forEach(function (timer) { return clearTimeout(timer); });
122
149
  this.polls.clear();
150
+ // Remove this instance from every provider's owner list. The lists
151
+ // live on LONG-LIVED provider objects; leaving disposed trackers in
152
+ // them retains each old instance's whole object graph across rebuilds,
153
+ // growing without bound under HMR.
154
+ this.wrappedProviders.forEach(function (provider) {
155
+ var owners = provider[types_1.WRAPPED_REQUEST_OWNER_SYMBOL];
156
+ if (Array.isArray(owners)) {
157
+ var idx = owners.indexOf(_this);
158
+ if (idx !== -1)
159
+ owners.splice(idx, 1);
160
+ }
161
+ });
162
+ this.wrappedProviders.clear();
123
163
  };
124
164
  /** Re-arm a poll, unless this tracker has been torn down. */
125
165
  EvmRequestTracker.prototype.schedulePoll = function (fn, delayMs) {
@@ -145,19 +185,118 @@ var EvmRequestTracker = /** @class */ (function () {
145
185
  logger_1.logger.error("Provider not found for request (signature, transaction) tracking");
146
186
  return false;
147
187
  }
148
- // Check if the provider is already wrapped with our SDK's wrapper
188
+ // Already wrapped: take OWNERSHIP rather than skipping. The wrapper
189
+ // survives an SDK rebuild and closes over the instance that installed
190
+ // it, whose queue is closed after cleanup - "skip" made the rebuilt
191
+ // instance report success while every request-derived event died in
192
+ // the dead instance's queue. The wrapper reads the owner slot per call.
149
193
  var currentRequest = provider.request;
150
194
  if (this.registry.isWrapped(provider, currentRequest)) {
151
- logger_1.logger.info("Provider already wrapped with our SDK; skipping request wrapping.");
195
+ var owners = provider[types_1.WRAPPED_REQUEST_OWNER_SYMBOL];
196
+ if (!Array.isArray(owners)) {
197
+ // A wrapper without its owner list cannot be taken over, and
198
+ // claiming success would silence every request event.
199
+ logger_1.logger.warn("wrapped without owner list; cannot rebind");
200
+ return false;
201
+ }
202
+ var idx = owners.indexOf(this);
203
+ if (idx !== -1)
204
+ owners.splice(idx, 1);
205
+ owners.push(this);
206
+ this.wrappedProviders.add(provider);
207
+ logger_1.logger.info("Provider already wrapped; rebinding the wrapper to this instance.");
152
208
  return true;
153
209
  }
154
210
  var request = provider.request.bind(provider);
155
211
  var wrappedRequest = function (_a) { return __awaiter(_this, [_a], void 0, function (_b) {
212
+ var owners, liveOwner;
213
+ var method = _b.method, params = _b.params;
214
+ return __generator(this, function (_c) {
215
+ owners = provider[types_1.WRAPPED_REQUEST_OWNER_SYMBOL];
216
+ if (dispatchInFlight.has(provider)) {
217
+ // An outer Formo wrapper is already instrumenting this very
218
+ // request; this layer only forwards.
219
+ return [2 /*return*/, request({ method: method, params: params })];
220
+ }
221
+ liveOwner = Array.isArray(owners)
222
+ ? __spreadArray([], owners, true).reverse().find(function (o) { return !o.disposed; })
223
+ : undefined;
224
+ dispatchInFlight.add(provider);
225
+ try {
226
+ return [2 /*return*/, (liveOwner !== null && liveOwner !== void 0 ? liveOwner : this).dispatchWrappedRequest({ method: method, params: params }, provider, request)];
227
+ }
228
+ finally {
229
+ // Cleared as soon as the dispatch call RETURNS its promise: the
230
+ // underlying request has been issued synchronously by then, so
231
+ // the window covers exactly the nested layers of this one call
232
+ // and never a concurrent request.
233
+ dispatchInFlight.delete(provider);
234
+ }
235
+ return [2 /*return*/];
236
+ });
237
+ }); };
238
+ try {
239
+ // MERGE with any existing list rather than overwriting: a wallet that
240
+ // replaced `request` forces a re-wrap, and discarding the prior list
241
+ // would drop other live instances from ownership - the newest-live
242
+ // fallback then has nobody to fall back to.
243
+ var slot = provider;
244
+ var prior = slot[types_1.WRAPPED_REQUEST_OWNER_SYMBOL];
245
+ var owners = Array.isArray(prior) ? prior : [];
246
+ var idx = owners.indexOf(this);
247
+ if (idx !== -1)
248
+ owners.splice(idx, 1);
249
+ owners.push(this);
250
+ if (!Array.isArray(prior)) {
251
+ slot[types_1.WRAPPED_REQUEST_OWNER_SYMBOL] = owners;
252
+ }
253
+ this.wrappedProviders.add(provider);
254
+ }
255
+ catch (_a) {
256
+ /* frozen provider: the request write below fails too and aborts */
257
+ }
258
+ return this.installWrappedRequest(provider, wrappedRequest);
259
+ };
260
+ /** Install the wrapper function onto the provider; separated so the
261
+ * routing shim above stays small. */
262
+ EvmRequestTracker.prototype.installWrappedRequest = function (provider, wrappedRequest) {
263
+ // Mark the wrapper so we can detect if request is replaced externally and keep a reference on provider
264
+ wrappedRequest[types_1.WRAPPED_REQUEST_SYMBOL] = true;
265
+ // Both writes go inside the try. A frozen or non-extensible provider
266
+ // throws on the symbol assignment just as readily as on `request`, and
267
+ // that one used to sit outside the guard, so an unwrappable provider
268
+ // aborted registration instead of being skipped.
269
+ try {
270
+ provider[types_1.WRAPPED_REQUEST_REF_SYMBOL] =
271
+ wrappedRequest;
272
+ provider.request = wrappedRequest;
273
+ // Read back: an accessor or Proxy can ACCEPT the assignment without
274
+ // installing it, and success here is a promise that capture works.
275
+ if (provider.request !== wrappedRequest) {
276
+ logger_1.logger.warn("request assignment swallowed; not wrapped");
277
+ return false;
278
+ }
279
+ return true;
280
+ }
281
+ catch (e) {
282
+ logger_1.logger.warn("Failed to wrap provider.request; skipping", e);
283
+ return false;
284
+ }
285
+ };
286
+ /**
287
+ * The wrapper body proper: everything a request observation does, run
288
+ * against THIS instance's registry, wallet state, and event queue. Kept
289
+ * as a method so a surviving wrapper installed by a previous SDK
290
+ * instance can hand calls to the current one.
291
+ */
292
+ EvmRequestTracker.prototype.dispatchWrappedRequest = function (_a, provider_2, request_1) {
293
+ return __awaiter(this, arguments, void 0, function (_b, provider, request) {
156
294
  var generation_1, responsePromise, capturedChainId_1, response_1, error_1, rpcError, txPromise, txChainId_1, transactionHash_1, error_2, rpcError;
157
295
  var _this = this;
296
+ var _c, _d, _e, _f;
158
297
  var method = _b.method, params = _b.params;
159
- return __generator(this, function (_c) {
160
- switch (_c.label) {
298
+ return __generator(this, function (_g) {
299
+ switch (_g.label) {
161
300
  case 0:
162
301
  // Learn the chain from a call the APP was making anyway.
163
302
  //
@@ -186,6 +325,10 @@ var EvmRequestTracker = /** @class */ (function () {
186
325
  logger_1.logger.debug("Signature event skipped (autocapture.signature: false)", { method: method });
187
326
  return [2 /*return*/, request({ method: method, params: params })];
188
327
  }
328
+ if ((_d = (_c = this.deps).shouldSkipRequestCapture) === null || _d === void 0 ? void 0 : _d.call(_c, method, params)) {
329
+ // A pending wagmi mutation owns this capture.
330
+ return [2 /*return*/, request({ method: method, params: params })];
331
+ }
189
332
  responsePromise = request({ method: method, params: params });
190
333
  // Attach a no-op handler now so a rejection arriving before the await
191
334
  // below is never reported as unhandled. The real handling is there.
@@ -198,7 +341,7 @@ var EvmRequestTracker = /** @class */ (function () {
198
341
  switch (_a.label) {
199
342
  case 0:
200
343
  _a.trys.push([0, 2, , 3]);
201
- return [4 /*yield*/, this.deps.signature(__assign({ status: types_1.SignatureStatus.REQUESTED }, this.buildSignatureEventPayload(method, params, undefined, capturedChainId_1, provider)))];
344
+ return [4 /*yield*/, this.deps.signature(__assign({ status: types_1.SignatureStatus.REQUESTED }, this.buildSignatureEventPayload(method, params, undefined, capturedChainId_1, provider)), this.attributionFor(provider))];
202
345
  case 1:
203
346
  _a.sent();
204
347
  return [3 /*break*/, 3];
@@ -210,12 +353,12 @@ var EvmRequestTracker = /** @class */ (function () {
210
353
  }
211
354
  });
212
355
  }); })();
213
- _c.label = 1;
356
+ _g.label = 1;
214
357
  case 1:
215
- _c.trys.push([1, 3, , 4]);
358
+ _g.trys.push([1, 3, , 4]);
216
359
  return [4 /*yield*/, responsePromise];
217
360
  case 2:
218
- response_1 = _c.sent();
361
+ response_1 = _g.sent();
219
362
  // Track signature confirmation only for truthy responses
220
363
  if (response_1) {
221
364
  (function () { return __awaiter(_this, void 0, void 0, function () {
@@ -224,7 +367,7 @@ var EvmRequestTracker = /** @class */ (function () {
224
367
  switch (_a.label) {
225
368
  case 0:
226
369
  _a.trys.push([0, 2, , 3]);
227
- return [4 /*yield*/, this.deps.signature(__assign({ status: types_1.SignatureStatus.CONFIRMED }, this.buildSignatureEventPayload(method, params, response_1, capturedChainId_1, provider)))];
370
+ return [4 /*yield*/, this.deps.signature(__assign({ status: types_1.SignatureStatus.CONFIRMED }, this.buildSignatureEventPayload(method, params, response_1, capturedChainId_1, provider)), this.attributionFor(provider))];
228
371
  case 1:
229
372
  _a.sent();
230
373
  return [3 /*break*/, 3];
@@ -239,9 +382,9 @@ var EvmRequestTracker = /** @class */ (function () {
239
382
  }
240
383
  return [2 /*return*/, response_1];
241
384
  case 3:
242
- error_1 = _c.sent();
385
+ error_1 = _g.sent();
243
386
  rpcError = error_1;
244
- if ((rpcError === null || rpcError === void 0 ? void 0 : rpcError.code) === 4001) {
387
+ if ((0, provider_1.isUserRejectionError)(rpcError)) {
245
388
  // Use the already cast rpcError to avoid duplication
246
389
  (function () { return __awaiter(_this, void 0, void 0, function () {
247
390
  var e_3;
@@ -249,7 +392,7 @@ var EvmRequestTracker = /** @class */ (function () {
249
392
  switch (_a.label) {
250
393
  case 0:
251
394
  _a.trys.push([0, 2, , 3]);
252
- return [4 /*yield*/, this.deps.signature(__assign({ status: types_1.SignatureStatus.REJECTED }, this.buildSignatureEventPayload(method, params, undefined, capturedChainId_1, provider)))];
395
+ return [4 /*yield*/, this.deps.signature(__assign({ status: types_1.SignatureStatus.REJECTED }, this.buildSignatureEventPayload(method, params, undefined, capturedChainId_1, provider)), this.attributionFor(provider))];
253
396
  case 1:
254
397
  _a.sent();
255
398
  return [3 /*break*/, 3];
@@ -281,6 +424,10 @@ var EvmRequestTracker = /** @class */ (function () {
281
424
  logger_1.logger.debug("Transaction event skipped (autocapture.transaction: false)", { method: method });
282
425
  return [2 /*return*/, request({ method: method, params: params })];
283
426
  }
427
+ if ((_f = (_e = this.deps).shouldSkipRequestCapture) === null || _f === void 0 ? void 0 : _f.call(_e, method, params)) {
428
+ // A pending wagmi mutation owns this capture.
429
+ return [2 /*return*/, request({ method: method, params: params })];
430
+ }
284
431
  txPromise = request({ method: method, params: params });
285
432
  txPromise.catch(function () { return undefined; });
286
433
  txChainId_1 = this.registry.resolveChainId(provider);
@@ -293,7 +440,7 @@ var EvmRequestTracker = /** @class */ (function () {
293
440
  return [4 /*yield*/, this.buildTransactionEventPayload(params, provider, txChainId_1)];
294
441
  case 1:
295
442
  payload = _a.sent();
296
- return [4 /*yield*/, this.deps.transaction(__assign({ status: types_1.TransactionStatus.STARTED }, payload))];
443
+ return [4 /*yield*/, this.deps.transaction(__assign({ status: types_1.TransactionStatus.STARTED }, payload), this.attributionFor(provider))];
297
444
  case 2:
298
445
  _a.sent();
299
446
  return [3 /*break*/, 4];
@@ -305,12 +452,12 @@ var EvmRequestTracker = /** @class */ (function () {
305
452
  }
306
453
  });
307
454
  }); })();
308
- _c.label = 5;
455
+ _g.label = 5;
309
456
  case 5:
310
- _c.trys.push([5, 7, , 8]);
457
+ _g.trys.push([5, 7, , 8]);
311
458
  return [4 /*yield*/, txPromise];
312
459
  case 6:
313
- transactionHash_1 = _c.sent();
460
+ transactionHash_1 = _g.sent();
314
461
  (function () { return __awaiter(_this, void 0, void 0, function () {
315
462
  var payload, e_5;
316
463
  return __generator(this, function (_a) {
@@ -320,7 +467,7 @@ var EvmRequestTracker = /** @class */ (function () {
320
467
  return [4 /*yield*/, this.buildTransactionEventPayload(params, provider, txChainId_1)];
321
468
  case 1:
322
469
  payload = _a.sent();
323
- return [4 /*yield*/, this.deps.transaction(__assign(__assign({ status: types_1.TransactionStatus.BROADCASTED }, payload), { transactionHash: transactionHash_1 }))];
470
+ return [4 /*yield*/, this.deps.transaction(__assign(__assign({ status: types_1.TransactionStatus.BROADCASTED }, payload), { transactionHash: transactionHash_1 }), this.attributionFor(provider))];
324
471
  case 2:
325
472
  _a.sent();
326
473
  // Start async polling for transaction receipt
@@ -336,9 +483,9 @@ var EvmRequestTracker = /** @class */ (function () {
336
483
  }); })();
337
484
  return [2 /*return*/, transactionHash_1];
338
485
  case 7:
339
- error_2 = _c.sent();
486
+ error_2 = _g.sent();
340
487
  rpcError = error_2;
341
- if ((rpcError === null || rpcError === void 0 ? void 0 : rpcError.code) === 4001) {
488
+ if ((0, provider_1.isUserRejectionError)(rpcError)) {
342
489
  // Use the already cast rpcError to avoid duplication
343
490
  (function () { return __awaiter(_this, void 0, void 0, function () {
344
491
  var payload, e_6;
@@ -349,7 +496,7 @@ var EvmRequestTracker = /** @class */ (function () {
349
496
  return [4 /*yield*/, this.buildTransactionEventPayload(params, provider, txChainId_1)];
350
497
  case 1:
351
498
  payload = _a.sent();
352
- return [4 /*yield*/, this.deps.transaction(__assign({ status: types_1.TransactionStatus.REJECTED }, payload))];
499
+ return [4 /*yield*/, this.deps.transaction(__assign({ status: types_1.TransactionStatus.REJECTED }, payload), this.attributionFor(provider))];
353
500
  case 2:
354
501
  _a.sent();
355
502
  return [3 /*break*/, 4];
@@ -366,23 +513,19 @@ var EvmRequestTracker = /** @class */ (function () {
366
513
  case 8: return [2 /*return*/, request({ method: method, params: params })];
367
514
  }
368
515
  });
369
- }); };
370
- // Mark the wrapper so we can detect if request is replaced externally and keep a reference on provider
371
- wrappedRequest[types_1.WRAPPED_REQUEST_SYMBOL] = true;
372
- // Both writes go inside the try. A frozen or non-extensible provider
373
- // throws on the symbol assignment just as readily as on `request`, and
374
- // that one used to sit outside the guard, so an unwrappable provider
375
- // aborted registration instead of being skipped.
376
- try {
377
- provider[types_1.WRAPPED_REQUEST_REF_SYMBOL] =
378
- wrappedRequest;
379
- provider.request = wrappedRequest;
380
- return true;
381
- }
382
- catch (e) {
383
- logger_1.logger.warn("Failed to wrap provider.request; skipping", e);
384
- return false;
385
- }
516
+ });
517
+ };
518
+ /**
519
+ * Wallet attribution for request-derived events.
520
+ *
521
+ * Live per read through the registry, so a WalletConnect session names
522
+ * its actual signer ("MetaMask Wallet", "Ledger Live") - the live-test
523
+ * rows had provider_name EMPTY on every signature and transaction, which
524
+ * made per-wallet activity unanswerable in the warehouse.
525
+ */
526
+ EvmRequestTracker.prototype.attributionFor = function (provider) {
527
+ var info = this.registry.infoFor(provider);
528
+ return { providerName: info.name, rdns: info.rdns };
386
529
  };
387
530
  EvmRequestTracker.prototype.buildSignatureEventPayload = function (method, params,
388
531
  // Intentionally not read. Kept for positional call-site arity.
@@ -448,7 +591,7 @@ var EvmRequestTracker = /** @class */ (function () {
448
591
  /**
449
592
  * Polls for transaction receipt and emits tx.status = CONFIRMED or REVERTED.
450
593
  */
451
- EvmRequestTracker.prototype.pollTransactionReceipt = function (provider_1, transactionHash_2, payload_1) {
594
+ EvmRequestTracker.prototype.pollTransactionReceipt = function (provider_2, transactionHash_2, payload_1) {
452
595
  return __awaiter(this, arguments, void 0, function (provider, transactionHash, payload, maxAttempts, intervalMs) {
453
596
  var attempts, poll;
454
597
  var _this = this;
@@ -478,7 +621,7 @@ var EvmRequestTracker = /** @class */ (function () {
478
621
  // status: 1 = success, 0 = reverted
479
622
  if (receipt.status === "0x1" || receipt.status === 1) {
480
623
  this.deps
481
- .transaction(__assign(__assign({ status: types_1.TransactionStatus.CONFIRMED }, payload), { transactionHash: transactionHash }))
624
+ .transaction(__assign(__assign({ status: types_1.TransactionStatus.CONFIRMED }, payload), { transactionHash: transactionHash }), this.attributionFor(provider))
482
625
  .catch(function (e) {
483
626
  return logger_1.logger.error("Formo: Failed to track transaction confirmation", e);
484
627
  });
@@ -486,7 +629,7 @@ var EvmRequestTracker = /** @class */ (function () {
486
629
  }
487
630
  else if (receipt.status === "0x0" || receipt.status === 0) {
488
631
  this.deps
489
- .transaction(__assign(__assign({ status: types_1.TransactionStatus.REVERTED }, payload), { transactionHash: transactionHash }))
632
+ .transaction(__assign(__assign({ status: types_1.TransactionStatus.REVERTED }, payload), { transactionHash: transactionHash }), this.attributionFor(provider))
490
633
  .catch(function (e) {
491
634
  return logger_1.logger.error("Formo: Failed to track transaction revert", e);
492
635
  });
@@ -514,11 +657,14 @@ var EvmRequestTracker = /** @class */ (function () {
514
657
  /**
515
658
  * One `transaction` event per call in an EIP-5792 batch.
516
659
  *
517
- * A batch is not a transaction. It maps to several on-chain transactions,
518
- * so reporting it as one event would understate volume and make revenue and
519
- * per-contract attribution wrong for every app that adopts smart accounts.
520
- * Each call is reported on its own, carrying the batch id so the calls can
521
- * be reassembled downstream.
660
+ * The CALL is the unit of attribution: each has its own target, calldata,
661
+ * and value, and folding a batch into one event would misattribute revenue
662
+ * and per-contract activity for every app that adopts smart accounts. How
663
+ * many on-chain transactions a batch becomes depends on execution - an
664
+ * atomic batch lands as ONE transaction, a non-atomic fallback as several -
665
+ * so on-chain volume is `count(distinct transaction_hash)`, wallet actions
666
+ * `count(distinct batch_id)`, never the event count. Each call is reported
667
+ * on its own, carrying the batch id so the calls reassemble downstream.
522
668
  *
523
669
  * Status is per BATCH, because that is what `wallet_getCallsStatus` reports.
524
670
  * When it resolves, every call in the batch moves together, except where
@@ -526,10 +672,10 @@ var EvmRequestTracker = /** @class */ (function () {
526
672
  */
527
673
  EvmRequestTracker.prototype.trackBatchedCalls = function (provider, request, params) {
528
674
  return __awaiter(this, void 0, void 0, function () {
529
- 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;
530
- var _c;
531
- return __generator(this, function (_d) {
532
- switch (_d.label) {
675
+ 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;
676
+ var _c, _d, _e;
677
+ return __generator(this, function (_f) {
678
+ switch (_f.label) {
533
679
  case 0:
534
680
  if (!this.deps.isAutocaptureEnabled("transaction")) {
535
681
  logger_1.logger.debug("Transaction event skipped (autocapture.transaction: false)", {
@@ -537,13 +683,17 @@ var EvmRequestTracker = /** @class */ (function () {
537
683
  });
538
684
  return [2 /*return*/, request({ method: "wallet_sendCalls", params: params })];
539
685
  }
686
+ if ((_d = (_c = this.deps).shouldSkipRequestCapture) === null || _d === void 0 ? void 0 : _d.call(_c, "wallet_sendCalls", params)) {
687
+ // A pending wagmi sendCalls mutation owns this capture.
688
+ return [2 /*return*/, request({ method: "wallet_sendCalls", params: params })];
689
+ }
540
690
  sendPromise = request({ method: "wallet_sendCalls", params: params });
541
691
  sendPromise.catch(function () { return undefined; });
542
692
  batch = params[0];
543
693
  calls = Array.isArray(batch === null || batch === void 0 ? void 0 : batch.calls) ? batch.calls : [];
544
694
  declared = typeof (batch === null || batch === void 0 ? void 0 : batch.chainId) === "string" ? (0, chain_1.parseChainId)(batch.chainId) : undefined;
545
695
  chainId = declared || this.registry.resolveChainId(provider);
546
- address = (0, address_1.validateAndChecksumAddress)((_c = batch === null || batch === void 0 ? void 0 : batch.from) !== null && _c !== void 0 ? _c : "");
696
+ address = (0, address_1.validateAndChecksumAddress)((_e = batch === null || batch === void 0 ? void 0 : batch.from) !== null && _e !== void 0 ? _e : "");
547
697
  if (!address) {
548
698
  // Nothing can be attributed without a sender, and inventing one would
549
699
  // be worse than reporting nothing. The user's call still goes through.
@@ -562,16 +712,14 @@ var EvmRequestTracker = /** @class */ (function () {
562
712
  if (!provider || provider === this.wallet.provider || !this.wallet.provider) {
563
713
  this.wallet.backfill(address, chainId, provider);
564
714
  }
715
+ attribution = this.attributionFor(provider);
565
716
  payloads = calls.map(function (call, index) { return ({
566
717
  chainId: chainId,
567
718
  address: address,
568
719
  to: call === null || call === void 0 ? void 0 : call.to,
569
720
  value: call === null || call === void 0 ? void 0 : call.value,
570
721
  data: call === null || call === void 0 ? void 0 : call.data,
571
- properties: {
572
- batch_size: calls.length,
573
- batch_index: index,
574
- },
722
+ properties: __assign({ batch_size: calls.length, batch_index: index }, attribution),
575
723
  }); });
576
724
  // STARTED carries no batch id: the wallet has not issued one yet, exactly
577
725
  // as `eth_sendTransaction` has no hash at this point. Position within the
@@ -583,13 +731,13 @@ var EvmRequestTracker = /** @class */ (function () {
583
731
  .transaction(__assign({ status: types_1.TransactionStatus.STARTED }, rest), properties)
584
732
  .catch(function (e) { return logger_1.logger.error("Formo: Failed to track batch call start", e); });
585
733
  }
586
- _d.label = 1;
734
+ _f.label = 1;
587
735
  case 1:
588
- _d.trys.push([1, 3, , 4]);
736
+ _f.trys.push([1, 3, , 4]);
589
737
  return [4 /*yield*/, sendPromise];
590
738
  case 2:
591
- result = _d.sent();
592
- batchId = this.readBatchId(result);
739
+ result = _f.sent();
740
+ batchId = (0, batch_1.readBatchId)(result);
593
741
  for (_a = 0, payloads_2 = payloads; _a < payloads_2.length; _a++) {
594
742
  p = payloads_2[_a];
595
743
  properties = p.properties, rest = __rest(p, ["properties"]);
@@ -603,9 +751,9 @@ var EvmRequestTracker = /** @class */ (function () {
603
751
  this.pollBatchStatus(provider, batchId, payloads);
604
752
  return [2 /*return*/, result];
605
753
  case 3:
606
- error_3 = _d.sent();
754
+ error_3 = _f.sent();
607
755
  rpcError = error_3;
608
- if ((rpcError === null || rpcError === void 0 ? void 0 : rpcError.code) === 4001) {
756
+ if ((0, provider_1.isUserRejectionError)(rpcError)) {
609
757
  // One rejection dismisses the whole prompt, so every call in it is
610
758
  // rejected. Reporting only the first would undercount.
611
759
  for (_b = 0, payloads_3 = payloads; _b < payloads_3.length; _b++) {
@@ -624,54 +772,6 @@ var EvmRequestTracker = /** @class */ (function () {
624
772
  });
625
773
  });
626
774
  };
627
- /**
628
- * How one call in a settled batch ended.
629
- *
630
- * A per-call receipt is authoritative where it exists: that is what makes a
631
- * partially reverted non-atomic batch report honestly rather than tarring
632
- * every call with the batch's worst outcome. A receipt whose own status is
633
- * unreadable falls back to the batch verdict rather than being assumed good.
634
- *
635
- * The codes are EIP-5792's: 200 confirmed, 400 failed BEFORE landing on
636
- * chain, 500 reverted, 600 partially reverted. 400 is a rejection, not a
637
- * revert - nothing was mined, so calling it reverted would misreport gas
638
- * spent and on-chain activity that never happened.
639
- *
640
- * Returns undefined when the call cannot be decided, which happens on 600
641
- * for a call the wallet gave no receipt for.
642
- */
643
- EvmRequestTracker.prototype.batchCallOutcome = function (code, receipt) {
644
- var receiptStatus = receipt === null || receipt === void 0 ? void 0 : receipt.status;
645
- if (receiptStatus !== undefined) {
646
- return receiptStatus === "0x0" || receiptStatus === 0
647
- ? types_1.TransactionStatus.REVERTED
648
- : types_1.TransactionStatus.CONFIRMED;
649
- }
650
- if (code >= 600)
651
- return undefined;
652
- if (code >= 500)
653
- return types_1.TransactionStatus.REVERTED;
654
- if (code >= 400)
655
- return types_1.TransactionStatus.REJECTED;
656
- return types_1.TransactionStatus.CONFIRMED;
657
- };
658
- /**
659
- * The batch identifier from a `wallet_sendCalls` result.
660
- *
661
- * EIP-5792 settled on `{ id }`, but wallets shipped against the earlier
662
- * draft return a bare string. Both are accepted so a wallet on either
663
- * version is still grouped.
664
- */
665
- EvmRequestTracker.prototype.readBatchId = function (result) {
666
- if (typeof result === "string" && result.length > 0)
667
- return result;
668
- if (result && typeof result === "object") {
669
- var id = result.id;
670
- if (typeof id === "string" && id.length > 0)
671
- return id;
672
- }
673
- return undefined;
674
- };
675
775
  /**
676
776
  * Resolve a batch through `wallet_getCallsStatus`.
677
777
  *
@@ -683,7 +783,7 @@ var EvmRequestTracker = /** @class */ (function () {
683
783
  * what makes a partially reverted non-atomic batch report honestly instead
684
784
  * of marking every call with the batch's worst outcome.
685
785
  */
686
- EvmRequestTracker.prototype.pollBatchStatus = function (provider_1, batchId_1, payloads_4) {
786
+ EvmRequestTracker.prototype.pollBatchStatus = function (provider_2, batchId_1, payloads_4) {
687
787
  return __awaiter(this, arguments, void 0, function (provider, batchId, payloads, maxAttempts, intervalMs) {
688
788
  var attempts, poll;
689
789
  var _this = this;
@@ -694,7 +794,7 @@ var EvmRequestTracker = /** @class */ (function () {
694
794
  return [2 /*return*/];
695
795
  attempts = 0;
696
796
  poll = function () { return __awaiter(_this, void 0, void 0, function () {
697
- var res, code_1, receipts_1, e_8;
797
+ var res_1, code_1, e_8;
698
798
  var _this = this;
699
799
  return __generator(this, function (_a) {
700
800
  switch (_a.label) {
@@ -709,13 +809,14 @@ var EvmRequestTracker = /** @class */ (function () {
709
809
  params: [batchId],
710
810
  })];
711
811
  case 2:
712
- res = (_a.sent());
713
- code_1 = typeof (res === null || res === void 0 ? void 0 : res.status) === "number" ? res.status : undefined;
812
+ res_1 = (_a.sent());
813
+ code_1 = (0, batch_1.readBatchStatusCode)(res_1);
714
814
  if (code_1 !== undefined && code_1 >= 200) {
715
- receipts_1 = Array.isArray(res === null || res === void 0 ? void 0 : res.receipts) ? res.receipts : [];
716
815
  payloads.forEach(function (p, index) {
717
- var receipt = receipts_1[index];
718
- var outcome = _this.batchCallOutcome(code_1, receipt);
816
+ // Atomic-aware: one receipt covering the whole batch reaches
817
+ // every call, hash included, not just call 0.
818
+ var receipt = (0, batch_1.batchReceiptForCall)(res_1, index, payloads.length);
819
+ var outcome = (0, batch_1.batchCallOutcome)(code_1, receipt);
719
820
  // 600 means SOME calls reverted, so a call with no receipt of its
720
821
  // own has not been decided. Reporting it either way would invent
721
822
  // a result; leaving it unsettled is the honest answer.