@formo/analytics 1.38.1 → 1.39.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 (60) hide show
  1. package/dist/cjs/src/FormoAnalytics.d.ts +10 -1
  2. package/dist/cjs/src/FormoAnalytics.js +20 -5
  3. package/dist/cjs/src/FormoAnalyticsProvider.js +20 -0
  4. package/dist/cjs/src/evm/EvmEventTracker.d.ts +100 -9
  5. package/dist/cjs/src/evm/EvmEventTracker.js +357 -19
  6. package/dist/cjs/src/evm/EvmProviderRegistry.d.ts +34 -2
  7. package/dist/cjs/src/evm/EvmProviderRegistry.js +61 -4
  8. package/dist/cjs/src/evm/EvmRequestTracker.d.ts +9 -4
  9. package/dist/cjs/src/evm/EvmRequestTracker.js +26 -16
  10. package/dist/cjs/src/solana/SolanaManager.d.ts +37 -9
  11. package/dist/cjs/src/solana/SolanaManager.js +135 -20
  12. package/dist/cjs/src/solana/SolanaStoreHandler.d.ts +6 -0
  13. package/dist/cjs/src/solana/SolanaStoreHandler.js +17 -8
  14. package/dist/cjs/src/solana/SolanaWalletStandardRegistry.d.ts +163 -0
  15. package/dist/cjs/src/solana/SolanaWalletStandardRegistry.js +447 -0
  16. package/dist/cjs/src/solana/index.d.ts +10 -5
  17. package/dist/cjs/src/solana/index.js +12 -6
  18. package/dist/cjs/src/solana/storeTypes.d.ts +20 -1
  19. package/dist/cjs/src/solana/types.d.ts +32 -9
  20. package/dist/cjs/src/solana/types.js +15 -0
  21. package/dist/cjs/src/solana/walletStandardTypes.d.ts +54 -0
  22. package/dist/cjs/src/solana/walletStandardTypes.js +21 -0
  23. package/dist/cjs/src/types/base.d.ts +15 -5
  24. package/dist/cjs/src/version.d.ts +1 -1
  25. package/dist/cjs/src/version.js +1 -1
  26. package/dist/cjs/src/wagmi/WagmiEventHandler.d.ts +30 -1
  27. package/dist/cjs/src/wagmi/WagmiEventHandler.js +80 -1
  28. package/dist/cjs/src/wagmi/types.d.ts +6 -0
  29. package/dist/cjs/src/wagmi/utils.js +9 -1
  30. package/dist/esm/src/FormoAnalytics.d.ts +10 -1
  31. package/dist/esm/src/FormoAnalytics.js +20 -5
  32. package/dist/esm/src/FormoAnalyticsProvider.js +20 -0
  33. package/dist/esm/src/evm/EvmEventTracker.d.ts +100 -9
  34. package/dist/esm/src/evm/EvmEventTracker.js +357 -19
  35. package/dist/esm/src/evm/EvmProviderRegistry.d.ts +34 -2
  36. package/dist/esm/src/evm/EvmProviderRegistry.js +61 -4
  37. package/dist/esm/src/evm/EvmRequestTracker.d.ts +9 -4
  38. package/dist/esm/src/evm/EvmRequestTracker.js +26 -16
  39. package/dist/esm/src/solana/SolanaManager.d.ts +37 -9
  40. package/dist/esm/src/solana/SolanaManager.js +135 -20
  41. package/dist/esm/src/solana/SolanaStoreHandler.d.ts +6 -0
  42. package/dist/esm/src/solana/SolanaStoreHandler.js +18 -9
  43. package/dist/esm/src/solana/SolanaWalletStandardRegistry.d.ts +163 -0
  44. package/dist/esm/src/solana/SolanaWalletStandardRegistry.js +444 -0
  45. package/dist/esm/src/solana/index.d.ts +10 -5
  46. package/dist/esm/src/solana/index.js +10 -5
  47. package/dist/esm/src/solana/storeTypes.d.ts +20 -1
  48. package/dist/esm/src/solana/types.d.ts +32 -9
  49. package/dist/esm/src/solana/types.js +14 -0
  50. package/dist/esm/src/solana/walletStandardTypes.d.ts +54 -0
  51. package/dist/esm/src/solana/walletStandardTypes.js +18 -0
  52. package/dist/esm/src/types/base.d.ts +15 -5
  53. package/dist/esm/src/version.d.ts +1 -1
  54. package/dist/esm/src/version.js +1 -1
  55. package/dist/esm/src/wagmi/WagmiEventHandler.d.ts +30 -1
  56. package/dist/esm/src/wagmi/WagmiEventHandler.js +80 -1
  57. package/dist/esm/src/wagmi/types.d.ts +6 -0
  58. package/dist/esm/src/wagmi/utils.js +9 -1
  59. package/dist/index.umd.min.js +1 -1
  60. package/package.json +2 -2
@@ -45,6 +45,24 @@ export declare class EvmProviderRegistry {
45
45
  private listeners;
46
46
  /** The window-injected provider, once detected, so it is not re-wrapped. */
47
47
  private injectedDetail?;
48
+ /**
49
+ * Attribution supplied by the integration layer for a provider that was
50
+ * never announced: the wagmi fallback wrap names a connector's provider
51
+ * after the CONNECTOR, so request-derived events carry the same wallet
52
+ * name as the hook-driven events from the same connection. Without it
53
+ * the name came from flag sniffing, and a custom or embedded connector
54
+ * whose provider exposes no recognised flag split one wallet's activity
55
+ * between "Injected Provider" and the connector's name.
56
+ *
57
+ * A RESOLVER, read at each `infoFor`, not a value: the name is live for
58
+ * the connector the integration layer bound it to (a WalletConnect
59
+ * session that resolves or changes its peer renames without a re-wrap),
60
+ * and the integration layer names events on its hook path with the same
61
+ * function, so the two paths cannot disagree. Which connector a
62
+ * provider is bound to is the integration layer's decision; it is not
63
+ * necessarily the connector that is current when the request runs.
64
+ */
65
+ private attributions;
48
66
  constructor(deps: EvmProviderRegistryDeps);
49
67
  get all(): readonly EIP6963ProviderDetail[];
50
68
  isTracked(provider: EIP1193Provider): boolean;
@@ -62,11 +80,25 @@ export declare class EvmProviderRegistry {
62
80
  };
63
81
  /** Add a discovered provider once. Returns false if already present. */
64
82
  add(detail: EIP6963ProviderDetail): boolean;
83
+ /**
84
+ * Record (or, with `undefined`, forget) the integration layer's own
85
+ * attribution resolver for a provider. See `attributions`.
86
+ */
87
+ rememberAttribution(provider: EIP1193Provider | undefined, resolve: (() => {
88
+ name: string;
89
+ rdns?: string;
90
+ } | undefined) | undefined): void;
91
+ /** The supplied attribution for a provider, if any resolves right now. */
92
+ private suppliedAttributionFor;
65
93
  /**
66
94
  * A provider's display name and rdns.
67
95
  *
68
- * EIP-6963 metadata is authoritative when we have it; otherwise fall back to
69
- * sniffing the injected provider, which is all a pre-6963 wallet offers.
96
+ * EIP-6963 metadata is authoritative when we have it. A supplied
97
+ * attribution (the connector a wagmi app connected through) comes next:
98
+ * it describes the SESSION, which is what the hook-driven events report.
99
+ * Otherwise fall back to sniffing the injected provider, which is all a
100
+ * pre-6963 wallet offers; a supplied name without an rdns keeps the
101
+ * sniffed rdns.
70
102
  */
71
103
  infoFor(provider: EIP1193Provider): {
72
104
  name: string;
@@ -77,6 +77,24 @@ var EvmProviderRegistry = /** @class */ (function () {
77
77
  this.chainGenerations = new WeakMap();
78
78
  /** Listeners this SDK attached, so teardown can remove exactly those. */
79
79
  this.listeners = new Map();
80
+ /**
81
+ * Attribution supplied by the integration layer for a provider that was
82
+ * never announced: the wagmi fallback wrap names a connector's provider
83
+ * after the CONNECTOR, so request-derived events carry the same wallet
84
+ * name as the hook-driven events from the same connection. Without it
85
+ * the name came from flag sniffing, and a custom or embedded connector
86
+ * whose provider exposes no recognised flag split one wallet's activity
87
+ * between "Injected Provider" and the connector's name.
88
+ *
89
+ * A RESOLVER, read at each `infoFor`, not a value: the name is live for
90
+ * the connector the integration layer bound it to (a WalletConnect
91
+ * session that resolves or changes its peer renames without a re-wrap),
92
+ * and the integration layer names events on its hook path with the same
93
+ * function, so the two paths cannot disagree. Which connector a
94
+ * provider is bound to is the integration layer's decision; it is not
95
+ * necessarily the connector that is current when the request runs.
96
+ */
97
+ this.attributions = new WeakMap();
80
98
  }
81
99
  Object.defineProperty(EvmProviderRegistry.prototype, "all", {
82
100
  // ── the provider set ─────────────────────────────────────────────────────
@@ -135,19 +153,55 @@ var EvmProviderRegistry = /** @class */ (function () {
135
153
  this.seen.add(provider);
136
154
  return true;
137
155
  };
156
+ /**
157
+ * Record (or, with `undefined`, forget) the integration layer's own
158
+ * attribution resolver for a provider. See `attributions`.
159
+ */
160
+ EvmProviderRegistry.prototype.rememberAttribution = function (provider, resolve) {
161
+ if (!provider)
162
+ return;
163
+ if (!resolve) {
164
+ this.attributions.delete(provider);
165
+ return;
166
+ }
167
+ this.attributions.set(provider, resolve);
168
+ };
169
+ /** The supplied attribution for a provider, if any resolves right now. */
170
+ EvmProviderRegistry.prototype.suppliedAttributionFor = function (provider) {
171
+ var resolve = this.attributions.get(provider);
172
+ if (!resolve)
173
+ return undefined;
174
+ try {
175
+ var info = resolve();
176
+ return (info === null || info === void 0 ? void 0 : info.name) ? info : undefined;
177
+ }
178
+ catch (_a) {
179
+ return undefined;
180
+ }
181
+ };
138
182
  /**
139
183
  * A provider's display name and rdns.
140
184
  *
141
- * EIP-6963 metadata is authoritative when we have it; otherwise fall back to
142
- * sniffing the injected provider, which is all a pre-6963 wallet offers.
185
+ * EIP-6963 metadata is authoritative when we have it. A supplied
186
+ * attribution (the connector a wagmi app connected through) comes next:
187
+ * it describes the SESSION, which is what the hook-driven events report.
188
+ * Otherwise fall back to sniffing the injected provider, which is all a
189
+ * pre-6963 wallet offers; a supplied name without an rdns keeps the
190
+ * sniffed rdns.
143
191
  */
144
192
  EvmProviderRegistry.prototype.infoFor = function (provider) {
145
193
  var announced = this.details.find(function (p) { return p.provider === provider; });
194
+ var supplied = announced
195
+ ? undefined
196
+ : this.suppliedAttributionFor(provider);
146
197
  var info = announced
147
198
  ? { name: announced.info.name, rdns: announced.info.rdns }
148
199
  : (function () {
200
+ var _a;
149
201
  var injected = (0, provider_1.detectInjectedProviderInfo)(provider);
150
- return { name: injected.name, rdns: injected.rdns };
202
+ return supplied
203
+ ? { name: supplied.name, rdns: (_a = supplied.rdns) !== null && _a !== void 0 ? _a : injected.rdns }
204
+ : { name: injected.name, rdns: injected.rdns };
151
205
  })();
152
206
  // WalletConnect names the TRANSPORT; the session's peer names the
153
207
  // wallet. Resolved live, per read: a session established after the
@@ -157,7 +211,10 @@ var EvmProviderRegistry = /** @class */ (function () {
157
211
  // is included because a flagless WalletConnect-compatible provider
158
212
  // registered BEFORE its session exists detects as nothing at all;
159
213
  // the peer appearing later is itself the proof of what it was, so
160
- // the rdns upgrades with it.
214
+ // the rdns upgrades with it. A supplied name gets the same treatment
215
+ // and no more: the integration layer resolves its own peer names
216
+ // where it wants them, and a branded connector must keep its name on
217
+ // both paths.
161
218
  if (info.name === "WalletConnect" || info.name === "Injected Provider") {
162
219
  var peer = (0, provider_1.readWalletConnectPeer)(provider);
163
220
  if (peer === null || peer === void 0 ? void 0 : peer.name) {
@@ -83,10 +83,15 @@ export declare class EvmRequestTracker {
83
83
  /**
84
84
  * Wallet attribution for request-derived events.
85
85
  *
86
- * Live per read through the registry, so a WalletConnect session names
87
- * its actual signer ("MetaMask Wallet", "Ledger Live") - the live-test
88
- * rows had provider_name EMPTY on every signature and transaction, which
89
- * made per-wallet activity unanswerable in the warehouse.
86
+ * Resolved through the registry at the START of each request and held
87
+ * for that request's whole lifecycle, receipt included. Reading it per
88
+ * status let a connector switch (two connectors sharing one provider) or
89
+ * a session change made while a prompt was open split one operation
90
+ * between two wallet names. Still live across requests: a WalletConnect
91
+ * session names its actual signer ("MetaMask Wallet", "Ledger Live") -
92
+ * the live-test rows had provider_name EMPTY on every signature and
93
+ * transaction, which made per-wallet activity unanswerable in the
94
+ * warehouse.
90
95
  */
91
96
  private attributionFor;
92
97
  private buildSignatureEventPayload;
@@ -299,7 +299,7 @@ var EvmRequestTracker = /** @class */ (function () {
299
299
  */
300
300
  EvmRequestTracker.prototype.dispatchWrappedRequest = function (_a, provider_2, request_1) {
301
301
  return __awaiter(this, arguments, void 0, function (_b, provider, request) {
302
- var generation_1, responsePromise, capturedChainId_1, response_1, error_1, rpcError, txPromise, txChainId_1, transactionHash_1, error_2, rpcError;
302
+ var generation_1, responsePromise, capturedChainId_1, attribution_1, response_1, error_1, rpcError, txPromise, txChainId_1, attribution_2, transactionHash_1, error_2, rpcError;
303
303
  var _this = this;
304
304
  var _c, _d, _e, _f;
305
305
  var method = _b.method, params = _b.params;
@@ -342,6 +342,7 @@ var EvmRequestTracker = /** @class */ (function () {
342
342
  // below is never reported as unhandled. The real handling is there.
343
343
  responsePromise.catch(function () { return undefined; });
344
344
  capturedChainId_1 = this.registry.resolveChainId(provider);
345
+ attribution_1 = this.attributionFor(provider);
345
346
  // Fire-and-forget tracking
346
347
  (function () { return __awaiter(_this, void 0, void 0, function () {
347
348
  var e_1;
@@ -349,7 +350,7 @@ var EvmRequestTracker = /** @class */ (function () {
349
350
  switch (_a.label) {
350
351
  case 0:
351
352
  _a.trys.push([0, 2, , 3]);
352
- return [4 /*yield*/, this.deps.signature(__assign({ status: types_1.SignatureStatus.REQUESTED }, this.buildSignatureEventPayload(method, params, undefined, capturedChainId_1, provider)), this.attributionFor(provider))];
353
+ return [4 /*yield*/, this.deps.signature(__assign({ status: types_1.SignatureStatus.REQUESTED }, this.buildSignatureEventPayload(method, params, undefined, capturedChainId_1, provider)), attribution_1)];
353
354
  case 1:
354
355
  _a.sent();
355
356
  return [3 /*break*/, 3];
@@ -375,7 +376,7 @@ var EvmRequestTracker = /** @class */ (function () {
375
376
  switch (_a.label) {
376
377
  case 0:
377
378
  _a.trys.push([0, 2, , 3]);
378
- return [4 /*yield*/, this.deps.signature(__assign({ status: types_1.SignatureStatus.CONFIRMED }, this.buildSignatureEventPayload(method, params, response_1, capturedChainId_1, provider)), this.attributionFor(provider))];
379
+ return [4 /*yield*/, this.deps.signature(__assign({ status: types_1.SignatureStatus.CONFIRMED }, this.buildSignatureEventPayload(method, params, response_1, capturedChainId_1, provider)), attribution_1)];
379
380
  case 1:
380
381
  _a.sent();
381
382
  return [3 /*break*/, 3];
@@ -400,7 +401,7 @@ var EvmRequestTracker = /** @class */ (function () {
400
401
  switch (_a.label) {
401
402
  case 0:
402
403
  _a.trys.push([0, 2, , 3]);
403
- return [4 /*yield*/, this.deps.signature(__assign({ status: types_1.SignatureStatus.REJECTED }, this.buildSignatureEventPayload(method, params, undefined, capturedChainId_1, provider)), this.attributionFor(provider))];
404
+ return [4 /*yield*/, this.deps.signature(__assign({ status: types_1.SignatureStatus.REJECTED }, this.buildSignatureEventPayload(method, params, undefined, capturedChainId_1, provider)), attribution_1)];
404
405
  case 1:
405
406
  _a.sent();
406
407
  return [3 /*break*/, 3];
@@ -439,6 +440,7 @@ var EvmRequestTracker = /** @class */ (function () {
439
440
  txPromise = request({ method: method, params: params });
440
441
  txPromise.catch(function () { return undefined; });
441
442
  txChainId_1 = this.registry.resolveChainId(provider);
443
+ attribution_2 = this.attributionFor(provider);
442
444
  (function () { return __awaiter(_this, void 0, void 0, function () {
443
445
  var payload, e_4;
444
446
  return __generator(this, function (_a) {
@@ -448,7 +450,7 @@ var EvmRequestTracker = /** @class */ (function () {
448
450
  return [4 /*yield*/, this.buildTransactionEventPayload(params, provider, txChainId_1)];
449
451
  case 1:
450
452
  payload = _a.sent();
451
- return [4 /*yield*/, this.deps.transaction(__assign({ status: types_1.TransactionStatus.STARTED }, payload), this.attributionFor(provider))];
453
+ return [4 /*yield*/, this.deps.transaction(__assign({ status: types_1.TransactionStatus.STARTED }, payload), attribution_2)];
452
454
  case 2:
453
455
  _a.sent();
454
456
  return [3 /*break*/, 4];
@@ -475,11 +477,11 @@ var EvmRequestTracker = /** @class */ (function () {
475
477
  return [4 /*yield*/, this.buildTransactionEventPayload(params, provider, txChainId_1)];
476
478
  case 1:
477
479
  payload = _a.sent();
478
- return [4 /*yield*/, this.deps.transaction(__assign(__assign({ status: types_1.TransactionStatus.BROADCASTED }, payload), { transactionHash: transactionHash_1 }), this.attributionFor(provider))];
480
+ return [4 /*yield*/, this.deps.transaction(__assign(__assign({ status: types_1.TransactionStatus.BROADCASTED }, payload), { transactionHash: transactionHash_1 }), attribution_2)];
479
481
  case 2:
480
482
  _a.sent();
481
483
  // Start async polling for transaction receipt
482
- this.pollTransactionReceipt(provider, transactionHash_1, payload);
484
+ this.pollTransactionReceipt(provider, transactionHash_1, payload, attribution_2);
483
485
  return [3 /*break*/, 4];
484
486
  case 3:
485
487
  e_5 = _a.sent();
@@ -504,7 +506,7 @@ var EvmRequestTracker = /** @class */ (function () {
504
506
  return [4 /*yield*/, this.buildTransactionEventPayload(params, provider, txChainId_1)];
505
507
  case 1:
506
508
  payload = _a.sent();
507
- return [4 /*yield*/, this.deps.transaction(__assign({ status: types_1.TransactionStatus.REJECTED }, payload), this.attributionFor(provider))];
509
+ return [4 /*yield*/, this.deps.transaction(__assign({ status: types_1.TransactionStatus.REJECTED }, payload), attribution_2)];
508
510
  case 2:
509
511
  _a.sent();
510
512
  return [3 /*break*/, 4];
@@ -526,10 +528,15 @@ var EvmRequestTracker = /** @class */ (function () {
526
528
  /**
527
529
  * Wallet attribution for request-derived events.
528
530
  *
529
- * Live per read through the registry, so a WalletConnect session names
530
- * its actual signer ("MetaMask Wallet", "Ledger Live") - the live-test
531
- * rows had provider_name EMPTY on every signature and transaction, which
532
- * made per-wallet activity unanswerable in the warehouse.
531
+ * Resolved through the registry at the START of each request and held
532
+ * for that request's whole lifecycle, receipt included. Reading it per
533
+ * status let a connector switch (two connectors sharing one provider) or
534
+ * a session change made while a prompt was open split one operation
535
+ * between two wallet names. Still live across requests: a WalletConnect
536
+ * session names its actual signer ("MetaMask Wallet", "Ledger Live") -
537
+ * the live-test rows had provider_name EMPTY on every signature and
538
+ * transaction, which made per-wallet activity unanswerable in the
539
+ * warehouse.
533
540
  */
534
541
  EvmRequestTracker.prototype.attributionFor = function (provider) {
535
542
  var info = this.registry.infoFor(provider);
@@ -599,8 +606,11 @@ var EvmRequestTracker = /** @class */ (function () {
599
606
  /**
600
607
  * Polls for transaction receipt and emits tx.status = CONFIRMED or REVERTED.
601
608
  */
602
- EvmRequestTracker.prototype.pollTransactionReceipt = function (provider_2, transactionHash_2, payload_1) {
603
- return __awaiter(this, arguments, void 0, function (provider, transactionHash, payload, maxAttempts, intervalMs) {
609
+ EvmRequestTracker.prototype.pollTransactionReceipt = function (provider_2, transactionHash_2, payload_1, attribution_3) {
610
+ return __awaiter(this, arguments, void 0, function (provider, transactionHash, payload,
611
+ // Snapshot from the broadcast: a connector or session change during
612
+ // the poll window must not relabel the receipt.
613
+ attribution, maxAttempts, intervalMs) {
604
614
  var attempts, poll;
605
615
  var _this = this;
606
616
  if (maxAttempts === void 0) { maxAttempts = 10; }
@@ -629,7 +639,7 @@ var EvmRequestTracker = /** @class */ (function () {
629
639
  // status: 1 = success, 0 = reverted
630
640
  if (receipt.status === "0x1" || receipt.status === 1) {
631
641
  this.deps
632
- .transaction(__assign(__assign({ status: types_1.TransactionStatus.CONFIRMED }, payload), { transactionHash: transactionHash }), this.attributionFor(provider))
642
+ .transaction(__assign(__assign({ status: types_1.TransactionStatus.CONFIRMED }, payload), { transactionHash: transactionHash }), attribution)
633
643
  .catch(function (e) {
634
644
  return logger_1.logger.error("Formo: Failed to track transaction confirmation", e);
635
645
  });
@@ -637,7 +647,7 @@ var EvmRequestTracker = /** @class */ (function () {
637
647
  }
638
648
  else if (receipt.status === "0x0" || receipt.status === 0) {
639
649
  this.deps
640
- .transaction(__assign(__assign({ status: types_1.TransactionStatus.REVERTED }, payload), { transactionHash: transactionHash }), this.attributionFor(provider))
650
+ .transaction(__assign(__assign({ status: types_1.TransactionStatus.REVERTED }, payload), { transactionHash: transactionHash }), attribution)
641
651
  .catch(function (e) {
642
652
  return logger_1.logger.error("Formo: Failed to track transaction revert", e);
643
653
  });
@@ -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.