@formo/analytics 1.38.2 → 1.40.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 (74) hide show
  1. package/dist/cjs/src/FormoAnalytics.d.ts +4 -1
  2. package/dist/cjs/src/FormoAnalytics.js +56 -36
  3. package/dist/cjs/src/FormoAnalyticsProvider.js +20 -0
  4. package/dist/cjs/src/event/EventFactory.d.ts +9 -1
  5. package/dist/cjs/src/event/EventFactory.js +31 -16
  6. package/dist/cjs/src/event/EventManager.d.ts +3 -1
  7. package/dist/cjs/src/event/EventManager.js +33 -4
  8. package/dist/cjs/src/event/cancellation.d.ts +4 -0
  9. package/dist/cjs/src/event/cancellation.js +7 -0
  10. package/dist/cjs/src/event/type.d.ts +1 -0
  11. package/dist/cjs/src/event/utils.d.ts +5 -1
  12. package/dist/cjs/src/event/utils.js +57 -2
  13. package/dist/cjs/src/evm/EvmEventTracker.d.ts +7 -0
  14. package/dist/cjs/src/evm/EvmEventTracker.js +31 -5
  15. package/dist/cjs/src/queue/EventQueue.d.ts +1 -0
  16. package/dist/cjs/src/queue/EventQueue.js +11 -6
  17. package/dist/cjs/src/solana/SolanaManager.d.ts +37 -9
  18. package/dist/cjs/src/solana/SolanaManager.js +135 -20
  19. package/dist/cjs/src/solana/SolanaStoreHandler.d.ts +6 -0
  20. package/dist/cjs/src/solana/SolanaStoreHandler.js +17 -8
  21. package/dist/cjs/src/solana/SolanaWalletStandardRegistry.d.ts +163 -0
  22. package/dist/cjs/src/solana/SolanaWalletStandardRegistry.js +447 -0
  23. package/dist/cjs/src/solana/index.d.ts +10 -5
  24. package/dist/cjs/src/solana/index.js +12 -6
  25. package/dist/cjs/src/solana/storeTypes.d.ts +20 -1
  26. package/dist/cjs/src/solana/types.d.ts +32 -9
  27. package/dist/cjs/src/solana/types.js +15 -0
  28. package/dist/cjs/src/solana/walletStandardTypes.d.ts +54 -0
  29. package/dist/cjs/src/solana/walletStandardTypes.js +21 -0
  30. package/dist/cjs/src/storage/StorageManager.d.ts +2 -0
  31. package/dist/cjs/src/storage/StorageManager.js +6 -0
  32. package/dist/cjs/src/storage/index.d.ts +1 -0
  33. package/dist/cjs/src/storage/index.js +7 -1
  34. package/dist/cjs/src/types/base.d.ts +15 -5
  35. package/dist/cjs/src/version.d.ts +1 -1
  36. package/dist/cjs/src/version.js +1 -1
  37. package/dist/esm/src/FormoAnalytics.d.ts +4 -1
  38. package/dist/esm/src/FormoAnalytics.js +54 -34
  39. package/dist/esm/src/FormoAnalyticsProvider.js +20 -0
  40. package/dist/esm/src/event/EventFactory.d.ts +9 -1
  41. package/dist/esm/src/event/EventFactory.js +31 -16
  42. package/dist/esm/src/event/EventManager.d.ts +3 -1
  43. package/dist/esm/src/event/EventManager.js +33 -4
  44. package/dist/esm/src/event/cancellation.d.ts +4 -0
  45. package/dist/esm/src/event/cancellation.js +4 -0
  46. package/dist/esm/src/event/type.d.ts +1 -0
  47. package/dist/esm/src/event/utils.d.ts +5 -1
  48. package/dist/esm/src/event/utils.js +56 -3
  49. package/dist/esm/src/evm/EvmEventTracker.d.ts +7 -0
  50. package/dist/esm/src/evm/EvmEventTracker.js +31 -5
  51. package/dist/esm/src/queue/EventQueue.d.ts +1 -0
  52. package/dist/esm/src/queue/EventQueue.js +11 -6
  53. package/dist/esm/src/solana/SolanaManager.d.ts +37 -9
  54. package/dist/esm/src/solana/SolanaManager.js +135 -20
  55. package/dist/esm/src/solana/SolanaStoreHandler.d.ts +6 -0
  56. package/dist/esm/src/solana/SolanaStoreHandler.js +18 -9
  57. package/dist/esm/src/solana/SolanaWalletStandardRegistry.d.ts +163 -0
  58. package/dist/esm/src/solana/SolanaWalletStandardRegistry.js +444 -0
  59. package/dist/esm/src/solana/index.d.ts +10 -5
  60. package/dist/esm/src/solana/index.js +10 -5
  61. package/dist/esm/src/solana/storeTypes.d.ts +20 -1
  62. package/dist/esm/src/solana/types.d.ts +32 -9
  63. package/dist/esm/src/solana/types.js +14 -0
  64. package/dist/esm/src/solana/walletStandardTypes.d.ts +54 -0
  65. package/dist/esm/src/solana/walletStandardTypes.js +18 -0
  66. package/dist/esm/src/storage/StorageManager.d.ts +2 -0
  67. package/dist/esm/src/storage/StorageManager.js +6 -0
  68. package/dist/esm/src/storage/index.d.ts +1 -0
  69. package/dist/esm/src/storage/index.js +5 -0
  70. package/dist/esm/src/types/base.d.ts +15 -5
  71. package/dist/esm/src/version.d.ts +1 -1
  72. package/dist/esm/src/version.js +1 -1
  73. package/dist/index.umd.min.js +1 -1
  74. package/package.json +2 -2
@@ -11,6 +11,8 @@ export interface EvmEventTrackerDeps {
11
11
  isTrackingSuppressed(): boolean;
12
12
  /** Whether an event on this chain would actually be sent. */
13
13
  willTrackEvent(chainId?: ChainID): boolean;
14
+ /** Retry detect events after the active chain changes. */
15
+ retryDetection(): void;
14
16
  /** In wagmi mode the SDK does not wrap providers itself. */
15
17
  isWagmiMode(): boolean;
16
18
  /** Emission. The tracker decides WHEN; the SDK owns the event API. */
@@ -152,6 +154,8 @@ export declare class EvmEventTracker {
152
154
  * had already replaced.
153
155
  */
154
156
  private unsubscribeDiscovery?;
157
+ /** Discovery store retained for complete listener cleanup. */
158
+ private discoveryStore?;
155
159
  constructor(wallet: WalletStateStore, registry: EvmProviderRegistry, deps: EvmEventTrackerDeps);
156
160
  /** Stop listening for wallet announcements. Called from SDK teardown. */
157
161
  cleanup(): void;
@@ -259,8 +263,11 @@ export declare class EvmEventTracker {
259
263
  private registerConnectListener;
260
264
  private registerDisconnectListener;
261
265
  private onConnected;
266
+ /** Whether Formo owns provider lifecycle tracking. */
267
+ private tracksDiscovered;
262
268
  getProviders(): Promise<readonly EIP6963ProviderDetail[]>;
263
269
  detectWallets(providers: readonly EIP6963ProviderDetail[]): Promise<void>;
270
+ detectableProviders(): readonly EIP6963ProviderDetail[];
264
271
  /**
265
272
  * Seed a provider's chain from whatever it already exposes synchronously.
266
273
  *
@@ -270,7 +270,7 @@ var EvmEventTracker = /** @class */ (function () {
270
270
  };
271
271
  /** Stop listening for wallet announcements. Called from SDK teardown. */
272
272
  EvmEventTracker.prototype.cleanup = function () {
273
- var _a;
273
+ var _a, _b;
274
274
  // A replay in flight (awaiting the active wallet's accounts) must not
275
275
  // resume into a torn-down instance and commit a session there.
276
276
  this.disposed = true;
@@ -280,11 +280,13 @@ var EvmEventTracker = /** @class */ (function () {
280
280
  this.retryRequested = false;
281
281
  try {
282
282
  (_a = this.unsubscribeDiscovery) === null || _a === void 0 ? void 0 : _a.call(this);
283
+ (_b = this.discoveryStore) === null || _b === void 0 ? void 0 : _b.destroy();
283
284
  }
284
285
  catch (e) {
285
286
  logger_1.logger.warn("Failed to unsubscribe from provider discovery", e);
286
287
  }
287
288
  this.unsubscribeDiscovery = undefined;
289
+ this.discoveryStore = undefined;
288
290
  };
289
291
  EvmEventTracker.prototype.sessionGeneration = function (provider) {
290
292
  var _a;
@@ -1018,6 +1020,7 @@ var EvmEventTracker = /** @class */ (function () {
1018
1020
  this.wallet.provider = provider;
1019
1021
  }
1020
1022
  this.wallet.set('evm', { chainId: nextChainId });
1023
+ this.deps.retryDetection();
1021
1024
  _a.label = 1;
1022
1025
  case 1:
1023
1026
  _a.trys.push([1, 5, , 6]);
@@ -1335,12 +1338,17 @@ var EvmEventTracker = /** @class */ (function () {
1335
1338
  });
1336
1339
  });
1337
1340
  };
1341
+ /** Whether Formo owns provider lifecycle tracking. */
1342
+ EvmEventTracker.prototype.tracksDiscovered = function () {
1343
+ return !this.deps.isWagmiMode();
1344
+ };
1338
1345
  EvmEventTracker.prototype.getProviders = function () {
1339
1346
  return __awaiter(this, void 0, void 0, function () {
1340
1347
  var store, providers, injected, injectedProviderInfo, injectedDetail, uniqueProviders, _i, uniqueProviders_1, detail;
1341
1348
  var _this = this;
1342
1349
  return __generator(this, function (_a) {
1343
1350
  store = (0, mipd_1.createStore)();
1351
+ this.discoveryStore = store;
1344
1352
  providers = store.getProviders();
1345
1353
  this.unsubscribeDiscovery = store.subscribe(function (providerDetails) {
1346
1354
  providers = providerDetails;
@@ -1359,8 +1367,12 @@ var EvmEventTracker = /** @class */ (function () {
1359
1367
  var p = detail === null || detail === void 0 ? void 0 : detail.provider;
1360
1368
  return !!p && !_this.registry.isTracked(p);
1361
1369
  });
1362
- if (newDetails.length > 0) {
1370
+ // Wagmi detects only providers added by this announcement.
1371
+ var toDetect = _this.tracksDiscovered() ? newDetails : newlyAddedDetails;
1372
+ if (_this.tracksDiscovered() && newDetails.length > 0) {
1363
1373
  _this.trackProviders(newDetails);
1374
+ }
1375
+ if (toDetect.length > 0) {
1364
1376
  // Detect newly discovered wallets (session de-dupes) with error handling
1365
1377
  (function () { return __awaiter(_this, void 0, void 0, function () {
1366
1378
  var e_3;
@@ -1368,7 +1380,7 @@ var EvmEventTracker = /** @class */ (function () {
1368
1380
  switch (_a.label) {
1369
1381
  case 0:
1370
1382
  _a.trys.push([0, 2, , 3]);
1371
- return [4 /*yield*/, this.detectWallets(newDetails)];
1383
+ return [4 /*yield*/, this.detectWallets(toDetect)];
1372
1384
  case 1:
1373
1385
  _a.sent();
1374
1386
  return [3 /*break*/, 3];
@@ -1394,7 +1406,7 @@ var EvmEventTracker = /** @class */ (function () {
1394
1406
  if (this.registry.injected &&
1395
1407
  this.registry.injected.provider === injected) {
1396
1408
  // Ensure it's tracked
1397
- if (!this.registry.isTracked(injected)) {
1409
+ if (this.tracksDiscovered() && !this.registry.isTracked(injected)) {
1398
1410
  this.trackEIP1193Provider(injected);
1399
1411
  }
1400
1412
  // Merge with existing providers instead of overwriting
@@ -1402,7 +1414,7 @@ var EvmEventTracker = /** @class */ (function () {
1402
1414
  return [2 /*return*/, this.registry.all];
1403
1415
  }
1404
1416
  // Re-check if the injected provider is already tracked just before tracking
1405
- if (!this.registry.isTracked(injected)) {
1417
+ if (this.tracksDiscovered() && !this.registry.isTracked(injected)) {
1406
1418
  this.trackEIP1193Provider(injected);
1407
1419
  }
1408
1420
  injectedProviderInfo = (0, provider_1.detectInjectedProviderInfo)(injected);
@@ -1462,6 +1474,20 @@ var EvmEventTracker = /** @class */ (function () {
1462
1474
  });
1463
1475
  });
1464
1476
  };
1477
+ EvmEventTracker.prototype.detectableProviders = function () {
1478
+ var _a;
1479
+ var available = new Set();
1480
+ (_a = this.discoveryStore) === null || _a === void 0 ? void 0 : _a.getProviders().forEach(function (detail) {
1481
+ return available.add(detail.provider);
1482
+ });
1483
+ var injected = typeof window !== "undefined" ? window.ethereum : undefined;
1484
+ if (injected)
1485
+ available.add(injected);
1486
+ this.externallyRegistered.forEach(function (provider) { return available.add(provider); });
1487
+ return this.registry.all.filter(function (detail) {
1488
+ return available.has(detail.provider);
1489
+ });
1490
+ };
1465
1491
  /**
1466
1492
  * Seed a provider's chain from whatever it already exposes synchronously.
1467
1493
  *
@@ -28,6 +28,7 @@ export declare class EventQueue implements IEventQueue {
28
28
  private pendingFlush;
29
29
  private payloadHashes;
30
30
  private canSend?;
31
+ private generation;
31
32
  private closed;
32
33
  private disposePageLeave;
33
34
  constructor(writeKey: string, options: Options);
@@ -89,6 +89,7 @@ var EventQueue = /** @class */ (function () {
89
89
  this.queue = [];
90
90
  this.queueByteSize = 0; // running total of queued items' byteSize
91
91
  this.payloadHashes = new Set();
92
+ this.generation = 0;
92
93
  // Terminal shutdown flag. Once set, enqueue() and flush() are no-ops for
93
94
  // the rest of this instance's life. See close().
94
95
  this.closed = false;
@@ -222,6 +223,7 @@ var EventQueue = /** @class */ (function () {
222
223
  * withdrawal / SDK teardown so nothing buffered can be sent later.
223
224
  */
224
225
  EventQueue.prototype.clear = function () {
226
+ this.generation++;
225
227
  if (this.timer) {
226
228
  clearTimeout(this.timer);
227
229
  this.timer = null;
@@ -229,6 +231,8 @@ var EventQueue = /** @class */ (function () {
229
231
  this.queue = [];
230
232
  this.queueByteSize = 0;
231
233
  this.payloadHashes.clear();
234
+ this.flushed = false;
235
+ this.pendingFlush = null;
232
236
  };
233
237
  /**
234
238
  * Terminal shutdown. Unlike clear(), which only empties the buffer and can
@@ -265,11 +269,12 @@ var EventQueue = /** @class */ (function () {
265
269
  });
266
270
  EventQueue.prototype.enqueue = function (event, callback) {
267
271
  return __awaiter(this, void 0, void 0, function () {
268
- var message_id, queueItem, hasReachedFlushAt, hasReachedQueueSize;
272
+ var generation, message_id, queueItem, hasReachedFlushAt, hasReachedQueueSize;
269
273
  return __generator(this, function (_a) {
270
274
  switch (_a.label) {
271
275
  case 0:
272
276
  callback = callback || noop;
277
+ generation = this.generation;
273
278
  // A torn-down instance must never buffer, however late the caller
274
279
  // arrives. See close().
275
280
  if (this.closed)
@@ -282,12 +287,12 @@ var EventQueue = /** @class */ (function () {
282
287
  return [4 /*yield*/, this.generateMessageId(event)];
283
288
  case 1:
284
289
  message_id = _a.sent();
285
- // Re-check after the await. A caller that entered before close() is
286
- // suspended here, and on a queue that has not flushed yet its event
287
- // would push and flush immediately - the exact shape of the bug close()
288
- // exists to stop.
289
- if (this.closed)
290
+ if (this.closed || generation !== this.generation)
290
291
  return [2 /*return*/];
292
+ if (this.canSend && !this.canSend()) {
293
+ this.clear();
294
+ return [2 /*return*/];
295
+ }
291
296
  // check if the message already exists
292
297
  if (this.isDuplicate(message_id)) {
293
298
  logger_1.logger.warn("Event already enqueued, try again after ".concat((0, utils_1.millisecondsToSecond)(this.flushIntervalMs), " seconds."));
@@ -1,14 +1,27 @@
1
1
  /**
2
2
  * SolanaManager
3
3
  *
4
- * Manages the lifecycle of the Solana store integration.
5
- * Subscribes to framework-kit's zustand store for automatic event capture
6
- * of wallet connect/disconnect and transaction lifecycle events.
4
+ * Owns the two ways the SDK learns about Solana wallets:
7
5
  *
8
- * For signMessage/signTransaction tracking (not captured by the store),
6
+ * 1. `SolanaWalletStandardRegistry`: discovers wallets through the Wallet
7
+ * Standard and reports detect / connect / disconnect. On by default, so
8
+ * compatible wallets registered by Solana Kit, wallet-adapter,
9
+ * framework-kit, or another host are covered with no configuration,
10
+ * exactly like EVM wallets through EIP-6963. `solana: false` turns it off.
11
+ * 2. `SolanaStoreHandler`: subscribes to framework-kit's zustand store for
12
+ * connect / disconnect / cluster changes AND the transaction lifecycle.
13
+ * Opt-in through `solana: { store }` or `formo.solana.setStore()`.
14
+ *
15
+ * Both observe the same Wallet Standard connection when a framework-kit app
16
+ * connects. A store supplied at initialization owns wallet events; a store
17
+ * attached later takes ownership when it observes its first connection and
18
+ * adopts any connect the registry already reported. One connect per
19
+ * connection, whichever path an app is on.
20
+ *
21
+ * For signMessage/signTransaction tracking (not captured by either path),
9
22
  * use formo.signature() directly with the address and chainId.
10
23
  *
11
- * For manual event tracking without the store, use the core API directly:
24
+ * For manual event tracking, use the core API directly:
12
25
  * formo.transaction(), formo.signature(), formo.connect(), formo.disconnect().
13
26
  */
14
27
  import { FormoAnalytics } from "../FormoAnalytics";
@@ -16,9 +29,19 @@ import { SolanaCluster, SolanaOptions } from "./types";
16
29
  import { SolanaClientStore } from "./storeTypes";
17
30
  export declare class SolanaManager {
18
31
  private formo;
32
+ private readonly enabled;
19
33
  private storeHandler?;
34
+ private registry?;
20
35
  private pendingCluster?;
21
- constructor(formo: FormoAnalytics, options?: SolanaOptions);
36
+ private storeOwnsWalletEvents;
37
+ /**
38
+ * @param formo - The SDK instance events are reported to.
39
+ * @param options - `options.solana` as passed to the SDK, if an object.
40
+ * @param enabled - Whether Solana tracking is enabled. False only when the
41
+ * host app passed `solana: false`; both discovery and stores then stay off.
42
+ */
43
+ constructor(formo: FormoAnalytics, options?: SolanaOptions, enabled?: boolean);
44
+ private attachStore;
22
45
  /**
23
46
  * Set the framework-kit zustand store for automatic event tracking.
24
47
  * This enables autocapture mode — connect/disconnect and transaction events
@@ -39,11 +62,16 @@ export declare class SolanaManager {
39
62
  cluster?: SolanaCluster;
40
63
  }): void;
41
64
  /**
42
- * Update the cluster/network. Only needed if the store endpoint doesn't
43
- * contain a recognizable cluster name (e.g. custom RPC URLs).
44
- * In most cases, the cluster is auto-detected from the store's endpoint.
65
+ * Update the cluster/network.
66
+ *
67
+ * With a framework-kit store, only needed if the store endpoint doesn't
68
+ * contain a recognizable cluster name (e.g. custom RPC URLs). Without one,
69
+ * this is how a non-mainnet app tells the SDK which cluster its Wallet
70
+ * Standard connections are on, since the standard itself cannot say.
45
71
  */
46
72
  setCluster(cluster: SolanaCluster): void;
73
+ /** Names of the Wallet Standard wallets discovered so far. */
74
+ get discoveredWallets(): string[];
47
75
  cleanup(): void;
48
76
  }
49
77
  //# sourceMappingURL=SolanaManager.d.ts.map
@@ -2,34 +2,126 @@
2
2
  /**
3
3
  * SolanaManager
4
4
  *
5
- * Manages the lifecycle of the Solana store integration.
6
- * Subscribes to framework-kit's zustand store for automatic event capture
7
- * of wallet connect/disconnect and transaction lifecycle events.
5
+ * Owns the two ways the SDK learns about Solana wallets:
8
6
  *
9
- * For signMessage/signTransaction tracking (not captured by the store),
7
+ * 1. `SolanaWalletStandardRegistry`: discovers wallets through the Wallet
8
+ * Standard and reports detect / connect / disconnect. On by default, so
9
+ * compatible wallets registered by Solana Kit, wallet-adapter,
10
+ * framework-kit, or another host are covered with no configuration,
11
+ * exactly like EVM wallets through EIP-6963. `solana: false` turns it off.
12
+ * 2. `SolanaStoreHandler`: subscribes to framework-kit's zustand store for
13
+ * connect / disconnect / cluster changes AND the transaction lifecycle.
14
+ * Opt-in through `solana: { store }` or `formo.solana.setStore()`.
15
+ *
16
+ * Both observe the same Wallet Standard connection when a framework-kit app
17
+ * connects. A store supplied at initialization owns wallet events; a store
18
+ * attached later takes ownership when it observes its first connection and
19
+ * adopts any connect the registry already reported. One connect per
20
+ * connection, whichever path an app is on.
21
+ *
22
+ * For signMessage/signTransaction tracking (not captured by either path),
10
23
  * use formo.signature() directly with the address and chainId.
11
24
  *
12
- * For manual event tracking without the store, use the core API directly:
25
+ * For manual event tracking, use the core API directly:
13
26
  * formo.transaction(), formo.signature(), formo.connect(), formo.disconnect().
14
27
  */
15
28
  Object.defineProperty(exports, "__esModule", { value: true });
16
29
  exports.SolanaManager = void 0;
17
30
  var logger_1 = require("../logger");
18
31
  var SolanaStoreHandler_1 = require("./SolanaStoreHandler");
32
+ var SolanaWalletStandardRegistry_1 = require("./SolanaWalletStandardRegistry");
33
+ var types_1 = require("./types");
19
34
  var SolanaManager = /** @class */ (function () {
20
- function SolanaManager(formo, options) {
35
+ /**
36
+ * @param formo - The SDK instance events are reported to.
37
+ * @param options - `options.solana` as passed to the SDK, if an object.
38
+ * @param enabled - Whether Solana tracking is enabled. False only when the
39
+ * host app passed `solana: false`; both discovery and stores then stay off.
40
+ */
41
+ function SolanaManager(formo, options, enabled) {
42
+ if (enabled === void 0) { enabled = true; }
43
+ var _this = this;
21
44
  this.formo = formo;
22
- if (options === null || options === void 0 ? void 0 : options.store) {
23
- logger_1.logger.info("SolanaManager: Initializing store-based Solana tracking");
24
- this.storeHandler = new SolanaStoreHandler_1.SolanaStoreHandler(formo, options.store, {
25
- cluster: options.cluster,
26
- });
27
- }
28
- else if (options === null || options === void 0 ? void 0 : options.cluster) {
45
+ this.enabled = enabled;
46
+ this.storeOwnsWalletEvents = false;
47
+ if (!enabled)
48
+ return;
49
+ if (options === null || options === void 0 ? void 0 : options.cluster) {
29
50
  // Store pending cluster for when setStore is called later
30
51
  this.pendingCluster = options.cluster;
31
52
  }
53
+ // A store supplied at initialization owns wallet events from the outset:
54
+ // unlike a store attached later, it has not missed any prior registry
55
+ // state and it knows the cluster more precisely.
56
+ this.storeOwnsWalletEvents = !!(options === null || options === void 0 ? void 0 : options.store);
57
+ this.registry = new SolanaWalletStandardRegistry_1.SolanaWalletStandardRegistry({
58
+ isAutocaptureEnabled: function (t) { return _this.formo.isAutocaptureEnabled(t); },
59
+ willTrackEvent: function (chainId) { return _this.formo.willTrackEvent(chainId); },
60
+ detect: function (params) { return _this.formo.detect(params); },
61
+ connect: function (params, properties) {
62
+ return _this.formo.connect(params, properties);
63
+ },
64
+ disconnect: function (params) { return _this.formo.disconnect(params); },
65
+ chain: function (params) { return _this.formo.chain(params); },
66
+ syncWalletState: function (params) { return _this.formo.syncWalletState(params); },
67
+ currentAddress: function () { return _this.formo.currentAddress; },
68
+ ownsWalletEvents: function () { return !_this.storeOwnsWalletEvents; },
69
+ }, { cluster: options === null || options === void 0 ? void 0 : options.cluster });
70
+ if (options === null || options === void 0 ? void 0 : options.store) {
71
+ logger_1.logger.info("SolanaManager: Initializing store-based Solana tracking");
72
+ this.attachStore(options.store, options.cluster);
73
+ }
32
74
  }
75
+ SolanaManager.prototype.attachStore = function (store, cluster) {
76
+ var _this = this;
77
+ var _a;
78
+ this.storeHandler = new SolanaStoreHandler_1.SolanaStoreHandler(this.formo, store, {
79
+ cluster: cluster,
80
+ beforeWalletConnect: function (connection) {
81
+ var _a, _b;
82
+ // The store's cluster is authoritative even when chain autocapture is
83
+ // disabled. Keep central attribution correct without manufacturing a
84
+ // chain event in that mode.
85
+ _this.formo.syncWalletState({
86
+ address: connection.address,
87
+ chainId: connection.chainId,
88
+ });
89
+ if (_this.storeOwnsWalletEvents)
90
+ return true;
91
+ var reported = (_a = _this.registry) === null || _a === void 0 ? void 0 : _a.takeReportedConnection(connection.address, connection.rdns);
92
+ _this.storeOwnsWalletEvents = true;
93
+ // If Wallet Standard got there first, the store adopts that live
94
+ // connection instead of emitting it again. Correct its cluster if
95
+ // the store has more precise information.
96
+ if (!reported) {
97
+ // Nothing to adopt. Either the registry never reported this
98
+ // connection, or it reported it under another identity, in which
99
+ // case the store is about to emit a second connect for the same
100
+ // live connection. Both paths derive the rdns from the wallet's
101
+ // own name, so a mismatch means the store's connector is labelled
102
+ // differently from the registered wallet.
103
+ var held = (_b = _this.registry) === null || _b === void 0 ? void 0 : _b.reportedConnectionRdns(connection.address);
104
+ if (held) {
105
+ logger_1.logger.warn("SolanaManager: Store connector does not match the discovered wallet; the connection is reported twice", { storeRdns: connection.rdns, walletRdns: held });
106
+ }
107
+ return true;
108
+ }
109
+ if (reported.chainId !== connection.chainId &&
110
+ _this.formo.isAutocaptureEnabled("chain")) {
111
+ _this.formo.chain(connection).catch(function (error) {
112
+ logger_1.logger.error("SolanaManager: Error correcting cluster during store handoff", error);
113
+ });
114
+ }
115
+ return false;
116
+ },
117
+ });
118
+ // Keep the registry's snapshot on the store's detected cluster. This is
119
+ // silent once the store owns events; during a late handoff it corrects a
120
+ // registry-reported connection before the store adopts it.
121
+ var detectedCluster = types_1.SOLANA_CLUSTERS_BY_ID[this.storeHandler.getChainId()];
122
+ if (detectedCluster)
123
+ (_a = this.registry) === null || _a === void 0 ? void 0 : _a.setCluster(detectedCluster);
124
+ };
33
125
  /**
34
126
  * Set the framework-kit zustand store for automatic event tracking.
35
127
  * This enables autocapture mode — connect/disconnect and transaction events
@@ -48,29 +140,52 @@ var SolanaManager = /** @class */ (function () {
48
140
  */
49
141
  SolanaManager.prototype.setStore = function (store, options) {
50
142
  var _a;
143
+ if (!this.enabled) {
144
+ logger_1.logger.warn("SolanaManager: Ignoring setStore. Solana tracking is off for this instance (solana: false, or the SDK was cleaned up)");
145
+ return;
146
+ }
51
147
  (_a = this.storeHandler) === null || _a === void 0 ? void 0 : _a.cleanup();
52
- this.storeHandler = new SolanaStoreHandler_1.SolanaStoreHandler(this.formo, store, {
53
- cluster: (options === null || options === void 0 ? void 0 : options.cluster) || this.pendingCluster,
54
- });
148
+ this.storeHandler = undefined;
149
+ this.storeOwnsWalletEvents = false;
150
+ this.attachStore(store, (options === null || options === void 0 ? void 0 : options.cluster) || this.pendingCluster);
55
151
  this.pendingCluster = undefined;
56
152
  };
57
153
  /**
58
- * Update the cluster/network. Only needed if the store endpoint doesn't
59
- * contain a recognizable cluster name (e.g. custom RPC URLs).
60
- * In most cases, the cluster is auto-detected from the store's endpoint.
154
+ * Update the cluster/network.
155
+ *
156
+ * With a framework-kit store, only needed if the store endpoint doesn't
157
+ * contain a recognizable cluster name (e.g. custom RPC URLs). Without one,
158
+ * this is how a non-mainnet app tells the SDK which cluster its Wallet
159
+ * Standard connections are on, since the standard itself cannot say.
61
160
  */
62
161
  SolanaManager.prototype.setCluster = function (cluster) {
162
+ var _a;
163
+ if (!this.enabled)
164
+ return;
63
165
  if (this.storeHandler) {
64
166
  this.storeHandler.setCluster(cluster);
65
167
  }
66
168
  else {
67
169
  this.pendingCluster = cluster;
68
170
  }
171
+ (_a = this.registry) === null || _a === void 0 ? void 0 : _a.setCluster(cluster);
69
172
  };
173
+ Object.defineProperty(SolanaManager.prototype, "discoveredWallets", {
174
+ /** Names of the Wallet Standard wallets discovered so far. */
175
+ get: function () {
176
+ var _a, _b;
177
+ return (_b = (_a = this.registry) === null || _a === void 0 ? void 0 : _a.walletNames) !== null && _b !== void 0 ? _b : [];
178
+ },
179
+ enumerable: false,
180
+ configurable: true
181
+ });
70
182
  SolanaManager.prototype.cleanup = function () {
71
- var _a;
183
+ var _a, _b;
72
184
  (_a = this.storeHandler) === null || _a === void 0 ? void 0 : _a.cleanup();
73
185
  this.storeHandler = undefined;
186
+ this.storeOwnsWalletEvents = false;
187
+ (_b = this.registry) === null || _b === void 0 ? void 0 : _b.cleanup();
188
+ this.registry = undefined;
74
189
  };
75
190
  return SolanaManager;
76
191
  }());
@@ -48,8 +48,14 @@ export declare class SolanaStoreHandler {
48
48
  * When true, auto-detection from the store endpoint is disabled.
49
49
  */
50
50
  private explicitCluster;
51
+ private beforeWalletConnect?;
51
52
  constructor(formoAnalytics: FormoAnalytics, store: SolanaClientStore, options?: {
52
53
  cluster?: SolanaCluster;
54
+ beforeWalletConnect?: (connection: {
55
+ address: string;
56
+ chainId: number;
57
+ rdns: string;
58
+ }) => boolean;
53
59
  });
54
60
  /**
55
61
  * Update the cluster/network.
@@ -69,6 +69,7 @@ var SolanaStoreHandler = /** @class */ (function () {
69
69
  this.formo = formoAnalytics;
70
70
  this.store = store;
71
71
  this.explicitCluster = !!(options === null || options === void 0 ? void 0 : options.cluster);
72
+ this.beforeWalletConnect = options === null || options === void 0 ? void 0 : options.beforeWalletConnect;
72
73
  this.cluster = (options === null || options === void 0 ? void 0 : options.cluster) || this.detectClusterFromStore(store) || "mainnet-beta";
73
74
  this.chainId = types_1.SOLANA_CHAIN_IDS[this.cluster];
74
75
  logger_1.logger.info("SolanaStoreHandler: Initializing framework-kit store integration", {
@@ -141,7 +142,7 @@ var SolanaStoreHandler = /** @class */ (function () {
141
142
  logger_1.logger.info("SolanaStoreHandler: Wallet subscription set up");
142
143
  };
143
144
  SolanaStoreHandler.prototype.checkInitialWalletState = function () {
144
- var _a;
145
+ var _a, _b, _c;
145
146
  var state = this.store.getState();
146
147
  var wallet = state.wallet;
147
148
  if (wallet.status === "connected") {
@@ -154,11 +155,17 @@ var SolanaStoreHandler = /** @class */ (function () {
154
155
  address: address,
155
156
  chainId: this.chainId,
156
157
  });
157
- if (this.formo.isAutocaptureEnabled("connect")) {
158
- var connectorName = ((_a = wallet.session.connector) === null || _a === void 0 ? void 0 : _a.name) || wallet.connectorId;
158
+ var connectorName = ((_a = wallet.session.connector) === null || _a === void 0 ? void 0 : _a.name) || wallet.connectorId;
159
+ var rdns = (0, types_1.solanaWalletRdns)(connectorName);
160
+ var shouldEmit = (_c = (_b = this.beforeWalletConnect) === null || _b === void 0 ? void 0 : _b.call(this, {
161
+ address: address,
162
+ chainId: this.chainId,
163
+ rdns: rdns,
164
+ })) !== null && _c !== void 0 ? _c : true;
165
+ if (shouldEmit && this.formo.isAutocaptureEnabled("connect")) {
159
166
  this.formo.connect({ chainId: this.chainId, address: address }, {
160
167
  providerName: connectorName,
161
- rdns: "sol.wallet.".concat(connectorName.toLowerCase().replace(/\s+/g, "")),
168
+ rdns: rdns,
162
169
  }).catch(function (error) {
163
170
  logger_1.logger.error("SolanaStoreHandler: Error emitting initial connect", error);
164
171
  });
@@ -190,7 +197,7 @@ var SolanaStoreHandler = /** @class */ (function () {
190
197
  this.lastWalletStatus = wallet.status;
191
198
  };
192
199
  SolanaStoreHandler.prototype.handleConnect = function (wallet) {
193
- var _a;
200
+ var _a, _b, _c;
194
201
  var address = wallet.session.account.address;
195
202
  if (!address || (0, address_1.isBlockedSolanaAddress)(address)) {
196
203
  return;
@@ -206,11 +213,13 @@ var SolanaStoreHandler = /** @class */ (function () {
206
213
  chainId: chainId,
207
214
  connector: wallet.connectorId,
208
215
  });
209
- if (this.formo.isAutocaptureEnabled("connect")) {
210
- var connectorName = ((_a = wallet.session.connector) === null || _a === void 0 ? void 0 : _a.name) || wallet.connectorId;
216
+ var connectorName = ((_a = wallet.session.connector) === null || _a === void 0 ? void 0 : _a.name) || wallet.connectorId;
217
+ var rdns = (0, types_1.solanaWalletRdns)(connectorName);
218
+ var shouldEmit = (_c = (_b = this.beforeWalletConnect) === null || _b === void 0 ? void 0 : _b.call(this, { address: address, chainId: chainId, rdns: rdns })) !== null && _c !== void 0 ? _c : true;
219
+ if (shouldEmit && this.formo.isAutocaptureEnabled("connect")) {
211
220
  this.formo.connect({ chainId: chainId, address: address }, {
212
221
  providerName: connectorName,
213
- rdns: "sol.wallet.".concat(connectorName.toLowerCase().replace(/\s+/g, "")),
222
+ rdns: rdns,
214
223
  }).catch(function (error) {
215
224
  logger_1.logger.error("SolanaStoreHandler: Error emitting connect", error);
216
225
  });