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