@formo/analytics 1.35.2 → 1.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/cjs/src/FormoAnalytics.d.ts +79 -363
  2. package/dist/cjs/src/FormoAnalytics.js +368 -2234
  3. package/dist/cjs/src/event/EventManager.d.ts +3 -1
  4. package/dist/cjs/src/event/EventManager.js +5 -1
  5. package/dist/cjs/src/event/type.d.ts +1 -0
  6. package/dist/cjs/src/evm/EvmEventTracker.d.ts +175 -0
  7. package/dist/cjs/src/evm/EvmEventTracker.js +1030 -0
  8. package/dist/cjs/src/evm/EvmProviderRegistry.d.ts +132 -0
  9. package/dist/cjs/src/evm/EvmProviderRegistry.js +348 -0
  10. package/dist/cjs/src/evm/EvmRequestTracker.d.ts +98 -0
  11. package/dist/cjs/src/evm/EvmRequestTracker.js +713 -0
  12. package/dist/cjs/src/evm/batch.d.ts +94 -0
  13. package/dist/cjs/src/evm/batch.js +130 -0
  14. package/dist/cjs/src/queue/EventQueue.d.ts +28 -0
  15. package/dist/cjs/src/queue/EventQueue.js +97 -19
  16. package/dist/cjs/src/queue/type.d.ts +1 -0
  17. package/dist/cjs/src/tracking/TrackingPolicy.d.ts +146 -0
  18. package/dist/cjs/src/tracking/TrackingPolicy.js +200 -0
  19. package/dist/cjs/src/validators/address.d.ts +1 -1
  20. package/dist/cjs/src/version.d.ts +1 -1
  21. package/dist/cjs/src/version.js +1 -1
  22. package/dist/cjs/src/wagmi/WagmiEventHandler.d.ts +43 -0
  23. package/dist/cjs/src/wagmi/WagmiEventHandler.js +253 -2
  24. package/dist/cjs/src/wallet/WalletStateStore.d.ts +223 -0
  25. package/dist/cjs/src/wallet/WalletStateStore.js +515 -0
  26. package/dist/esm/src/FormoAnalytics.d.ts +79 -363
  27. package/dist/esm/src/FormoAnalytics.js +369 -2235
  28. package/dist/esm/src/event/EventManager.d.ts +3 -1
  29. package/dist/esm/src/event/EventManager.js +5 -1
  30. package/dist/esm/src/event/type.d.ts +1 -0
  31. package/dist/esm/src/evm/EvmEventTracker.d.ts +175 -0
  32. package/dist/esm/src/evm/EvmEventTracker.js +1027 -0
  33. package/dist/esm/src/evm/EvmProviderRegistry.d.ts +132 -0
  34. package/dist/esm/src/evm/EvmProviderRegistry.js +345 -0
  35. package/dist/esm/src/evm/EvmRequestTracker.d.ts +98 -0
  36. package/dist/esm/src/evm/EvmRequestTracker.js +710 -0
  37. package/dist/esm/src/evm/batch.d.ts +94 -0
  38. package/dist/esm/src/evm/batch.js +123 -0
  39. package/dist/esm/src/queue/EventQueue.d.ts +28 -0
  40. package/dist/esm/src/queue/EventQueue.js +97 -19
  41. package/dist/esm/src/queue/type.d.ts +1 -0
  42. package/dist/esm/src/tracking/TrackingPolicy.d.ts +146 -0
  43. package/dist/esm/src/tracking/TrackingPolicy.js +197 -0
  44. package/dist/esm/src/version.d.ts +1 -1
  45. package/dist/esm/src/version.js +1 -1
  46. package/dist/esm/src/wagmi/WagmiEventHandler.d.ts +43 -0
  47. package/dist/esm/src/wagmi/WagmiEventHandler.js +253 -2
  48. package/dist/esm/src/wallet/WalletStateStore.d.ts +223 -0
  49. package/dist/esm/src/wallet/WalletStateStore.js +512 -0
  50. package/dist/index.umd.min.js +1 -1
  51. package/package.json +6 -4
@@ -0,0 +1,1030 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
13
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.EvmEventTracker = void 0;
40
+ var mipd_1 = require("mipd");
41
+ var logger_1 = require("../logger");
42
+ var chain_1 = require("../utils/chain");
43
+ var address_1 = require("../utils/address");
44
+ var provider_1 = require("../provider");
45
+ /** Why the tracker moved the active slot to another provider. Log copy. */
46
+ var PROVIDER_SWITCH_REASONS = {
47
+ ADDRESS_MISMATCH: "Address mismatch indicates wallet switch",
48
+ NO_ACCOUNTS: "Current provider has no accounts",
49
+ CHECK_FAILED: "Could not check current provider accounts",
50
+ };
51
+ /**
52
+ * The EIP-1193 side of wallet tracking: which providers to watch, and what
53
+ * their events mean.
54
+ *
55
+ * Split out of `FormoAnalytics` (#336). It holds no wallet state and no
56
+ * provider registry of its own - those have owners already - so what is left
57
+ * here is the part that is genuinely about interpreting wallet events:
58
+ * deciding when a connect has to be reported, when a switch is stale, and
59
+ * when a provider has stopped being the one we follow.
60
+ */
61
+ var EvmEventTracker = /** @class */ (function () {
62
+ function EvmEventTracker(wallet, registry, deps) {
63
+ this.wallet = wallet;
64
+ this.registry = registry;
65
+ this.deps = deps;
66
+ /**
67
+ * The connect this SDK has already reported for a provider.
68
+ *
69
+ * Connection REPORTING, which is why it lives with the handlers rather than
70
+ * with wallet state. The store tells us when a provider stops being active,
71
+ * which is when a record must lapse.
72
+ */
73
+ this._announcedConnect = new WeakMap();
74
+ }
75
+ /** Stop listening for wallet announcements. Called from SDK teardown. */
76
+ EvmEventTracker.prototype.cleanup = function () {
77
+ var _a;
78
+ try {
79
+ (_a = this.unsubscribeDiscovery) === null || _a === void 0 ? void 0 : _a.call(this);
80
+ }
81
+ catch (e) {
82
+ logger_1.logger.warn("Failed to unsubscribe from provider discovery", e);
83
+ }
84
+ this.unsubscribeDiscovery = undefined;
85
+ };
86
+ /** Drop a provider's reported connect. Called when it stops being active. */
87
+ EvmEventTracker.prototype.forgetAnnouncedConnect = function (provider) {
88
+ this._announcedConnect.delete(provider);
89
+ };
90
+ /**
91
+ * Helper method to check if a provider is different from the currently active one
92
+ * @param provider The provider to check
93
+ * @returns true if there's a provider mismatch, false otherwise
94
+ */
95
+ EvmEventTracker.prototype.isProviderMismatch = function (provider) {
96
+ // Only consider it a mismatch if we have an active provider AND the provider is different
97
+ // This allows legitimate provider switching while preventing race conditions
98
+ return this.wallet.provider != null && this.wallet.provider !== provider;
99
+ };
100
+ /**
101
+ * Track an EIP-1193 provider by wrapping its request method and adding event listeners
102
+ * Note: This is only used in non-Wagmi mode. When Wagmi is enabled, all tracking
103
+ * happens through Wagmi's connector system instead of EIP-1193/EIP-6963.
104
+ * @param provider The EIP-1193 provider to track
105
+ */
106
+ EvmEventTracker.prototype.trackEIP1193Provider = function (provider) {
107
+ logger_1.logger.info("trackEIP1193Provider", provider);
108
+ // Defensive check: Skip provider tracking in Wagmi mode
109
+ // This should never be called in Wagmi mode due to guards in init(),
110
+ // but we check here for safety in case of future code changes
111
+ if (this.deps.isWagmiMode()) {
112
+ logger_1.logger.debug("trackEIP1193Provider: Skipping EIP-1193 provider tracking (Wagmi mode - using connector system instead)");
113
+ return;
114
+ }
115
+ try {
116
+ // Validate provider exists and has required methods
117
+ if (!(0, provider_1.isValidProvider)(provider)) {
118
+ logger_1.logger.warn("trackEIP1193Provider: Invalid provider - missing required methods");
119
+ return;
120
+ }
121
+ if (this.registry.isTracked(provider)) {
122
+ logger_1.logger.warn("trackEIP1193Provider: Provider already tracked");
123
+ return;
124
+ }
125
+ // CRITICAL: Always register accountsChanged for state management
126
+ // This ensures currentAddress, currentChainId, and _provider are always up-to-date
127
+ // Event emission is controlled conditionally inside the handlers
128
+ this.registerAccountsChangedListener(provider);
129
+ // `chainChanged` and `connect` are registered UNCONDITIONALLY: they are
130
+ // how this provider's chain is observed, and every signature and
131
+ // transaction has to be labelled with it. Gating registration on
132
+ // `autocapture.chain` conflated observing a chain with reporting one, so
133
+ // `{ chain: false, signature: true }` left the chain frozen at whatever
134
+ // was first seen - a switch to an excluded chain went unnoticed and its
135
+ // signatures were emitted under the old, allowed chain. Whether an
136
+ // event is emitted is decided inside each handler.
137
+ this.registerChainChangedListener(provider);
138
+ if (this.deps.isAutocaptureEnabled("connect")) {
139
+ this.registerConnectListener(provider);
140
+ }
141
+ else {
142
+ // Observation only. The full connect handler calls `getAddress()`,
143
+ // which issues `eth_accounts`, and nothing analytics-only may go on
144
+ // the wallet's transport - a stalled request there sits in front of
145
+ // the next signature the dapp makes. The chain rides along on the
146
+ // event itself, so it costs nothing to record.
147
+ this.registerConnectChainObserver(provider);
148
+ }
149
+ // Seed the chain from the provider's own synchronous state if it exposes
150
+ // one. Deliberately a property read and never an RPC: see
151
+ // resolveChainIdForProvider for why nothing analytics-only may go on the
152
+ // wallet's transport.
153
+ this.seedProviderChainFromState(provider);
154
+ // Wrapped UNCONDITIONALLY. The wrapper checks the autocapture flags per
155
+ // request, so skipping it here bought nothing and cost a real case: an
156
+ // app that turned signature or transaction capture on after init had a
157
+ // provider that was never wrapped, and never would be.
158
+ if (!this.deps.registerRequestListeners(provider)) {
159
+ // Not tracked: discovery would otherwise never retry it, and every
160
+ // signature and transaction from this wallet would be missed.
161
+ logger_1.logger.warn("TrackProvider: Could not wrap provider; leaving it untracked");
162
+ return;
163
+ }
164
+ // Registered UNCONDITIONALLY: this listener also ends the provider's
165
+ // reported-connect record, and with `{ connect: true, disconnect: false }`
166
+ // a wallet that disconnected and reconnected would otherwise find its
167
+ // old record still standing and have the new connect suppressed. Whether
168
+ // a disconnect EVENT is emitted is decided inside the handler.
169
+ {
170
+ this.registerDisconnectListener(provider);
171
+ }
172
+ // Only add to tracked providers after all listeners are successfully registered
173
+ this.registry.markTracked(provider);
174
+ }
175
+ catch (error) {
176
+ logger_1.logger.error("Error tracking provider:", error);
177
+ }
178
+ };
179
+ EvmEventTracker.prototype.trackProviders = function (providers) {
180
+ try {
181
+ for (var _i = 0, providers_1 = providers; _i < providers_1.length; _i++) {
182
+ var eip6963ProviderDetail = providers_1[_i];
183
+ var provider = eip6963ProviderDetail === null || eip6963ProviderDetail === void 0 ? void 0 : eip6963ProviderDetail.provider;
184
+ if (provider && !this.registry.isTracked(provider)) {
185
+ this.trackEIP1193Provider(provider);
186
+ }
187
+ }
188
+ }
189
+ catch (error) {
190
+ logger_1.logger.error("Failed to track EIP-6963 providers during initialization:", error);
191
+ }
192
+ };
193
+ EvmEventTracker.prototype.registerAccountsChangedListener = function (provider) {
194
+ var _this = this;
195
+ logger_1.logger.info("registerAccountsChangedListener");
196
+ var listener = function () {
197
+ var args = [];
198
+ for (var _i = 0; _i < arguments.length; _i++) {
199
+ args[_i] = arguments[_i];
200
+ }
201
+ return _this.onAccountsChanged(provider, args[0]);
202
+ };
203
+ provider.on("accountsChanged", listener);
204
+ this.registry.addListener(provider, "accountsChanged", listener);
205
+ };
206
+ EvmEventTracker.prototype.onAccountsChanged = function (provider, accounts) {
207
+ return __awaiter(this, void 0, void 0, function () {
208
+ var claims, observation;
209
+ return __generator(this, function (_a) {
210
+ switch (_a.label) {
211
+ case 0:
212
+ logger_1.logger.info("onAccountsChanged", accounts);
213
+ claims = accounts.length > 0 || this.wallet.provider === provider;
214
+ observation = claims
215
+ ? this.wallet.observe("evm")
216
+ : this.wallet.currentObservation("evm");
217
+ // Take the ticket BEFORE any async work, so the order recorded is the
218
+ // order the wallet's signals arrived in. This path used to DROP a second
219
+ // `accountsChanged` while the first was in flight; dropping is not
220
+ // ordering, and a wallet that disconnected and came straight back had its
221
+ // reconnect thrown away.
222
+ return [4 /*yield*/, this._handleAccountsChanged(provider, accounts, observation)];
223
+ case 1:
224
+ // Take the ticket BEFORE any async work, so the order recorded is the
225
+ // order the wallet's signals arrived in. This path used to DROP a second
226
+ // `accountsChanged` while the first was in flight; dropping is not
227
+ // ordering, and a wallet that disconnected and came straight back had its
228
+ // reconnect thrown away.
229
+ _a.sent();
230
+ return [2 /*return*/];
231
+ }
232
+ });
233
+ });
234
+ };
235
+ /**
236
+ * Handles changes to the accounts of a given EIP-1193 provider.
237
+ *
238
+ * @param provider - The EIP-1193 provider whose accounts have changed.
239
+ * @param accounts - The new array of account addresses. An empty array indicates a disconnect.
240
+ * @returns A promise that resolves when the account change has been processed.
241
+ *
242
+ * If the accounts array is empty and the provider is the active provider, this method triggers
243
+ * a disconnect flow. Otherwise, it updates the state to reflect the new accounts as needed.
244
+ */
245
+ EvmEventTracker.prototype._handleAccountsChanged = function (provider, accounts, observation) {
246
+ return __awaiter(this, void 0, void 0, function () {
247
+ var error_1, address, currentStoredAddress, newProviderAddress, activeProviderAccounts, error_2, nextChainId, wasDisconnected, providerInfo, effectiveChainId;
248
+ return __generator(this, function (_a) {
249
+ switch (_a.label) {
250
+ case 0:
251
+ if (!(accounts.length === 0)) return [3 /*break*/, 9];
252
+ if (!(this.wallet.provider === provider)) return [3 /*break*/, 7];
253
+ logger_1.logger.info("OnAccountsChanged: Detecting disconnect, current state:", {
254
+ evmAddress: this.wallet.evmAddress,
255
+ evmChainId: this.wallet.evmChainId,
256
+ providerMatch: this.wallet.provider === provider,
257
+ });
258
+ // The reported connect ends with the connection, so a genuine
259
+ // reconnect later reports again rather than being taken for a
260
+ // duplicate.
261
+ this._announcedConnect.delete(provider);
262
+ if (!this.deps.isAutocaptureEnabled("disconnect")) return [3 /*break*/, 5];
263
+ _a.label = 1;
264
+ case 1:
265
+ _a.trys.push([1, 3, , 4]);
266
+ // Pass EVM state explicitly to ensure we have the data for the disconnect event
267
+ return [4 /*yield*/, this.deps.disconnect({
268
+ chainId: this.wallet.evmChainId,
269
+ address: this.wallet.evmAddress,
270
+ })];
271
+ case 2:
272
+ // Pass EVM state explicitly to ensure we have the data for the disconnect event
273
+ _a.sent();
274
+ return [3 /*break*/, 4];
275
+ case 3:
276
+ error_1 = _a.sent();
277
+ logger_1.logger.error("Failed to disconnect provider on accountsChanged", error_1);
278
+ return [3 /*break*/, 4];
279
+ case 4: return [3 /*break*/, 6];
280
+ case 5:
281
+ logger_1.logger.debug("OnAccountsChanged: Disconnect event skipped (autocapture.disconnect: false)");
282
+ // Whether the app opted into the EVENT has no bearing on ordering.
283
+ this.wallet.beginDisconnect("evm");
284
+ // Still clear state even if not tracking the event
285
+ this.wallet.clear('evm');
286
+ _a.label = 6;
287
+ case 6: return [3 /*break*/, 8];
288
+ case 7:
289
+ logger_1.logger.info("OnAccountsChanged: Ignoring disconnect for non-active provider");
290
+ _a.label = 8;
291
+ case 8: return [2 /*return*/];
292
+ case 9:
293
+ address = (0, address_1.validateAndChecksumAddress)(accounts[0]);
294
+ if (!address) {
295
+ logger_1.logger.warn("onAccountsChanged: Invalid address received", accounts[0]);
296
+ return [2 /*return*/];
297
+ }
298
+ if (!(this.wallet.provider && this.wallet.provider !== provider)) return [3 /*break*/, 26];
299
+ currentStoredAddress = this.wallet.evmAddress;
300
+ newProviderAddress = (0, address_1.validateAndChecksumAddress)(address);
301
+ logger_1.logger.info("OnAccountsChanged: Different provider attempting to connect", {
302
+ activeProvider: this.registry.infoFor(this.wallet.provider).name,
303
+ eventProvider: this.registry.infoFor(provider).name,
304
+ currentStoredAddress: currentStoredAddress,
305
+ newProviderAddress: newProviderAddress,
306
+ });
307
+ _a.label = 10;
308
+ case 10:
309
+ _a.trys.push([10, 22, , 26]);
310
+ return [4 /*yield*/, this.registry.accountsOf(this.wallet.provider)];
311
+ case 11:
312
+ activeProviderAccounts = _a.sent();
313
+ // The probe is asynchronous too, so check before issuing anything.
314
+ // Every branch below reads the CURRENT evm state, so a switch that
315
+ // went stale during the probe would emit a false disconnect for
316
+ // whoever claimed the namespace, and clear them.
317
+ if (!this.wallet.isCurrent(observation))
318
+ return [2 /*return*/];
319
+ logger_1.logger.info("OnAccountsChanged: Checking current provider accounts", {
320
+ activeProvider: this.registry.infoFor(this.wallet.provider).name,
321
+ accountsLength: activeProviderAccounts
322
+ ? activeProviderAccounts.length
323
+ : 0,
324
+ accounts: activeProviderAccounts,
325
+ });
326
+ if (!(activeProviderAccounts && activeProviderAccounts.length > 0)) return [3 /*break*/, 17];
327
+ if (!(newProviderAddress &&
328
+ currentStoredAddress &&
329
+ newProviderAddress !== currentStoredAddress)) return [3 /*break*/, 15];
330
+ logger_1.logger.info("OnAccountsChanged: Different address detected, switching providers despite current provider having accounts", {
331
+ activeProvider: this.registry.infoFor(this.wallet.provider).name,
332
+ eventProvider: this.registry.infoFor(provider).name,
333
+ currentAddress: currentStoredAddress,
334
+ newAddress: newProviderAddress,
335
+ reason: PROVIDER_SWITCH_REASONS.ADDRESS_MISMATCH,
336
+ });
337
+ if (!this.deps.isAutocaptureEnabled("disconnect")) return [3 /*break*/, 13];
338
+ return [4 /*yield*/, this.deps.disconnect({
339
+ chainId: this.wallet.evmChainId,
340
+ address: this.wallet.evmAddress,
341
+ })];
342
+ case 12:
343
+ _a.sent();
344
+ return [3 /*break*/, 14];
345
+ case 13:
346
+ logger_1.logger.debug("OnAccountsChanged: Disconnect event skipped during provider switch (autocapture.disconnect: false)");
347
+ // Still clear state even if not tracking the event
348
+ this.wallet.clear('evm');
349
+ _a.label = 14;
350
+ case 14:
351
+ if (!this.wallet.isCurrent(observation))
352
+ return [2 /*return*/];
353
+ // Clear state and let the new provider become active
354
+ this.wallet.clearProvider();
355
+ return [3 /*break*/, 16];
356
+ case 15:
357
+ logger_1.logger.info("OnAccountsChanged: Current provider still has accounts and same address, ignoring new provider", {
358
+ activeProvider: this.registry.infoFor(this.wallet.provider).name,
359
+ eventProvider: this.registry.infoFor(provider).name,
360
+ activeProviderAccountsCount: activeProviderAccounts.length,
361
+ currentAddress: currentStoredAddress,
362
+ newAddress: newProviderAddress,
363
+ });
364
+ return [2 /*return*/];
365
+ case 16: return [3 /*break*/, 21];
366
+ case 17:
367
+ logger_1.logger.info("OnAccountsChanged: Current provider has no accounts, switching to new provider", {
368
+ oldProvider: this.registry.infoFor(this.wallet.provider).name,
369
+ newProvider: this.registry.infoFor(provider).name,
370
+ reason: PROVIDER_SWITCH_REASONS.NO_ACCOUNTS,
371
+ });
372
+ if (!this.deps.isAutocaptureEnabled("disconnect")) return [3 /*break*/, 19];
373
+ return [4 /*yield*/, this.deps.disconnect({
374
+ chainId: this.wallet.evmChainId,
375
+ address: this.wallet.evmAddress,
376
+ })];
377
+ case 18:
378
+ _a.sent();
379
+ return [3 /*break*/, 20];
380
+ case 19:
381
+ logger_1.logger.debug("OnAccountsChanged: Disconnect event skipped for old provider (autocapture.disconnect: false)");
382
+ // Still clear state even if not tracking the event
383
+ this.wallet.clear('evm');
384
+ _a.label = 20;
385
+ case 20:
386
+ if (!this.wallet.isCurrent(observation))
387
+ return [2 /*return*/];
388
+ _a.label = 21;
389
+ case 21: return [3 /*break*/, 26];
390
+ case 22:
391
+ error_2 = _a.sent();
392
+ logger_1.logger.warn("OnAccountsChanged: Could not check current provider accounts, switching to new provider", {
393
+ error: error_2 instanceof Error ? error_2.message : String(error_2),
394
+ errorType: error_2 instanceof Error ? error_2.constructor.name : typeof error_2,
395
+ oldProvider: this.wallet.provider
396
+ ? this.registry.infoFor(this.wallet.provider).name
397
+ : "unknown",
398
+ newProvider: this.registry.infoFor(provider).name,
399
+ reason: PROVIDER_SWITCH_REASONS.CHECK_FAILED,
400
+ });
401
+ if (!this.deps.isAutocaptureEnabled("disconnect")) return [3 /*break*/, 24];
402
+ return [4 /*yield*/, this.deps.disconnect({
403
+ chainId: this.wallet.evmChainId,
404
+ address: this.wallet.evmAddress,
405
+ })];
406
+ case 23:
407
+ _a.sent();
408
+ return [3 /*break*/, 25];
409
+ case 24:
410
+ logger_1.logger.debug("OnAccountsChanged: Disconnect event skipped for failed provider check (autocapture.disconnect: false)");
411
+ // Still clear state even if not tracking the event
412
+ this.wallet.clear('evm');
413
+ _a.label = 25;
414
+ case 25:
415
+ if (!this.wallet.isCurrent(observation))
416
+ return [2 /*return*/];
417
+ return [3 /*break*/, 26];
418
+ case 26:
419
+ // Set provider if none exists (first connection)
420
+ if (!this.wallet.provider) {
421
+ this.wallet.provider = provider;
422
+ }
423
+ // If both the provider and address are the same, no-op
424
+ if (this.wallet.provider === provider && address === this.wallet.evmAddress) {
425
+ return [2 /*return*/];
426
+ }
427
+ nextChainId = this.registry.resolveChainId(provider);
428
+ wasDisconnected = !this.wallet.evmAddress;
429
+ // Update state regardless of whether connect *event* tracking is enabled,
430
+ // so disconnect events keep valid address/chainId values. (excludeChains is
431
+ // NOT suppression - it still updates state so currentChainId can gate
432
+ // events.)
433
+ if (this.deps.isTrackingSuppressed()) {
434
+ this.wallet.clearStaleEvmWalletOnSwitchWhileSuppressed(address);
435
+ }
436
+ else {
437
+ this.wallet.set('evm', { address: address, chainId: nextChainId });
438
+ }
439
+ providerInfo = this.registry.infoFor(provider);
440
+ effectiveChainId = nextChainId || 0;
441
+ if (this.deps.isAutocaptureEnabled("connect") &&
442
+ this.shouldReportConnect(provider, address)) {
443
+ logger_1.logger.info("OnAccountsChanged: Detected wallet connection, emitting connect event", {
444
+ chainId: nextChainId,
445
+ address: address,
446
+ wasDisconnected: wasDisconnected,
447
+ providerName: providerInfo.name,
448
+ rdns: providerInfo.rdns,
449
+ hasChainId: !!nextChainId,
450
+ });
451
+ if (effectiveChainId === 0) {
452
+ logger_1.logger.info("OnAccountsChanged: Using fallback chainId 0 for connect event");
453
+ }
454
+ this.markConnectReported(provider, address, effectiveChainId);
455
+ this.deps.connect({
456
+ chainId: effectiveChainId,
457
+ address: address,
458
+ }, {
459
+ providerName: providerInfo.name,
460
+ rdns: providerInfo.rdns,
461
+ }).catch(function (error) {
462
+ logger_1.logger.error("Failed to track connect event during account change:", error);
463
+ });
464
+ }
465
+ else {
466
+ logger_1.logger.debug("OnAccountsChanged: Connect event skipped (autocapture.connect: false)", {
467
+ chainId: nextChainId,
468
+ address: address,
469
+ providerName: providerInfo.name,
470
+ });
471
+ }
472
+ return [2 /*return*/];
473
+ }
474
+ });
475
+ });
476
+ };
477
+ EvmEventTracker.prototype.registerChainChangedListener = function (provider) {
478
+ var _this = this;
479
+ logger_1.logger.info("registerChainChangedListener");
480
+ var listener = function () {
481
+ var args = [];
482
+ for (var _i = 0; _i < arguments.length; _i++) {
483
+ args[_i] = arguments[_i];
484
+ }
485
+ return _this.onChainChanged(provider, args[0]);
486
+ };
487
+ provider.on("chainChanged", listener);
488
+ this.registry.addListener(provider, "chainChanged", listener);
489
+ };
490
+ EvmEventTracker.prototype.onChainChanged = function (provider, chainIdHex) {
491
+ return __awaiter(this, void 0, void 0, function () {
492
+ var nextChainId, error_3;
493
+ return __generator(this, function (_a) {
494
+ switch (_a.label) {
495
+ case 0:
496
+ logger_1.logger.info("onChainChanged", chainIdHex);
497
+ nextChainId = (0, chain_1.parseChainId)(chainIdHex);
498
+ // Record it for THIS provider regardless of which one is active. This is
499
+ // the only way an autocaptured event from a non-active wallet can learn
500
+ // its chain without putting an RPC on that wallet's transport.
501
+ this.registry.rememberChain(provider, nextChainId);
502
+ // Beyond that, a chain event from a NON-active provider is observation
503
+ // only when chain autocapture is off.
504
+ //
505
+ // This listener is now registered unconditionally, so that a signature can
506
+ // be labelled with its signer's chain. `handleProviderMismatch()` treats a
507
+ // chain event from another wallet as a wallet switch and clears the active
508
+ // wallet's address and chain. That is the established behaviour of the
509
+ // chain feature and stays exactly as it was, but it must not start firing
510
+ // for apps that never asked for chain tracking: a second wallet switching
511
+ // network would silently erase the active wallet's attribution.
512
+ // Observation only when chain autocapture is off, whether or not an
513
+ // active provider has been established yet.
514
+ //
515
+ // `isProviderMismatch()` is false while `_provider` is undefined, which is
516
+ // exactly the state left by restoring a wallet from the active-wallet
517
+ // cookie. A background wallet's `chainChanged` could therefore claim the
518
+ // active slot and overwrite the restored wallet's chain - suppressing
519
+ // allowed events, or letting excluded ones through. The active provider is
520
+ // established by an actual account/connect/request association, not by
521
+ // another wallet changing network.
522
+ if (!this.deps.isAutocaptureEnabled("chain") && provider !== this.wallet.provider) {
523
+ return [2 /*return*/];
524
+ }
525
+ // Only handle chain changes for the active provider (or if none is set yet)
526
+ // A chain event from a non-active EVM provider is only a wallet switch
527
+ // on the EVM side. While Solana holds the active slot it is background
528
+ // noise: letting it through relabelled the session as EVM with no wallet
529
+ // and wiped the EVM wallet we were tracking, on the strength of a network
530
+ // change in a wallet nobody was using. The chain itself is still
531
+ // recorded above, so a later request through that provider is labelled
532
+ // correctly.
533
+ if (this.isProviderMismatch(provider)) {
534
+ if (this.wallet.activeNamespace === "solana")
535
+ return [2 /*return*/];
536
+ this.handleProviderMismatch(provider);
537
+ }
538
+ // Chain changes only matter for connected users
539
+ if (!this.wallet.evmAddress) {
540
+ logger_1.logger.info("OnChainChanged: No current address, user appears disconnected");
541
+ return [2 /*return*/, Promise.resolve()];
542
+ }
543
+ // Set provider if none exists
544
+ if (!this.wallet.provider) {
545
+ this.wallet.provider = provider;
546
+ }
547
+ this.wallet.set('evm', { chainId: nextChainId });
548
+ _a.label = 1;
549
+ case 1:
550
+ _a.trys.push([1, 5, , 6]);
551
+ if (!this.deps.isAutocaptureEnabled("chain")) return [3 /*break*/, 3];
552
+ // Awaited, so a failing emission is caught below rather than escaping
553
+ // as an unhandled rejection out of the provider's event listener.
554
+ // `return`ing the promise left the catch here unreachable.
555
+ return [4 /*yield*/, this.deps.chain({
556
+ chainId: nextChainId,
557
+ address: this.wallet.evmAddress,
558
+ })];
559
+ case 2:
560
+ // Awaited, so a failing emission is caught below rather than escaping
561
+ // as an unhandled rejection out of the provider's event listener.
562
+ // `return`ing the promise left the catch here unreachable.
563
+ _a.sent();
564
+ return [3 /*break*/, 4];
565
+ case 3:
566
+ logger_1.logger.debug("OnChainChanged: Chain event skipped (autocapture.chain: false)", {
567
+ chainId: this.wallet.evmChainId,
568
+ address: this.wallet.evmAddress,
569
+ });
570
+ _a.label = 4;
571
+ case 4: return [3 /*break*/, 6];
572
+ case 5:
573
+ error_3 = _a.sent();
574
+ logger_1.logger.error("OnChainChanged: Failed to emit chain event:", error_3);
575
+ return [3 /*break*/, 6];
576
+ case 6: return [2 /*return*/];
577
+ }
578
+ });
579
+ });
580
+ };
581
+ /**
582
+ * Record a provider's chain from its `connect` event, and nothing else.
583
+ *
584
+ * Used when connect autocapture is off. `connect` carries `chainId` in its
585
+ * payload, so this needs no RPC - unlike the full handler, which resolves
586
+ * the account.
587
+ */
588
+ EvmEventTracker.prototype.registerConnectChainObserver = function (provider) {
589
+ var _this = this;
590
+ var listener = function () {
591
+ var args = [];
592
+ for (var _i = 0; _i < arguments.length; _i++) {
593
+ args[_i] = arguments[_i];
594
+ }
595
+ var connection = args[0];
596
+ if (typeof (connection === null || connection === void 0 ? void 0 : connection.chainId) !== "string")
597
+ return;
598
+ _this.registry.rememberChain(provider, (0, chain_1.parseChainId)(connection.chainId));
599
+ };
600
+ provider.on("connect", listener);
601
+ this.registry.addListener(provider, "connect", listener);
602
+ };
603
+ /**
604
+ * Whether a connect for this wallet still needs reporting.
605
+ *
606
+ * True when nothing has been reported for this provider, or when the account
607
+ * changed. A wallet already reported is not reported again.
608
+ *
609
+ * Deliberately does NOT re-report to correct a chain. When `accountsChanged`
610
+ * wins the race on a provider that exposes no synchronous `chainId`, the
611
+ * connect carries 0 - honestly, since the chain is unknown at that instant -
612
+ * and the `connect` payload that follows knows the real one. Emitting again
613
+ * to relabel would mean two connects for one connection, which is the bug
614
+ * this whole path exists to prevent. That payload still corrects
615
+ * `currentChainId`, so everything after it is attributed properly.
616
+ */
617
+ EvmEventTracker.prototype.shouldReportConnect = function (provider, address) {
618
+ var reported = this._announcedConnect.get(provider);
619
+ if (!reported)
620
+ return true;
621
+ return reported.address.toLowerCase() !== address.toLowerCase();
622
+ };
623
+ /**
624
+ * Record a connect as reported - but only if it will actually be sent.
625
+ *
626
+ * `connect()` passes through `shouldTrack()`, which refuses an unresolvable
627
+ * chain when `tracking.excludeChains` is configured. Marking a refused event
628
+ * as reported would suppress the authoritative one that follows.
629
+ */
630
+ EvmEventTracker.prototype.markConnectReported = function (provider, address, chainId) {
631
+ if (!this.deps.willTrackEvent(chainId))
632
+ return;
633
+ this._announcedConnect.set(provider, { address: address, chainId: chainId });
634
+ };
635
+ EvmEventTracker.prototype.registerConnectListener = function (provider) {
636
+ var _this = this;
637
+ logger_1.logger.info("registerConnectListener");
638
+ var listener = function () {
639
+ var args = [];
640
+ for (var _i = 0; _i < arguments.length; _i++) {
641
+ args[_i] = arguments[_i];
642
+ }
643
+ var connection = args[0];
644
+ _this.onConnected(provider, connection);
645
+ };
646
+ provider.on("connect", listener);
647
+ this.registry.addListener(provider, "connect", listener);
648
+ };
649
+ EvmEventTracker.prototype.registerDisconnectListener = function (provider) {
650
+ var _this = this;
651
+ logger_1.logger.info("registerDisconnectListener");
652
+ var listener = function (_error) { return __awaiter(_this, void 0, void 0, function () {
653
+ var e_1;
654
+ return __generator(this, function (_a) {
655
+ switch (_a.label) {
656
+ case 0:
657
+ if (this.wallet.provider !== provider)
658
+ return [2 /*return*/];
659
+ // As in the accountsChanged disconnect path: the reported connect ends
660
+ // with the connection.
661
+ this._announcedConnect.delete(provider);
662
+ logger_1.logger.info("OnDisconnect: Wallet disconnect event received, current state:", {
663
+ currentAddress: this.wallet.evmAddress,
664
+ currentChainId: this.wallet.evmChainId,
665
+ });
666
+ if (!this.deps.isAutocaptureEnabled("disconnect")) return [3 /*break*/, 5];
667
+ _a.label = 1;
668
+ case 1:
669
+ _a.trys.push([1, 3, , 4]);
670
+ // Pass current state explicitly to ensure we have the data for the disconnect event
671
+ return [4 /*yield*/, this.deps.disconnect({
672
+ chainId: this.wallet.evmChainId,
673
+ address: this.wallet.evmAddress,
674
+ })];
675
+ case 2:
676
+ // Pass current state explicitly to ensure we have the data for the disconnect event
677
+ _a.sent();
678
+ return [3 /*break*/, 4];
679
+ case 3:
680
+ e_1 = _a.sent();
681
+ logger_1.logger.error("Error during disconnect in disconnect listener", e_1);
682
+ return [3 /*break*/, 4];
683
+ case 4: return [3 /*break*/, 6];
684
+ case 5:
685
+ logger_1.logger.debug("OnDisconnect: Disconnect event skipped (autocapture.disconnect: false)");
686
+ this.wallet.beginDisconnect("evm");
687
+ // Still clear state even if not tracking the event
688
+ this.wallet.clear('evm');
689
+ _a.label = 6;
690
+ case 6: return [2 /*return*/];
691
+ }
692
+ });
693
+ }); };
694
+ provider.on("disconnect", listener);
695
+ this.registry.addListener(provider, "disconnect", listener);
696
+ };
697
+ EvmEventTracker.prototype.onConnected = function (provider, connection) {
698
+ return __awaiter(this, void 0, void 0, function () {
699
+ var disconnectsBefore, stateTicket, chainId, address, committed, movedElsewhere, wasDisconnected, isActiveProvider, providerInfo, effectiveChainId, providerInfo, e_2;
700
+ return __generator(this, function (_a) {
701
+ switch (_a.label) {
702
+ case 0:
703
+ logger_1.logger.info("onConnected", connection);
704
+ disconnectsBefore = this.wallet.disconnectsSoFar("evm");
705
+ stateTicket = this.wallet.currentObservation("evm");
706
+ _a.label = 1;
707
+ case 1:
708
+ _a.trys.push([1, 3, , 4]);
709
+ if (!(connection === null || connection === void 0 ? void 0 : connection.chainId) || typeof connection.chainId !== "string")
710
+ return [2 /*return*/];
711
+ chainId = (0, chain_1.parseChainId)(connection.chainId);
712
+ // Record it for this provider before anything can bail out below.
713
+ this.registry.rememberChain(provider, chainId);
714
+ return [4 /*yield*/, this.registry.addressOf(provider)];
715
+ case 2:
716
+ address = _a.sent();
717
+ // A newer signal arrived while we were resolving the address. Claiming
718
+ // the namespace now would write this stale view over it, and would make
719
+ // a disconnect still in flight look stale so it skipped its cleanup. A
720
+ // reconnect that started AFTER the disconnect holds a newer ticket and
721
+ // is reported normally.
722
+ if (this.wallet.disconnectsSoFar("evm") !== disconnectsBefore) {
723
+ logger_1.logger.info("onConnected: The wallet disconnected after this observation began; dropping it");
724
+ return [2 /*return*/];
725
+ }
726
+ committed = this.wallet.evmAddress;
727
+ movedElsewhere = !!committed &&
728
+ !!address &&
729
+ committed.toLowerCase() !== address.toLowerCase();
730
+ if (provider === this.wallet.provider &&
731
+ !this.wallet.isCurrent(stateTicket) &&
732
+ movedElsewhere) {
733
+ logger_1.logger.info("onConnected: A newer signal from this provider has overtaken this connect observation; dropping it");
734
+ return [2 /*return*/];
735
+ }
736
+ if (chainId && address) {
737
+ wasDisconnected = !this.wallet.evmAddress;
738
+ // Set provider if none exists
739
+ if (!this.wallet.provider) {
740
+ this.wallet.provider = provider;
741
+ }
742
+ isActiveProvider = this.wallet.provider === provider;
743
+ // Update state from active provider so disconnect events keep valid
744
+ // address/chainId values - except while suppressed, where we must not
745
+ // LEARN identity (only drop a stale EVM wallet on a switch).
746
+ if (isActiveProvider) {
747
+ if (this.deps.isTrackingSuppressed()) {
748
+ this.wallet.clearStaleEvmWalletOnSwitchWhileSuppressed(address);
749
+ }
750
+ else {
751
+ this.wallet.set('evm', {
752
+ chainId: chainId,
753
+ address: (0, address_1.validateAndChecksumAddress)(address) || undefined,
754
+ });
755
+ }
756
+ }
757
+ // Conditionally emit connect event based on tracking configuration.
758
+ //
759
+ // Both handlers observe one connection, so `shouldReportConnect()`
760
+ // decides which of them reports it. It keys on what was actually
761
+ // REPORTED, not on whether an address is known: an address can be
762
+ // present with no connect ever sent - restored from the active-wallet
763
+ // cookie, or reported with an unresolved chain and then refused by
764
+ // `excludeChains` - and this payload carries the authoritative chain,
765
+ // so it must be able to supersede such a report.
766
+ if (isActiveProvider &&
767
+ this.wallet.evmAddress &&
768
+ this.shouldReportConnect(provider, address)) {
769
+ providerInfo = this.registry.infoFor(provider);
770
+ effectiveChainId = chainId || 0;
771
+ if (this.deps.isAutocaptureEnabled("connect")) {
772
+ logger_1.logger.info("OnConnected: Detected wallet connection, emitting connect event", {
773
+ chainId: chainId,
774
+ wasDisconnected: wasDisconnected,
775
+ providerName: providerInfo.name,
776
+ rdns: providerInfo.rdns,
777
+ hasChainId: !!chainId,
778
+ isActiveProvider: isActiveProvider,
779
+ });
780
+ if (effectiveChainId === 0) {
781
+ logger_1.logger.info("OnConnected: Using fallback chainId 0 for connect event");
782
+ }
783
+ this.markConnectReported(provider, address, effectiveChainId);
784
+ this.deps.connect({
785
+ chainId: effectiveChainId,
786
+ address: address,
787
+ }, {
788
+ providerName: providerInfo.name,
789
+ rdns: providerInfo.rdns,
790
+ }).catch(function (error) {
791
+ logger_1.logger.error("Failed to track connect event during provider connection:", error);
792
+ });
793
+ }
794
+ else {
795
+ logger_1.logger.debug("OnConnected: Connect event skipped (autocapture.connect: false)", {
796
+ chainId: chainId,
797
+ address: address,
798
+ providerName: providerInfo.name,
799
+ });
800
+ }
801
+ }
802
+ else if (address && !isActiveProvider) {
803
+ providerInfo = this.registry.infoFor(provider);
804
+ logger_1.logger.debug("OnConnected: Skipping connect event for non-active provider", {
805
+ chainId: chainId,
806
+ providerName: providerInfo.name,
807
+ rdns: providerInfo.rdns,
808
+ isActiveProvider: isActiveProvider,
809
+ activeProviderInfo: this.wallet.provider
810
+ ? this.registry.infoFor(this.wallet.provider)
811
+ : null,
812
+ });
813
+ }
814
+ }
815
+ return [3 /*break*/, 4];
816
+ case 3:
817
+ e_2 = _a.sent();
818
+ logger_1.logger.error("Error handling connect event", e_2);
819
+ return [3 /*break*/, 4];
820
+ case 4: return [2 /*return*/];
821
+ }
822
+ });
823
+ });
824
+ };
825
+ EvmEventTracker.prototype.getProviders = function () {
826
+ return __awaiter(this, void 0, void 0, function () {
827
+ var store, providers, injected, injectedProviderInfo, injectedDetail, uniqueProviders, _i, uniqueProviders_1, detail;
828
+ var _this = this;
829
+ return __generator(this, function (_a) {
830
+ store = (0, mipd_1.createStore)();
831
+ providers = store.getProviders();
832
+ this.unsubscribeDiscovery = store.subscribe(function (providerDetails) {
833
+ providers = providerDetails;
834
+ // Process newly added providers with proper deduplication
835
+ var newlyAddedDetails = providerDetails.filter(function (detail) {
836
+ var provider = detail === null || detail === void 0 ? void 0 : detail.provider;
837
+ return provider && !_this.registry.isSeen(provider);
838
+ });
839
+ // Add new providers to the array without overwriting existing ones
840
+ for (var _i = 0, newlyAddedDetails_1 = newlyAddedDetails; _i < newlyAddedDetails_1.length; _i++) {
841
+ var detail = newlyAddedDetails_1[_i];
842
+ _this.registry.add(detail);
843
+ }
844
+ // Track listeners for newly discovered providers only
845
+ var newDetails = providerDetails.filter(function (detail) {
846
+ var p = detail === null || detail === void 0 ? void 0 : detail.provider;
847
+ return !!p && !_this.registry.isTracked(p);
848
+ });
849
+ if (newDetails.length > 0) {
850
+ _this.trackProviders(newDetails);
851
+ // Detect newly discovered wallets (session de-dupes) with error handling
852
+ (function () { return __awaiter(_this, void 0, void 0, function () {
853
+ var e_3;
854
+ return __generator(this, function (_a) {
855
+ switch (_a.label) {
856
+ case 0:
857
+ _a.trys.push([0, 2, , 3]);
858
+ return [4 /*yield*/, this.detectWallets(newDetails)];
859
+ case 1:
860
+ _a.sent();
861
+ return [3 /*break*/, 3];
862
+ case 2:
863
+ e_3 = _a.sent();
864
+ logger_1.logger.error("Formo: Failed to detect wallets", e_3);
865
+ return [3 /*break*/, 3];
866
+ case 3: return [2 /*return*/];
867
+ }
868
+ });
869
+ }); })();
870
+ }
871
+ // Clean up providers that are no longer available. Compared against
872
+ // THIS announcement: the registry's list is historical and append-only,
873
+ // so comparing against it could never find anything missing.
874
+ _this.cleanupUnavailableProviders(providerDetails);
875
+ });
876
+ // Fallback to injected provider if no providers are found
877
+ if (providers.length === 0) {
878
+ injected = typeof window !== "undefined" ? window.ethereum : undefined;
879
+ if (injected) {
880
+ // If we have already detected and cached the injected provider, and it's the same instance, return the cached result
881
+ if (this.registry.injected &&
882
+ this.registry.injected.provider === injected) {
883
+ // Ensure it's tracked
884
+ if (!this.registry.isTracked(injected)) {
885
+ this.trackEIP1193Provider(injected);
886
+ }
887
+ // Merge with existing providers instead of overwriting
888
+ this.registry.add(this.registry.injected);
889
+ return [2 /*return*/, this.registry.all];
890
+ }
891
+ // Re-check if the injected provider is already tracked just before tracking
892
+ if (!this.registry.isTracked(injected)) {
893
+ this.trackEIP1193Provider(injected);
894
+ }
895
+ injectedProviderInfo = (0, provider_1.detectInjectedProviderInfo)(injected);
896
+ injectedDetail = {
897
+ provider: injected,
898
+ info: injectedProviderInfo,
899
+ };
900
+ // Cache the detected injected provider detail
901
+ this.registry.injected = injectedDetail;
902
+ // Merge with existing providers instead of overwriting
903
+ this.registry.add(injectedDetail);
904
+ }
905
+ return [2 /*return*/, this.registry.all];
906
+ }
907
+ uniqueProviders = providers.filter(function (detail) {
908
+ var provider = detail === null || detail === void 0 ? void 0 : detail.provider;
909
+ return provider && !_this.registry.isSeen(provider);
910
+ });
911
+ // Add to seen providers and instances, ensuring no duplicates in _providers
912
+ for (_i = 0, uniqueProviders_1 = uniqueProviders; _i < uniqueProviders_1.length; _i++) {
913
+ detail = uniqueProviders_1[_i];
914
+ this.registry.add(detail);
915
+ }
916
+ return [2 /*return*/, this.registry.all];
917
+ });
918
+ });
919
+ };
920
+ EvmEventTracker.prototype.detectWallets = function (providers) {
921
+ return __awaiter(this, void 0, void 0, function () {
922
+ var _i, providers_2, eip6963ProviderDetail, err_1;
923
+ return __generator(this, function (_a) {
924
+ switch (_a.label) {
925
+ case 0:
926
+ _a.trys.push([0, 5, , 6]);
927
+ _i = 0, providers_2 = providers;
928
+ _a.label = 1;
929
+ case 1:
930
+ if (!(_i < providers_2.length)) return [3 /*break*/, 4];
931
+ eip6963ProviderDetail = providers_2[_i];
932
+ return [4 /*yield*/, this.deps.detect({
933
+ providerName: eip6963ProviderDetail === null || eip6963ProviderDetail === void 0 ? void 0 : eip6963ProviderDetail.info.name,
934
+ rdns: eip6963ProviderDetail === null || eip6963ProviderDetail === void 0 ? void 0 : eip6963ProviderDetail.info.rdns,
935
+ })];
936
+ case 2:
937
+ _a.sent();
938
+ _a.label = 3;
939
+ case 3:
940
+ _i++;
941
+ return [3 /*break*/, 1];
942
+ case 4: return [3 /*break*/, 6];
943
+ case 5:
944
+ err_1 = _a.sent();
945
+ logger_1.logger.error("Error detect all wallets:", err_1);
946
+ return [3 /*break*/, 6];
947
+ case 6: return [2 /*return*/];
948
+ }
949
+ });
950
+ });
951
+ };
952
+ /**
953
+ * Seed a provider's chain from whatever it already exposes synchronously.
954
+ *
955
+ * Most EIP-1193 implementations carry a `chainId` property (MetaMask,
956
+ * WalletConnect, Coinbase). Reading it costs nothing and cannot block.
957
+ *
958
+ * There is deliberately no RPC fallback. An earlier version probed with
959
+ * `eth_chainId` when a provider was first tracked, on the theory that
960
+ * tracking time is off the user's critical path. It is not: a serialized
961
+ * transport has ONE queue, so a stalled probe sits in front of every later
962
+ * signature and transaction the dapp makes. It could also land out of order
963
+ * - a slow probe response overwriting a newer `chainChanged` - and relabel
964
+ * events onto a chain the wallet had already left.
965
+ *
966
+ * A provider that exposes nothing stays unknown until it emits
967
+ * `chainChanged` or `connect`, and unknown is reported honestly as 0.
968
+ */
969
+ EvmEventTracker.prototype.seedProviderChainFromState = function (provider) {
970
+ var raw = provider.chainId;
971
+ var chainId = typeof raw === "string"
972
+ ? (0, chain_1.parseChainId)(raw)
973
+ : typeof raw === "number"
974
+ ? raw
975
+ : undefined;
976
+ this.registry.rememberChain(provider, chainId);
977
+ };
978
+ EvmEventTracker.prototype.untrackProvider = function (provider) {
979
+ try {
980
+ this.registry.removeListeners(provider);
981
+ // Only stop tracking it if the listeners actually came off. "Tracked"
982
+ // means "has our listeners wired up", which is still true when removal
983
+ // threw, and `cleanup()` iterates the tracked set. Forgetting it here
984
+ // regardless is what made a retained listener unreachable: the entry
985
+ // survived but nothing ever looked at it again, so the retry that
986
+ // retention exists for could never happen.
987
+ if (this.registry.attachedEvents(provider).length === 0) {
988
+ this.registry.forgetTracked(provider);
989
+ }
990
+ if (this.wallet.provider === provider) {
991
+ this.wallet.clearProvider();
992
+ }
993
+ }
994
+ catch (e) {
995
+ logger_1.logger.warn("Failed to untrack provider", e);
996
+ }
997
+ };
998
+ /**
999
+ * Clean up providers that are no longer available
1000
+ * This helps maintain consistent state and prevents memory leaks
1001
+ */
1002
+ EvmEventTracker.prototype.cleanupUnavailableProviders = function (current) {
1003
+ // Remove providers that are no longer in the current providers list
1004
+ var currentProviderInstances = new Set(current.map(function (detail) { return detail.provider; }));
1005
+ for (var _i = 0, _a = this.registry.trackedProviders(); _i < _a.length; _i++) {
1006
+ var provider = _a[_i];
1007
+ if (!currentProviderInstances.has(provider)) {
1008
+ logger_1.logger.info("Cleaning up unavailable provider: ".concat(provider.constructor.name));
1009
+ this.untrackProvider(provider);
1010
+ }
1011
+ }
1012
+ };
1013
+ /**
1014
+ * Handle provider mismatch by switching to the new provider and invalidating old tokens
1015
+ * @param provider The new provider to switch to
1016
+ */
1017
+ EvmEventTracker.prototype.handleProviderMismatch = function (provider) {
1018
+ // If this is a different provider, allow the switch
1019
+ if (this.wallet.provider) {
1020
+ // Clear any provider-specific state when switching
1021
+ this.wallet.set('evm', { address: undefined, chainId: undefined, provider: provider });
1022
+ }
1023
+ else {
1024
+ this.wallet.provider = provider;
1025
+ }
1026
+ };
1027
+ return EvmEventTracker;
1028
+ }());
1029
+ exports.EvmEventTracker = EvmEventTracker;
1030
+ //# sourceMappingURL=EvmEventTracker.js.map