@formo/analytics 1.38.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.
@@ -402,9 +402,9 @@ export declare class FormoAnalytics implements IFormoAnalytics {
402
402
  *
403
403
  * With several live SDK instances (multi write-key pages) registering
404
404
  * the SAME provider, request-derived events (signatures, transactions)
405
- * go to the most recently registered live instance - the same
406
- * single-observer semantics discovery has always had for the request
407
- * wrapper. Lifecycle events (connect, chain, disconnect) reach every
405
+ * go to the most recently CREATED live instance, regardless of the
406
+ * order registrations happen to land in - the same single-observer
407
+ * semantics discovery has always had for the request wrapper. Lifecycle events (connect, chain, disconnect) reach every
408
408
  * instance. Fanning request observations out to all instances is a
409
409
  * separate feature.
410
410
  *
@@ -429,7 +429,9 @@ export declare class FormoAnalytics implements IFormoAnalytics {
429
429
  * request wrapper installs here. Double counting is prevented in the
430
430
  * wrapper via `shouldSkipRequestCapture`.
431
431
  */
432
- _wrapWagmiProvider(provider: EIP1193Provider): void;
432
+ _wrapWagmiProvider(provider: EIP1193Provider, chainId?: number): boolean;
433
+ /** INTERNAL. Chain updates for the fallback-wrapped provider. */
434
+ _rememberWagmiProviderChain(provider: EIP1193Provider, chainId: number | undefined): void;
433
435
  registerProvider(provider: EIP1193Provider, info?: {
434
436
  name?: string;
435
437
  rdns?: string;
@@ -1337,9 +1337,9 @@ var FormoAnalytics = /** @class */ (function () {
1337
1337
  *
1338
1338
  * With several live SDK instances (multi write-key pages) registering
1339
1339
  * the SAME provider, request-derived events (signatures, transactions)
1340
- * go to the most recently registered live instance - the same
1341
- * single-observer semantics discovery has always had for the request
1342
- * wrapper. Lifecycle events (connect, chain, disconnect) reach every
1340
+ * go to the most recently CREATED live instance, regardless of the
1341
+ * order registrations happen to land in - the same single-observer
1342
+ * semantics discovery has always had for the request wrapper. Lifecycle events (connect, chain, disconnect) reach every
1343
1343
  * instance. Fanning request observations out to all instances is a
1344
1344
  * separate feature.
1345
1345
  *
@@ -1364,14 +1364,35 @@ var FormoAnalytics = /** @class */ (function () {
1364
1364
  * request wrapper installs here. Double counting is prevented in the
1365
1365
  * wrapper via `shouldSkipRequestCapture`.
1366
1366
  */
1367
- FormoAnalytics.prototype._wrapWagmiProvider = function (provider) {
1367
+ FormoAnalytics.prototype._wrapWagmiProvider = function (provider, chainId) {
1368
1368
  if (this.isCleanedUp || !(0, provider_1.isValidProvider)(provider))
1369
- return;
1369
+ return false;
1370
1370
  try {
1371
- this.evmRequests.registerRequestListeners(provider);
1371
+ // The tracker reports refusals (frozen provider, unrebindable
1372
+ // wrapper) by returning false rather than throwing; treat those as
1373
+ // failures too so the caller can retry later.
1374
+ if (!this.evmRequests.registerRequestListeners(provider)) {
1375
+ return false;
1376
+ }
1377
+ if (chainId !== undefined) {
1378
+ this.evm.rememberChain(provider, chainId);
1379
+ }
1380
+ return true;
1372
1381
  }
1373
1382
  catch (e) {
1374
1383
  logger_1.logger.warn("Failed to wrap wagmi provider for hybrid capture", e);
1384
+ return false;
1385
+ }
1386
+ };
1387
+ /** INTERNAL. Chain updates for the fallback-wrapped provider. */
1388
+ FormoAnalytics.prototype._rememberWagmiProviderChain = function (provider, chainId) {
1389
+ if (this.isCleanedUp)
1390
+ return;
1391
+ try {
1392
+ this.evm.rememberChain(provider, chainId);
1393
+ }
1394
+ catch (_a) {
1395
+ /* bookkeeping only */
1375
1396
  }
1376
1397
  };
1377
1398
  FormoAnalytics.prototype.registerProvider = function (provider, info) {
@@ -7,6 +7,14 @@ export interface FormoAnalyticsProviderProps {
7
7
  children: ReactNode;
8
8
  }
9
9
  export declare const FormoAnalyticsContext: import("react").Context<IFormoAnalytics>;
10
+ /**
11
+ * A stable key over the serializable parts of Options. The provider effect
12
+ * re-initialises the SDK when this key changes; anything that alters SDK
13
+ * behaviour must be represented here, or a runtime change to it is silently
14
+ * ignored. Complex objects are tracked by presence, plus the flags that
15
+ * change what the SDK does with them.
16
+ */
17
+ export declare const computeOptionsKey: (options?: Options) => string;
10
18
  export declare const FormoAnalyticsProvider: FC<FormoAnalyticsProviderProps>;
11
19
  export declare const useFormo: () => IFormoAnalytics;
12
20
  //# sourceMappingURL=FormoAnalyticsProvider.d.ts.map
@@ -47,7 +47,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
47
47
  }
48
48
  };
49
49
  Object.defineProperty(exports, "__esModule", { value: true });
50
- exports.useFormo = exports.FormoAnalyticsProvider = exports.FormoAnalyticsContext = void 0;
50
+ exports.useFormo = exports.FormoAnalyticsProvider = exports.computeOptionsKey = exports.FormoAnalyticsContext = void 0;
51
51
  var jsx_runtime_1 = require("react/jsx-runtime");
52
52
  var react_1 = require("react");
53
53
  var FormoAnalytics_1 = require("./FormoAnalytics");
@@ -72,6 +72,45 @@ var defaultContext = {
72
72
  hasOptedOutTracking: function () { return false; },
73
73
  };
74
74
  exports.FormoAnalyticsContext = (0, react_1.createContext)(defaultContext);
75
+ /**
76
+ * A stable key over the serializable parts of Options. The provider effect
77
+ * re-initialises the SDK when this key changes; anything that alters SDK
78
+ * behaviour must be represented here, or a runtime change to it is silently
79
+ * ignored. Complex objects are tracked by presence, plus the flags that
80
+ * change what the SDK does with them.
81
+ */
82
+ var computeOptionsKey = function (options) {
83
+ var _a;
84
+ if (!options)
85
+ return 'undefined';
86
+ var serializableOptions = {
87
+ tracking: options.tracking,
88
+ autocapture: options.autocapture,
89
+ crossSubdomainCookies: options.crossSubdomainCookies,
90
+ apiHost: options.apiHost,
91
+ flushAt: options.flushAt,
92
+ flushInterval: options.flushInterval,
93
+ retryCount: options.retryCount,
94
+ maxQueueSize: options.maxQueueSize,
95
+ logger: options.logger,
96
+ referral: options.referral,
97
+ evm: options.evm,
98
+ // For complex objects, just track their presence, not their content
99
+ hasProvider: !!options.provider,
100
+ hasWagmi: !!options.wagmi,
101
+ wagmiEip1193Fallback: !!((_a = options.wagmi) === null || _a === void 0 ? void 0 : _a.eip1193Fallback),
102
+ hasReady: !!options.ready,
103
+ };
104
+ try {
105
+ return JSON.stringify(serializableOptions);
106
+ }
107
+ catch (error) {
108
+ // Fallback to timestamp if serialization fails
109
+ logger_1.logger.warn('Failed to serialize options, using timestamp', error);
110
+ return Date.now().toString();
111
+ }
112
+ };
113
+ exports.computeOptionsKey = computeOptionsKey;
75
114
  var FormoAnalyticsProvider = function (props) {
76
115
  var writeKey = props.writeKey, _a = props.disabled, disabled = _a === void 0 ? false : _a, children = props.children;
77
116
  // Keep the app running without analytics if no Write Key is provided or disabled
@@ -91,37 +130,7 @@ var InitializedAnalytics = function (_a) {
91
130
  var _b = (0, react_1.useState)(defaultContext), sdk = _b[0], setSdk = _b[1];
92
131
  var sdkRef = (0, react_1.useRef)(defaultContext);
93
132
  (0, storage_1.initStorageManager)(writeKey);
94
- // Create a stable key from options that ignores complex objects and functions
95
- // We only care about serializable config values that would affect SDK behavior
96
- var optionsKey = (0, react_1.useMemo)(function () {
97
- if (!options)
98
- return 'undefined';
99
- // Extract only the serializable parts of options
100
- var serializableOptions = {
101
- tracking: options.tracking,
102
- autocapture: options.autocapture,
103
- crossSubdomainCookies: options.crossSubdomainCookies,
104
- apiHost: options.apiHost,
105
- flushAt: options.flushAt,
106
- flushInterval: options.flushInterval,
107
- retryCount: options.retryCount,
108
- maxQueueSize: options.maxQueueSize,
109
- logger: options.logger,
110
- referral: options.referral,
111
- // For complex objects, just track their presence, not their content
112
- hasProvider: !!options.provider,
113
- hasWagmi: !!options.wagmi,
114
- hasReady: !!options.ready,
115
- };
116
- try {
117
- return JSON.stringify(serializableOptions);
118
- }
119
- catch (error) {
120
- // Fallback to timestamp if serialization fails
121
- logger_1.logger.warn('Failed to serialize options, using timestamp', error);
122
- return Date.now().toString();
123
- }
124
- }, [options]);
133
+ var optionsKey = (0, react_1.useMemo)(function () { return (0, exports.computeOptionsKey)(options); }, [options]);
125
134
  (0, react_1.useEffect)(function () {
126
135
  var isCleanedUp = false;
127
136
  var initialize = function () { return __awaiter(void 0, void 0, void 0, function () {
@@ -57,6 +57,18 @@ export declare class EvmRequestTracker {
57
57
  * never retry it, and every signature and transaction from that wallet
58
58
  * would be missed for the rest of the session.
59
59
  */
60
+ /** Monotonic creation stamp; owner precedence is decided by it. */
61
+ private static nextCreationSeq;
62
+ readonly creationSeq: number;
63
+ /**
64
+ * Put this tracker into an owner list at its CREATION-ORDER position.
65
+ * Registration can arrive out of order - an older instance's async wrap
66
+ * kick may resolve after a newer instance's - and dispatch picks from
67
+ * the END of the list, so append-on-registration would let resolution
68
+ * order decide which instance owns capture. Creation order is what the
69
+ * newest-live contract promises.
70
+ */
71
+ private insertBySeniority;
60
72
  registerRequestListeners(provider: EIP1193Provider): boolean;
61
73
  /** Install the wrapper function onto the provider; separated so the
62
74
  * routing shim above stays small. */
@@ -140,6 +140,7 @@ var EvmRequestTracker = /** @class */ (function () {
140
140
  this.disposed = false;
141
141
  /** Providers whose owner list includes this instance; pruned on cleanup. */
142
142
  this.wrappedProviders = new Set();
143
+ this.creationSeq = EvmRequestTracker.nextCreationSeq++;
143
144
  }
144
145
  /** Stop every poll in flight. Terminal, like the event queue's close(). */
145
146
  EvmRequestTracker.prototype.cleanup = function () {
@@ -173,11 +174,24 @@ var EvmRequestTracker = /** @class */ (function () {
173
174
  this.polls.add(timer);
174
175
  };
175
176
  /**
176
- * Wrap a provider's `request`. Returns whether the wrapper is installed:
177
- * a caller that marks the provider as tracked on a false return would
178
- * never retry it, and every signature and transaction from that wallet
179
- * would be missed for the rest of the session.
177
+ * Put this tracker into an owner list at its CREATION-ORDER position.
178
+ * Registration can arrive out of order - an older instance's async wrap
179
+ * kick may resolve after a newer instance's - and dispatch picks from
180
+ * the END of the list, so append-on-registration would let resolution
181
+ * order decide which instance owns capture. Creation order is what the
182
+ * newest-live contract promises.
180
183
  */
184
+ EvmRequestTracker.prototype.insertBySeniority = function (owners) {
185
+ var _this = this;
186
+ var idx = owners.indexOf(this);
187
+ if (idx !== -1)
188
+ owners.splice(idx, 1);
189
+ var insertAt = owners.findIndex(function (o) { return o.creationSeq > _this.creationSeq; });
190
+ if (insertAt === -1)
191
+ owners.push(this);
192
+ else
193
+ owners.splice(insertAt, 0, this);
194
+ };
181
195
  EvmRequestTracker.prototype.registerRequestListeners = function (provider) {
182
196
  var _this = this;
183
197
  logger_1.logger.info("registerRequestListeners");
@@ -199,10 +213,7 @@ var EvmRequestTracker = /** @class */ (function () {
199
213
  logger_1.logger.warn("wrapped without owner list; cannot rebind");
200
214
  return false;
201
215
  }
202
- var idx = owners.indexOf(this);
203
- if (idx !== -1)
204
- owners.splice(idx, 1);
205
- owners.push(this);
216
+ this.insertBySeniority(owners);
206
217
  this.wrappedProviders.add(provider);
207
218
  logger_1.logger.info("Provider already wrapped; rebinding the wrapper to this instance.");
208
219
  return true;
@@ -243,10 +254,7 @@ var EvmRequestTracker = /** @class */ (function () {
243
254
  var slot = provider;
244
255
  var prior = slot[types_1.WRAPPED_REQUEST_OWNER_SYMBOL];
245
256
  var owners = Array.isArray(prior) ? prior : [];
246
- var idx = owners.indexOf(this);
247
- if (idx !== -1)
248
- owners.splice(idx, 1);
249
- owners.push(this);
257
+ this.insertBySeniority(owners);
250
258
  if (!Array.isArray(prior)) {
251
259
  slot[types_1.WRAPPED_REQUEST_OWNER_SYMBOL] = owners;
252
260
  }
@@ -851,6 +859,14 @@ var EvmRequestTracker = /** @class */ (function () {
851
859
  });
852
860
  });
853
861
  };
862
+ /**
863
+ * Wrap a provider's `request`. Returns whether the wrapper is installed:
864
+ * a caller that marks the provider as tracked on a false return would
865
+ * never retry it, and every signature and transaction from that wallet
866
+ * would be missed for the rest of the session.
867
+ */
868
+ /** Monotonic creation stamp; owner precedence is decided by it. */
869
+ EvmRequestTracker.nextCreationSeq = 0;
854
870
  return EvmRequestTracker;
855
871
  }());
856
872
  exports.EvmRequestTracker = EvmRequestTracker;
@@ -1,2 +1,2 @@
1
- export declare const version = "1.38.0";
1
+ export declare const version = "1.38.1";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -3,5 +3,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.version = void 0;
4
4
  // This file is auto-generated by scripts/update-version.js during npm version
5
5
  // Do not edit manually - it will be overwritten
6
- exports.version = '1.38.0';
6
+ exports.version = '1.38.1';
7
7
  //# sourceMappingURL=version.js.map
@@ -362,6 +362,43 @@ export declare class WagmiEventHandler {
362
362
  * dedup. Fire-and-forget per connection; a provider that cannot be
363
363
  * produced simply keeps mutation-only capture.
364
364
  */
365
+ /**
366
+ * Connections THIS instance has wrapped. Deliberately per-instance, not
367
+ * module-level: a module-level guard let a rebuilt SDK instance skip
368
+ * re-wrapping, so ownership stayed with the torn-down instance and every
369
+ * capture died in its closed queue - and it also stopped a second
370
+ * write-key instance from ever registering. Re-wrapping is safe and
371
+ * cheap: the request tracker rebinds ownership of an intact wrapper.
372
+ */
373
+ /**
374
+ * Latest wrap attempt per connector. Every kick re-resolves
375
+ * getProvider() - a connector can hand out a REPLACEMENT provider after
376
+ * a reconnect, so a once-only guard would leave the new session's
377
+ * provider unwrapped forever. Re-wrapping is idempotent: the request
378
+ * tracker rebinds ownership of an intact wrapper. The epoch lets a slow
379
+ * resolution from a superseded kick (an earlier session, an earlier
380
+ * chain) be discarded instead of overwriting fresher state.
381
+ */
382
+ private wrapEpochs;
383
+ /** Consecutive automatic wrap retries per connector; see below. */
384
+ private wrapRetryCounts;
385
+ /** Pending retry timers, cancelled by cleanup(). */
386
+ private wrapRetryTimers;
387
+ /**
388
+ * Bumped on every observed disconnect. Every wrap attempt captures it
389
+ * at kick time and its resolution stands down on a mismatch, so a
390
+ * disconnect invalidates ALL pending wraps at once - including ones
391
+ * whose getProvider() had not resolved yet, which no per-connector
392
+ * bookkeeping can reach because nothing has been recorded for them.
393
+ */
394
+ private wrapSessionGeneration;
395
+ /** The active connector this instance wrapped, and its provider, for
396
+ * chain updates. Kept as a pair so a chain report is only ever applied
397
+ * to the provider of the connector it describes. Keyed by connector,
398
+ * not by connection record: wagmi REPLACES the connection object on
399
+ * every account or chain update, so record identity is not stable. */
400
+ private fallbackConnector?;
401
+ private fallbackProvider?;
365
402
  private wrapActiveConnectorProvider;
366
403
  private kickWalletConnectPeerLookup;
367
404
  /**
@@ -242,8 +242,6 @@ var walletConnectPeerLookups = new WeakSet();
242
242
  * newest kicked one, so a slow resolution from a PREVIOUS session cannot
243
243
  * land after the current session's and overwrite it. */
244
244
  var walletConnectPeerLatest = new WeakMap();
245
- /** Connections whose provider already has the hybrid-capture wrapper. */
246
- var wagmiWrappedConnections = new WeakSet();
247
245
  /**
248
246
  * Pending transactions, shared per destination rather than per handler.
249
247
  *
@@ -428,6 +426,57 @@ var WagmiEventHandler = /** @class */ (function () {
428
426
  * Key format: `${queryHash}:${status}`
429
427
  */
430
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;
431
480
  this.formo = formoAnalytics;
432
481
  this.wagmiConfig = wagmiConfig;
433
482
  this.queryClient = queryClient;
@@ -602,6 +651,10 @@ var WagmiEventHandler = /** @class */ (function () {
602
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) {
603
652
  var _a = key.split("|"), address = _a[1];
604
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());
605
658
  _this.handleActiveAddressChange(address || undefined, prevAddress || undefined);
606
659
  });
607
660
  this.unsubscribers.push(connectionUnsubscribe);
@@ -907,6 +960,19 @@ var WagmiEventHandler = /** @class */ (function () {
907
960
  return __generator(this, function (_e) {
908
961
  switch (_e.label) {
909
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
+ }
910
976
  // Prevent concurrent processing
911
977
  if (this.trackingState.isProcessing) {
912
978
  // Dropped, not queued - but remember that a disconnect went past.
@@ -1443,10 +1509,31 @@ var WagmiEventHandler = /** @class */ (function () {
1443
1509
  */
1444
1510
  WagmiEventHandler.prototype.handleChainChange = function (chainId, prevChainId) {
1445
1511
  return __awaiter(this, void 0, void 0, function () {
1446
- var state, address, hadAddress, wasAnnounced, announcedNow;
1447
- return __generator(this, function (_a) {
1448
- 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) {
1449
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
+ }
1450
1537
  if (chainId === prevChainId || chainId === undefined) {
1451
1538
  return [2 /*return*/];
1452
1539
  }
@@ -1492,7 +1579,7 @@ var WagmiEventHandler = /** @class */ (function () {
1492
1579
  });
1493
1580
  return [4 /*yield*/, this.applyChainForTrackedWallet(address, chainId)];
1494
1581
  case 1:
1495
- _a.sent();
1582
+ _d.sent();
1496
1583
  return [2 /*return*/];
1497
1584
  }
1498
1585
  });
@@ -2271,30 +2358,10 @@ var WagmiEventHandler = /** @class */ (function () {
2271
2358
  this.kickWalletConnectPeerLookup(state);
2272
2359
  return connector.name;
2273
2360
  };
2274
- /**
2275
- * Start resolving the wallet behind a WalletConnect connection.
2276
- *
2277
- * Fire-and-forget on purpose: emission paths are synchronous by design
2278
- * and must never wait (see the connect marker comment). Called at the
2279
- * START of the status/address flows rather than only at read time - the
2280
- * lookup is one microtask for an initialised connector, and the emission
2281
- * sits behind several awaits, so kicking early usually means even the
2282
- * FIRST connect names the real wallet. When the race is lost the event
2283
- * honestly says "WalletConnect" and every later event names the peer.
2284
- */
2285
- /**
2286
- * Install the request wrapper on the active connector's provider.
2287
- *
2288
- * This is what lets wagmi mode capture IMPERATIVE viem calls
2289
- * (walletClient.sendTransaction / .signMessage / .writeContract / raw
2290
- * request), which create no mutation and were silently lost. Hook-driven
2291
- * calls stay owned by the mutation handlers via the pending-mutation
2292
- * dedup. Fire-and-forget per connection; a provider that cannot be
2293
- * produced simply keeps mutation-only capture.
2294
- */
2295
- WagmiEventHandler.prototype.wrapActiveConnectorProvider = function (state) {
2361
+ WagmiEventHandler.prototype.wrapActiveConnectorProvider = function (state, isRetry) {
2296
2362
  var _this = this;
2297
- var _a, _b;
2363
+ var _a, _b, _c;
2364
+ if (isRetry === void 0) { isRetry = false; }
2298
2365
  // OPT-IN only. Wagmi mode's baseline never touches the signing
2299
2366
  // transport; instrumenting the provider is an explicit integrator
2300
2367
  // decision (options.wagmi.eip1193Fallback), made auditable in
@@ -2303,25 +2370,124 @@ var WagmiEventHandler = /** @class */ (function () {
2303
2370
  if (!optedIn) {
2304
2371
  return;
2305
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
+ }
2306
2378
  var connection = state.current
2307
2379
  ? state.connections.get(state.current)
2308
2380
  : undefined;
2309
2381
  var connector = connection === null || connection === void 0 ? void 0 : connection.connector;
2310
- if (!connection ||
2311
- typeof (connector === null || connector === void 0 ? void 0 : connector.getProvider) !== "function" ||
2312
- wagmiWrappedConnections.has(connection)) {
2382
+ if (!connection || typeof (connector === null || connector === void 0 ? void 0 : connector.getProvider) !== "function") {
2313
2383
  return;
2314
2384
  }
2315
- wagmiWrappedConnections.add(connection);
2316
- connector
2317
- .getProvider()
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
2318
2443
  .then(function (provider) {
2319
- var _a, _b;
2320
- (_b = (_a = _this.formo)._wrapWagmiProvider) === null || _b === void 0 ? void 0 : _b.call(_a, 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;
2321
2487
  })
2322
2488
  .catch(function () {
2323
- // Mutation-only capture remains; retry on the next connection.
2324
- wagmiWrappedConnections.delete(connection);
2489
+ // Mutation-only capture remains.
2490
+ retryAfterFailure();
2325
2491
  });
2326
2492
  };
2327
2493
  WagmiEventHandler.prototype.kickWalletConnectPeerLookup = function (state) {
@@ -2411,6 +2577,8 @@ var WagmiEventHandler = /** @class */ (function () {
2411
2577
  * Clean up all subscriptions
2412
2578
  */
2413
2579
  WagmiEventHandler.prototype.cleanup = function () {
2580
+ this.wrapRetryTimers.forEach(function (t) { return clearTimeout(t); });
2581
+ this.wrapRetryTimers.clear();
2414
2582
  logger_1.logger.debug("WagmiEventHandler: Cleaning up subscriptions");
2415
2583
  if (!this.disposed) {
2416
2584
  this.disposed = true;