@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.
- package/dist/cjs/src/FormoAnalytics.d.ts +4 -1
- package/dist/cjs/src/FormoAnalytics.js +56 -36
- package/dist/cjs/src/FormoAnalyticsProvider.js +20 -0
- package/dist/cjs/src/event/EventFactory.d.ts +9 -1
- package/dist/cjs/src/event/EventFactory.js +31 -16
- package/dist/cjs/src/event/EventManager.d.ts +3 -1
- package/dist/cjs/src/event/EventManager.js +33 -4
- package/dist/cjs/src/event/cancellation.d.ts +4 -0
- package/dist/cjs/src/event/cancellation.js +7 -0
- package/dist/cjs/src/event/type.d.ts +1 -0
- package/dist/cjs/src/event/utils.d.ts +5 -1
- package/dist/cjs/src/event/utils.js +57 -2
- package/dist/cjs/src/evm/EvmEventTracker.d.ts +7 -0
- package/dist/cjs/src/evm/EvmEventTracker.js +31 -5
- package/dist/cjs/src/queue/EventQueue.d.ts +1 -0
- package/dist/cjs/src/queue/EventQueue.js +11 -6
- package/dist/cjs/src/solana/SolanaManager.d.ts +37 -9
- package/dist/cjs/src/solana/SolanaManager.js +135 -20
- package/dist/cjs/src/solana/SolanaStoreHandler.d.ts +6 -0
- package/dist/cjs/src/solana/SolanaStoreHandler.js +17 -8
- package/dist/cjs/src/solana/SolanaWalletStandardRegistry.d.ts +163 -0
- package/dist/cjs/src/solana/SolanaWalletStandardRegistry.js +447 -0
- package/dist/cjs/src/solana/index.d.ts +10 -5
- package/dist/cjs/src/solana/index.js +12 -6
- package/dist/cjs/src/solana/storeTypes.d.ts +20 -1
- package/dist/cjs/src/solana/types.d.ts +32 -9
- package/dist/cjs/src/solana/types.js +15 -0
- package/dist/cjs/src/solana/walletStandardTypes.d.ts +54 -0
- package/dist/cjs/src/solana/walletStandardTypes.js +21 -0
- package/dist/cjs/src/storage/StorageManager.d.ts +2 -0
- package/dist/cjs/src/storage/StorageManager.js +6 -0
- package/dist/cjs/src/storage/index.d.ts +1 -0
- package/dist/cjs/src/storage/index.js +7 -1
- package/dist/cjs/src/types/base.d.ts +15 -5
- package/dist/cjs/src/version.d.ts +1 -1
- package/dist/cjs/src/version.js +1 -1
- package/dist/esm/src/FormoAnalytics.d.ts +4 -1
- package/dist/esm/src/FormoAnalytics.js +54 -34
- package/dist/esm/src/FormoAnalyticsProvider.js +20 -0
- package/dist/esm/src/event/EventFactory.d.ts +9 -1
- package/dist/esm/src/event/EventFactory.js +31 -16
- package/dist/esm/src/event/EventManager.d.ts +3 -1
- package/dist/esm/src/event/EventManager.js +33 -4
- package/dist/esm/src/event/cancellation.d.ts +4 -0
- package/dist/esm/src/event/cancellation.js +4 -0
- package/dist/esm/src/event/type.d.ts +1 -0
- package/dist/esm/src/event/utils.d.ts +5 -1
- package/dist/esm/src/event/utils.js +56 -3
- package/dist/esm/src/evm/EvmEventTracker.d.ts +7 -0
- package/dist/esm/src/evm/EvmEventTracker.js +31 -5
- package/dist/esm/src/queue/EventQueue.d.ts +1 -0
- package/dist/esm/src/queue/EventQueue.js +11 -6
- package/dist/esm/src/solana/SolanaManager.d.ts +37 -9
- package/dist/esm/src/solana/SolanaManager.js +135 -20
- package/dist/esm/src/solana/SolanaStoreHandler.d.ts +6 -0
- package/dist/esm/src/solana/SolanaStoreHandler.js +18 -9
- package/dist/esm/src/solana/SolanaWalletStandardRegistry.d.ts +163 -0
- package/dist/esm/src/solana/SolanaWalletStandardRegistry.js +444 -0
- package/dist/esm/src/solana/index.d.ts +10 -5
- package/dist/esm/src/solana/index.js +10 -5
- package/dist/esm/src/solana/storeTypes.d.ts +20 -1
- package/dist/esm/src/solana/types.d.ts +32 -9
- package/dist/esm/src/solana/types.js +14 -0
- package/dist/esm/src/solana/walletStandardTypes.d.ts +54 -0
- package/dist/esm/src/solana/walletStandardTypes.js +18 -0
- package/dist/esm/src/storage/StorageManager.d.ts +2 -0
- package/dist/esm/src/storage/StorageManager.js +6 -0
- package/dist/esm/src/storage/index.d.ts +1 -0
- package/dist/esm/src/storage/index.js +5 -0
- package/dist/esm/src/types/base.d.ts +15 -5
- package/dist/esm/src/version.d.ts +1 -1
- package/dist/esm/src/version.js +1 -1
- package/dist/index.umd.min.js +1 -1
- 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
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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)
|
|
54
|
-
* When provided,
|
|
55
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
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
|