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