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