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