@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
@@ -0,0 +1,444 @@
1
+ /**
2
+ * SolanaWalletStandardRegistry
3
+ *
4
+ * Which Solana wallets exist, and whether each is connected. The Solana
5
+ * analogue of `EvmProviderRegistry` + EIP-6963.
6
+ *
7
+ * Compatible wallets (Phantom, Solflare, Backpack, ...) announce themselves
8
+ * through the Wallet Standard's window-event handshake. Solana Kit,
9
+ * wallet-adapter, and framework-kit use these registered wallets. Observing
10
+ * the handshake covers those wallets without coupling Formo to the host
11
+ * library: the registry announces `wallet-standard:app-ready`, listens for
12
+ * `wallet-standard:register-wallet`, and subscribes to each wallet's
13
+ * `standard:events` `change` event.
14
+ *
15
+ * What it reports:
16
+ * - `detect` when a wallet registers (session-deduped by the SDK on rdns).
17
+ * - `connect` when a wallet's Solana accounts go from none to some, or the
18
+ * first account changes; `disconnect` when they go from some to none.
19
+ * - `chain` when the configured cluster changes while a wallet is connected.
20
+ *
21
+ * What it does not do: wrap any wallet method, issue any request to a
22
+ * wallet, or depend on `@wallet-standard/*` (the handshake is a few lines,
23
+ * and the SDK's dependency policy is two runtime dependencies, forever).
24
+ *
25
+ * Cluster: the Wallet Standard lists every cluster a wallet SUPPORTS, not
26
+ * the one the app is using, so the cluster is `options.solana.cluster` when
27
+ * given, else mainnet-beta when the wallet supports it, else the first
28
+ * Solana cluster it lists. A framework-kit app gets the cluster from its
29
+ * store instead, through `SolanaStoreHandler`.
30
+ *
31
+ * @see https://github.com/wallet-standard/wallet-standard
32
+ */
33
+ import { logger } from "../logger";
34
+ import { isBlockedSolanaAddress, isSolanaAddress } from "./address";
35
+ import { DEFAULT_SOLANA_CHAIN_ID, SOLANA_CHAIN_IDS, solanaWalletRdns, } from "./types";
36
+ import { WALLET_STANDARD_APP_READY_EVENT, WALLET_STANDARD_EVENTS_FEATURE, WALLET_STANDARD_REGISTER_WALLET_EVENT, } from "./walletStandardTypes";
37
+ var SOLANA_CHAIN_PREFIX = "solana:";
38
+ /** Wallet Standard chain identifiers, keyed to Formo cluster names. */
39
+ var CLUSTER_BY_CHAIN = {
40
+ "solana:mainnet": "mainnet-beta",
41
+ "solana:devnet": "devnet",
42
+ "solana:testnet": "testnet",
43
+ "solana:localnet": "localnet",
44
+ };
45
+ /** Whether a chain identifier belongs to the Solana namespace. */
46
+ function isSolanaChain(chain) {
47
+ return typeof chain === "string" && chain.startsWith(SOLANA_CHAIN_PREFIX);
48
+ }
49
+ /**
50
+ * Accept only what looks like a Wallet Standard wallet that speaks Solana.
51
+ *
52
+ * A multichain wallet may register once and list Solana beside other
53
+ * namespaces; that is fine. A wallet that lists no Solana chain at all is
54
+ * not ours to track.
55
+ */
56
+ function asSolanaWallet(candidate) {
57
+ if (!candidate || typeof candidate !== "object")
58
+ return undefined;
59
+ var wallet = candidate;
60
+ if (typeof wallet.name !== "string" || !wallet.name)
61
+ return undefined;
62
+ if (!Array.isArray(wallet.chains) || !wallet.chains.some(isSolanaChain)) {
63
+ return undefined;
64
+ }
65
+ if (!wallet.features || typeof wallet.features !== "object")
66
+ return undefined;
67
+ if (!Array.isArray(wallet.accounts))
68
+ return undefined;
69
+ return wallet;
70
+ }
71
+ /**
72
+ * The first account that is a Solana wallet account, or undefined.
73
+ *
74
+ * An account declares the chains it signs for. On a multichain wallet the
75
+ * list can hold an EVM account beside a Solana one, and the SDK's connect
76
+ * validates the address against the chain id, so only a Solana account may
77
+ * be reported under a Solana chain id. An account with no chain list is
78
+ * judged on its address alone.
79
+ */
80
+ function firstSolanaAccount(accounts) {
81
+ for (var _i = 0, accounts_1 = accounts; _i < accounts_1.length; _i++) {
82
+ var candidate = accounts_1[_i];
83
+ if (!candidate || typeof candidate !== "object")
84
+ continue;
85
+ var account = candidate;
86
+ if (Array.isArray(account.chains) && !account.chains.some(isSolanaChain)) {
87
+ continue;
88
+ }
89
+ if (!isSolanaAddress(account.address))
90
+ continue;
91
+ if (isBlockedSolanaAddress(account.address))
92
+ continue;
93
+ return account;
94
+ }
95
+ return undefined;
96
+ }
97
+ var SolanaWalletStandardRegistry = /** @class */ (function () {
98
+ function SolanaWalletStandardRegistry(deps, options) {
99
+ var _this = this;
100
+ this.deps = deps;
101
+ this.wallets = new Map();
102
+ /** Set by cleanup(); a torn-down registry refuses late registrations. */
103
+ this.isCleanedUp = false;
104
+ this.cluster = options === null || options === void 0 ? void 0 : options.cluster;
105
+ this.api = Object.freeze({
106
+ register: function () {
107
+ var wallets = [];
108
+ for (var _i = 0; _i < arguments.length; _i++) {
109
+ wallets[_i] = arguments[_i];
110
+ }
111
+ return _this.register.apply(_this, wallets);
112
+ },
113
+ });
114
+ this.listen();
115
+ }
116
+ // ── discovery ───────────────────────────────────────────────────────────
117
+ /**
118
+ * The Wallet Standard handshake, both directions.
119
+ *
120
+ * A wallet injected before us hears `app-ready` and registers at once; a
121
+ * wallet injected after us dispatches `register-wallet`, which we answer.
122
+ * Neither is optional: an extension's content script and the app's bundle
123
+ * race, and which one wins differs per page load.
124
+ */
125
+ SolanaWalletStandardRegistry.prototype.listen = function () {
126
+ var _this = this;
127
+ if (typeof window === "undefined" ||
128
+ typeof window.addEventListener !== "function") {
129
+ return;
130
+ }
131
+ var onRegisterWallet = function (event) {
132
+ var callback = event.detail;
133
+ if (typeof callback !== "function")
134
+ return;
135
+ try {
136
+ callback(_this.api);
137
+ }
138
+ catch (error) {
139
+ logger.warn("SolanaWalletStandardRegistry: A wallet threw while registering", error);
140
+ }
141
+ };
142
+ try {
143
+ window.addEventListener(WALLET_STANDARD_REGISTER_WALLET_EVENT, onRegisterWallet);
144
+ this.removeWindowListener = function () {
145
+ return window.removeEventListener(WALLET_STANDARD_REGISTER_WALLET_EVENT, onRegisterWallet);
146
+ };
147
+ }
148
+ catch (error) {
149
+ logger.warn("SolanaWalletStandardRegistry: Could not listen for wallet registrations", error);
150
+ }
151
+ if (typeof CustomEvent !== "function")
152
+ return;
153
+ try {
154
+ window.dispatchEvent(new CustomEvent(WALLET_STANDARD_APP_READY_EVENT, { detail: this.api }));
155
+ }
156
+ catch (error) {
157
+ logger.warn("SolanaWalletStandardRegistry: Could not announce app-ready", error);
158
+ }
159
+ };
160
+ /**
161
+ * Add wallets once. Returns an unregister function, as the standard asks.
162
+ *
163
+ * Public so an integration that already holds a wallet object (its own
164
+ * `getWallets()` call, say) can hand it over without the window handshake.
165
+ */
166
+ SolanaWalletStandardRegistry.prototype.register = function () {
167
+ var _this = this;
168
+ var wallets = [];
169
+ for (var _i = 0; _i < arguments.length; _i++) {
170
+ wallets[_i] = arguments[_i];
171
+ }
172
+ var added = [];
173
+ if (this.isCleanedUp)
174
+ return function () { return undefined; };
175
+ for (var _a = 0, wallets_1 = wallets; _a < wallets_1.length; _a++) {
176
+ var candidate = wallets_1[_a];
177
+ var wallet = asSolanaWallet(candidate);
178
+ if (!wallet || this.wallets.has(wallet))
179
+ continue;
180
+ var tracked = {
181
+ wallet: wallet,
182
+ name: wallet.name,
183
+ rdns: solanaWalletRdns(wallet.name),
184
+ connectWasReported: false,
185
+ };
186
+ this.wallets.set(wallet, tracked);
187
+ added.push(tracked);
188
+ this.track(tracked);
189
+ }
190
+ return function () {
191
+ for (var _i = 0, added_1 = added; _i < added_1.length; _i++) {
192
+ var tracked = added_1[_i];
193
+ _this.untrack(tracked);
194
+ }
195
+ };
196
+ };
197
+ SolanaWalletStandardRegistry.prototype.track = function (tracked) {
198
+ var _this = this;
199
+ logger.info("SolanaWalletStandardRegistry: Discovered wallet", {
200
+ name: tracked.name,
201
+ chains: tracked.wallet.chains,
202
+ });
203
+ this.deps
204
+ .detect({ providerName: tracked.name, rdns: tracked.rdns })
205
+ .catch(function (error) {
206
+ logger.error("SolanaWalletStandardRegistry: Error emitting detect", error);
207
+ });
208
+ var events = tracked.wallet.features[WALLET_STANDARD_EVENTS_FEATURE];
209
+ if (events && typeof events.on === "function") {
210
+ try {
211
+ tracked.unsubscribe = events.on("change", function (properties) {
212
+ return _this.onChange(tracked, properties);
213
+ });
214
+ }
215
+ catch (error) {
216
+ logger.warn("SolanaWalletStandardRegistry: Could not subscribe to ".concat(tracked.name, " events"), error);
217
+ }
218
+ }
219
+ else {
220
+ logger.info("SolanaWalletStandardRegistry: ".concat(tracked.name, " has no standard:events feature; connections cannot be observed"));
221
+ }
222
+ // A wallet that is already authorized when we first see it (the app
223
+ // connected before the SDK initialised, or the wallet restored a trusted
224
+ // session on load) will never fire a change event for that connection.
225
+ this.reconcile(tracked, tracked.wallet.accounts);
226
+ };
227
+ SolanaWalletStandardRegistry.prototype.untrack = function (tracked) {
228
+ var _a;
229
+ try {
230
+ (_a = tracked.unsubscribe) === null || _a === void 0 ? void 0 : _a.call(tracked);
231
+ }
232
+ catch (error) {
233
+ logger.warn("SolanaWalletStandardRegistry: Could not unsubscribe from ".concat(tracked.name), error);
234
+ }
235
+ tracked.unsubscribe = undefined;
236
+ this.wallets.delete(tracked.wallet);
237
+ };
238
+ // ── connection state ────────────────────────────────────────────────────
239
+ SolanaWalletStandardRegistry.prototype.onChange = function (tracked, properties) {
240
+ if (this.isCleanedUp)
241
+ return;
242
+ // `change` carries only what changed. A wallet that omits `accounts` is
243
+ // reporting something else (chains, features); the wallet's own
244
+ // `accounts` is the ground truth either way, so read that when the
245
+ // event does not say.
246
+ var accounts = Array.isArray(properties === null || properties === void 0 ? void 0 : properties.accounts)
247
+ ? properties.accounts
248
+ : tracked.wallet.accounts;
249
+ this.reconcile(tracked, Array.isArray(accounts) ? accounts : []);
250
+ };
251
+ /**
252
+ * Compare what the wallet now authorizes with what was last reported.
253
+ *
254
+ * none → some is a connect, some → none a disconnect, and a different
255
+ * first account is a disconnect followed by a connect, matching what the
256
+ * framework-kit store handler reports for the same transitions.
257
+ */
258
+ SolanaWalletStandardRegistry.prototype.reconcile = function (tracked, accounts) {
259
+ var next = firstSolanaAccount(accounts);
260
+ var previous = tracked.connected;
261
+ if (!previous && !next)
262
+ return;
263
+ if (previous && next && previous.address === next.address)
264
+ return;
265
+ if (!this.deps.ownsWalletEvents()) {
266
+ // The store handler reports this connection. Still record it, so a
267
+ // later change is judged against what the wallet actually did rather
268
+ // than against a stale snapshot.
269
+ tracked.connected = next
270
+ ? { address: next.address, chainId: this.chainIdFor(tracked) }
271
+ : undefined;
272
+ tracked.connectWasReported = false;
273
+ return;
274
+ }
275
+ if (previous)
276
+ this.reportDisconnect(tracked, previous);
277
+ if (next)
278
+ this.reportConnect(tracked, next.address);
279
+ };
280
+ SolanaWalletStandardRegistry.prototype.reportConnect = function (tracked, address) {
281
+ var chainId = this.chainIdFor(tracked);
282
+ tracked.connected = { address: address, chainId: chainId };
283
+ tracked.connectWasReported = false;
284
+ logger.info("SolanaWalletStandardRegistry: Wallet connected", {
285
+ name: tracked.name,
286
+ address: address,
287
+ chainId: chainId,
288
+ });
289
+ if (!this.deps.isAutocaptureEnabled("connect"))
290
+ return;
291
+ // FormoAnalytics.connect() deliberately resolves without enqueueing when
292
+ // tracking is suppressed or this chain is excluded. Only a connect that
293
+ // can actually be accepted may suppress the authoritative store event
294
+ // that can follow.
295
+ if (!this.deps.willTrackEvent(chainId))
296
+ return;
297
+ tracked.connectWasReported = true;
298
+ this.deps
299
+ .connect({ chainId: chainId, address: address }, { providerName: tracked.name, rdns: tracked.rdns })
300
+ .catch(function (error) {
301
+ logger.error("SolanaWalletStandardRegistry: Error emitting connect", error);
302
+ });
303
+ };
304
+ SolanaWalletStandardRegistry.prototype.reportDisconnect = function (tracked, previous) {
305
+ tracked.connected = undefined;
306
+ tracked.connectWasReported = false;
307
+ logger.info("SolanaWalletStandardRegistry: Wallet disconnected", {
308
+ name: tracked.name,
309
+ address: previous.address,
310
+ chainId: previous.chainId,
311
+ });
312
+ if (!this.deps.isAutocaptureEnabled("disconnect"))
313
+ return;
314
+ this.deps.disconnect(previous).catch(function (error) {
315
+ logger.error("SolanaWalletStandardRegistry: Error emitting disconnect", error);
316
+ });
317
+ };
318
+ // ── cluster ─────────────────────────────────────────────────────────────
319
+ /**
320
+ * The chain id to report for a wallet.
321
+ *
322
+ * The Wallet Standard cannot say which cluster the app is on, only which
323
+ * ones the wallet supports, so an explicit cluster wins, then mainnet-beta
324
+ * if supported (nearly every wallet lists every cluster, and production
325
+ * traffic is mainnet), then the first cluster listed.
326
+ */
327
+ SolanaWalletStandardRegistry.prototype.chainIdFor = function (tracked) {
328
+ if (this.cluster)
329
+ return SOLANA_CHAIN_IDS[this.cluster];
330
+ var chains = tracked.wallet.chains;
331
+ if (chains.includes("solana:mainnet"))
332
+ return DEFAULT_SOLANA_CHAIN_ID;
333
+ for (var _i = 0, chains_1 = chains; _i < chains_1.length; _i++) {
334
+ var chain = chains_1[_i];
335
+ var cluster = CLUSTER_BY_CHAIN[chain];
336
+ if (cluster)
337
+ return SOLANA_CHAIN_IDS[cluster];
338
+ }
339
+ return DEFAULT_SOLANA_CHAIN_ID;
340
+ };
341
+ /**
342
+ * Set the cluster the app uses. A connected wallet's chain id follows,
343
+ * with a `chain` event, the same as the store handler on a cluster switch.
344
+ */
345
+ SolanaWalletStandardRegistry.prototype.setCluster = function (cluster) {
346
+ var _a;
347
+ if (this.cluster === cluster)
348
+ return;
349
+ this.cluster = cluster;
350
+ var chainId = SOLANA_CHAIN_IDS[cluster];
351
+ var all = Array.from(this.wallets.values());
352
+ // Central state follows ONE wallet. Two wallets can hold an authorized
353
+ // account at once, and writing each of them here would hand the wallet
354
+ // slot to the last REGISTERED wallet rather than to the one the SDK
355
+ // already treats as active, which is the last CONNECTED one.
356
+ var active = this.deps.currentAddress();
357
+ var owner = (_a = all.find(function (t) { return t.connected && t.connected.address === active; })) !== null && _a !== void 0 ? _a : all.find(function (t) { return t.connected; });
358
+ for (var _i = 0, all_1 = all; _i < all_1.length; _i++) {
359
+ var tracked = all_1[_i];
360
+ var connected = tracked.connected;
361
+ if (!connected || connected.chainId === chainId)
362
+ continue;
363
+ tracked.connected = { address: connected.address, chainId: chainId };
364
+ if (!this.deps.ownsWalletEvents())
365
+ continue;
366
+ // Central state moves first, so a suppressed chain event still leaves
367
+ // the SDK on the cluster the wallet is actually on. `chain()` writes
368
+ // the cluster itself, but only when the event is not suppressed.
369
+ if (tracked === owner) {
370
+ this.deps.syncWalletState({ chainId: chainId, address: connected.address });
371
+ }
372
+ if (!this.deps.isAutocaptureEnabled("chain"))
373
+ continue;
374
+ this.deps
375
+ .chain({ chainId: chainId, address: connected.address })
376
+ .catch(function (error) {
377
+ logger.error("SolanaWalletStandardRegistry: Error emitting chain event", error);
378
+ });
379
+ }
380
+ };
381
+ Object.defineProperty(SolanaWalletStandardRegistry.prototype, "walletNames", {
382
+ // ── introspection ───────────────────────────────────────────────────────
383
+ /** Names of every wallet discovered so far, for the debug helpers. */
384
+ get: function () {
385
+ return Array.from(this.wallets.values()).map(function (t) { return t.name; });
386
+ },
387
+ enumerable: false,
388
+ configurable: true
389
+ });
390
+ /**
391
+ * The rdns this registry reported a still-live connect for `address`
392
+ * under, if any. Lets a failed store adoption name both identities.
393
+ */
394
+ SolanaWalletStandardRegistry.prototype.reportedConnectionRdns = function (address) {
395
+ var _a;
396
+ for (var _i = 0, _b = Array.from(this.wallets.values()); _i < _b.length; _i++) {
397
+ var tracked = _b[_i];
398
+ if (tracked.connectWasReported &&
399
+ ((_a = tracked.connected) === null || _a === void 0 ? void 0 : _a.address) === address) {
400
+ return tracked.rdns;
401
+ }
402
+ }
403
+ return undefined;
404
+ };
405
+ /**
406
+ * Transfer a connect already emitted by this registry to a store handler.
407
+ * The state remains connected, but the same transition must not be emitted
408
+ * a second time when framework-kit's store catches up.
409
+ */
410
+ SolanaWalletStandardRegistry.prototype.takeReportedConnection = function (address, rdns) {
411
+ var _a;
412
+ for (var _i = 0, _b = Array.from(this.wallets.values()); _i < _b.length; _i++) {
413
+ var tracked = _b[_i];
414
+ if (tracked.connectWasReported &&
415
+ ((_a = tracked.connected) === null || _a === void 0 ? void 0 : _a.address) === address &&
416
+ tracked.rdns === rdns) {
417
+ tracked.connectWasReported = false;
418
+ return tracked.connected;
419
+ }
420
+ }
421
+ return undefined;
422
+ };
423
+ // ── teardown ────────────────────────────────────────────────────────────
424
+ SolanaWalletStandardRegistry.prototype.cleanup = function () {
425
+ var _a;
426
+ this.isCleanedUp = true;
427
+ try {
428
+ (_a = this.removeWindowListener) === null || _a === void 0 ? void 0 : _a.call(this);
429
+ }
430
+ catch (error) {
431
+ logger.warn("SolanaWalletStandardRegistry: Could not remove window listener", error);
432
+ }
433
+ this.removeWindowListener = undefined;
434
+ for (var _i = 0, _b = Array.from(this.wallets.values()); _i < _b.length; _i++) {
435
+ var tracked = _b[_i];
436
+ this.untrack(tracked);
437
+ }
438
+ this.wallets.clear();
439
+ logger.debug("SolanaWalletStandardRegistry: Cleanup complete");
440
+ };
441
+ return SolanaWalletStandardRegistry;
442
+ }());
443
+ export { SolanaWalletStandardRegistry };
444
+ //# sourceMappingURL=SolanaWalletStandardRegistry.js.map
@@ -1,17 +1,22 @@
1
1
  /**
2
2
  * Solana integration module
3
3
  *
4
- * Provides automatic event capture for Solana wallets via framework-kit's
5
- * zustand store. Connect/disconnect and transaction events are tracked
6
- * automatically. Signature events (signMessage/signTransaction) require
7
- * explicit tracking via formo.signature() since framework-kit
8
- * doesn't track these in store state.
4
+ * Wallets are discovered through the Wallet Standard and their connect /
5
+ * disconnect events captured automatically for wallets registered with the
6
+ * page through Wallet Standard.
7
+ * Framework-kit apps can additionally pass their zustand store for
8
+ * transaction lifecycle events. Signature events (signMessage /
9
+ * signTransaction) require explicit tracking via formo.signature(), since
10
+ * neither source reports them.
9
11
  *
12
+ * @see https://github.com/wallet-standard/wallet-standard
10
13
  * @see https://github.com/solana-foundation/framework-kit
11
14
  */
12
15
  export { SolanaStoreHandler } from "./SolanaStoreHandler";
16
+ export { SolanaWalletStandardRegistry } from "./SolanaWalletStandardRegistry";
13
17
  export { SolanaManager } from "./SolanaManager";
14
18
  export * from "./types";
15
19
  export * from "./storeTypes";
20
+ export * from "./walletStandardTypes";
16
21
  export * from "./address";
17
22
  //# sourceMappingURL=index.d.ts.map
@@ -1,17 +1,22 @@
1
1
  /**
2
2
  * Solana integration module
3
3
  *
4
- * Provides automatic event capture for Solana wallets via framework-kit's
5
- * zustand store. Connect/disconnect and transaction events are tracked
6
- * automatically. Signature events (signMessage/signTransaction) require
7
- * explicit tracking via formo.signature() since framework-kit
8
- * doesn't track these in store state.
4
+ * Wallets are discovered through the Wallet Standard and their connect /
5
+ * disconnect events captured automatically for wallets registered with the
6
+ * page through Wallet Standard.
7
+ * Framework-kit apps can additionally pass their zustand store for
8
+ * transaction lifecycle events. Signature events (signMessage /
9
+ * signTransaction) require explicit tracking via formo.signature(), since
10
+ * neither source reports them.
9
11
  *
12
+ * @see https://github.com/wallet-standard/wallet-standard
10
13
  * @see https://github.com/solana-foundation/framework-kit
11
14
  */
12
15
  export { SolanaStoreHandler } from "./SolanaStoreHandler";
16
+ export { SolanaWalletStandardRegistry } from "./SolanaWalletStandardRegistry";
13
17
  export { SolanaManager } from "./SolanaManager";
14
18
  export * from "./types";
15
19
  export * from "./storeTypes";
20
+ export * from "./walletStandardTypes";
16
21
  export * from "./address";
17
22
  //# sourceMappingURL=index.js.map
@@ -72,13 +72,32 @@ export interface SolanaWalletConnectorMetadata {
72
72
  readonly name: string;
73
73
  readonly icon?: string;
74
74
  }
75
+ /** Cluster health as exposed by framework-kit. */
76
+ export type SolanaClusterStatus = Readonly<{
77
+ status: "idle";
78
+ }> | Readonly<{
79
+ status: "connecting";
80
+ }> | Readonly<{
81
+ status: "ready";
82
+ latencyMs?: number;
83
+ }> | Readonly<{
84
+ status: "error";
85
+ error: unknown;
86
+ }>;
87
+ /** @deprecated framework-kit emits {@link SolanaClusterStatus} objects. */
88
+ export type LegacySolanaClusterStatus = SolanaClusterStatus["status"];
75
89
  /**
76
90
  * Cluster state from the store.
77
91
  */
78
92
  export interface SolanaClusterState {
79
93
  readonly endpoint: string;
80
94
  readonly commitment?: string;
81
- readonly status: "idle" | "connecting" | "ready" | "error";
95
+ /**
96
+ * framework-kit's `ClusterStatus` is a discriminated union such as
97
+ * `{ status: 'ready', latencyMs?: number }`. The pre-1.39 string form is
98
+ * retained so custom stores and test doubles remain source-compatible.
99
+ */
100
+ readonly status: SolanaClusterStatus | LegacySolanaClusterStatus;
82
101
  }
83
102
  /**
84
103
  * The zustand vanilla store API that framework-kit exposes via `client.store`.
@@ -46,15 +46,32 @@ export interface SolanaPublicKey {
46
46
  */
47
47
  export type UnsubscribeFn = () => void;
48
48
  /**
49
- * Solana options for FormoAnalytics
49
+ * The rdns reported for a Solana wallet.
50
+ *
51
+ * The Wallet Standard has no reverse-domain identifier, so one is derived
52
+ * from the wallet's normalized name. Lowercasing and removing whitespace
53
+ * preserves the values historically reported by the framework-kit store;
54
+ * encoding keeps delimiters and other special characters safe for storage.
55
+ * Both Solana paths (Wallet Standard discovery and the framework-kit store)
56
+ * derive it the same way, so a wallet's `detect` and its `connect` share one
57
+ * rdns whichever path reported each.
58
+ */
59
+ export declare function solanaWalletRdns(walletName: string): string;
60
+ /**
61
+ * Solana options for FormoAnalytics.
62
+ *
63
+ * Wallet discovery through the Wallet Standard is on by default and needs
64
+ * none of these. They add framework-kit's store, or name the cluster.
50
65
  */
51
66
  export interface SolanaOptions {
52
67
  /**
53
- * The framework-kit client store (client.store) for automatic event tracking.
54
- * When provided, wallet connect/disconnect and transaction events are tracked
55
- * automatically by subscribing to zustand store state changes.
68
+ * The framework-kit client store (client.store).
69
+ * When provided, transaction lifecycle events and cluster switches are
70
+ * tracked from the store, and connect/disconnect come from the store
71
+ * rather than from Wallet Standard discovery.
56
72
  *
57
- * This is the recommended approach for apps using framework-kit.
73
+ * Only for apps using framework-kit (`@solana/client`). Other integrations
74
+ * do not need a store when their wallets register through Wallet Standard.
58
75
  *
59
76
  * @example
60
77
  * ```tsx
@@ -65,10 +82,16 @@ export interface SolanaOptions {
65
82
  */
66
83
  store?: import("./storeTypes").SolanaClientStore;
67
84
  /**
68
- * The Solana cluster/network.
69
- * Usually auto-detected from the store's endpoint URL.
70
- * Only needed for custom RPC URLs that don't contain a recognizable cluster name.
71
- * @default auto-detected, or "mainnet-beta" if detection fails
85
+ * The Solana cluster/network the app is on.
86
+ *
87
+ * With a store, usually auto-detected from its endpoint URL; only needed
88
+ * for custom RPC URLs that don't contain a recognizable cluster name.
89
+ * Without a store, the Wallet Standard cannot say which cluster the app
90
+ * uses, so a devnet or testnet app should set this (or call
91
+ * `formo.solana.setCluster()`); otherwise connections are reported on
92
+ * mainnet-beta.
93
+ * @default auto-detected from the store; otherwise mainnet-beta when the
94
+ * wallet supports it, or its first supported Solana cluster
72
95
  */
73
96
  cluster?: SolanaCluster;
74
97
  }
@@ -40,4 +40,18 @@ export function isSolanaChainId(chainId) {
40
40
  return false;
41
41
  return Object.values(SOLANA_CHAIN_IDS).includes(chainId);
42
42
  }
43
+ /**
44
+ * The rdns reported for a Solana wallet.
45
+ *
46
+ * The Wallet Standard has no reverse-domain identifier, so one is derived
47
+ * from the wallet's normalized name. Lowercasing and removing whitespace
48
+ * preserves the values historically reported by the framework-kit store;
49
+ * encoding keeps delimiters and other special characters safe for storage.
50
+ * Both Solana paths (Wallet Standard discovery and the framework-kit store)
51
+ * derive it the same way, so a wallet's `detect` and its `connect` share one
52
+ * rdns whichever path reported each.
53
+ */
54
+ export function solanaWalletRdns(walletName) {
55
+ return "sol.wallet.".concat(encodeURIComponent(walletName.toLowerCase().replace(/\s+/g, "")));
56
+ }
43
57
  //# sourceMappingURL=types.js.map
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The subset of the Wallet Standard this SDK reads.
3
+ *
4
+ * Duck-typed on purpose: the SDK must not depend on `@wallet-standard/*`
5
+ * packages (see the dependency policy), and a wallet is discovered through
6
+ * window events, so nothing here is ever imported from a wallet library.
7
+ * Every field is what a conforming wallet exposes; the shapes are looser
8
+ * than the reference types so a slightly off wallet is skipped, not thrown
9
+ * on.
10
+ *
11
+ * @see https://github.com/wallet-standard/wallet-standard
12
+ */
13
+ /** `namespace:reference`, e.g. `solana:mainnet` or `solana:devnet`. */
14
+ export type WalletStandardChain = string;
15
+ export interface WalletStandardAccount {
16
+ readonly address: string;
17
+ readonly publicKey?: unknown;
18
+ /** Chains this account can sign for. Absent on a non-conforming wallet. */
19
+ readonly chains?: readonly WalletStandardChain[];
20
+ readonly features?: readonly string[];
21
+ readonly label?: string;
22
+ readonly icon?: string;
23
+ }
24
+ export interface WalletStandardWallet {
25
+ readonly version?: string;
26
+ readonly name: string;
27
+ readonly icon?: string;
28
+ /** Every chain the wallet supports, NOT the one currently selected. */
29
+ readonly chains: readonly WalletStandardChain[];
30
+ readonly features: Readonly<Record<string, unknown>>;
31
+ /** Accounts the app is currently authorized for; empty until connected. */
32
+ readonly accounts: readonly WalletStandardAccount[];
33
+ }
34
+ /** What a `standard:events` `change` event carries: only what changed. */
35
+ export interface WalletStandardChangeProperties {
36
+ readonly chains?: readonly WalletStandardChain[];
37
+ readonly features?: Readonly<Record<string, unknown>>;
38
+ readonly accounts?: readonly WalletStandardAccount[];
39
+ }
40
+ /** The `standard:events` feature. */
41
+ export interface WalletStandardEventsFeature {
42
+ readonly version?: string;
43
+ on(event: "change", listener: (properties: WalletStandardChangeProperties) => void): () => void;
44
+ }
45
+ /** The API an app hands to wallets so they can announce themselves. */
46
+ export interface WalletStandardRegisterApi {
47
+ register(...wallets: WalletStandardWallet[]): () => void;
48
+ }
49
+ /** Dispatched by the app on `window`; `detail` is the register API. */
50
+ export declare const WALLET_STANDARD_APP_READY_EVENT = "wallet-standard:app-ready";
51
+ /** Dispatched by a wallet on `window`; `detail` is a callback taking the API. */
52
+ export declare const WALLET_STANDARD_REGISTER_WALLET_EVENT = "wallet-standard:register-wallet";
53
+ export declare const WALLET_STANDARD_EVENTS_FEATURE = "standard:events";
54
+ //# sourceMappingURL=walletStandardTypes.d.ts.map