@kheopskit/core 5.1.0 → 5.1.2

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 (76) hide show
  1. package/dist/errors-Caq2csKx.mjs +181 -0
  2. package/dist/errors-Caq2csKx.mjs.map +1 -0
  3. package/dist/errors-DPw6pc2H.js +222 -0
  4. package/dist/errors-DPw6pc2H.js.map +1 -0
  5. package/dist/ethereum.d.mts +39 -38
  6. package/dist/ethereum.d.ts +39 -38
  7. package/dist/ethereum.js +214 -281
  8. package/dist/ethereum.js.map +1 -1
  9. package/dist/ethereum.mjs +211 -282
  10. package/dist/ethereum.mjs.map +1 -1
  11. package/dist/{getCachedObservable-C4E8dfMp.d.ts → getCachedObservable-LUq9IfEx.d.mts} +5 -2
  12. package/dist/{getCachedObservable-C4E8dfMp.d.mts → getCachedObservable-LUq9IfEx.d.ts} +5 -2
  13. package/dist/iconCache-BSa42Sui.mjs +343 -0
  14. package/dist/iconCache-BSa42Sui.mjs.map +1 -0
  15. package/dist/iconCache-DS4zNiP1.js +396 -0
  16. package/dist/iconCache-DS4zNiP1.js.map +1 -0
  17. package/dist/index.d.mts +65 -59
  18. package/dist/index.d.ts +65 -59
  19. package/dist/index.js +901 -1015
  20. package/dist/index.js.map +1 -1
  21. package/dist/index.mjs +885 -1016
  22. package/dist/index.mjs.map +1 -1
  23. package/dist/injectedWallets-B1MCxAE9.js +148 -0
  24. package/dist/injectedWallets-B1MCxAE9.js.map +1 -0
  25. package/dist/injectedWallets-B3Vysfvr.mjs +119 -0
  26. package/dist/injectedWallets-B3Vysfvr.mjs.map +1 -0
  27. package/dist/internal.d.mts +18 -16
  28. package/dist/internal.d.ts +18 -16
  29. package/dist/internal.js +9 -20
  30. package/dist/internal.mjs +3 -20
  31. package/dist/polkadot.d.mts +37 -36
  32. package/dist/polkadot.d.ts +37 -36
  33. package/dist/polkadot.js +178 -220
  34. package/dist/polkadot.js.map +1 -1
  35. package/dist/polkadot.mjs +175 -221
  36. package/dist/polkadot.mjs.map +1 -1
  37. package/dist/polkadotExtensions-BoVKhNU_.mjs +23 -0
  38. package/dist/polkadotExtensions-BoVKhNU_.mjs.map +1 -0
  39. package/dist/polkadotExtensions-y4J9UzYl.js +28 -0
  40. package/dist/polkadotExtensions-y4J9UzYl.js.map +1 -0
  41. package/dist/solana.d.mts +50 -48
  42. package/dist/solana.d.ts +50 -48
  43. package/dist/solana.js +320 -378
  44. package/dist/solana.js.map +1 -1
  45. package/dist/solana.mjs +314 -379
  46. package/dist/solana.mjs.map +1 -1
  47. package/dist/types-Cb-vuEru.d.mts +330 -0
  48. package/dist/types-Cb-vuEru.d.ts +330 -0
  49. package/dist/types-DKU-SNio.js +169 -0
  50. package/dist/{chunk-P7BPHHVV.js.map → types-DKU-SNio.js.map} +1 -1
  51. package/dist/types-jnifAAA0.mjs +104 -0
  52. package/dist/types-jnifAAA0.mjs.map +1 -0
  53. package/package.json +12 -12
  54. package/dist/chunk-3Y5N4N3Q.mjs +0 -267
  55. package/dist/chunk-3Y5N4N3Q.mjs.map +0 -1
  56. package/dist/chunk-BWUUHUDK.mjs +0 -24
  57. package/dist/chunk-BWUUHUDK.mjs.map +0 -1
  58. package/dist/chunk-D3EQMFZ2.js +0 -24
  59. package/dist/chunk-D3EQMFZ2.js.map +0 -1
  60. package/dist/chunk-DF7EKK4G.mjs +0 -106
  61. package/dist/chunk-DF7EKK4G.mjs.map +0 -1
  62. package/dist/chunk-EH73P476.js +0 -106
  63. package/dist/chunk-EH73P476.js.map +0 -1
  64. package/dist/chunk-JVSCMJY5.mjs +0 -147
  65. package/dist/chunk-JVSCMJY5.mjs.map +0 -1
  66. package/dist/chunk-P7BPHHVV.js +0 -76
  67. package/dist/chunk-SO64R7Z6.js +0 -267
  68. package/dist/chunk-SO64R7Z6.js.map +0 -1
  69. package/dist/chunk-WOPCW2OX.mjs +0 -76
  70. package/dist/chunk-WOPCW2OX.mjs.map +0 -1
  71. package/dist/chunk-XOGQMEV2.js +0 -147
  72. package/dist/chunk-XOGQMEV2.js.map +0 -1
  73. package/dist/internal.js.map +0 -1
  74. package/dist/internal.mjs.map +0 -1
  75. package/dist/types-0iDBydsK.d.mts +0 -327
  76. package/dist/types-0iDBydsK.d.ts +0 -327
package/dist/index.mjs CHANGED
@@ -1,1020 +1,889 @@
1
- import {
2
- cookieStorage,
3
- getCachedIcon,
4
- hydrateAccount,
5
- hydrateWallet,
6
- safeLocalStorage,
7
- serializeAccount,
8
- serializeWallet,
9
- setCachedIcons
10
- } from "./chunk-3Y5N4N3Q.mjs";
11
- import "./chunk-BWUUHUDK.mjs";
12
- import {
13
- KheopskitError,
14
- getWalletAccountId,
15
- isValidAddress,
16
- parseWalletAccountId
17
- } from "./chunk-DF7EKK4G.mjs";
18
- import {
19
- WALLET_CONNECT_WALLET_ID,
20
- clearAllCachedObservables,
21
- clearCachedObservablesByPrefix,
22
- getWalletId,
23
- isInjectedWallet,
24
- isValidWalletId,
25
- isWalletConnectWallet,
26
- isWalletPlatform,
27
- parseWalletId
28
- } from "./chunk-WOPCW2OX.mjs";
29
-
30
- // src/api/appKit.ts
31
- import {
32
- BehaviorSubject,
33
- distinctUntilChanged,
34
- from,
35
- map,
36
- Observable,
37
- of,
38
- shareReplay,
39
- switchMap,
40
- tap
41
- } from "rxjs";
42
- var loadAppKit = async () => {
43
- try {
44
- const { createAppKit } = await import("@reown/appkit/core");
45
- return createAppKit;
46
- } catch (cause) {
47
- console.error(
48
- "[kheopskit] WalletConnect is configured but @reown/appkit could not be loaded. Install it with `pnpm add @reown/appkit` (or remove config.walletConnect). WalletConnect wallets are disabled; injected wallets still work.",
49
- cause
50
- );
51
- return null;
52
- }
53
- };
54
- var WALLET_CONNECT_ICON = "data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjQwMCIgdmlld0JveD0iMCAwIDQwMCA0MDAiIHdpZHRoPSI0MDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxjbGlwUGF0aCBpZD0iYSI+PHBhdGggZD0ibTAgMGg0MDB2NDAwaC00MDB6Ii8+PC9jbGlwUGF0aD48ZyBjbGlwLXBhdGg9InVybCgjYSkiPjxjaXJjbGUgY3g9IjIwMCIgY3k9IjIwMCIgZmlsbD0iIzMzOTZmZiIgcj0iMTk5LjUiIHN0cm9rZT0iIzY2YjFmZiIvPjxwYXRoIGQ9Im0xMjIuNTE5IDE0OC45NjVjNDIuNzkxLTQxLjcyOSAxMTIuMTcxLTQxLjcyOSAxNTQuOTYyIDBsNS4xNSA1LjAyMmMyLjE0IDIuMDg2IDIuMTQgNS40NjkgMCA3LjU1NWwtMTcuNjE3IDE3LjE4Yy0xLjA3IDEuMDQzLTIuODA0IDEuMDQzLTMuODc0IDBsLTcuMDg3LTYuOTExYy0yOS44NTMtMjkuMTExLTc4LjI1My0yOS4xMTEtMTA4LjEwNiAwbC03LjU5IDcuNDAxYy0xLjA3IDEuMDQzLTIuODA0IDEuMDQzLTMuODc0IDBsLTE3LjYxNy0xNy4xOGMtMi4xNC0yLjA4Ni0yLjE0LTUuNDY5IDAtNy41NTV6bTE5MS4zOTcgMzUuNTI5IDE1LjY3OSAxNS4yOWMyLjE0IDIuMDg2IDIuMTQgNS40NjkgMCA3LjU1NWwtNzAuNyA2OC45NDRjLTIuMTM5IDIuMDg3LTUuNjA4IDIuMDg3LTcuNzQ4IDBsLTUwLjE3OC00OC45MzFjLS41MzUtLjUyMi0xLjQwMi0uNTIyLTEuOTM3IDBsLTUwLjE3OCA0OC45MzFjLTIuMTM5IDIuMDg3LTUuNjA4IDIuMDg3LTcuNzQ4IDBsLTcwLjcwMTUtNjguOTQ1Yy0yLjEzOTYtMi4wODYtMi4xMzk2LTUuNDY5IDAtNy41NTVsMTUuNjc5NS0xNS4yOWMyLjEzOTYtMi4wODYgNS42MDg1LTIuMDg2IDcuNzQ4MSAwbDUwLjE3ODkgNDguOTMyYy41MzUuNTIyIDEuNDAyLjUyMiAxLjkzNyAwbDUwLjE3Ny00OC45MzJjMi4xMzktMi4wODcgNS42MDgtMi4wODcgNy43NDggMGw1MC4xNzkgNDguOTMyYy41MzUuNTIyIDEuNDAyLjUyMiAxLjkzNyAwbDUwLjE3OS00OC45MzFjMi4xMzktMi4wODcgNS42MDgtMi4wODcgNy43NDggMHoiIGZpbGw9IiNmZmYiLz48L2c+PC9zdmc+";
55
- var APPKIT_SYMBOL = /* @__PURE__ */ Symbol.for("kheopskit.cachedAppKit");
56
- var APPKIT_PROJECT_ID_SYMBOL = /* @__PURE__ */ Symbol.for("kheopskit.cachedAppKitProjectId");
57
- var getCachedAppKit = () => globalThis[APPKIT_SYMBOL];
58
- var getCachedAppKitProjectId = () => globalThis[APPKIT_PROJECT_ID_SYMBOL];
59
- var setCachedAppKit = (value, projectId) => {
60
- globalThis[APPKIT_SYMBOL] = value;
61
- globalThis[APPKIT_PROJECT_ID_SYMBOL] = projectId;
62
- };
63
- var resetAppKitCache = () => {
64
- setCachedAppKit(void 0);
65
- };
66
- var dropAccountsCache = (platform) => {
67
- clearCachedObservablesByPrefix(
68
- `accounts:${WALLET_CONNECT_WALLET_ID}:${platform}:`
69
- );
70
- };
71
- var getWalletConnectWallet$ = (config) => {
72
- if (!config.walletConnect) return of(null);
73
- if (typeof window === "undefined") return of(null);
74
- const walletConnect = config.walletConnect;
75
- const cachedProjectId = getCachedAppKitProjectId();
76
- if (cachedProjectId !== void 0 && cachedProjectId !== walletConnect.projectId) {
77
- console.warn(
78
- "[kheopskit] WalletConnect is already initialised with projectId %s; AppKit is a process-wide singleton, so the first configuration wins and projectId %s is ignored. Call resetAppKitCache() before re-initialising if the WalletConnect config must change.",
79
- cachedProjectId,
80
- walletConnect.projectId
81
- );
82
- }
83
- let cachedAppKit = getCachedAppKit();
84
- if (!cachedAppKit) {
85
- cachedAppKit = from(loadAppKit()).pipe(
86
- switchMap((createAppKit) => {
87
- if (!createAppKit) return of(null);
88
- return new Observable((subscriber) => {
89
- const appKit = createAppKit({
90
- projectId: walletConnect.projectId,
91
- metadata: walletConnect.metadata,
92
- // Loosely typed in WalletConnectConfig to keep @reown/appkit's
93
- // types out of core; forwarded to AppKit verbatim.
94
- networks: walletConnect.networks,
95
- themeMode: walletConnect.themeMode,
96
- themeVariables: walletConnect.themeVariables,
97
- universalProviderConfigOverride: {
98
- methods: {
99
- polkadot: ["polkadot_signTransaction", "polkadot_signMessage"],
100
- solana: [
101
- "solana_signTransaction",
102
- "solana_signMessage",
103
- "solana_signAndSendTransaction"
104
- ]
105
- }
106
- },
107
- allWallets: "HIDE",
108
- debug: config.debug,
109
- allowUnsupportedChain: true
110
- });
111
- const appKitInstance = appKit;
112
- const status$ = new BehaviorSubject({
113
- isPolkadotConnected: false,
114
- isEthereumConnected: false,
115
- isSolanaConnected: false
116
- });
117
- const unsubProviders = appKit.subscribeProviders((providers) => {
118
- status$.next({
119
- isPolkadotConnected: !!providers.polkadot,
120
- isEthereumConnected: !!providers.eip155,
121
- isSolanaConnected: !!providers.solana
122
- });
123
- });
124
- const namespaceOf = {
125
- polkadot: "polkadot",
126
- ethereum: "eip155",
127
- solana: "solana"
128
- };
129
- const allPlatforms = [
130
- "polkadot",
131
- "ethereum",
132
- "solana"
133
- ];
134
- const enabledPlatforms = allPlatforms.filter(
135
- (p) => appKit.chainNamespaces.includes(namespaceOf[p])
136
- );
137
- let prevConnected = {
138
- polkadot: false,
139
- ethereum: false,
140
- solana: false
141
- };
142
- const sub = status$.pipe(
143
- map((s) => ({
144
- polkadot: s.isPolkadotConnected,
145
- ethereum: s.isEthereumConnected,
146
- solana: s.isSolanaConnected
147
- })),
148
- distinctUntilChanged(
149
- (a, b) => a.polkadot === b.polkadot && a.ethereum === b.ethereum && a.solana === b.solana
150
- ),
151
- tap((connected) => {
152
- for (const platform of allPlatforms)
153
- if (prevConnected[platform] && !connected[platform])
154
- dropAccountsCache(platform);
155
- prevConnected = connected;
156
- }),
157
- map((connected) => {
158
- const platforms = enabledPlatforms.filter((p) => connected[p]);
159
- const isConnected = platforms.length > 0;
160
- const walletInfo = appKit.getWalletInfo();
161
- return {
162
- id: WALLET_CONNECT_WALLET_ID,
163
- type: "walletconnect",
164
- platforms,
165
- appKit: appKitInstance,
166
- name: walletInfo?.name ?? "WalletConnect",
167
- icon: walletInfo?.icon ?? WALLET_CONNECT_ICON,
168
- // One shared session: connecting opens the modal; the wallet
169
- // approves namespaces in that single pairing. Disconnect is
170
- // session-wide; re-pair to change the approved set.
171
- connect: async () => {
172
- if (!isConnected) await appKit.open();
173
- },
174
- disconnect: async () => {
175
- if (isConnected) await appKit.disconnect();
176
- },
177
- isConnected
178
- };
179
- })
180
- ).subscribe(subscriber);
181
- return () => {
182
- sub.unsubscribe();
183
- unsubProviders();
184
- };
185
- });
186
- }),
187
- // refCount:false keeps the AppKit instance alive for the process lifetime
188
- // once created: createAppKit runs at most once (Reown AppKit cannot be
189
- // instantiated twice). With refCount:true the producer would re-run
190
- // createAppKit whenever every subscriber drained and a new one
191
- // re-subscribed — e.g. a React unmount/remount or StrictMode's
192
- // mount→unmount→remount — throwing on the duplicate Lit web components and
193
- // WalletConnect modal singleton. The replayed connector value also keeps
194
- // the wallet list stable across such teardown/rebuild cycles.
195
- shareReplay({ refCount: false, bufferSize: 1 })
196
- );
197
- setCachedAppKit(cachedAppKit, walletConnect.projectId);
198
- }
199
- return cachedAppKit;
200
- };
201
-
202
- // src/api/config.ts
203
- var DEFAULT_STORAGE_KEY = "kheopskit";
204
- var DEFAULTS = {
205
- autoReconnect: true,
206
- debug: false,
207
- storageKey: DEFAULT_STORAGE_KEY,
208
- hydrationGracePeriod: 500
209
- };
210
- var resolveConfig = (config) => {
211
- const platforms = config?.platforms ?? [];
212
- const invalidPlatforms = platforms.filter(
213
- (p) => typeof p !== "object" || p === null || typeof p.getWallets$ !== "function"
214
- );
215
- if (invalidPlatforms.length > 0) {
216
- throw new Error(
217
- `[kheopskit] config.platforms must contain plugin instances created by the per-platform factories (e.g. platforms: [polkadot(), ethereum(), solana()] imported from "@kheopskit/core/<platform>"). Invalid entries: ${JSON.stringify(invalidPlatforms)}. String platform names like "polkadot" were removed in v4 \u2014 see MIGRATING_TO_V4.md.`
218
- );
219
- }
220
- if (platforms.length === 0) {
221
- console.warn(
222
- '[kheopskit] No platforms configured; wallets and accounts will be empty. Pass e.g. platforms: [polkadot()] from "@kheopskit/core/polkadot".'
223
- );
224
- }
225
- return Object.assign({}, DEFAULTS, config, { platforms });
226
- };
227
-
228
- // src/utils/sortAccounts.ts
229
- var PLATFORM_ORDER = {
230
- polkadot: 0,
231
- ethereum: 1,
232
- solana: 2
233
- };
234
- var byWalletName = (a1, a2) => {
235
- if (a1.walletName.toLowerCase() === "talisman") return -1;
236
- if (a2.walletName.toLowerCase() === "talisman") return 1;
237
- return a1.walletName.localeCompare(a2.walletName);
238
- };
239
- var sortAccounts = (a1, a2) => {
240
- if (a1.platform !== a2.platform)
241
- return PLATFORM_ORDER[a1.platform] - PLATFORM_ORDER[a2.platform];
242
- if (a1.walletName !== a2.walletName) return byWalletName(a1, a2);
243
- if (a1.platform === "polkadot" && a2.platform === "polkadot")
244
- return a1.name !== a2.name ? (a1.name ?? "").localeCompare(a2.name ?? "") : a1.address.localeCompare(a2.address);
245
- return a1.id.localeCompare(a2.id);
246
- };
247
-
248
- // src/utils/sortWallets.ts
249
- var PLATFORM_ORDER2 = {
250
- polkadot: 0,
251
- ethereum: 1,
252
- solana: 2
253
- };
254
- var orderOf = (wallet) => isWalletConnectWallet(wallet) ? 3 : PLATFORM_ORDER2[wallet.platform];
255
- var sortWallets = (w1, w2) => {
256
- const o1 = orderOf(w1);
257
- const o2 = orderOf(w2);
258
- if (o1 !== o2) return o1 - o2;
259
- if (w1.name.toLowerCase() === "talisman") return -1;
260
- if (w2.name.toLowerCase() === "talisman") return 1;
261
- return w1.name.localeCompare(w2.name);
262
- };
263
-
264
- // src/api/platform.ts
265
- var acceptsCachedAccount = (cached, platforms) => {
266
- const plugin = platforms.find((p) => p.platform === cached.platform);
267
- return plugin?.acceptsCachedAccount?.(cached) ?? true;
268
- };
269
-
270
- // src/api/hydration.ts
271
- var getHydratedSnapshot = (store, platforms, options = {}) => {
272
- const cached = store.getCachedState();
273
- const wallets = cached.wallets.map((cachedWallet) => {
274
- const wallet = hydrateWallet(cachedWallet);
275
- if (!options.icons || wallet.icon) return wallet;
276
- const icon = getCachedIcon(wallet.id);
277
- return icon ? { ...wallet, icon } : wallet;
278
- }).sort(sortWallets);
279
- const accounts = cached.accounts.filter((account) => acceptsCachedAccount(account, platforms)).map(hydrateAccount).sort(sortAccounts);
280
- return { wallets, accounts };
281
- };
282
-
283
- // src/api/kheopskit.ts
284
- import {
285
- combineLatest as combineLatest4,
286
- debounceTime,
287
- distinctUntilChanged as distinctUntilChanged2,
288
- filter as filter3,
289
- map as map5,
290
- Observable as Observable5,
291
- shareReplay as shareReplay5,
292
- throttleTime
293
- } from "rxjs";
294
-
295
- // src/utils/createHydrationBuffer.ts
296
- import {
297
- BehaviorSubject as BehaviorSubject2,
298
- combineLatest,
299
- filter,
300
- map as map2,
301
- Observable as Observable2,
302
- Subscription,
303
- shareReplay as shareReplay2,
304
- startWith,
305
- take,
306
- timer
307
- } from "rxjs";
308
- var createBufferCore = (cachedItems, liveItems$, gracePeriodMs, mergeFn, isConverged) => {
309
- if (gracePeriodMs <= 0 || cachedItems.length === 0) {
310
- return liveItems$.pipe(map2((items) => ({ items, isHydrating: false })));
311
- }
312
- return new Observable2((subscriber) => {
313
- const subscriptions = new Subscription();
314
- const isHydrating$ = new BehaviorSubject2(true);
315
- const liveWithInitial$ = liveItems$.pipe(startWith([]));
316
- if (isConverged) {
317
- const timerFired$ = timer(gracePeriodMs).pipe(
318
- map2(() => true),
319
- startWith(false),
320
- shareReplay2({ bufferSize: 1, refCount: true })
321
- );
322
- subscriptions.add(
323
- combineLatest([liveWithInitial$, timerFired$]).pipe(
324
- filter(([, timerFired]) => timerFired),
325
- filter(([liveItems]) => isConverged(liveItems, cachedItems)),
326
- take(1)
327
- ).subscribe(() => {
328
- isHydrating$.next(false);
329
- })
330
- );
331
- subscriptions.add(
332
- timer(gracePeriodMs * 6).subscribe(() => {
333
- if (isHydrating$.value) {
334
- isHydrating$.next(false);
335
- }
336
- })
337
- );
338
- } else {
339
- subscriptions.add(
340
- timer(gracePeriodMs).subscribe(() => {
341
- isHydrating$.next(false);
342
- })
343
- );
344
- }
345
- subscriptions.add(
346
- combineLatest([liveWithInitial$, isHydrating$]).pipe(
347
- map2(([liveItems, isHydrating]) => {
348
- if (!isHydrating) {
349
- return { items: liveItems, isHydrating: false };
350
- }
351
- return {
352
- items: mergeFn(liveItems, cachedItems),
353
- isHydrating: true
354
- };
355
- })
356
- ).subscribe(subscriber)
357
- );
358
- return () => {
359
- subscriptions.unsubscribe();
360
- isHydrating$.complete();
361
- };
362
- });
363
- };
364
- var createHydrationBuffer = (cachedItems, liveItems$, gracePeriodMs, getKey, isConverged, mergeItem, transformCachedOnly) => {
365
- return createBufferCore(
366
- cachedItems,
367
- liveItems$,
368
- gracePeriodMs,
369
- (liveItems, cached) => {
370
- const cachedByKey = new Map(cached.map((item) => [getKey(item), item]));
371
- const liveKeys = new Set(liveItems.map(getKey));
372
- const mergedItems = liveItems.map((liveItem) => {
373
- const key = getKey(liveItem);
374
- const cachedItem = cachedByKey.get(key);
375
- if (!cachedItem) return liveItem;
376
- return mergeItem ? mergeItem(liveItem, cachedItem) : cachedItem;
377
- });
378
- for (const cachedItem of cached) {
379
- if (!liveKeys.has(getKey(cachedItem))) {
380
- const transformed = transformCachedOnly ? transformCachedOnly(cachedItem) : cachedItem;
381
- mergedItems.push(transformed);
382
- }
383
- }
384
- return mergedItems;
385
- },
386
- isConverged
387
- );
388
- };
389
- var createAccountHydrationBuffer = (cachedAccounts, liveAccounts$, gracePeriodMs, getWalletId2, isConverged) => {
390
- return createBufferCore(
391
- cachedAccounts,
392
- liveAccounts$,
393
- gracePeriodMs,
394
- (liveAccounts, cached) => {
395
- const walletsWithLiveAccounts = new Set(liveAccounts.map(getWalletId2));
396
- const mergedAccounts = [...liveAccounts];
397
- for (const cachedAccount of cached) {
398
- if (!walletsWithLiveAccounts.has(getWalletId2(cachedAccount))) {
399
- mergedAccounts.push(cachedAccount);
400
- }
401
- }
402
- return mergedAccounts;
403
- },
404
- isConverged
405
- );
406
- };
407
-
408
- // src/utils/logObservable.ts
409
- import { tap as tap2 } from "rxjs";
410
- var logObservable = (label, opts) => tap2((value) => {
411
- const { printValue = false, enabled = true } = opts || {};
412
- if (!label || !enabled) return;
413
- const text = `[kheopskit] observable ${label} emit`;
414
- if (printValue) console.debug(text, value);
415
- else console.debug(text);
416
- });
417
-
418
- // src/api/accounts.ts
419
- import { combineLatest as combineLatest2, map as map3, Observable as Observable3, of as of2, shareReplay as shareReplay3 } from "rxjs";
420
- var getAccounts$ = (config, wallets) => {
421
- return new Observable3((subscriber) => {
422
- const sources = config.platforms.map(
423
- (plugin) => plugin.getAccounts$(
424
- wallets.pipe(
425
- map3(
426
- (ws) => ws.filter(
427
- (w) => isWalletConnectWallet(w) || w.platform === plugin.platform
428
- )
429
- )
430
- )
431
- )
432
- );
433
- const accounts$ = sources.length ? combineLatest2(sources).pipe(
434
- map3((accounts) => accounts.flat().sort(sortAccounts))
435
- ) : of2([]);
436
- const sub = accounts$.subscribe(subscriber);
437
- return () => {
438
- sub.unsubscribe();
439
- };
440
- }).pipe(shareReplay3({ refCount: true, bufferSize: 1 }));
441
- };
442
-
443
- // src/api/store.ts
1
+ import { a as isValidWalletId, c as clearAllCachedObservables, i as getWalletId, n as isWalletConnectWallet, o as parseWalletId, r as WALLET_CONNECT_WALLET_ID, s as isWalletPlatform, t as isInjectedWallet, u as clearCachedObservablesByPrefix } from "./types-jnifAAA0.mjs";
2
+ import { i as isValidAddress, n as getWalletAccountId, r as parseWalletAccountId, t as KheopskitError } from "./errors-Caq2csKx.mjs";
3
+ import { a as safeLocalStorage, c as serializeAccount, l as serializeWallet, n as setCachedIcons, o as hydrateAccount, r as cookieStorage, s as hydrateWallet, t as getCachedIcon } from "./iconCache-BSa42Sui.mjs";
4
+ import { BehaviorSubject, Observable, Subscription, combineLatest, debounceTime, distinctUntilChanged, filter, from, map, mergeMap, of, shareReplay, startWith, switchMap, take, tap, throttleTime, timer } from "rxjs";
444
5
  import { uniq } from "lodash-es";
445
-
446
- // src/utils/createStore.ts
447
- import { BehaviorSubject as BehaviorSubject3 } from "rxjs";
448
- var createStore = (key, defaultValue, storage = safeLocalStorage) => {
449
- const subject = new BehaviorSubject3(
450
- getStoredData(key, defaultValue, storage)
451
- );
452
- let unsubscribeStorage;
453
- if (typeof window !== "undefined" && storage.subscribe) {
454
- unsubscribeStorage = storage.subscribe(key, (newValue) => {
455
- subject.next(parseData(newValue, defaultValue));
456
- });
457
- }
458
- const update = (val) => {
459
- setStoredData(key, val, storage);
460
- subject.next(val);
461
- };
462
- return {
463
- observable: subject.asObservable(),
464
- set: (val) => update(val),
465
- mutate: (transform) => update(transform(subject.getValue())),
466
- get: () => structuredClone(subject.getValue()),
467
- /**
468
- * Cleanup subscriptions. Call this when the store is no longer needed.
469
- */
470
- destroy: () => {
471
- unsubscribeStorage?.();
472
- subject.complete();
473
- }
474
- };
475
- };
476
- var parseData = (str, defaultValue) => {
477
- try {
478
- if (str) return JSON.parse(str);
479
- } catch {
480
- }
481
- return defaultValue;
482
- };
483
- var getStoredData = (key, defaultValue, storage) => {
484
- const str = storage.getItem(key);
485
- return parseData(str, defaultValue);
486
- };
487
- var setStoredData = (key, val, storage) => {
488
- const str = JSON.stringify(val);
489
- storage.setItem(key, str);
490
- };
491
-
492
- // src/api/compactStore.ts
493
- var EMPTY_STORE_DATA = {};
494
- var toCompactPolkadotAccountType = (type) => {
495
- switch (type) {
496
- case "sr25519":
497
- return 0;
498
- case "ed25519":
499
- return 1;
500
- case "ecdsa":
501
- return 2;
502
- case "ethereum":
503
- return 3;
504
- default:
505
- return null;
506
- }
507
- };
508
- var fromCompactPolkadotAccountType = (type) => {
509
- switch (type) {
510
- case 0:
511
- return "sr25519";
512
- case 1:
513
- return "ed25519";
514
- case 2:
515
- return "ecdsa";
516
- case 3:
517
- return "ethereum";
518
- default:
519
- return void 0;
520
- }
521
- };
522
- var toCompactPlatform = (platform) => {
523
- switch (platform) {
524
- case "polkadot":
525
- return 0;
526
- case "ethereum":
527
- return 1;
528
- case "solana":
529
- return 2;
530
- }
531
- };
532
- var fromCompactPlatform = (code) => {
533
- switch (code) {
534
- case 0:
535
- return "polkadot";
536
- case 1:
537
- return "ethereum";
538
- case 2:
539
- return "solana";
540
- default:
541
- return void 0;
542
- }
543
- };
544
- var isCompactStore = (value) => {
545
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
546
- if ("cachedWallets" in value || "cachedAccounts" in value) return false;
547
- return "v" in value || "w" in value || "a" in value || "r" in value;
548
- };
549
- var toCompactStore = (data) => {
550
- const wallets = data.cachedWallets?.map(
551
- (wallet) => [
552
- wallet.id,
553
- wallet.name,
554
- wallet.isConnected ? 1 : 0,
555
- wallet.type === "walletconnect" ? 1 : 0
556
- ]
557
- );
558
- const accounts = data.cachedAccounts?.map(
559
- (account) => [
560
- account.walletId,
561
- account.address,
562
- account.name ?? null,
563
- account.chainId ?? null,
564
- toCompactPolkadotAccountType(account.polkadotAccountType),
565
- toCompactPlatform(account.platform)
566
- ]
567
- );
568
- return {
569
- v: 1,
570
- r: data.autoReconnect,
571
- w: wallets?.length ? wallets : void 0,
572
- a: accounts?.length ? accounts : void 0
573
- };
574
- };
575
- var fromCompactStore = (data) => {
576
- const walletNameMap = /* @__PURE__ */ new Map();
577
- const wallets = [];
578
- for (const item of Array.isArray(data.w) ? data.w : []) {
579
- if (!Array.isArray(item)) continue;
580
- const [id, name, isConnected, type] = item;
581
- if (!isValidWalletId(id)) continue;
582
- const isWalletConnect = id === WALLET_CONNECT_WALLET_ID;
583
- const walletType = type === 1 ? "walletconnect" : "injected";
584
- if (isWalletConnect !== (walletType === "walletconnect")) continue;
585
- walletNameMap.set(id, name);
586
- wallets.push({
587
- id,
588
- platform: isWalletConnect ? void 0 : parseWalletId(id).platform,
589
- type: walletType,
590
- name,
591
- isConnected: isConnected === 1
592
- });
593
- }
594
- const accounts = [];
595
- for (const item of Array.isArray(data.a) ? data.a : []) {
596
- if (!Array.isArray(item)) continue;
597
- const [
598
- walletId,
599
- address,
600
- name,
601
- chainId,
602
- polkadotAccountType,
603
- platformCode
604
- ] = item;
605
- if (!isValidWalletId(walletId) || typeof address !== "string" || !address)
606
- continue;
607
- const platform = platformCode != null ? fromCompactPlatform(platformCode) : walletId === WALLET_CONNECT_WALLET_ID ? void 0 : parseWalletId(walletId).platform;
608
- if (!isWalletPlatform(platform)) continue;
609
- accounts.push({
610
- id: getWalletAccountId(walletId, address),
611
- platform,
612
- address,
613
- name: name ?? void 0,
614
- chainId: chainId ?? void 0,
615
- polkadotAccountType: platform === "polkadot" ? fromCompactPolkadotAccountType(polkadotAccountType) : void 0,
616
- walletId,
617
- walletName: walletNameMap.get(walletId) ?? walletId
618
- });
619
- }
620
- return {
621
- autoReconnect: data.r,
622
- cachedWallets: wallets,
623
- cachedAccounts: accounts
624
- };
625
- };
626
- var decodeStore = (raw, fallback) => {
627
- try {
628
- const parsed = JSON.parse(raw);
629
- if (isCompactStore(parsed)) return fromCompactStore(parsed);
630
- return parsed;
631
- } catch {
632
- return fallback;
633
- }
634
- };
635
- var encodeStore = (data) => JSON.stringify(toCompactStore(data));
636
- var createCompactCookieStorage = (initialCookies) => {
637
- const base = cookieStorage(initialCookies);
638
- return {
639
- getItem: (key) => {
640
- const raw = base.getItem(key);
641
- if (!raw) return null;
642
- const expanded = decodeStore(raw, EMPTY_STORE_DATA);
643
- if (typeof document !== "undefined") {
644
- try {
645
- const parsed = JSON.parse(raw);
646
- if (!isCompactStore(parsed)) {
647
- base.setItem(key, encodeStore(expanded));
648
- }
649
- } catch {
650
- }
651
- }
652
- return JSON.stringify(expanded);
653
- },
654
- setItem: (key, value) => {
655
- const expanded = decodeStore(value, EMPTY_STORE_DATA);
656
- base.setItem(key, encodeStore(expanded));
657
- },
658
- removeItem: base.removeItem,
659
- subscribe: (key, callback) => {
660
- const unsubscribe = base.subscribe?.(key, (value) => {
661
- if (!value) {
662
- callback(null);
663
- return;
664
- }
665
- const expanded = decodeStore(value, EMPTY_STORE_DATA);
666
- callback(JSON.stringify(expanded));
667
- });
668
- return () => {
669
- unsubscribe?.();
670
- };
671
- }
672
- };
673
- };
674
-
675
- // src/api/store.ts
676
- var DEFAULT_SETTINGS = {};
677
- var isValidCachedWallet = (value) => {
678
- if (!value || typeof value !== "object") return false;
679
- const w = value;
680
- if (!isValidWalletId(w.id)) return false;
681
- if (typeof w.name !== "string" || typeof w.isConnected !== "boolean")
682
- return false;
683
- if (w.type === "walletconnect")
684
- return w.id === WALLET_CONNECT_WALLET_ID && w.platform === void 0;
685
- return w.type === "injected" && isWalletPlatform(w.platform);
686
- };
687
- var isValidCachedAccount = (value) => {
688
- if (!value || typeof value !== "object") return false;
689
- const a = value;
690
- return typeof a.id === "string" && !!a.id && isWalletPlatform(a.platform) && typeof a.address === "string" && !!a.address && isValidWalletId(a.walletId) && typeof a.walletName === "string";
691
- };
692
- var createKheopskitStore = (options = {}) => {
693
- const { ssrCookies, storageKey = DEFAULT_STORAGE_KEY } = options;
694
- const storage = ssrCookies !== void 0 ? createCompactCookieStorage(ssrCookies) : safeLocalStorage;
695
- const store = createStore(storageKey, DEFAULT_SETTINGS, storage);
696
- const addEnabledWalletId = (walletId) => {
697
- parseWalletId(walletId);
698
- store.mutate((prev) => ({
699
- ...prev,
700
- autoReconnect: uniq((prev.autoReconnect ?? []).concat(walletId))
701
- }));
702
- };
703
- const removeEnabledWalletId = (walletId) => {
704
- store.mutate((prev) => ({
705
- ...prev,
706
- autoReconnect: uniq(
707
- (prev.autoReconnect ?? []).filter((id) => id !== walletId)
708
- )
709
- }));
710
- };
711
- const getCachedState = () => {
712
- const data = store.get();
713
- const cachedWallets = Array.isArray(data?.cachedWallets) ? data.cachedWallets : [];
714
- const cachedAccounts = Array.isArray(data?.cachedAccounts) ? data.cachedAccounts : [];
715
- return {
716
- wallets: cachedWallets.filter(isValidCachedWallet),
717
- accounts: cachedAccounts.filter(isValidCachedAccount)
718
- };
719
- };
720
- const setCachedState = (wallets, accounts) => {
721
- store.mutate((prev) => ({
722
- ...prev,
723
- cachedWallets: wallets,
724
- cachedAccounts: accounts
725
- }));
726
- };
727
- return {
728
- observable: store.observable,
729
- addEnabledWalletId,
730
- removeEnabledWalletId,
731
- getCachedState,
732
- setCachedState
733
- };
734
- };
735
- var DEFAULT_STORE_SYMBOL = /* @__PURE__ */ Symbol.for("kheopskit.defaultStore");
736
- var getDefaultStore = () => {
737
- const g = globalThis;
738
- if (!g[DEFAULT_STORE_SYMBOL]) {
739
- g[DEFAULT_STORE_SYMBOL] = createKheopskitStore();
740
- }
741
- return g[DEFAULT_STORE_SYMBOL];
742
- };
743
-
744
- // src/api/wallets.ts
745
- import {
746
- combineLatest as combineLatest3,
747
- filter as filter2,
748
- map as map4,
749
- mergeMap,
750
- Observable as Observable4,
751
- of as of3,
752
- shareReplay as shareReplay4,
753
- take as take2
754
- } from "rxjs";
755
-
756
- // src/api/reconnectPolicy.ts
757
- var createReconnectPolicy = ({
758
- maxAttempts = 3
759
- } = {}) => {
760
- const reconnecting = /* @__PURE__ */ new Set();
761
- const reconnected = /* @__PURE__ */ new Set();
762
- const failedAttempts = /* @__PURE__ */ new Map();
763
- const shouldAttempt = (walletId) => !reconnecting.has(walletId) && !reconnected.has(walletId) && (failedAttempts.get(walletId) ?? 0) < maxAttempts;
764
- const attempt = async (walletId, connect) => {
765
- if (!shouldAttempt(walletId)) return false;
766
- reconnecting.add(walletId);
767
- try {
768
- await connect();
769
- reconnected.add(walletId);
770
- failedAttempts.delete(walletId);
771
- return true;
772
- } catch (err) {
773
- failedAttempts.set(walletId, (failedAttempts.get(walletId) ?? 0) + 1);
774
- throw err;
775
- } finally {
776
- reconnecting.delete(walletId);
777
- }
778
- };
779
- return { shouldAttempt, attempt };
780
- };
781
-
782
- // src/api/wallets.ts
783
- var getWallets$ = (config, store) => {
784
- const autoReconnectWalletIds$ = store.observable.pipe(
785
- map4((s) => s.autoReconnect ?? []),
786
- take2(1),
787
- shareReplay4({ bufferSize: 1, refCount: true })
788
- );
789
- return new Observable4((subscriber) => {
790
- const ctx = { config, store };
791
- const observables = config.platforms.map(
792
- (plugin) => plugin.getWallets$(ctx)
793
- );
794
- const platformWallets$ = observables.length ? combineLatest3(observables).pipe(map4((wallets) => wallets.flat())) : of3([]);
795
- const wallets$ = combineLatest3([
796
- platformWallets$,
797
- getWalletConnectWallet$(config)
798
- ]).pipe(
799
- map4(([platformWallets, walletConnect]) => {
800
- const all = walletConnect ? [...platformWallets, walletConnect] : platformWallets;
801
- return all.sort(sortWallets);
802
- })
803
- // Note: No startWith([]) here - the hydration buffer handles initial state
804
- );
805
- const reconnectPolicy = createReconnectPolicy();
806
- const subAutoReconnect = combineLatest3([wallets$, autoReconnectWalletIds$]).pipe(
807
- filter2(([, walletIds]) => config.autoReconnect && !!walletIds?.length),
808
- mergeMap(
809
- ([wallets, walletIds]) => wallets.filter((wallet) => walletIds?.includes(wallet.id))
810
- )
811
- ).subscribe(async (wallet) => {
812
- if (wallet.isConnected) return;
813
- try {
814
- await reconnectPolicy.attempt(wallet.id, () => wallet.connect());
815
- } catch (err) {
816
- console.error("Failed to reconnect wallet %s", wallet.id, { err });
817
- }
818
- });
819
- const subWallets = wallets$.subscribe(subscriber);
820
- return () => {
821
- subAutoReconnect.unsubscribe();
822
- subWallets.unsubscribe();
823
- };
824
- }).pipe(shareReplay4({ refCount: true, bufferSize: 1 }));
825
- };
826
-
827
- // src/api/kheopskit.ts
828
- var getKheopskit$ = (config, options = {}) => {
829
- const { ssrCookies, store: existingStore } = options;
830
- const kc = resolveConfig(config);
831
- const store = existingStore ?? createKheopskitStore({ ssrCookies, storageKey: kc.storageKey });
832
- if (kc.debug) console.debug("[kheopskit] config", kc);
833
- if (kc.debug && typeof window === "undefined" && ssrCookies === void 0) {
834
- console.warn(
835
- "[kheopskit] Running on server without `ssrCookies`. Wallet state will not be hydrated. Pass cookies for SSR support."
836
- );
837
- }
838
- const { wallets: cachedWallets, accounts: cachedAccounts } = getHydratedSnapshot(store, kc.platforms, { icons: true });
839
- if (kc.debug && cachedWallets.length > 0) {
840
- console.debug("[kheopskit] hydrating from cache:", {
841
- wallets: cachedWallets.length,
842
- accounts: cachedAccounts.length
843
- });
844
- }
845
- return new Observable5((subscriber) => {
846
- const liveWallets$ = getWallets$(kc, store);
847
- const liveAccounts$ = getAccounts$(kc, liveWallets$);
848
- const bufferedWallets$ = createHydrationBuffer(
849
- cachedWallets,
850
- liveWallets$,
851
- kc.hydrationGracePeriod,
852
- (w) => w.id,
853
- // Hydration converges when all cached-connected wallets are connected in live
854
- (liveWallets, cached) => {
855
- const cachedConnectedIds = new Set(
856
- cached.filter((w) => w.isConnected).map((w) => w.id)
857
- );
858
- if (cachedConnectedIds.size === 0) return true;
859
- return [...cachedConnectedIds].every(
860
- (id) => liveWallets.some((w) => w.id === id && w.isConnected)
861
- );
862
- },
863
- // Merge: prefer cached isConnected state but get icon from cache or live
864
- (live, cached) => ({
865
- ...cached,
866
- // Priority: cached icon > localStorage cache > live icon
867
- icon: cached.icon || getCachedIcon(cached.id) || live.icon,
868
- // Use live wallet's connect/disconnect functions
869
- connect: live.connect,
870
- disconnect: live.disconnect
871
- }),
872
- // Transform cached-only items: add icon from localStorage cache
873
- (cached) => ({
874
- ...cached,
875
- icon: cached.icon || getCachedIcon(cached.id) || ""
876
- })
877
- );
878
- const bufferedAccounts$ = createAccountHydrationBuffer(
879
- cachedAccounts,
880
- liveAccounts$,
881
- kc.hydrationGracePeriod,
882
- (a) => a.walletId,
883
- // Hydration converges when all wallets with cached accounts have provided live accounts
884
- (liveAccounts, cached) => {
885
- const cachedWalletIds = new Set(cached.map((a) => a.walletId));
886
- if (cachedWalletIds.size === 0) return true;
887
- const liveWalletIds = new Set(liveAccounts.map((a) => a.walletId));
888
- return [...cachedWalletIds].every((id) => liveWalletIds.has(id));
889
- }
890
- );
891
- const sharedWallets$ = bufferedWallets$.pipe(
892
- shareReplay5({ bufferSize: 1, refCount: true })
893
- );
894
- const sharedAccounts$ = bufferedAccounts$.pipe(
895
- shareReplay5({ bufferSize: 1, refCount: true })
896
- );
897
- const subscription = combineLatest4({
898
- wallets: sharedWallets$,
899
- accounts: sharedAccounts$
900
- }).pipe(
901
- map5(({ wallets, accounts }) => {
902
- if (kc.debug) {
903
- console.debug("[kheopskit] hydration state", {
904
- walletsHydrating: wallets.isHydrating,
905
- accountsHydrating: accounts.isHydrating,
906
- walletsConnected: wallets.items.filter((w) => w.isConnected).length,
907
- walletsTotal: wallets.items.length
908
- });
909
- }
910
- return {
911
- config: kc,
912
- wallets: [...wallets.items].sort(sortWallets),
913
- accounts: [...accounts.items].sort(sortAccounts),
914
- isHydrating: wallets.isHydrating || accounts.isHydrating
915
- };
916
- })
917
- ).subscribe(subscriber);
918
- const persistSub = combineLatest4({
919
- wallets: sharedWallets$,
920
- accounts: sharedAccounts$
921
- }).pipe(
922
- // Wait for hydration to complete
923
- filter3(
924
- ({ wallets, accounts }) => !wallets.isHydrating && !accounts.isHydrating
925
- ),
926
- // Debounce to avoid excessive writes
927
- debounceTime(1e3),
928
- // Only persist if the serialized snapshot would actually change.
929
- // Compare the persisted fields (not just ids): an Ethereum chain switch
930
- // keeps the same account id but changes the cached chainId, so an
931
- // id-only comparator would skip persisting it.
932
- distinctUntilChanged2((prev, curr) => {
933
- const prevWalletKeys = prev.wallets.items.map(walletPersistKey);
934
- const currWalletKeys = curr.wallets.items.map(walletPersistKey);
935
- const prevAccountKeys = prev.accounts.items.map(accountChangeKey);
936
- const currAccountKeys = curr.accounts.items.map(accountChangeKey);
937
- return arraysEqual(prevWalletKeys, currWalletKeys) && arraysEqual(prevAccountKeys, currAccountKeys);
938
- })
939
- ).subscribe(({ wallets, accounts }) => {
940
- const connectedWalletIds = new Set(
941
- wallets.items.filter((w) => w.isConnected).map((w) => w.id)
942
- );
943
- const relevantAccounts = accounts.items.filter(
944
- (a) => connectedWalletIds.has(a.walletId)
945
- );
946
- if (kc.debug) {
947
- console.debug("[kheopskit] persisting state snapshot:", {
948
- wallets: wallets.items.length,
949
- accounts: relevantAccounts.length
950
- });
951
- }
952
- store.setCachedState(
953
- wallets.items.map(serializeWallet),
954
- relevantAccounts.map(serializeAccount)
955
- );
956
- const icons = {};
957
- for (const wallet of wallets.items) {
958
- if (wallet.icon) {
959
- icons[wallet.id] = wallet.icon;
960
- }
961
- }
962
- setCachedIcons(icons);
963
- });
964
- return () => {
965
- subscription.unsubscribe();
966
- persistSub.unsubscribe();
967
- };
968
- }).pipe(
969
- distinctUntilChanged2(statesEqual),
970
- throttleTime(16, void 0, { leading: true, trailing: true }),
971
- // ~1 frame at 60fps
972
- logObservable("kheopskit$", { enabled: kc.debug, printValue: true }),
973
- shareReplay5({ bufferSize: 1, refCount: true })
974
- // The runtime objects are the concrete per-plugin wallet/account types;
975
- // recover the precise KheopskitState<P> the caller's plugins imply.
976
- );
977
- };
978
- var arraysEqual = (a, b) => a.length === b.length && a.every((v, i) => v === b[i]);
979
- var accountChangeKey = (account) => {
980
- switch (account.platform) {
981
- case "ethereum":
982
- return `${account.id}|${account.chainId ?? ""}`;
983
- case "polkadot":
984
- return `${account.id}|${account.type ?? ""}`;
985
- case "solana":
986
- return `${account.id}|${(account.chains ?? []).join(",")}`;
987
- default:
988
- return account.id;
989
- }
990
- };
991
- var walletPlatforms = (wallet) => isWalletConnectWallet(wallet) ? wallet.platforms.join(",") : "";
992
- var walletPersistKey = (wallet) => `${wallet.id}|${wallet.isConnected ? 1 : 0}|${wallet.name}`;
993
- var walletUiKey = (wallet) => `${walletPersistKey(wallet)}|${wallet.icon}|${walletPlatforms(wallet)}`;
994
- var statesEqual = (a, b) => a.isHydrating === b.isHydrating && a.wallets.length === b.wallets.length && a.accounts.length === b.accounts.length && a.wallets.every((w, i) => {
995
- const other = b.wallets[i];
996
- return !!other && walletUiKey(w) === walletUiKey(other);
6
+ //#region src/api/appKit.ts
7
+ /**
8
+ * Dynamically import @reown/appkit/core to avoid loading browser-only code
9
+ * during SSR or in edge runtimes like Cloudflare Workers.
10
+ * The AppKit library uses Lit for web components which requires document.
11
+ */
12
+ const loadAppKit = async () => {
13
+ try {
14
+ const { createAppKit } = await import("@reown/appkit/core");
15
+ return createAppKit;
16
+ } catch (cause) {
17
+ console.error("[kheopskit] WalletConnect is configured but @reown/appkit could not be loaded. Install it with `pnpm add @reown/appkit` (or remove config.walletConnect). WalletConnect wallets are disabled; injected wallets still work.", cause);
18
+ return null;
19
+ }
20
+ };
21
+ const WALLET_CONNECT_ICON = "data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjQwMCIgdmlld0JveD0iMCAwIDQwMCA0MDAiIHdpZHRoPSI0MDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxjbGlwUGF0aCBpZD0iYSI+PHBhdGggZD0ibTAgMGg0MDB2NDAwaC00MDB6Ii8+PC9jbGlwUGF0aD48ZyBjbGlwLXBhdGg9InVybCgjYSkiPjxjaXJjbGUgY3g9IjIwMCIgY3k9IjIwMCIgZmlsbD0iIzMzOTZmZiIgcj0iMTk5LjUiIHN0cm9rZT0iIzY2YjFmZiIvPjxwYXRoIGQ9Im0xMjIuNTE5IDE0OC45NjVjNDIuNzkxLTQxLjcyOSAxMTIuMTcxLTQxLjcyOSAxNTQuOTYyIDBsNS4xNSA1LjAyMmMyLjE0IDIuMDg2IDIuMTQgNS40NjkgMCA3LjU1NWwtMTcuNjE3IDE3LjE4Yy0xLjA3IDEuMDQzLTIuODA0IDEuMDQzLTMuODc0IDBsLTcuMDg3LTYuOTExYy0yOS44NTMtMjkuMTExLTc4LjI1My0yOS4xMTEtMTA4LjEwNiAwbC03LjU5IDcuNDAxYy0xLjA3IDEuMDQzLTIuODA0IDEuMDQzLTMuODc0IDBsLTE3LjYxNy0xNy4xOGMtMi4xNC0yLjA4Ni0yLjE0LTUuNDY5IDAtNy41NTV6bTE5MS4zOTcgMzUuNTI5IDE1LjY3OSAxNS4yOWMyLjE0IDIuMDg2IDIuMTQgNS40NjkgMCA3LjU1NWwtNzAuNyA2OC45NDRjLTIuMTM5IDIuMDg3LTUuNjA4IDIuMDg3LTcuNzQ4IDBsLTUwLjE3OC00OC45MzFjLS41MzUtLjUyMi0xLjQwMi0uNTIyLTEuOTM3IDBsLTUwLjE3OCA0OC45MzFjLTIuMTM5IDIuMDg3LTUuNjA4IDIuMDg3LTcuNzQ4IDBsLTcwLjcwMTUtNjguOTQ1Yy0yLjEzOTYtMi4wODYtMi4xMzk2LTUuNDY5IDAtNy41NTVsMTUuNjc5NS0xNS4yOWMyLjEzOTYtMi4wODYgNS42MDg1LTIuMDg2IDcuNzQ4MSAwbDUwLjE3ODkgNDguOTMyYy41MzUuNTIyIDEuNDAyLjUyMiAxLjkzNyAwbDUwLjE3Ny00OC45MzJjMi4xMzktMi4wODcgNS42MDgtMi4wODcgNy43NDggMGw1MC4xNzkgNDguOTMyYy41MzUuNTIyIDEuNDAyLjUyMiAxLjkzNyAwbDUwLjE3OS00OC45MzFjMi4xMzktMi4wODcgNS42MDgtMi4wODcgNy43NDggMHoiIGZpbGw9IiNmZmYiLz48L2c+PC9zdmc+";
22
+ const APPKIT_SYMBOL = Symbol.for("kheopskit.cachedAppKit");
23
+ const APPKIT_PROJECT_ID_SYMBOL = Symbol.for("kheopskit.cachedAppKitProjectId");
24
+ const getCachedAppKit = () => globalThis[APPKIT_SYMBOL];
25
+ const getCachedAppKitProjectId = () => globalThis[APPKIT_PROJECT_ID_SYMBOL];
26
+ const setCachedAppKit = (value, projectId) => {
27
+ globalThis[APPKIT_SYMBOL] = value;
28
+ globalThis[APPKIT_PROJECT_ID_SYMBOL] = projectId;
29
+ };
30
+ /**
31
+ * Clears the cached AppKit observable.
32
+ * Use when configuration changes or for testing purposes.
33
+ * Note: This does NOT destroy the appKit instance created by Reown.
34
+ */
35
+ const resetAppKitCache = () => {
36
+ setCachedAppKit(void 0);
37
+ };
38
+ /**
39
+ * Drops a WalletConnect namespace's cached account observables. Called when a
40
+ * namespace transitions to disconnected — including external disconnects (from
41
+ * the wallet app), which flip status via `subscribeProviders` without ever
42
+ * calling `disconnect()` — so a later reconnect rebuilds the account observables
43
+ * against the fresh session instead of a stale closure.
44
+ *
45
+ * Keys are `accounts:<walletId>:...`; the trailing colon scopes the prefix to
46
+ * this wallet and avoids matching a sibling whose id is a string prefix.
47
+ */
48
+ const dropAccountsCache = (platform) => {
49
+ clearCachedObservablesByPrefix(`accounts:${WALLET_CONNECT_WALLET_ID}:${platform}:`);
50
+ };
51
+ /**
52
+ * The single, platform-less WalletConnect connector for the given config — or
53
+ * `null` when WalletConnect isn't configured, on the server, or when
54
+ * `@reown/appkit` isn't installed.
55
+ *
56
+ * @remarks
57
+ * The AppKit instance is a process-wide singleton (Reown AppKit itself cannot
58
+ * be instantiated twice). The **first** call with a `walletConnect` config wins;
59
+ * later calls with a *different* `walletConnect` config reuse that first
60
+ * instance. Call {@link resetAppKitCache} before re-initialising if the
61
+ * WalletConnect config must change.
62
+ *
63
+ * One WalletConnect session is shared across every namespace, and a namespace
64
+ * can only be established during the initial pairing — it can't be added to a
65
+ * live session afterwards. So this exposes ONE connector (not one per platform):
66
+ * `connect()` opens the modal; the wallet approves whichever namespaces it
67
+ * supports in that single pairing, and `platforms` reflects what the live
68
+ * session carries. Per-namespace accounts are derived by each platform plugin.
69
+ */
70
+ const getWalletConnectWallet$ = (config) => {
71
+ if (!config.walletConnect) return of(null);
72
+ if (typeof window === "undefined") return of(null);
73
+ const walletConnect = config.walletConnect;
74
+ const cachedProjectId = getCachedAppKitProjectId();
75
+ if (cachedProjectId !== void 0 && cachedProjectId !== walletConnect.projectId) console.warn("[kheopskit] WalletConnect is already initialised with projectId %s; AppKit is a process-wide singleton, so the first configuration wins and projectId %s is ignored. Call resetAppKitCache() before re-initialising if the WalletConnect config must change.", cachedProjectId, walletConnect.projectId);
76
+ let cachedAppKit = getCachedAppKit();
77
+ if (!cachedAppKit) {
78
+ cachedAppKit = from(loadAppKit()).pipe(switchMap((createAppKit) => {
79
+ if (!createAppKit) return of(null);
80
+ return new Observable((subscriber) => {
81
+ const appKit = createAppKit({
82
+ projectId: walletConnect.projectId,
83
+ metadata: walletConnect.metadata,
84
+ networks: walletConnect.networks,
85
+ themeMode: walletConnect.themeMode,
86
+ themeVariables: walletConnect.themeVariables,
87
+ universalProviderConfigOverride: { methods: {
88
+ polkadot: ["polkadot_signTransaction", "polkadot_signMessage"],
89
+ solana: [
90
+ "solana_signTransaction",
91
+ "solana_signMessage",
92
+ "solana_signAndSendTransaction"
93
+ ]
94
+ } },
95
+ allWallets: "HIDE",
96
+ debug: config.debug,
97
+ allowUnsupportedChain: true
98
+ });
99
+ const appKitInstance = appKit;
100
+ const status$ = new BehaviorSubject({
101
+ isPolkadotConnected: false,
102
+ isEthereumConnected: false,
103
+ isSolanaConnected: false
104
+ });
105
+ const unsubProviders = appKit.subscribeProviders((providers) => {
106
+ status$.next({
107
+ isPolkadotConnected: !!providers.polkadot,
108
+ isEthereumConnected: !!providers.eip155,
109
+ isSolanaConnected: !!providers.solana
110
+ });
111
+ });
112
+ const namespaceOf = {
113
+ polkadot: "polkadot",
114
+ ethereum: "eip155",
115
+ solana: "solana"
116
+ };
117
+ const allPlatforms = [
118
+ "polkadot",
119
+ "ethereum",
120
+ "solana"
121
+ ];
122
+ const enabledPlatforms = allPlatforms.filter((p) => appKit.chainNamespaces.includes(namespaceOf[p]));
123
+ let prevConnected = {
124
+ polkadot: false,
125
+ ethereum: false,
126
+ solana: false
127
+ };
128
+ const sub = status$.pipe(map((s) => ({
129
+ polkadot: s.isPolkadotConnected,
130
+ ethereum: s.isEthereumConnected,
131
+ solana: s.isSolanaConnected
132
+ })), distinctUntilChanged((a, b) => a.polkadot === b.polkadot && a.ethereum === b.ethereum && a.solana === b.solana), tap((connected) => {
133
+ for (const platform of allPlatforms) if (prevConnected[platform] && !connected[platform]) dropAccountsCache(platform);
134
+ prevConnected = connected;
135
+ }), map((connected) => {
136
+ const platforms = enabledPlatforms.filter((p) => connected[p]);
137
+ const isConnected = platforms.length > 0;
138
+ const walletInfo = appKit.getWalletInfo();
139
+ return {
140
+ id: WALLET_CONNECT_WALLET_ID,
141
+ type: "walletconnect",
142
+ platforms,
143
+ appKit: appKitInstance,
144
+ name: walletInfo?.name ?? "WalletConnect",
145
+ icon: walletInfo?.icon ?? WALLET_CONNECT_ICON,
146
+ connect: async () => {
147
+ if (!isConnected) await appKit.open();
148
+ },
149
+ disconnect: async () => {
150
+ if (isConnected) await appKit.disconnect();
151
+ },
152
+ isConnected
153
+ };
154
+ })).subscribe(subscriber);
155
+ return () => {
156
+ sub.unsubscribe();
157
+ unsubProviders();
158
+ };
159
+ });
160
+ }), shareReplay({
161
+ refCount: false,
162
+ bufferSize: 1
163
+ }));
164
+ setCachedAppKit(cachedAppKit, walletConnect.projectId);
165
+ }
166
+ return cachedAppKit;
167
+ };
168
+ //#endregion
169
+ //#region src/api/config.ts
170
+ /**
171
+ * Default storage key for persisting wallet connection state.
172
+ * Can be overridden via config.storageKey to avoid conflicts
173
+ * when running multiple dapps on the same domain.
174
+ */
175
+ const DEFAULT_STORAGE_KEY = "kheopskit";
176
+ const DEFAULTS = {
177
+ autoReconnect: true,
178
+ debug: false,
179
+ storageKey: DEFAULT_STORAGE_KEY,
180
+ hydrationGracePeriod: 500
181
+ };
182
+ const resolveConfig = (config) => {
183
+ const platforms = config?.platforms ?? [];
184
+ const invalidPlatforms = platforms.filter((p) => typeof p !== "object" || p === null || typeof p.getWallets$ !== "function");
185
+ if (invalidPlatforms.length > 0) throw new Error(`[kheopskit] config.platforms must contain plugin instances created by the per-platform factories (e.g. platforms: [polkadot(), ethereum(), solana()] imported from "@kheopskit/core/<platform>"). Invalid entries: ${JSON.stringify(invalidPlatforms)}. String platform names like "polkadot" were removed in v4 — see MIGRATING_TO_V4.md.`);
186
+ if (platforms.length === 0) console.warn("[kheopskit] No platforms configured; wallets and accounts will be empty. Pass e.g. platforms: [polkadot()] from \"@kheopskit/core/polkadot\".");
187
+ return Object.assign({}, DEFAULTS, config, { platforms });
188
+ };
189
+ //#endregion
190
+ //#region src/utils/sortAccounts.ts
191
+ const PLATFORM_ORDER$1 = {
192
+ polkadot: 0,
193
+ ethereum: 1,
194
+ solana: 2
195
+ };
196
+ const byWalletName = (a1, a2) => {
197
+ if (a1.walletName.toLowerCase() === "talisman") return -1;
198
+ if (a2.walletName.toLowerCase() === "talisman") return 1;
199
+ return a1.walletName.localeCompare(a2.walletName);
200
+ };
201
+ const sortAccounts = (a1, a2) => {
202
+ if (a1.platform !== a2.platform) return PLATFORM_ORDER$1[a1.platform] - PLATFORM_ORDER$1[a2.platform];
203
+ if (a1.walletName !== a2.walletName) return byWalletName(a1, a2);
204
+ if (a1.platform === "polkadot" && a2.platform === "polkadot") return a1.name !== a2.name ? (a1.name ?? "").localeCompare(a2.name ?? "") : a1.address.localeCompare(a2.address);
205
+ return a1.id.localeCompare(a2.id);
206
+ };
207
+ //#endregion
208
+ //#region src/utils/sortWallets.ts
209
+ const PLATFORM_ORDER = {
210
+ polkadot: 0,
211
+ ethereum: 1,
212
+ solana: 2
213
+ };
214
+ const orderOf = (wallet) => isWalletConnectWallet(wallet) ? 3 : PLATFORM_ORDER[wallet.platform];
215
+ const sortWallets = (w1, w2) => {
216
+ const o1 = orderOf(w1);
217
+ const o2 = orderOf(w2);
218
+ if (o1 !== o2) return o1 - o2;
219
+ if (w1.name.toLowerCase() === "talisman") return -1;
220
+ if (w2.name.toLowerCase() === "talisman") return 1;
221
+ return w1.name.localeCompare(w2.name);
222
+ };
223
+ //#endregion
224
+ //#region src/api/platform.ts
225
+ /**
226
+ * Whether a cached account should survive SSR hydration, per its platform
227
+ * plugin's `acceptsCachedAccount` hook. Plugins without the hook (and platforms
228
+ * with no enabled plugin) accept all — matching the pre-plugin behavior where
229
+ * only Polkadot accounts were filtered (by key type).
230
+ */
231
+ const acceptsCachedAccount = (cached, platforms) => {
232
+ return platforms.find((p) => p.platform === cached.platform)?.acceptsCachedAccount?.(cached) ?? true;
233
+ };
234
+ //#endregion
235
+ //#region src/api/hydration.ts
236
+ /**
237
+ * Builds the cached-state snapshot every consumer must agree on: read the
238
+ * store's cached state, hydrate wallets/accounts into placeholders, filter
239
+ * accounts through each platform plugin's `acceptsCachedAccount` hook, and sort
240
+ * with the same comparators the live pipeline uses.
241
+ *
242
+ * This is the single implementation behind the observable's initial seed and
243
+ * both React snapshot sources (`serverValue`/`initialValue`). They must produce
244
+ * the same shape in the same order or the UI flickers/reorders on reload — by
245
+ * sharing this helper, that agreement holds by construction.
246
+ */
247
+ const getHydratedSnapshot = (store, platforms, options = {}) => {
248
+ const cached = store.getCachedState();
249
+ return {
250
+ wallets: cached.wallets.map((cachedWallet) => {
251
+ const wallet = hydrateWallet(cachedWallet);
252
+ if (!options.icons || wallet.icon) return wallet;
253
+ const icon = getCachedIcon(wallet.id);
254
+ return icon ? {
255
+ ...wallet,
256
+ icon
257
+ } : wallet;
258
+ }).sort(sortWallets),
259
+ accounts: cached.accounts.filter((account) => acceptsCachedAccount(account, platforms)).map(hydrateAccount).sort(sortAccounts)
260
+ };
261
+ };
262
+ //#endregion
263
+ //#region src/utils/createHydrationBuffer.ts
264
+ /**
265
+ * Core hydration buffer logic shared between wallets and accounts.
266
+ *
267
+ * During the grace period:
268
+ * - Emits cached items immediately (before any live emissions)
269
+ * - As live items arrive, merges them using the provided merge function
270
+ *
271
+ * After the grace period (and convergence, if provided):
272
+ * - Only live items are returned
273
+ * - isHydrating becomes false
274
+ *
275
+ * If an `isConverged` function is provided, hydration ends only when BOTH
276
+ * the grace period has elapsed AND the live items have converged with cached
277
+ * items (e.g., all previously-connected wallets have reconnected).
278
+ * A max timeout (6x grace period) prevents infinite hydration.
279
+ *
280
+ * @param cachedItems - Initial cached items from storage
281
+ * @param liveItems$ - Observable of live items as they become available
282
+ * @param gracePeriodMs - Time in ms to wait before syncing to live state
283
+ * @param mergeFn - Function to merge live and cached items during hydration
284
+ * @param isConverged - Optional function to check if live items have caught up to cached items
285
+ */
286
+ const createBufferCore = (cachedItems, liveItems$, gracePeriodMs, mergeFn, isConverged) => {
287
+ if (gracePeriodMs <= 0 || cachedItems.length === 0) return liveItems$.pipe(map((items) => ({
288
+ items,
289
+ isHydrating: false
290
+ })));
291
+ return new Observable((subscriber) => {
292
+ const subscriptions = new Subscription();
293
+ const isHydrating$ = new BehaviorSubject(true);
294
+ const liveWithInitial$ = liveItems$.pipe(startWith([]));
295
+ if (isConverged) {
296
+ const timerFired$ = timer(gracePeriodMs).pipe(map(() => true), startWith(false), shareReplay({
297
+ bufferSize: 1,
298
+ refCount: true
299
+ }));
300
+ subscriptions.add(combineLatest([liveWithInitial$, timerFired$]).pipe(filter(([, timerFired]) => timerFired), filter(([liveItems]) => isConverged(liveItems, cachedItems)), take(1)).subscribe(() => {
301
+ isHydrating$.next(false);
302
+ }));
303
+ subscriptions.add(timer(gracePeriodMs * 6).subscribe(() => {
304
+ if (isHydrating$.value) isHydrating$.next(false);
305
+ }));
306
+ } else subscriptions.add(timer(gracePeriodMs).subscribe(() => {
307
+ isHydrating$.next(false);
308
+ }));
309
+ subscriptions.add(combineLatest([liveWithInitial$, isHydrating$]).pipe(map(([liveItems, isHydrating]) => {
310
+ if (!isHydrating) return {
311
+ items: liveItems,
312
+ isHydrating: false
313
+ };
314
+ return {
315
+ items: mergeFn(liveItems, cachedItems),
316
+ isHydrating: true
317
+ };
318
+ })).subscribe(subscriber));
319
+ return () => {
320
+ subscriptions.unsubscribe();
321
+ isHydrating$.complete();
322
+ };
323
+ });
324
+ };
325
+ /**
326
+ * Creates a hydration buffer that merges cached items with live items.
327
+ *
328
+ * During hydration, cached items take precedence over live items with the same key.
329
+ * This preserves the cached connected state (e.g., isConnected: true) while
330
+ * auto-reconnect is still in progress. New live items not in the cache are added.
331
+ *
332
+ * @param cachedItems - Initial cached items from storage
333
+ * @param liveItems$ - Observable of live items as they become available
334
+ * @param gracePeriodMs - Time in ms to wait before syncing to live state
335
+ * @param getKey - Function to extract a unique key from an item
336
+ * @param isConverged - Optional function to check if live items have caught up to cached items
337
+ * @param mergeItem - Optional function to merge a live item with its cached counterpart
338
+ * @param transformCachedOnly - Optional function to transform cached items not yet in the live stream
339
+ */
340
+ const createHydrationBuffer = (cachedItems, liveItems$, gracePeriodMs, getKey, isConverged, mergeItem, transformCachedOnly) => {
341
+ return createBufferCore(cachedItems, liveItems$, gracePeriodMs, (liveItems, cached) => {
342
+ const cachedByKey = new Map(cached.map((item) => [getKey(item), item]));
343
+ const liveKeys = new Set(liveItems.map(getKey));
344
+ const mergedItems = liveItems.map((liveItem) => {
345
+ const key = getKey(liveItem);
346
+ const cachedItem = cachedByKey.get(key);
347
+ if (!cachedItem) return liveItem;
348
+ return mergeItem ? mergeItem(liveItem, cachedItem) : cachedItem;
349
+ });
350
+ for (const cachedItem of cached) if (!liveKeys.has(getKey(cachedItem))) {
351
+ const transformed = transformCachedOnly ? transformCachedOnly(cachedItem) : cachedItem;
352
+ mergedItems.push(transformed);
353
+ }
354
+ return mergedItems;
355
+ }, isConverged);
356
+ };
357
+ /**
358
+ * Creates a hydration buffer specifically for accounts, which need special handling.
359
+ *
360
+ * Accounts are grouped by wallet - when a wallet provides its account list,
361
+ * we replace all cached accounts for that wallet with the live ones.
362
+ * Cached accounts for wallets that haven't provided accounts yet are preserved.
363
+ *
364
+ * @param cachedAccounts - Initial cached accounts from storage
365
+ * @param liveAccounts$ - Observable of live accounts as they become available
366
+ * @param gracePeriodMs - Time in ms to wait before syncing to live state
367
+ * @param getWalletId - Function to extract the wallet ID from an account
368
+ * @param isConverged - Optional function to check if live accounts have caught up to cached accounts
369
+ */
370
+ const createAccountHydrationBuffer = (cachedAccounts, liveAccounts$, gracePeriodMs, getWalletId, isConverged) => {
371
+ return createBufferCore(cachedAccounts, liveAccounts$, gracePeriodMs, (liveAccounts, cached) => {
372
+ const walletsWithLiveAccounts = new Set(liveAccounts.map(getWalletId));
373
+ const mergedAccounts = [...liveAccounts];
374
+ for (const cachedAccount of cached) if (!walletsWithLiveAccounts.has(getWalletId(cachedAccount))) mergedAccounts.push(cachedAccount);
375
+ return mergedAccounts;
376
+ }, isConverged);
377
+ };
378
+ //#endregion
379
+ //#region src/utils/logObservable.ts
380
+ const logObservable = (label, opts) => tap((value) => {
381
+ const { printValue = false, enabled = true } = opts || {};
382
+ if (!label || !enabled) return;
383
+ const text = `[kheopskit] observable ${label} emit`;
384
+ if (printValue) console.debug(text, value);
385
+ else console.debug(text);
386
+ });
387
+ //#endregion
388
+ //#region src/api/accounts.ts
389
+ const getAccounts$ = (config, wallets) => {
390
+ return new Observable((subscriber) => {
391
+ const sources = config.platforms.map((plugin) => plugin.getAccounts$(wallets.pipe(map((ws) => ws.filter((w) => isWalletConnectWallet(w) || w.platform === plugin.platform)))));
392
+ const sub = (sources.length ? combineLatest(sources).pipe(map((accounts) => accounts.flat().sort(sortAccounts))) : of([])).subscribe(subscriber);
393
+ return () => {
394
+ sub.unsubscribe();
395
+ };
396
+ }).pipe(shareReplay({
397
+ refCount: true,
398
+ bufferSize: 1
399
+ }));
400
+ };
401
+ //#endregion
402
+ //#region src/utils/createStore.ts
403
+ const createStore = (key, defaultValue, storage = safeLocalStorage) => {
404
+ const subject = new BehaviorSubject(getStoredData(key, defaultValue, storage));
405
+ let unsubscribeStorage;
406
+ if (typeof window !== "undefined" && storage.subscribe) unsubscribeStorage = storage.subscribe(key, (newValue) => {
407
+ subject.next(parseData(newValue, defaultValue));
408
+ });
409
+ const update = (val) => {
410
+ setStoredData(key, val, storage);
411
+ subject.next(val);
412
+ };
413
+ return {
414
+ observable: subject.asObservable(),
415
+ set: (val) => update(val),
416
+ mutate: (transform) => update(transform(subject.getValue())),
417
+ get: () => structuredClone(subject.getValue()),
418
+ /**
419
+ * Cleanup subscriptions. Call this when the store is no longer needed.
420
+ */
421
+ destroy: () => {
422
+ unsubscribeStorage?.();
423
+ subject.complete();
424
+ }
425
+ };
426
+ };
427
+ const parseData = (str, defaultValue) => {
428
+ try {
429
+ if (str) return JSON.parse(str);
430
+ } catch {}
431
+ return defaultValue;
432
+ };
433
+ const getStoredData = (key, defaultValue, storage) => {
434
+ const str = storage.getItem(key);
435
+ return parseData(str, defaultValue);
436
+ };
437
+ const setStoredData = (key, val, storage) => {
438
+ const str = JSON.stringify(val);
439
+ storage.setItem(key, str);
440
+ };
441
+ //#endregion
442
+ //#region src/api/compactStore.ts
443
+ const EMPTY_STORE_DATA = {};
444
+ const toCompactPolkadotAccountType = (type) => {
445
+ switch (type) {
446
+ case "sr25519": return 0;
447
+ case "ed25519": return 1;
448
+ case "ecdsa": return 2;
449
+ case "ethereum": return 3;
450
+ default: return null;
451
+ }
452
+ };
453
+ const fromCompactPolkadotAccountType = (type) => {
454
+ switch (type) {
455
+ case 0: return "sr25519";
456
+ case 1: return "ed25519";
457
+ case 2: return "ecdsa";
458
+ case 3: return "ethereum";
459
+ default: return;
460
+ }
461
+ };
462
+ const toCompactPlatform = (platform) => {
463
+ switch (platform) {
464
+ case "polkadot": return 0;
465
+ case "ethereum": return 1;
466
+ case "solana": return 2;
467
+ }
468
+ };
469
+ const fromCompactPlatform = (code) => {
470
+ switch (code) {
471
+ case 0: return "polkadot";
472
+ case 1: return "ethereum";
473
+ case 2: return "solana";
474
+ default: return;
475
+ }
476
+ };
477
+ const isCompactStore = (value) => {
478
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
479
+ if ("cachedWallets" in value || "cachedAccounts" in value) return false;
480
+ return "v" in value || "w" in value || "a" in value || "r" in value;
481
+ };
482
+ const toCompactStore = (data) => {
483
+ const wallets = data.cachedWallets?.map((wallet) => [
484
+ wallet.id,
485
+ wallet.name,
486
+ wallet.isConnected ? 1 : 0,
487
+ wallet.type === "walletconnect" ? 1 : 0
488
+ ]);
489
+ const accounts = data.cachedAccounts?.map((account) => [
490
+ account.walletId,
491
+ account.address,
492
+ account.name ?? null,
493
+ account.chainId ?? null,
494
+ toCompactPolkadotAccountType(account.polkadotAccountType),
495
+ toCompactPlatform(account.platform)
496
+ ]);
497
+ return {
498
+ v: 1,
499
+ r: data.autoReconnect,
500
+ w: wallets?.length ? wallets : void 0,
501
+ a: accounts?.length ? accounts : void 0
502
+ };
503
+ };
504
+ const fromCompactStore = (data) => {
505
+ const walletNameMap = /* @__PURE__ */ new Map();
506
+ const wallets = [];
507
+ for (const item of Array.isArray(data.w) ? data.w : []) {
508
+ if (!Array.isArray(item)) continue;
509
+ const [id, name, isConnected, type] = item;
510
+ if (!isValidWalletId(id)) continue;
511
+ const isWalletConnect = id === WALLET_CONNECT_WALLET_ID;
512
+ const walletType = type === 1 ? "walletconnect" : "injected";
513
+ if (isWalletConnect !== (walletType === "walletconnect")) continue;
514
+ walletNameMap.set(id, name);
515
+ wallets.push({
516
+ id,
517
+ platform: isWalletConnect ? void 0 : parseWalletId(id).platform,
518
+ type: walletType,
519
+ name,
520
+ isConnected: isConnected === 1
521
+ });
522
+ }
523
+ const accounts = [];
524
+ for (const item of Array.isArray(data.a) ? data.a : []) {
525
+ if (!Array.isArray(item)) continue;
526
+ const [walletId, address, name, chainId, polkadotAccountType, platformCode] = item;
527
+ if (!isValidWalletId(walletId) || typeof address !== "string" || !address) continue;
528
+ const platform = platformCode != null ? fromCompactPlatform(platformCode) : walletId === "walletconnect" ? void 0 : parseWalletId(walletId).platform;
529
+ if (!isWalletPlatform(platform)) continue;
530
+ accounts.push({
531
+ id: getWalletAccountId(walletId, address),
532
+ platform,
533
+ address,
534
+ name: name ?? void 0,
535
+ chainId: chainId ?? void 0,
536
+ polkadotAccountType: platform === "polkadot" ? fromCompactPolkadotAccountType(polkadotAccountType) : void 0,
537
+ walletId,
538
+ walletName: walletNameMap.get(walletId) ?? walletId
539
+ });
540
+ }
541
+ return {
542
+ autoReconnect: data.r,
543
+ cachedWallets: wallets,
544
+ cachedAccounts: accounts
545
+ };
546
+ };
547
+ const decodeStore = (raw, fallback) => {
548
+ try {
549
+ const parsed = JSON.parse(raw);
550
+ if (isCompactStore(parsed)) return fromCompactStore(parsed);
551
+ return parsed;
552
+ } catch {
553
+ return fallback;
554
+ }
555
+ };
556
+ const encodeStore = (data) => JSON.stringify(toCompactStore(data));
557
+ /**
558
+ * Cookie storage that transparently encodes/decodes {@link CompactStoreV1},
559
+ * migrating legacy verbose-JSON cookies to the compact format on first read.
560
+ */
561
+ const createCompactCookieStorage = (initialCookies) => {
562
+ const base = cookieStorage(initialCookies);
563
+ return {
564
+ getItem: (key) => {
565
+ const raw = base.getItem(key);
566
+ if (!raw) return null;
567
+ const expanded = decodeStore(raw, EMPTY_STORE_DATA);
568
+ if (typeof document !== "undefined") try {
569
+ const parsed = JSON.parse(raw);
570
+ if (!isCompactStore(parsed)) base.setItem(key, encodeStore(expanded));
571
+ } catch {}
572
+ return JSON.stringify(expanded);
573
+ },
574
+ setItem: (key, value) => {
575
+ const expanded = decodeStore(value, EMPTY_STORE_DATA);
576
+ base.setItem(key, encodeStore(expanded));
577
+ },
578
+ removeItem: base.removeItem,
579
+ subscribe: (key, callback) => {
580
+ const unsubscribe = base.subscribe?.(key, (value) => {
581
+ if (!value) {
582
+ callback(null);
583
+ return;
584
+ }
585
+ const expanded = decodeStore(value, EMPTY_STORE_DATA);
586
+ callback(JSON.stringify(expanded));
587
+ });
588
+ return () => {
589
+ unsubscribe?.();
590
+ };
591
+ }
592
+ };
593
+ };
594
+ //#endregion
595
+ //#region src/api/store.ts
596
+ const DEFAULT_SETTINGS = {};
597
+ /**
598
+ * Validates a cached wallet read from persisted storage. Cached state may have
599
+ * been written by an older (or corrupted) version with a different shape, so we
600
+ * drop anything that wouldn't survive hydration/sort rather than letting it
601
+ * throw at render time. Only the fields downstream code relies on are checked.
602
+ */
603
+ const isValidCachedWallet = (value) => {
604
+ if (!value || typeof value !== "object") return false;
605
+ const w = value;
606
+ if (!isValidWalletId(w.id)) return false;
607
+ if (typeof w.name !== "string" || typeof w.isConnected !== "boolean") return false;
608
+ if (w.type === "walletconnect") return w.id === "walletconnect" && w.platform === void 0;
609
+ return w.type === "injected" && isWalletPlatform(w.platform);
610
+ };
611
+ /** Validates a cached account read from persisted storage. See {@link isValidCachedWallet}. */
612
+ const isValidCachedAccount = (value) => {
613
+ if (!value || typeof value !== "object") return false;
614
+ const a = value;
615
+ return typeof a.id === "string" && !!a.id && isWalletPlatform(a.platform) && typeof a.address === "string" && !!a.address && isValidWalletId(a.walletId) && typeof a.walletName === "string";
616
+ };
617
+ /**
618
+ * Creates a kheopskit store with the appropriate storage backend.
619
+ * Uses cookieStorage when ssrCookies is provided (for SSR hydration),
620
+ * otherwise falls back to safeLocalStorage.
621
+ *
622
+ * @param options - Configuration options for the store
623
+ */
624
+ const createKheopskitStore = (options = {}) => {
625
+ const { ssrCookies, storageKey = DEFAULT_STORAGE_KEY } = options;
626
+ const storage = ssrCookies !== void 0 ? createCompactCookieStorage(ssrCookies) : safeLocalStorage;
627
+ const store = createStore(storageKey, DEFAULT_SETTINGS, storage);
628
+ const addEnabledWalletId = (walletId) => {
629
+ parseWalletId(walletId);
630
+ store.mutate((prev) => ({
631
+ ...prev,
632
+ autoReconnect: uniq((prev.autoReconnect ?? []).concat(walletId))
633
+ }));
634
+ };
635
+ const removeEnabledWalletId = (walletId) => {
636
+ store.mutate((prev) => ({
637
+ ...prev,
638
+ autoReconnect: uniq((prev.autoReconnect ?? []).filter((id) => id !== walletId))
639
+ }));
640
+ };
641
+ const getCachedState = () => {
642
+ const data = store.get();
643
+ const cachedWallets = Array.isArray(data?.cachedWallets) ? data.cachedWallets : [];
644
+ const cachedAccounts = Array.isArray(data?.cachedAccounts) ? data.cachedAccounts : [];
645
+ return {
646
+ wallets: cachedWallets.filter(isValidCachedWallet),
647
+ accounts: cachedAccounts.filter(isValidCachedAccount)
648
+ };
649
+ };
650
+ const setCachedState = (wallets, accounts) => {
651
+ store.mutate((prev) => ({
652
+ ...prev,
653
+ cachedWallets: wallets,
654
+ cachedAccounts: accounts
655
+ }));
656
+ };
657
+ return {
658
+ observable: store.observable,
659
+ addEnabledWalletId,
660
+ removeEnabledWalletId,
661
+ getCachedState,
662
+ setCachedState
663
+ };
664
+ };
665
+ /**
666
+ * Cached default store instance, anchored on globalThis so it stays a single
667
+ * instance even if this module is duplicated across bundle chunks (e.g. CJS
668
+ * subpath entries). Lazily initialized on first access to be SSR-safe.
669
+ */
670
+ const DEFAULT_STORE_SYMBOL = Symbol.for("kheopskit.defaultStore");
671
+ /**
672
+ * Gets the default store, creating it on first access.
673
+ * Uses localStorage on client, noop on server.
674
+ * Lazily initialized to avoid SSR issues with module-level code.
675
+ */
676
+ const getDefaultStore = () => {
677
+ const g = globalThis;
678
+ if (!g[DEFAULT_STORE_SYMBOL]) g[DEFAULT_STORE_SYMBOL] = createKheopskitStore();
679
+ return g[DEFAULT_STORE_SYMBOL];
680
+ };
681
+ //#endregion
682
+ //#region src/api/reconnectPolicy.ts
683
+ const createReconnectPolicy = ({ maxAttempts = 3 } = {}) => {
684
+ const reconnecting = /* @__PURE__ */ new Set();
685
+ const reconnected = /* @__PURE__ */ new Set();
686
+ const failedAttempts = /* @__PURE__ */ new Map();
687
+ const shouldAttempt = (walletId) => !reconnecting.has(walletId) && !reconnected.has(walletId) && (failedAttempts.get(walletId) ?? 0) < maxAttempts;
688
+ /**
689
+ * Runs `connect` under the policy. Resolves `false` without connecting when
690
+ * an attempt is already in flight, the wallet already reconnected, or it
691
+ * exhausted its attempts; resolves `true` on success. A rejection from
692
+ * `connect` is recorded as a failed attempt and rethrown for the caller to
693
+ * report.
694
+ */
695
+ const attempt = async (walletId, connect) => {
696
+ if (!shouldAttempt(walletId)) return false;
697
+ reconnecting.add(walletId);
698
+ try {
699
+ await connect();
700
+ reconnected.add(walletId);
701
+ failedAttempts.delete(walletId);
702
+ return true;
703
+ } catch (err) {
704
+ failedAttempts.set(walletId, (failedAttempts.get(walletId) ?? 0) + 1);
705
+ throw err;
706
+ } finally {
707
+ reconnecting.delete(walletId);
708
+ }
709
+ };
710
+ return {
711
+ shouldAttempt,
712
+ attempt
713
+ };
714
+ };
715
+ //#endregion
716
+ //#region src/api/wallets.ts
717
+ const getWallets$ = (config, store) => {
718
+ const autoReconnectWalletIds$ = store.observable.pipe(map((s) => s.autoReconnect ?? []), take(1), shareReplay({
719
+ bufferSize: 1,
720
+ refCount: true
721
+ }));
722
+ return new Observable((subscriber) => {
723
+ const ctx = {
724
+ config,
725
+ store
726
+ };
727
+ const observables = config.platforms.map((plugin) => plugin.getWallets$(ctx));
728
+ const wallets$ = combineLatest([observables.length ? combineLatest(observables).pipe(map((wallets) => wallets.flat())) : of([]), getWalletConnectWallet$(config)]).pipe(map(([platformWallets, walletConnect]) => {
729
+ return (walletConnect ? [...platformWallets, walletConnect] : platformWallets).sort(sortWallets);
730
+ }));
731
+ const reconnectPolicy = createReconnectPolicy();
732
+ const subAutoReconnect = combineLatest([wallets$, autoReconnectWalletIds$]).pipe(filter(([, walletIds]) => config.autoReconnect && !!walletIds?.length), mergeMap(([wallets, walletIds]) => wallets.filter((wallet) => walletIds?.includes(wallet.id)))).subscribe(async (wallet) => {
733
+ if (wallet.isConnected) return;
734
+ try {
735
+ await reconnectPolicy.attempt(wallet.id, () => wallet.connect());
736
+ } catch (err) {
737
+ console.error("Failed to reconnect wallet %s", wallet.id, { err });
738
+ }
739
+ });
740
+ const subWallets = wallets$.subscribe(subscriber);
741
+ return () => {
742
+ subAutoReconnect.unsubscribe();
743
+ subWallets.unsubscribe();
744
+ };
745
+ }).pipe(shareReplay({
746
+ refCount: true,
747
+ bufferSize: 1
748
+ }));
749
+ };
750
+ //#endregion
751
+ //#region src/api/kheopskit.ts
752
+ const getKheopskit$ = (config, options = {}) => {
753
+ const { ssrCookies, store: existingStore } = options;
754
+ const kc = resolveConfig(config);
755
+ const store = existingStore ?? createKheopskitStore({
756
+ ssrCookies,
757
+ storageKey: kc.storageKey
758
+ });
759
+ if (kc.debug) console.debug("[kheopskit] config", kc);
760
+ if (kc.debug && typeof window === "undefined" && ssrCookies === void 0) console.warn("[kheopskit] Running on server without `ssrCookies`. Wallet state will not be hydrated. Pass cookies for SSR support.");
761
+ const { wallets: cachedWallets, accounts: cachedAccounts } = getHydratedSnapshot(store, kc.platforms, { icons: true });
762
+ if (kc.debug && cachedWallets.length > 0) console.debug("[kheopskit] hydrating from cache:", {
763
+ wallets: cachedWallets.length,
764
+ accounts: cachedAccounts.length
765
+ });
766
+ return new Observable((subscriber) => {
767
+ const liveWallets$ = getWallets$(kc, store);
768
+ const liveAccounts$ = getAccounts$(kc, liveWallets$);
769
+ const bufferedWallets$ = createHydrationBuffer(cachedWallets, liveWallets$, kc.hydrationGracePeriod, (w) => w.id, (liveWallets, cached) => {
770
+ const cachedConnectedIds = new Set(cached.filter((w) => w.isConnected).map((w) => w.id));
771
+ if (cachedConnectedIds.size === 0) return true;
772
+ return [...cachedConnectedIds].every((id) => liveWallets.some((w) => w.id === id && w.isConnected));
773
+ }, (live, cached) => ({
774
+ ...cached,
775
+ icon: cached.icon || getCachedIcon(cached.id) || live.icon,
776
+ connect: live.connect,
777
+ disconnect: live.disconnect
778
+ }), (cached) => ({
779
+ ...cached,
780
+ icon: cached.icon || getCachedIcon(cached.id) || ""
781
+ }));
782
+ const bufferedAccounts$ = createAccountHydrationBuffer(cachedAccounts, liveAccounts$, kc.hydrationGracePeriod, (a) => a.walletId, (liveAccounts, cached) => {
783
+ const cachedWalletIds = new Set(cached.map((a) => a.walletId));
784
+ if (cachedWalletIds.size === 0) return true;
785
+ const liveWalletIds = new Set(liveAccounts.map((a) => a.walletId));
786
+ return [...cachedWalletIds].every((id) => liveWalletIds.has(id));
787
+ });
788
+ const sharedWallets$ = bufferedWallets$.pipe(shareReplay({
789
+ bufferSize: 1,
790
+ refCount: true
791
+ }));
792
+ const sharedAccounts$ = bufferedAccounts$.pipe(shareReplay({
793
+ bufferSize: 1,
794
+ refCount: true
795
+ }));
796
+ const subscription = combineLatest({
797
+ wallets: sharedWallets$,
798
+ accounts: sharedAccounts$
799
+ }).pipe(map(({ wallets, accounts }) => {
800
+ if (kc.debug) console.debug("[kheopskit] hydration state", {
801
+ walletsHydrating: wallets.isHydrating,
802
+ accountsHydrating: accounts.isHydrating,
803
+ walletsConnected: wallets.items.filter((w) => w.isConnected).length,
804
+ walletsTotal: wallets.items.length
805
+ });
806
+ return {
807
+ config: kc,
808
+ wallets: [...wallets.items].sort(sortWallets),
809
+ accounts: [...accounts.items].sort(sortAccounts),
810
+ isHydrating: wallets.isHydrating || accounts.isHydrating
811
+ };
812
+ })).subscribe(subscriber);
813
+ const persistSub = combineLatest({
814
+ wallets: sharedWallets$,
815
+ accounts: sharedAccounts$
816
+ }).pipe(filter(({ wallets, accounts }) => !wallets.isHydrating && !accounts.isHydrating), debounceTime(1e3), distinctUntilChanged((prev, curr) => {
817
+ const prevWalletKeys = prev.wallets.items.map(walletPersistKey);
818
+ const currWalletKeys = curr.wallets.items.map(walletPersistKey);
819
+ const prevAccountKeys = prev.accounts.items.map(accountChangeKey);
820
+ const currAccountKeys = curr.accounts.items.map(accountChangeKey);
821
+ return arraysEqual(prevWalletKeys, currWalletKeys) && arraysEqual(prevAccountKeys, currAccountKeys);
822
+ })).subscribe(({ wallets, accounts }) => {
823
+ const connectedWalletIds = new Set(wallets.items.filter((w) => w.isConnected).map((w) => w.id));
824
+ const relevantAccounts = accounts.items.filter((a) => connectedWalletIds.has(a.walletId));
825
+ if (kc.debug) console.debug("[kheopskit] persisting state snapshot:", {
826
+ wallets: wallets.items.length,
827
+ accounts: relevantAccounts.length
828
+ });
829
+ store.setCachedState(wallets.items.map(serializeWallet), relevantAccounts.map(serializeAccount));
830
+ const icons = {};
831
+ for (const wallet of wallets.items) if (wallet.icon) icons[wallet.id] = wallet.icon;
832
+ setCachedIcons(icons);
833
+ });
834
+ return () => {
835
+ subscription.unsubscribe();
836
+ persistSub.unsubscribe();
837
+ };
838
+ }).pipe(distinctUntilChanged(statesEqual), throttleTime(16, void 0, {
839
+ leading: true,
840
+ trailing: true
841
+ }), logObservable("kheopskit$", {
842
+ enabled: kc.debug,
843
+ printValue: true
844
+ }), shareReplay({
845
+ bufferSize: 1,
846
+ refCount: true
847
+ }));
848
+ };
849
+ const arraysEqual = (a, b) => a.length === b.length && a.every((v, i) => v === b[i]);
850
+ /**
851
+ * A signature of an account covering its id plus the platform-specific fields
852
+ * that can change without the id changing (Ethereum chainId, Polkadot key type,
853
+ * Solana clusters). Used to detect both UI-relevant changes and changes worth
854
+ * re-persisting, so the two comparators can't drift apart.
855
+ */
856
+ const accountChangeKey = (account) => {
857
+ switch (account.platform) {
858
+ case "ethereum": return `${account.id}|${account.chainId ?? ""}`;
859
+ case "polkadot": return `${account.id}|${account.type ?? ""}`;
860
+ case "solana": return `${account.id}|${(account.chains ?? []).join(",")}`;
861
+ default: return account.id;
862
+ }
863
+ };
864
+ /** Platforms a wallet's live session carries (only the WalletConnect connector). */
865
+ const walletPlatforms = (wallet) => isWalletConnectWallet(wallet) ? wallet.platforms.join(",") : "";
866
+ /** Fields persisted to the cache for a wallet (icon is stored separately). */
867
+ const walletPersistKey = (wallet) => `${wallet.id}|${wallet.isConnected ? 1 : 0}|${wallet.name}`;
868
+ /**
869
+ * UI-significant signature of a wallet: the persisted fields plus icon and the
870
+ * WalletConnect connector's `platforms` array, which drive what the UI shows but
871
+ * aren't part of the id (icon and platforms can arrive asynchronously after
872
+ * connect without isConnected flipping).
873
+ */
874
+ const walletUiKey = (wallet) => `${walletPersistKey(wallet)}|${wallet.icon}|${walletPlatforms(wallet)}`;
875
+ /**
876
+ * Equality check for KheopskitState to prevent unnecessary emissions. Compares
877
+ * the UI-significant signature of every wallet and account, in order.
878
+ */
879
+ const statesEqual = (a, b) => a.isHydrating === b.isHydrating && a.wallets.length === b.wallets.length && a.accounts.length === b.accounts.length && a.wallets.every((w, i) => {
880
+ const other = b.wallets[i];
881
+ return !!other && walletUiKey(w) === walletUiKey(other);
997
882
  }) && a.accounts.every((acc, i) => {
998
- const other = b.accounts[i];
999
- return !!other && accountChangeKey(acc) === accountChangeKey(other);
883
+ const other = b.accounts[i];
884
+ return !!other && accountChangeKey(acc) === accountChangeKey(other);
1000
885
  });
1001
- export {
1002
- DEFAULT_STORAGE_KEY,
1003
- KheopskitError,
1004
- clearAllCachedObservables,
1005
- createKheopskitStore,
1006
- getDefaultStore,
1007
- getHydratedSnapshot,
1008
- getKheopskit$,
1009
- getWalletAccountId,
1010
- getWalletId,
1011
- isInjectedWallet,
1012
- isValidAddress,
1013
- isValidWalletId,
1014
- isWalletConnectWallet,
1015
- parseWalletAccountId,
1016
- parseWalletId,
1017
- resetAppKitCache,
1018
- resolveConfig
1019
- };
886
+ //#endregion
887
+ export { DEFAULT_STORAGE_KEY, KheopskitError, clearAllCachedObservables, createKheopskitStore, getDefaultStore, getHydratedSnapshot, getKheopskit$, getWalletAccountId, getWalletId, isInjectedWallet, isValidAddress, isValidWalletId, isWalletConnectWallet, parseWalletAccountId, parseWalletId, resetAppKitCache, resolveConfig };
888
+
1020
889
  //# sourceMappingURL=index.mjs.map