@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
@@ -54,6 +54,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
54
54
  };
55
55
  import { SignatureStatus, TransactionStatus } from "../types/events";
56
56
  import { logger } from "../logger";
57
+ import { readWalletConnectPeer, isUserRejectionError } from "../provider";
57
58
  import { readBatchId, readBatchStatusCode, readBatchChainId, batchCallOutcome, batchReceiptForCall, } from "../evm/batch";
58
59
  import { encodeWriteContractData, concatCalldataWithSuffix, extractFunctionArgs, buildSafeFunctionArgs, } from "./utils";
59
60
  /**
@@ -197,25 +198,46 @@ var ownerKey = function (writeKey, config) {
197
198
  }
198
199
  return "".concat(writeKey, ":").concat(id);
199
200
  };
201
+ // User-rejection detection is shared with the EIP-1193 path (4001, the
202
+ // WalletConnect 5000-family, and viem's typed error): see
203
+ // provider/detection.ts for the dialects and why 4001 alone missed every
204
+ // WalletConnect rejection.
205
+ var isUserRejection = isUserRejectionError;
200
206
  /**
201
- * Was this mutation error the user dismissing the wallet prompt?
207
+ * Real wallet names behind WalletConnect connectors, resolved lazily.
202
208
  *
203
- * Matches the EIP-1193 path's rule (code 4001), but a wagmi mutation error
204
- * arrives wrapped: viem nests the RPC error under `cause`, sometimes twice.
205
- * Walk the chain rather than trusting the top level, and accept viem's
206
- * `UserRejectedRequestError` by name for wallets that map the rejection to a
207
- * typed error without preserving the numeric code.
209
+ * WalletConnect is a transport: the signing wallet (Ledger Live, MetaMask
210
+ * Mobile, Safe, ...) names itself in the session's peer metadata, reachable
211
+ * only through the connector's async `getProvider()`. Connect emission is
212
+ * deliberately synchronous (see the marker comments below), so the lookup
213
+ * can never be awaited in line - it is kicked fire-and-forget the first
214
+ * time a WalletConnect connector is seen, and every event after resolution
215
+ * carries the real wallet's name. The first connect may still say
216
+ * "WalletConnect"; that is the honest state at that instant.
217
+ *
218
+ * HONEST STATUS: with the new-connection invalidation below, no event the
219
+ * wagmi path emits TODAY observably carries the resolved name - connects
220
+ * fire before resolution and rebuilds suppress re-emission. The cache
221
+ * exists for the attribution work (wallet names on signature and
222
+ * transaction events), which fires mid-session, after resolution.
223
+ *
224
+ * Names are keyed by the CONNECTOR (stable across a page's sessions) so
225
+ * they actually serve reads; lookups are guarded per CONNECTION (wagmi
226
+ * replaces the connection object per session), so every new session
227
+ * re-resolves and OVERWRITES the name. A reconnect through the same
228
+ * connector to a different wallet can therefore mislabel at most the one
229
+ * event that fires between the new session's start and its resolution -
230
+ * one microtask for an initialised connector - and self-corrects. The
231
+ * alternative, keying names by connection, was tried and kept the name
232
+ * from ever surfacing: the only reader that fires per session runs before
233
+ * any lookup can resolve. WeakMaps, so nothing outlives its object.
208
234
  */
209
- function isUserRejection(error) {
210
- var cursor = error;
211
- for (var depth = 0; cursor && depth < 5; depth++) {
212
- if (cursor.code === 4001 || cursor.name === "UserRejectedRequestError") {
213
- return true;
214
- }
215
- cursor = cursor.cause;
216
- }
217
- return false;
218
- }
235
+ var walletConnectPeerNames = new WeakMap();
236
+ var walletConnectPeerLookups = new WeakSet();
237
+ /** The connection whose lookup may write the connector's name: always the
238
+ * newest kicked one, so a slow resolution from a PREVIOUS session cannot
239
+ * land after the current session's and overwrite it. */
240
+ var walletConnectPeerLatest = new WeakMap();
219
241
  /**
220
242
  * Pending transactions, shared per destination rather than per handler.
221
243
  *
@@ -400,6 +422,57 @@ var WagmiEventHandler = /** @class */ (function () {
400
422
  * Key format: `${queryHash}:${status}`
401
423
  */
402
424
  this.processedQueries = new Set();
425
+ /**
426
+ * Start resolving the wallet behind a WalletConnect connection.
427
+ *
428
+ * Fire-and-forget on purpose: emission paths are synchronous by design
429
+ * and must never wait (see the connect marker comment). Called at the
430
+ * START of the status/address flows rather than only at read time - the
431
+ * lookup is one microtask for an initialised connector, and the emission
432
+ * sits behind several awaits, so kicking early usually means even the
433
+ * FIRST connect names the real wallet. When the race is lost the event
434
+ * honestly says "WalletConnect" and every later event names the peer.
435
+ */
436
+ /**
437
+ * Install the request wrapper on the active connector's provider.
438
+ *
439
+ * This is what lets wagmi mode capture IMPERATIVE viem calls
440
+ * (walletClient.sendTransaction / .signMessage / .writeContract / raw
441
+ * request), which create no mutation and were silently lost. Hook-driven
442
+ * calls stay owned by the mutation handlers via the pending-mutation
443
+ * dedup. Fire-and-forget per connection; a provider that cannot be
444
+ * produced simply keeps mutation-only capture.
445
+ */
446
+ /**
447
+ * Connections THIS instance has wrapped. Deliberately per-instance, not
448
+ * module-level: a module-level guard let a rebuilt SDK instance skip
449
+ * re-wrapping, so ownership stayed with the torn-down instance and every
450
+ * capture died in its closed queue - and it also stopped a second
451
+ * write-key instance from ever registering. Re-wrapping is safe and
452
+ * cheap: the request tracker rebinds ownership of an intact wrapper.
453
+ */
454
+ /**
455
+ * Latest wrap attempt per connector. Every kick re-resolves
456
+ * getProvider() - a connector can hand out a REPLACEMENT provider after
457
+ * a reconnect, so a once-only guard would leave the new session's
458
+ * provider unwrapped forever. Re-wrapping is idempotent: the request
459
+ * tracker rebinds ownership of an intact wrapper. The epoch lets a slow
460
+ * resolution from a superseded kick (an earlier session, an earlier
461
+ * chain) be discarded instead of overwriting fresher state.
462
+ */
463
+ this.wrapEpochs = new WeakMap();
464
+ /** Consecutive automatic wrap retries per connector; see below. */
465
+ this.wrapRetryCounts = new WeakMap();
466
+ /** Pending retry timers, cancelled by cleanup(). */
467
+ this.wrapRetryTimers = new Set();
468
+ /**
469
+ * Bumped on every observed disconnect. Every wrap attempt captures it
470
+ * at kick time and its resolution stands down on a mismatch, so a
471
+ * disconnect invalidates ALL pending wraps at once - including ones
472
+ * whose getProvider() had not resolved yet, which no per-connector
473
+ * bookkeeping can reach because nothing has been recorded for them.
474
+ */
475
+ this.wrapSessionGeneration = 0;
403
476
  this.formo = formoAnalytics;
404
477
  this.wagmiConfig = wagmiConfig;
405
478
  this.queryClient = queryClient;
@@ -574,6 +647,10 @@ var WagmiEventHandler = /** @class */ (function () {
574
647
  var connectionUnsubscribe = this.wagmiConfig.subscribe(function (state) { var _a, _b; return "".concat((_a = state.current) !== null && _a !== void 0 ? _a : "", "|").concat((_b = _this.getConnectedAddress(state)) !== null && _b !== void 0 ? _b : ""); }, function (key, prevKey) {
575
648
  var _a = key.split("|"), address = _a[1];
576
649
  var _b = (prevKey !== null && prevKey !== void 0 ? prevKey : "|").split("|"), prevAddress = _b[1];
650
+ // A connector switch keeps status "connected", so the status
651
+ // subscription never re-wraps; the NEW active connection's provider
652
+ // must be instrumented from here or its imperative calls are lost.
653
+ _this.wrapActiveConnectorProvider(_this.getState());
577
654
  _this.handleActiveAddressChange(address || undefined, prevAddress || undefined);
578
655
  });
579
656
  this.unsubscribers.push(connectionUnsubscribe);
@@ -611,6 +688,17 @@ var WagmiEventHandler = /** @class */ (function () {
611
688
  */
612
689
  WagmiEventHandler.prototype.seedFromCurrentState = function () {
613
690
  var _a, _b;
691
+ // Fire-and-forget here: the seed is synchronous by design, so its own
692
+ // connect may carry the generic name; the status flow awaits instead.
693
+ // A throwing store must not break construction.
694
+ try {
695
+ var snapshot = this.getState();
696
+ this.kickWalletConnectPeerLookup(snapshot);
697
+ this.wrapActiveConnectorProvider(snapshot);
698
+ }
699
+ catch (_c) {
700
+ /* the seed continues; names fall back to the connector's own */
701
+ }
614
702
  try {
615
703
  var state = this.getState();
616
704
  if (state.status !== "connected") {
@@ -863,11 +951,24 @@ var WagmiEventHandler = /** @class */ (function () {
863
951
  */
864
952
  WagmiEventHandler.prototype.handleStatusChange = function (status, prevStatus) {
865
953
  return __awaiter(this, void 0, void 0, function () {
866
- var state, address, chainId, disconnectedAddress, disconnectedChainId, walletKey, connection, connectorName, error_1, error_2;
954
+ var snapshot, state, address, chainId, disconnectedAddress, disconnectedChainId, walletKey, connection, connectorName, error_1, error_2;
867
955
  var _a, _b, _c, _d;
868
956
  return __generator(this, function (_e) {
869
957
  switch (_e.label) {
870
958
  case 0:
959
+ if (status === "disconnected" || status === "reconnecting") {
960
+ // The wrapped session is over - or, for "reconnecting", about to be
961
+ // replaced without ever passing through "disconnected". This must
962
+ // run for EVERY such transition, before the processing lock and
963
+ // regardless of whether a wallet was being tracked, or a rapid
964
+ // same-connector reconnect retains the old provider and the chain
965
+ // callback writes the new session's chain onto it. The generation
966
+ // bump invalidates every wrap still in flight from the ended
967
+ // session, resolved or not. The "connected" that follows re-wraps.
968
+ this.wrapSessionGeneration += 1;
969
+ this.fallbackConnector = undefined;
970
+ this.fallbackProvider = undefined;
971
+ }
871
972
  // Prevent concurrent processing
872
973
  if (this.trackingState.isProcessing) {
873
974
  // Dropped, not queued - but remember that a disconnect went past.
@@ -884,6 +985,18 @@ var WagmiEventHandler = /** @class */ (function () {
884
985
  return [2 /*return*/];
885
986
  }
886
987
  this.trackingState.isProcessing = true;
988
+ // Start resolving the wallet behind a WalletConnect session. NEVER
989
+ // awaited: every path from a store signal to its emission is
990
+ // synchronous by design, and an await here reorders transitions (it
991
+ // demonstrably drops connects under rapid connect/disconnect cycles).
992
+ try {
993
+ snapshot = this.getState();
994
+ this.kickWalletConnectPeerLookup(snapshot);
995
+ this.wrapActiveConnectorProvider(snapshot);
996
+ }
997
+ catch (_f) {
998
+ // A throwing store must not break the status flow.
999
+ }
887
1000
  // A status change outranks any connection transition still in flight. A
888
1001
  // full disconnect advances no ticket of its own, so without this an older
889
1002
  // fallback continuation could resume and announce a wallet wagmi no longer
@@ -1151,6 +1264,16 @@ var WagmiEventHandler = /** @class */ (function () {
1151
1264
  * already recorded the new address synchronously by the time this runs. The
1152
1265
  * `lastAddress` check below is what makes the two paths mutually exclusive.
1153
1266
  */
1267
+ WagmiEventHandler.prototype.handleActiveAddressChangeEntryKick = function () {
1268
+ // An account switch replaces the connection object; kick a lookup for
1269
+ // the new one so events that follow can name the wallet.
1270
+ try {
1271
+ this.kickWalletConnectPeerLookup(this.getState());
1272
+ }
1273
+ catch (_a) {
1274
+ /* a throwing store must not break the flow */
1275
+ }
1276
+ };
1154
1277
  WagmiEventHandler.prototype.handleActiveAddressChange = function (address, prevAddress) {
1155
1278
  return __awaiter(this, void 0, void 0, function () {
1156
1279
  var state, trackedConnectionId, trackedConnectionGone, connectionChanged, liveChain, generation, chainId, stillConnected, goneChainId, error_3, liveNow, stillCurrent, walletKey, connection, alreadyAnnounced, connectorName, error_4;
@@ -1158,6 +1281,7 @@ var WagmiEventHandler = /** @class */ (function () {
1158
1281
  return __generator(this, function (_g) {
1159
1282
  switch (_g.label) {
1160
1283
  case 0:
1284
+ this.handleActiveAddressChangeEntryKick();
1161
1285
  state = this.getState();
1162
1286
  if (state.status !== "connected")
1163
1287
  return [2 /*return*/];
@@ -1381,10 +1505,31 @@ var WagmiEventHandler = /** @class */ (function () {
1381
1505
  */
1382
1506
  WagmiEventHandler.prototype.handleChainChange = function (chainId, prevChainId) {
1383
1507
  return __awaiter(this, void 0, void 0, function () {
1384
- var state, address, hadAddress, wasAnnounced, announcedNow;
1385
- return __generator(this, function (_a) {
1386
- switch (_a.label) {
1508
+ var live, activeConnector, state, address, hadAddress, wasAnnounced, announcedNow;
1509
+ var _a, _b, _c;
1510
+ return __generator(this, function (_d) {
1511
+ switch (_d.label) {
1387
1512
  case 0:
1513
+ // Keep the fallback-wrapped provider's registry chain in step with the
1514
+ // store, so request-derived events are labelled correctly even when the
1515
+ // provider exposes no synchronous chainId. Only when the report is
1516
+ // about the connection that provider belongs to: after a connector
1517
+ // switch the store's chain describes the NEW connection, and writing it
1518
+ // to the old provider would mislabel that wallet's events.
1519
+ if (this.fallbackProvider !== undefined) {
1520
+ live = this.getState();
1521
+ activeConnector = live.current
1522
+ ? (_a = live.connections.get(live.current)) === null || _a === void 0 ? void 0 : _a.connector
1523
+ : undefined;
1524
+ if (activeConnector === this.fallbackConnector) {
1525
+ try {
1526
+ (_c = (_b = this.formo)._rememberWagmiProviderChain) === null || _c === void 0 ? void 0 : _c.call(_b, this.fallbackProvider, chainId);
1527
+ }
1528
+ catch (_e) {
1529
+ /* never let bookkeeping break the chain flow */
1530
+ }
1531
+ }
1532
+ }
1388
1533
  if (chainId === prevChainId || chainId === undefined) {
1389
1534
  return [2 /*return*/];
1390
1535
  }
@@ -1430,7 +1575,7 @@ var WagmiEventHandler = /** @class */ (function () {
1430
1575
  });
1431
1576
  return [4 /*yield*/, this.applyChainForTrackedWallet(address, chainId)];
1432
1577
  case 1:
1433
- _a.sent();
1578
+ _d.sent();
1434
1579
  return [2 /*return*/];
1435
1580
  }
1436
1581
  });
@@ -1628,11 +1773,9 @@ var WagmiEventHandler = /** @class */ (function () {
1628
1773
  return;
1629
1774
  _this.formo.transaction(__assign(__assign(__assign(__assign({ status: outcome, chainId: settledChainId_1 || 0, address: pending.address }, (call.data && { data: call.data })), (call.to && { to: call.to })), (call.value && { value: call.value })), ((receipt === null || receipt === void 0 ? void 0 : receipt.transactionHash)
1630
1775
  ? { transactionHash: receipt.transactionHash }
1631
- : {})), {
1632
- batch_size: pending.calls.length,
1633
- batch_index: index,
1634
- batch_id: batchId,
1635
- });
1776
+ : {})), __assign({ batch_size: pending.calls.length, batch_index: index, batch_id: batchId }, (pending.providerName
1777
+ ? { providerName: pending.providerName }
1778
+ : {})));
1636
1779
  });
1637
1780
  // Settled; a later refetch of the same query must not re-emit.
1638
1781
  this.pendingBatches.delete(batchId);
@@ -1715,9 +1858,9 @@ var WagmiEventHandler = /** @class */ (function () {
1715
1858
  chainId: chainId,
1716
1859
  blockNumber: (_b = receipt === null || receipt === void 0 ? void 0 : receipt.blockNumber) === null || _b === void 0 ? void 0 : _b.toString(),
1717
1860
  });
1718
- this.formo.transaction(__assign(__assign(__assign(__assign(__assign({ status: txStatus, chainId: chainId || 0, address: address, transactionHash: transactionHash }, ((pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.data) && { data: pendingTx.data })), ((pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.to) && { to: pendingTx.to })), ((pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.value) && { value: pendingTx.value })), ((pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.function_name) && { function_name: pendingTx.function_name })), ((pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.function_args) && { function_args: pendingTx.function_args })),
1719
- // Spread function args as additional properties (only colliding keys are prefixed)
1720
- pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.safeFunctionArgs);
1861
+ this.formo.transaction(__assign(__assign(__assign(__assign(__assign({ status: txStatus, chainId: chainId || 0, address: address, transactionHash: transactionHash }, ((pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.data) && { data: pendingTx.data })), ((pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.to) && { to: pendingTx.to })), ((pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.value) && { value: pendingTx.value })), ((pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.function_name) && { function_name: pendingTx.function_name })), ((pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.function_args) && { function_args: pendingTx.function_args })), __assign(__assign({}, ((pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.providerName)
1862
+ ? { providerName: pendingTx.providerName }
1863
+ : this.mutationAttribution())), pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.safeFunctionArgs));
1721
1864
  // Clean up the pending transaction after confirmation
1722
1865
  this.pendingTransactions.delete(normalizedHash);
1723
1866
  }
@@ -1783,6 +1926,76 @@ var WagmiEventHandler = /** @class */ (function () {
1783
1926
  /**
1784
1927
  * Handle signature mutations (signMessage, signTypedData)
1785
1928
  */
1929
+ /**
1930
+ * Is a PENDING wagmi mutation already covering this wallet request?
1931
+ *
1932
+ * The hybrid-capture dedup. TanStack dispatches `pending` BEFORE the
1933
+ * mutationFn issues the wallet call (verified against query-core), so a
1934
+ * hook-driven request always finds its mutation here and the request
1935
+ * wrapper stands down; an imperative viem call never does, and the
1936
+ * wrapper captures it. Matching is by mutation type, refined with cheap
1937
+ * parameter checks where the shapes allow, and errs toward NOT skipping:
1938
+ * a duplicate is visible and diagnosable, a silent loss is neither.
1939
+ */
1940
+ WagmiEventHandler.prototype.hasMatchingPendingMutation = function (method, params) {
1941
+ var _a, _b, _c, _d, _e, _f;
1942
+ try {
1943
+ var cache = (_a = this.queryClient) === null || _a === void 0 ? void 0 : _a.getMutationCache();
1944
+ var mutations = (_b = cache === null || cache === void 0 ? void 0 : cache.getAll) === null || _b === void 0 ? void 0 : _b.call(cache);
1945
+ if (!Array.isArray(mutations))
1946
+ return false;
1947
+ var wanted = {
1948
+ personal_sign: ["signMessage"],
1949
+ eth_signTypedData_v4: ["signTypedData"],
1950
+ eth_sendTransaction: ["sendTransaction", "writeContract"],
1951
+ wallet_sendCalls: ["sendCalls"],
1952
+ };
1953
+ var types = wanted[method];
1954
+ if (!types)
1955
+ return false;
1956
+ for (var _i = 0, mutations_1 = mutations; _i < mutations_1.length; _i++) {
1957
+ var mutation = mutations_1[_i];
1958
+ if (((_c = mutation === null || mutation === void 0 ? void 0 : mutation.state) === null || _c === void 0 ? void 0 : _c.status) !== "pending")
1959
+ continue;
1960
+ var key = (_e = (_d = mutation === null || mutation === void 0 ? void 0 : mutation.options) === null || _d === void 0 ? void 0 : _d.mutationKey) === null || _e === void 0 ? void 0 : _e[0];
1961
+ if (typeof key !== "string" || !types.includes(key))
1962
+ continue;
1963
+ // Cheap refinements. On mismatch keep scanning; on no basis to
1964
+ // compare, treat the type-level match as decisive.
1965
+ var variables = (_f = mutation.state) === null || _f === void 0 ? void 0 : _f.variables;
1966
+ if (key === "sendTransaction" && variables) {
1967
+ var req = (Array.isArray(params) ? params[0] : undefined);
1968
+ var mutTo = variables.to;
1969
+ if (typeof (req === null || req === void 0 ? void 0 : req.to) === "string" &&
1970
+ typeof mutTo === "string" &&
1971
+ req.to.toLowerCase() !== mutTo.toLowerCase()) {
1972
+ continue;
1973
+ }
1974
+ }
1975
+ return true;
1976
+ }
1977
+ return false;
1978
+ }
1979
+ catch (_g) {
1980
+ return false;
1981
+ }
1982
+ };
1983
+ /**
1984
+ * Wallet attribution for mutation- and query-derived events.
1985
+ *
1986
+ * Mid-session events (signatures, transactions) fire after the peer
1987
+ * lookup has resolved, so a WalletConnect connector names its actual
1988
+ * signer here - the first observable consumer of the peer cache.
1989
+ */
1990
+ WagmiEventHandler.prototype.mutationAttribution = function () {
1991
+ try {
1992
+ var name_1 = this.getConnectorName(this.getState());
1993
+ return name_1 ? { providerName: name_1 } : undefined;
1994
+ }
1995
+ catch (_a) {
1996
+ return undefined;
1997
+ }
1998
+ };
1786
1999
  WagmiEventHandler.prototype.handleSignatureMutation = function (mutationType, mutation) {
1787
2000
  var _a, _b, _c, _d;
1788
2001
  if (!this.formo.isAutocaptureEnabled("signature")) {
@@ -1843,7 +2056,7 @@ var WagmiEventHandler = /** @class */ (function () {
1843
2056
  chainId: chainId,
1844
2057
  address: address,
1845
2058
  message: message,
1846
- });
2059
+ }, this.mutationAttribution());
1847
2060
  }
1848
2061
  catch (error) {
1849
2062
  logger.error("WagmiEventHandler: Error handling signature mutation:", error);
@@ -1853,7 +2066,7 @@ var WagmiEventHandler = /** @class */ (function () {
1853
2066
  * Handle transaction mutations (sendTransaction, writeContract)
1854
2067
  */
1855
2068
  WagmiEventHandler.prototype.handleTransactionMutation = function (mutationType, mutation) {
1856
- var _a;
2069
+ var _a, _b;
1857
2070
  if (!this.formo.isAutocaptureEnabled("transaction")) {
1858
2071
  return;
1859
2072
  }
@@ -1944,7 +2157,7 @@ var WagmiEventHandler = /** @class */ (function () {
1944
2157
  // Include the sender address to handle wallet switches between broadcast and confirmation
1945
2158
  if (status_2 === TransactionStatus.BROADCASTED && transactionHash) {
1946
2159
  var normalizedHash = transactionHash.toLowerCase();
1947
- var txDetails = __assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign({ address: userAddress }, (chainId !== undefined && { chainId: chainId })), { chainIdWasExplicit: explicitChainId !== undefined }), (data && { data: data })), (to && { to: to })), (value && { value: value })), (function_name && { function_name: function_name })), (function_args && { function_args: function_args })), (safeFunctionArgs && { safeFunctionArgs: safeFunctionArgs }));
2160
+ var txDetails = __assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign({ address: userAddress }, ((_b = this.mutationAttribution()) !== null && _b !== void 0 ? _b : {})), (chainId !== undefined && { chainId: chainId })), { chainIdWasExplicit: explicitChainId !== undefined }), (data && { data: data })), (to && { to: to })), (value && { value: value })), (function_name && { function_name: function_name })), (function_args && { function_args: function_args })), (safeFunctionArgs && { safeFunctionArgs: safeFunctionArgs }));
1948
2161
  this.pendingTransactions.set(normalizedHash, txDetails);
1949
2162
  logger.debug("WagmiEventHandler: Stored pending transaction for confirmation", {
1950
2163
  transactionHash: normalizedHash,
@@ -1958,9 +2171,7 @@ var WagmiEventHandler = /** @class */ (function () {
1958
2171
  }
1959
2172
  }
1960
2173
  }
1961
- this.formo.transaction(__assign(__assign(__assign(__assign(__assign(__assign({ status: status_2, chainId: chainId || 0, address: userAddress }, (data && { data: data })), (to && { to: to })), (value && { value: value })), (transactionHash && { transactionHash: transactionHash })), (function_name && { function_name: function_name })), (function_args && { function_args: function_args })),
1962
- // Spread function args as additional properties (only colliding keys are prefixed)
1963
- safeFunctionArgs);
2174
+ this.formo.transaction(__assign(__assign(__assign(__assign(__assign(__assign({ status: status_2, chainId: chainId || 0, address: userAddress }, (data && { data: data })), (to && { to: to })), (value && { value: value })), (transactionHash && { transactionHash: transactionHash })), (function_name && { function_name: function_name })), (function_args && { function_args: function_args })), __assign(__assign({}, this.mutationAttribution()), safeFunctionArgs));
1964
2175
  }
1965
2176
  catch (error) {
1966
2177
  logger.error("WagmiEventHandler: Error handling transaction mutation:", error);
@@ -2009,9 +2220,10 @@ var WagmiEventHandler = /** @class */ (function () {
2009
2220
  value: (call === null || call === void 0 ? void 0 : call.value) !== undefined ? String(call.value) : undefined,
2010
2221
  data: call === null || call === void 0 ? void 0 : call.data,
2011
2222
  }); });
2223
+ var attribution_1 = this.mutationAttribution();
2012
2224
  var emitAll = function (status, extra) {
2013
2225
  calls_1.forEach(function (call, index) {
2014
- _this.formo.transaction(__assign(__assign(__assign({ status: status, chainId: chainId || 0, address: userAddress }, (call.data && { data: call.data })), (call.to && { to: call.to })), (call.value && { value: call.value })), __assign({ batch_size: calls_1.length, batch_index: index }, extra));
2226
+ _this.formo.transaction(__assign(__assign(__assign({ status: status, chainId: chainId || 0, address: userAddress }, (call.data && { data: call.data })), (call.to && { to: call.to })), (call.value && { value: call.value })), __assign(__assign({ batch_size: calls_1.length, batch_index: index }, attribution_1), extra));
2015
2227
  });
2016
2228
  };
2017
2229
  if (state.status === "pending") {
@@ -2025,7 +2237,7 @@ var WagmiEventHandler = /** @class */ (function () {
2025
2237
  logger.debug("WagmiEventHandler: sendCalls broadcast", { batchId: batchId });
2026
2238
  emitAll(TransactionStatus.BROADCASTED, batchId ? { batch_id: batchId } : undefined);
2027
2239
  if (batchId) {
2028
- this.pendingBatches.set(batchId, __assign(__assign({ address: userAddress }, (chainId !== undefined && { chainId: chainId })), { chainIdWasExplicit: explicitChainId !== undefined, calls: calls_1 }));
2240
+ this.pendingBatches.set(batchId, __assign(__assign(__assign({ address: userAddress }, (attribution_1 !== null && attribution_1 !== void 0 ? attribution_1 : {})), (chainId !== undefined && { chainId: chainId })), { chainIdWasExplicit: explicitChainId !== undefined, calls: calls_1 }));
2029
2241
  // Same bound as pendingTransactions, same reason.
2030
2242
  if (this.pendingBatches.size > 100) {
2031
2243
  var keys = Array.from(this.pendingBatches.keys());
@@ -2129,12 +2341,240 @@ var WagmiEventHandler = /** @class */ (function () {
2129
2341
  return undefined;
2130
2342
  }
2131
2343
  var connection = state.connections.get(state.current);
2132
- return connection === null || connection === void 0 ? void 0 : connection.connector.name;
2344
+ var connector = connection === null || connection === void 0 ? void 0 : connection.connector;
2345
+ if (!connection || !connector) {
2346
+ return undefined;
2347
+ }
2348
+ var cached = walletConnectPeerNames.get(connector);
2349
+ if (cached) {
2350
+ return cached;
2351
+ }
2352
+ // Backstop kick; the flow entry points kick earlier so the lookup has
2353
+ // usually resolved by the time an emission reads the name.
2354
+ this.kickWalletConnectPeerLookup(state);
2355
+ return connector.name;
2356
+ };
2357
+ WagmiEventHandler.prototype.wrapActiveConnectorProvider = function (state, isRetry) {
2358
+ var _this = this;
2359
+ var _a, _b, _c;
2360
+ if (isRetry === void 0) { isRetry = false; }
2361
+ // OPT-IN only. Wagmi mode's baseline never touches the signing
2362
+ // transport; instrumenting the provider is an explicit integrator
2363
+ // decision (options.wagmi.eip1193Fallback), made auditable in
2364
+ // configuration rather than implied by a version bump.
2365
+ var optedIn = ((_b = (_a = this.formo.options) === null || _a === void 0 ? void 0 : _a.wagmi) === null || _b === void 0 ? void 0 : _b.eip1193Fallback) === true;
2366
+ if (!optedIn) {
2367
+ return;
2368
+ }
2369
+ if (state.status !== "connected") {
2370
+ // Status kicks run for every transition; only a connected snapshot
2371
+ // describes a session worth wrapping.
2372
+ return;
2373
+ }
2374
+ var connection = state.current
2375
+ ? state.connections.get(state.current)
2376
+ : undefined;
2377
+ var connector = connection === null || connection === void 0 ? void 0 : connection.connector;
2378
+ if (!connection || typeof (connector === null || connector === void 0 ? void 0 : connector.getProvider) !== "function") {
2379
+ return;
2380
+ }
2381
+ var epoch = ((_c = this.wrapEpochs.get(connector)) !== null && _c !== void 0 ? _c : 0) + 1;
2382
+ this.wrapEpochs.set(connector, epoch);
2383
+ var session = this.wrapSessionGeneration;
2384
+ // A store-driven kick resets the retry budget; automatic retries
2385
+ // (below) spend it.
2386
+ if (!isRetry) {
2387
+ this.wrapRetryCounts.delete(connector);
2388
+ }
2389
+ // When the NEWEST attempt fails, older in-flight resolutions have
2390
+ // been epoch-discarded (they may describe an earlier session) and
2391
+ // nothing else would wrap the provider until the next store update.
2392
+ // Recover by re-kicking fresh with live state, bounded so a
2393
+ // persistently failing connector cannot loop: after the budget is
2394
+ // spent, recovery waits for a real store update, which resets it.
2395
+ var retryAfterFailure = function () {
2396
+ var _a;
2397
+ if (_this.disposed)
2398
+ return;
2399
+ if (_this.wrapEpochs.get(connector) !== epoch) {
2400
+ // Superseded: the newer attempt owns recovery.
2401
+ return;
2402
+ }
2403
+ var failures = ((_a = _this.wrapRetryCounts.get(connector)) !== null && _a !== void 0 ? _a : 0) + 1;
2404
+ _this.wrapRetryCounts.set(connector, failures);
2405
+ if (failures > 3)
2406
+ return;
2407
+ var timer = setTimeout(function () {
2408
+ var _a;
2409
+ _this.wrapRetryTimers.delete(timer);
2410
+ if (_this.disposed)
2411
+ return;
2412
+ // Re-kick only while THIS connector is still the active one and
2413
+ // no newer attempt superseded this one. A retry for a connector
2414
+ // the user has left must not fire at the current connector: with
2415
+ // the retry flag it would skip the budget reset and, worse, its
2416
+ // fresh epoch would discard the current connector's own in-flight
2417
+ // resolution.
2418
+ var live = _this.getState();
2419
+ var stillCurrent = live.current !== undefined &&
2420
+ ((_a = live.connections.get(live.current)) === null || _a === void 0 ? void 0 : _a.connector) === connector;
2421
+ if (stillCurrent && _this.wrapEpochs.get(connector) === epoch) {
2422
+ _this.wrapActiveConnectorProvider(live, true);
2423
+ }
2424
+ }, 25 * failures);
2425
+ _this.wrapRetryTimers.add(timer);
2426
+ };
2427
+ var resolution;
2428
+ try {
2429
+ resolution = connector.getProvider();
2430
+ }
2431
+ catch (_d) {
2432
+ // A synchronous throw must not escape into the subscription
2433
+ // callback: address-change handling runs after this kick and a
2434
+ // propagated throw would silently skip it.
2435
+ retryAfterFailure();
2436
+ return;
2437
+ }
2438
+ resolution
2439
+ .then(function (provider) {
2440
+ var _a, _b, _c, _d;
2441
+ if (_this.wrapEpochs.get(connector) !== epoch) {
2442
+ // A newer kick is in flight or already resolved; this answer may
2443
+ // describe an earlier session. Let the newest one win.
2444
+ return;
2445
+ }
2446
+ // The fallback installs only the request wrapper, so a provider
2447
+ // without a synchronous chainId property would never teach the
2448
+ // registry its chain and its events would carry chain 0. The wagmi
2449
+ // store knows; feed it here and on every later chain change. Read
2450
+ // the chain from LIVE state, not from a snapshot taken before this
2451
+ // asynchronous resolution: a switch that lands while getProvider is
2452
+ // in flight fires handleChainChange before fallbackProvider exists,
2453
+ // so a pre-resolution snapshot would stick as the recorded chain.
2454
+ // Activity is judged by CONNECTOR identity: wagmi replaces the
2455
+ // connection record itself on every account or chain update.
2456
+ var live = _this.getState();
2457
+ // A wrap only describes a CONNECTED session. Status changes kick
2458
+ // this path for every transition, and a resolution landing while
2459
+ // disconnected would re-point the fallback pair at a provider
2460
+ // whose session is over.
2461
+ var stillActive = !_this.disposed &&
2462
+ _this.wrapSessionGeneration === session &&
2463
+ live.status === "connected" &&
2464
+ live.current !== undefined &&
2465
+ ((_a = live.connections.get(live.current)) === null || _a === void 0 ? void 0 : _a.connector) === connector;
2466
+ if (!stillActive) {
2467
+ // The user moved on while getProvider was in flight. Wrapping
2468
+ // now would instrument a provider whose chain nobody feeds -
2469
+ // its requests would report chain 0. Switching back re-kicks.
2470
+ return;
2471
+ }
2472
+ var chainId = (_b = _this.getActiveConnectionChainId(live)) !== null && _b !== void 0 ? _b : live.chainId;
2473
+ var wrapped = (_d = (_c = _this.formo)._wrapWagmiProvider) === null || _d === void 0 ? void 0 : _d.call(_c, provider, typeof chainId === "number" ? chainId : undefined);
2474
+ if (wrapped !== true) {
2475
+ // The provider was refused (invalid shape, frozen provider,
2476
+ // torn-down SDK).
2477
+ retryAfterFailure();
2478
+ return;
2479
+ }
2480
+ _this.wrapRetryCounts.delete(connector);
2481
+ _this.fallbackConnector = connector;
2482
+ _this.fallbackProvider = provider;
2483
+ })
2484
+ .catch(function () {
2485
+ // Mutation-only capture remains.
2486
+ retryAfterFailure();
2487
+ });
2488
+ };
2489
+ WagmiEventHandler.prototype.kickWalletConnectPeerLookup = function (state) {
2490
+ var _a, _b;
2491
+ var connection = state.current
2492
+ ? state.connections.get(state.current)
2493
+ : undefined;
2494
+ var connector = connection === null || connection === void 0 ? void 0 : connection.connector;
2495
+ if (!connection ||
2496
+ !connector ||
2497
+ typeof connector.name !== "string" ||
2498
+ !/walletconnect/i.test(connector.name) ||
2499
+ typeof connector.getProvider !== "function" ||
2500
+ walletConnectPeerLookups.has(connection)) {
2501
+ return;
2502
+ }
2503
+ walletConnectPeerLookups.add(connection);
2504
+ // A NEW connection invalidates the cached name SYNCHRONOUSLY. The
2505
+ // connect flow reads the cache in the same tick it kicks the lookup,
2506
+ // so retaining the previous session's name here deterministically
2507
+ // attributed a reconnect-to-a-different-wallet to the OLD wallet.
2508
+ // Wrong is worse than generic: the new session's connect now says
2509
+ // "WalletConnect" and the resolved peer serves the session's LATER
2510
+ // events (signatures, transactions - the attribution work) instead.
2511
+ // A rebuild over the SAME connection does not re-kick (guard above),
2512
+ // so it keeps its already-proven name.
2513
+ if (walletConnectPeerLatest.get(connector) !== connection) {
2514
+ walletConnectPeerNames.delete(connector);
2515
+ }
2516
+ walletConnectPeerLatest.set(connector, connection);
2517
+ var settled = false;
2518
+ // A cached name from a PREVIOUS session is unproven for this one. It
2519
+ // keeps serving only until this session's lookup settles or the grace
2520
+ // timer fires - whichever ends the uncertainty first - so a hung
2521
+ // lookup cannot leave the old wallet's name attached indefinitely.
2522
+ var staleTimer = setTimeout(function () {
2523
+ if (!settled &&
2524
+ walletConnectPeerLatest.get(connector) === connection) {
2525
+ walletConnectPeerNames.delete(connector);
2526
+ }
2527
+ }, 3000);
2528
+ (_b = (_a = staleTimer).unref) === null || _b === void 0 ? void 0 : _b.call(_a);
2529
+ connector
2530
+ .getProvider()
2531
+ .then(function (provider) {
2532
+ settled = true;
2533
+ clearTimeout(staleTimer);
2534
+ // Only the NEWEST session's lookup may write. A previous session's
2535
+ // slow resolution landing late would otherwise overwrite the
2536
+ // current wallet's name with the old one.
2537
+ if (walletConnectPeerLatest.get(connector) !== connection) {
2538
+ return;
2539
+ }
2540
+ var peer = readWalletConnectPeer(provider);
2541
+ if (peer === null || peer === void 0 ? void 0 : peer.name) {
2542
+ walletConnectPeerNames.set(connector, peer.name);
2543
+ logger.debug("WagmiEventHandler: WalletConnect peer resolved", {
2544
+ peer: peer.name,
2545
+ });
2546
+ }
2547
+ else {
2548
+ // Resolved WITHOUT peer metadata: the previous wallet's name is
2549
+ // disproven for this session, not merely unproven. Drop it, and
2550
+ // let a later event retry the lookup - the session may simply
2551
+ // not have populated its peer yet.
2552
+ walletConnectPeerNames.delete(connector);
2553
+ walletConnectPeerLookups.delete(connection);
2554
+ }
2555
+ })
2556
+ .catch(function () {
2557
+ settled = true;
2558
+ clearTimeout(staleTimer);
2559
+ // The new session could not be inspected, so the PREVIOUS wallet's
2560
+ // name must not keep serving: drop it and fall back to the
2561
+ // connector's own name until a later session resolves. Guarded so
2562
+ // an old session's late failure cannot clear a newer resolution.
2563
+ if (walletConnectPeerLatest.get(connector) === connection) {
2564
+ walletConnectPeerNames.delete(connector);
2565
+ }
2566
+ // A failed lookup must not permanently disqualify the connection:
2567
+ // the connector may just have been initialising. A later event
2568
+ // retries.
2569
+ walletConnectPeerLookups.delete(connection);
2570
+ });
2133
2571
  };
2134
2572
  /**
2135
2573
  * Clean up all subscriptions
2136
2574
  */
2137
2575
  WagmiEventHandler.prototype.cleanup = function () {
2576
+ this.wrapRetryTimers.forEach(function (t) { return clearTimeout(t); });
2577
+ this.wrapRetryTimers.clear();
2138
2578
  logger.debug("WagmiEventHandler: Cleaning up subscriptions");
2139
2579
  if (!this.disposed) {
2140
2580
  this.disposed = true;