@kheopskit/core 5.3.0 → 6.0.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CHANGELOG.md +426 -0
  2. package/MIGRATING_TO_V6.md +837 -0
  3. package/README.md +628 -12
  4. package/dist/ethereum.d.mts +12 -6
  5. package/dist/ethereum.d.ts +12 -6
  6. package/dist/ethereum.js +13 -10
  7. package/dist/ethereum.js.map +1 -1
  8. package/dist/ethereum.mjs +7 -4
  9. package/dist/ethereum.mjs.map +1 -1
  10. package/dist/{iconCache-pP2ES4pJ.mjs → iconCache-C-U3IEbP.mjs} +21 -40
  11. package/dist/iconCache-C-U3IEbP.mjs.map +1 -0
  12. package/dist/{iconCache-DUtkJlaX.js → iconCache-FR4iy6go.js} +21 -40
  13. package/dist/iconCache-FR4iy6go.js.map +1 -0
  14. package/dist/index.d.mts +17 -63
  15. package/dist/index.d.ts +17 -63
  16. package/dist/index.js +387 -373
  17. package/dist/index.js.map +1 -1
  18. package/dist/index.mjs +384 -369
  19. package/dist/index.mjs.map +1 -1
  20. package/dist/injectedWallets-48L-AL1I.mjs +173 -0
  21. package/dist/injectedWallets-48L-AL1I.mjs.map +1 -0
  22. package/dist/injectedWallets-Dy8YGKL-.js +178 -0
  23. package/dist/injectedWallets-Dy8YGKL-.js.map +1 -0
  24. package/dist/internal.d.mts +3 -27
  25. package/dist/internal.d.ts +3 -27
  26. package/dist/internal.js +2 -2
  27. package/dist/internal.mjs +2 -2
  28. package/dist/pageLoad-BZ7o8p5R.mjs +24 -0
  29. package/dist/pageLoad-BZ7o8p5R.mjs.map +1 -0
  30. package/dist/pageLoad-D1wLeuFG.js +28 -0
  31. package/dist/pageLoad-D1wLeuFG.js.map +1 -0
  32. package/dist/platformAccounts-BjIeRcFN.js +321 -0
  33. package/dist/platformAccounts-BjIeRcFN.js.map +1 -0
  34. package/dist/platformAccounts-yyRUrnel.mjs +256 -0
  35. package/dist/platformAccounts-yyRUrnel.mjs.map +1 -0
  36. package/dist/polkadot.d.mts +127 -11
  37. package/dist/polkadot.d.ts +127 -11
  38. package/dist/polkadot.js +170 -106
  39. package/dist/polkadot.js.map +1 -1
  40. package/dist/polkadot.mjs +163 -99
  41. package/dist/polkadot.mjs.map +1 -1
  42. package/dist/solana.d.mts +11 -5
  43. package/dist/solana.d.ts +11 -5
  44. package/dist/solana.js +18 -15
  45. package/dist/solana.js.map +1 -1
  46. package/dist/solana.mjs +7 -4
  47. package/dist/solana.mjs.map +1 -1
  48. package/dist/storage-D3AwNgqc.d.mts +27 -0
  49. package/dist/storage-D3AwNgqc.d.ts +27 -0
  50. package/dist/testing.d.mts +71 -0
  51. package/dist/testing.d.ts +71 -0
  52. package/dist/testing.js +136 -0
  53. package/dist/testing.js.map +1 -0
  54. package/dist/testing.mjs +131 -0
  55. package/dist/testing.mjs.map +1 -0
  56. package/dist/{types-DKU-SNio.js → types-BWHvNFlN.js} +149 -2
  57. package/dist/types-BWHvNFlN.js.map +1 -0
  58. package/dist/types-Dkyt9bf4.d.mts +683 -0
  59. package/dist/types-Dkyt9bf4.d.ts +683 -0
  60. package/dist/types-Yor672md.mjs +215 -0
  61. package/dist/types-Yor672md.mjs.map +1 -0
  62. package/dist/walletconnect.d.mts +41 -0
  63. package/dist/walletconnect.d.ts +41 -0
  64. package/dist/walletconnect.js +219 -0
  65. package/dist/walletconnect.js.map +1 -0
  66. package/dist/walletconnect.mjs +216 -0
  67. package/dist/walletconnect.mjs.map +1 -0
  68. package/package.json +30 -6
  69. package/dist/errors-BLFP1dQI.mjs +0 -181
  70. package/dist/errors-BLFP1dQI.mjs.map +0 -1
  71. package/dist/errors-DYWVPHrR.js +0 -222
  72. package/dist/errors-DYWVPHrR.js.map +0 -1
  73. package/dist/iconCache-DUtkJlaX.js.map +0 -1
  74. package/dist/iconCache-pP2ES4pJ.mjs.map +0 -1
  75. package/dist/injectedWallets-BqR-_aAy.js +0 -148
  76. package/dist/injectedWallets-BqR-_aAy.js.map +0 -1
  77. package/dist/injectedWallets-Uz4hPuoW.mjs +0 -119
  78. package/dist/injectedWallets-Uz4hPuoW.mjs.map +0 -1
  79. package/dist/types-AxUDtkR-.d.mts +0 -338
  80. package/dist/types-AxUDtkR-.d.ts +0 -338
  81. package/dist/types-DKU-SNio.js.map +0 -1
  82. package/dist/types-jnifAAA0.mjs +0 -104
  83. package/dist/types-jnifAAA0.mjs.map +0 -1
package/dist/index.js CHANGED
@@ -1,172 +1,10 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_types = require("./types-DKU-SNio.js");
3
- const require_errors = require("./errors-DYWVPHrR.js");
4
- const require_iconCache = require("./iconCache-DUtkJlaX.js");
2
+ const require_types = require("./types-BWHvNFlN.js");
3
+ const require_platformAccounts = require("./platformAccounts-BjIeRcFN.js");
4
+ const require_iconCache = require("./iconCache-FR4iy6go.js");
5
+ const require_pageLoad = require("./pageLoad-D1wLeuFG.js");
5
6
  let rxjs = require("rxjs");
6
7
  let lodash_es = require("lodash-es");
7
- //#region src/api/appKit.ts
8
- /**
9
- * Dynamically import @reown/appkit/core to avoid loading browser-only code
10
- * during SSR or in edge runtimes like Cloudflare Workers.
11
- * The AppKit library uses Lit for web components which requires document.
12
- */
13
- const loadAppKit = async () => {
14
- try {
15
- const { createAppKit } = await import("@reown/appkit/core");
16
- return createAppKit;
17
- } catch (cause) {
18
- 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);
19
- return null;
20
- }
21
- };
22
- const WALLET_CONNECT_ICON = "data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjQwMCIgdmlld0JveD0iMCAwIDQwMCA0MDAiIHdpZHRoPSI0MDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxjbGlwUGF0aCBpZD0iYSI+PHBhdGggZD0ibTAgMGg0MDB2NDAwaC00MDB6Ii8+PC9jbGlwUGF0aD48ZyBjbGlwLXBhdGg9InVybCgjYSkiPjxjaXJjbGUgY3g9IjIwMCIgY3k9IjIwMCIgZmlsbD0iIzMzOTZmZiIgcj0iMTk5LjUiIHN0cm9rZT0iIzY2YjFmZiIvPjxwYXRoIGQ9Im0xMjIuNTE5IDE0OC45NjVjNDIuNzkxLTQxLjcyOSAxMTIuMTcxLTQxLjcyOSAxNTQuOTYyIDBsNS4xNSA1LjAyMmMyLjE0IDIuMDg2IDIuMTQgNS40NjkgMCA3LjU1NWwtMTcuNjE3IDE3LjE4Yy0xLjA3IDEuMDQzLTIuODA0IDEuMDQzLTMuODc0IDBsLTcuMDg3LTYuOTExYy0yOS44NTMtMjkuMTExLTc4LjI1My0yOS4xMTEtMTA4LjEwNiAwbC03LjU5IDcuNDAxYy0xLjA3IDEuMDQzLTIuODA0IDEuMDQzLTMuODc0IDBsLTE3LjYxNy0xNy4xOGMtMi4xNC0yLjA4Ni0yLjE0LTUuNDY5IDAtNy41NTV6bTE5MS4zOTcgMzUuNTI5IDE1LjY3OSAxNS4yOWMyLjE0IDIuMDg2IDIuMTQgNS40NjkgMCA3LjU1NWwtNzAuNyA2OC45NDRjLTIuMTM5IDIuMDg3LTUuNjA4IDIuMDg3LTcuNzQ4IDBsLTUwLjE3OC00OC45MzFjLS41MzUtLjUyMi0xLjQwMi0uNTIyLTEuOTM3IDBsLTUwLjE3OCA0OC45MzFjLTIuMTM5IDIuMDg3LTUuNjA4IDIuMDg3LTcuNzQ4IDBsLTcwLjcwMTUtNjguOTQ1Yy0yLjEzOTYtMi4wODYtMi4xMzk2LTUuNDY5IDAtNy41NTVsMTUuNjc5NS0xNS4yOWMyLjEzOTYtMi4wODYgNS42MDg1LTIuMDg2IDcuNzQ4MSAwbDUwLjE3ODkgNDguOTMyYy41MzUuNTIyIDEuNDAyLjUyMiAxLjkzNyAwbDUwLjE3Ny00OC45MzJjMi4xMzktMi4wODcgNS42MDgtMi4wODcgNy43NDggMGw1MC4xNzkgNDguOTMyYy41MzUuNTIyIDEuNDAyLjUyMiAxLjkzNyAwbDUwLjE3OS00OC45MzFjMi4xMzktMi4wODcgNS42MDgtMi4wODcgNy43NDggMHoiIGZpbGw9IiNmZmYiLz48L2c+PC9zdmc+";
23
- const APPKIT_SYMBOL = Symbol.for("kheopskit.cachedAppKit");
24
- const APPKIT_PROJECT_ID_SYMBOL = Symbol.for("kheopskit.cachedAppKitProjectId");
25
- const getCachedAppKit = () => globalThis[APPKIT_SYMBOL];
26
- const getCachedAppKitProjectId = () => globalThis[APPKIT_PROJECT_ID_SYMBOL];
27
- const setCachedAppKit = (value, projectId) => {
28
- globalThis[APPKIT_SYMBOL] = value;
29
- globalThis[APPKIT_PROJECT_ID_SYMBOL] = projectId;
30
- };
31
- /**
32
- * Clears the cached AppKit observable.
33
- * Use when configuration changes or for testing purposes.
34
- * Note: This does NOT destroy the appKit instance created by Reown.
35
- */
36
- const resetAppKitCache = () => {
37
- setCachedAppKit(void 0);
38
- };
39
- /**
40
- * Drops a WalletConnect namespace's cached account observables. Called when a
41
- * namespace transitions to disconnected — including external disconnects (from
42
- * the wallet app), which flip status via `subscribeProviders` without ever
43
- * calling `disconnect()` — so a later reconnect rebuilds the account observables
44
- * against the fresh session instead of a stale closure.
45
- *
46
- * Keys are `accounts:<walletId>:...`; the trailing colon scopes the prefix to
47
- * this wallet and avoids matching a sibling whose id is a string prefix.
48
- */
49
- const dropAccountsCache = (platform) => {
50
- require_types.clearCachedObservablesByPrefix(`accounts:${require_types.WALLET_CONNECT_WALLET_ID}:${platform}:`);
51
- };
52
- /**
53
- * The single, platform-less WalletConnect connector for the given config — or
54
- * `null` when WalletConnect isn't configured, on the server, or when
55
- * `@reown/appkit` isn't installed.
56
- *
57
- * @remarks
58
- * The AppKit instance is a process-wide singleton (Reown AppKit itself cannot
59
- * be instantiated twice). The **first** call with a `walletConnect` config wins;
60
- * later calls with a *different* `walletConnect` config reuse that first
61
- * instance. Call {@link resetAppKitCache} before re-initialising if the
62
- * WalletConnect config must change.
63
- *
64
- * One WalletConnect session is shared across every namespace, and a namespace
65
- * can only be established during the initial pairing — it can't be added to a
66
- * live session afterwards. So this exposes ONE connector (not one per platform):
67
- * `connect()` opens the modal; the wallet approves whichever namespaces it
68
- * supports in that single pairing, and `platforms` reflects what the live
69
- * session carries. Per-namespace accounts are derived by each platform plugin.
70
- */
71
- const getWalletConnectWallet$ = (config) => {
72
- if (!config.walletConnect) return (0, rxjs.of)(null);
73
- if (typeof window === "undefined") return (0, rxjs.of)(null);
74
- const walletConnect = config.walletConnect;
75
- const cachedProjectId = getCachedAppKitProjectId();
76
- 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);
77
- let cachedAppKit = getCachedAppKit();
78
- if (!cachedAppKit) {
79
- cachedAppKit = (0, rxjs.from)(loadAppKit()).pipe((0, rxjs.switchMap)((createAppKit) => {
80
- if (!createAppKit) return (0, rxjs.of)(null);
81
- return new rxjs.Observable((subscriber) => {
82
- const appKit = createAppKit({
83
- projectId: walletConnect.projectId,
84
- metadata: walletConnect.metadata,
85
- networks: walletConnect.networks,
86
- themeMode: walletConnect.themeMode,
87
- themeVariables: walletConnect.themeVariables,
88
- universalProviderConfigOverride: { methods: {
89
- polkadot: ["polkadot_signTransaction", "polkadot_signMessage"],
90
- solana: [
91
- "solana_signTransaction",
92
- "solana_signMessage",
93
- "solana_signAndSendTransaction"
94
- ]
95
- } },
96
- allWallets: "HIDE",
97
- debug: config.debug,
98
- allowUnsupportedChain: true
99
- });
100
- const appKitInstance = appKit;
101
- const status$ = new rxjs.BehaviorSubject({
102
- isPolkadotConnected: false,
103
- isEthereumConnected: false,
104
- isSolanaConnected: false
105
- });
106
- const unsubProviders = appKit.subscribeProviders((providers) => {
107
- status$.next({
108
- isPolkadotConnected: !!providers.polkadot,
109
- isEthereumConnected: !!providers.eip155,
110
- isSolanaConnected: !!providers.solana
111
- });
112
- });
113
- const namespaceOf = {
114
- polkadot: "polkadot",
115
- ethereum: "eip155",
116
- solana: "solana"
117
- };
118
- const allPlatforms = [
119
- "polkadot",
120
- "ethereum",
121
- "solana"
122
- ];
123
- const enabledPlatforms = allPlatforms.filter((p) => appKit.chainNamespaces.includes(namespaceOf[p]));
124
- let prevConnected = {
125
- polkadot: false,
126
- ethereum: false,
127
- solana: false
128
- };
129
- const sub = status$.pipe((0, rxjs.map)((s) => ({
130
- polkadot: s.isPolkadotConnected,
131
- ethereum: s.isEthereumConnected,
132
- solana: s.isSolanaConnected
133
- })), (0, rxjs.distinctUntilChanged)((a, b) => a.polkadot === b.polkadot && a.ethereum === b.ethereum && a.solana === b.solana), (0, rxjs.tap)((connected) => {
134
- for (const platform of allPlatforms) if (prevConnected[platform] && !connected[platform]) dropAccountsCache(platform);
135
- prevConnected = connected;
136
- }), (0, rxjs.map)((connected) => {
137
- const platforms = enabledPlatforms.filter((p) => connected[p]);
138
- const isConnected = platforms.length > 0;
139
- const walletInfo = appKit.getWalletInfo();
140
- return {
141
- id: require_types.WALLET_CONNECT_WALLET_ID,
142
- type: "walletconnect",
143
- platforms,
144
- appKit: appKitInstance,
145
- name: walletInfo?.name ?? "WalletConnect",
146
- icon: walletInfo?.icon ?? WALLET_CONNECT_ICON,
147
- connect: async () => {
148
- if (!isConnected) await appKit.open();
149
- },
150
- disconnect: async () => {
151
- if (isConnected) await appKit.disconnect();
152
- },
153
- isConnected
154
- };
155
- })).subscribe(subscriber);
156
- return () => {
157
- sub.unsubscribe();
158
- unsubProviders();
159
- };
160
- });
161
- }), (0, rxjs.shareReplay)({
162
- refCount: false,
163
- bufferSize: 1
164
- }));
165
- setCachedAppKit(cachedAppKit, walletConnect.projectId);
166
- }
167
- return cachedAppKit;
168
- };
169
- //#endregion
170
8
  //#region src/api/config.ts
171
9
  /**
172
10
  * Default storage key for persisting wallet connection state.
@@ -178,12 +16,14 @@ const DEFAULTS = {
178
16
  autoReconnect: true,
179
17
  debug: false,
180
18
  storageKey: DEFAULT_STORAGE_KEY,
181
- hydrationGracePeriod: 500
19
+ hydrationGracePeriod: 500,
20
+ hydrationTimeout: 3e3
182
21
  };
183
22
  const resolveConfig = (config) => {
184
23
  const platforms = config?.platforms ?? [];
185
24
  const invalidPlatforms = platforms.filter((p) => typeof p !== "object" || p === null || typeof p.getWallets$ !== "function");
186
25
  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.`);
26
+ if (config?.walletConnect !== void 0 && typeof config.walletConnect?.getWallet$ !== "function") throw new require_types.KheopskitError("INVALID_CONFIG", "config.walletConnect must be created by walletConnect() imported from \"@kheopskit/core/walletconnect\", e.g. walletConnect: walletConnect({ projectId, metadata, networks }). Raw WalletConnect options were removed in v6, see MIGRATING_TO_V6.md.");
187
27
  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\".");
188
28
  return Object.assign({}, DEFAULTS, config, { platforms });
189
29
  };
@@ -194,17 +34,14 @@ const PLATFORM_ORDER$1 = {
194
34
  ethereum: 1,
195
35
  solana: 2
196
36
  };
37
+ const byPlatform = (a1, a2) => PLATFORM_ORDER$1[a1.platform] - PLATFORM_ORDER$1[a2.platform];
197
38
  const byWalletName = (a1, a2) => {
39
+ if (a1.walletName === a2.walletName) return 0;
198
40
  if (a1.walletName.toLowerCase() === "talisman") return -1;
199
41
  if (a2.walletName.toLowerCase() === "talisman") return 1;
200
42
  return a1.walletName.localeCompare(a2.walletName);
201
43
  };
202
- const sortAccounts = (a1, a2) => {
203
- if (a1.platform !== a2.platform) return PLATFORM_ORDER$1[a1.platform] - PLATFORM_ORDER$1[a2.platform];
204
- if (a1.walletName !== a2.walletName) return byWalletName(a1, a2);
205
- if (a1.platform === "polkadot" && a2.platform === "polkadot") return a1.name !== a2.name ? (a1.name ?? "").localeCompare(a2.name ?? "") : a1.address.localeCompare(a2.address);
206
- return a1.id.localeCompare(a2.id);
207
- };
44
+ const sortAccounts = (a1, a2) => byPlatform(a1, a2) || byWalletName(a1, a2);
208
45
  //#endregion
209
46
  //#region src/utils/sortWallets.ts
210
47
  const PLATFORM_ORDER = {
@@ -233,58 +70,80 @@ const acceptsCachedAccount = (cached, platforms) => {
233
70
  return platforms.find((p) => p.platform === cached.platform)?.acceptsCachedAccount?.(cached) ?? true;
234
71
  };
235
72
  //#endregion
73
+ //#region src/api/reconnectPolicy.ts
74
+ const createReconnectPolicy = ({ maxAttempts = 3 } = {}) => {
75
+ const states = /* @__PURE__ */ new Map();
76
+ const shouldAttempt = (walletId) => {
77
+ const state = states.get(walletId);
78
+ if (!state) return true;
79
+ return state.kind === "failed" && state.attempts < maxAttempts;
80
+ };
81
+ const attempt = async (walletId, connect) => {
82
+ if (!shouldAttempt(walletId)) return false;
83
+ const before = states.get(walletId);
84
+ states.set(walletId, { kind: "pending" });
85
+ const recordOutcome = (outcome) => {
86
+ if (states.get(walletId)?.kind !== "settled") states.set(walletId, outcome);
87
+ };
88
+ try {
89
+ await connect();
90
+ recordOutcome({ kind: "reconnected" });
91
+ return true;
92
+ } catch (err) {
93
+ recordOutcome({
94
+ kind: "failed",
95
+ attempts: before?.kind === "failed" ? before.attempts + 1 : 1
96
+ });
97
+ throw err;
98
+ }
99
+ };
100
+ const settle = (walletId) => {
101
+ states.set(walletId, { kind: "settled" });
102
+ };
103
+ return {
104
+ shouldAttempt,
105
+ attempt,
106
+ settle
107
+ };
108
+ };
109
+ const reconnectAccepts = (autoReconnect, wallet) => typeof autoReconnect === "function" ? autoReconnect(wallet) : autoReconnect;
110
+ //#endregion
236
111
  //#region src/api/hydration.ts
237
112
  /**
238
113
  * Builds the cached-state snapshot every consumer must agree on: read the
239
- * store's cached state, hydrate wallets/accounts into placeholders, filter
240
- * accounts through each platform plugin's `acceptsCachedAccount` hook, and sort
241
- * with the same comparators the live pipeline uses.
114
+ * store's cached state, hydrate wallets/accounts into placeholders, apply the
115
+ * `autoReconnect` rule (a cached connected wallet it rejects becomes
116
+ * `disconnected` and loses its cached accounts), filter accounts through each
117
+ * platform plugin's `acceptsCachedAccount` hook, and sort with the same
118
+ * comparators the live pipeline uses.
242
119
  *
243
120
  * This is the single implementation behind the observable's initial seed and
244
121
  * both React snapshot sources (`serverValue`/`initialValue`). They must produce
245
122
  * the same shape in the same order or the UI flickers/reorders on reload — by
246
123
  * sharing this helper, that agreement holds by construction.
247
124
  */
248
- const getHydratedSnapshot = (store, platforms, options = {}) => {
125
+ const getHydratedSnapshot = (store, config, options = {}) => {
249
126
  const cached = store.getCachedState();
127
+ const wallets = cached.wallets.map(require_iconCache.hydrateWallet).map((wallet) => wallet.status === "connecting" && require_types.isInjectedWallet(wallet) && !reconnectAccepts(config.autoReconnect, wallet) ? {
128
+ ...wallet,
129
+ status: "disconnected"
130
+ } : wallet).map((wallet) => {
131
+ if (!options.icons || wallet.icon) return wallet;
132
+ const icon = require_iconCache.getCachedIcon(wallet.id);
133
+ return icon ? {
134
+ ...wallet,
135
+ icon
136
+ } : wallet;
137
+ }).sort(sortWallets);
138
+ const disconnected = new Set(wallets.filter((w) => w.status === "disconnected").map((w) => w.id));
250
139
  return {
251
- wallets: cached.wallets.map((cachedWallet) => {
252
- const wallet = require_iconCache.hydrateWallet(cachedWallet);
253
- if (!options.icons || wallet.icon) return wallet;
254
- const icon = require_iconCache.getCachedIcon(wallet.id);
255
- return icon ? {
256
- ...wallet,
257
- icon
258
- } : wallet;
259
- }).sort(sortWallets),
260
- accounts: cached.accounts.filter((account) => acceptsCachedAccount(account, platforms)).map(require_iconCache.hydrateAccount).sort(sortAccounts)
140
+ wallets,
141
+ accounts: cached.accounts.filter((account) => !disconnected.has(account.walletId)).filter((account) => acceptsCachedAccount(account, config.platforms)).map(require_iconCache.hydrateAccount).sort(sortAccounts)
261
142
  };
262
143
  };
263
144
  //#endregion
264
145
  //#region src/utils/createHydrationBuffer.ts
265
- /**
266
- * Core hydration buffer logic shared between wallets and accounts.
267
- *
268
- * During the grace period:
269
- * - Emits cached items immediately (before any live emissions)
270
- * - As live items arrive, merges them using the provided merge function
271
- *
272
- * After the grace period (and convergence, if provided):
273
- * - Only live items are returned
274
- * - isHydrating becomes false
275
- *
276
- * If an `isConverged` function is provided, hydration ends only when BOTH
277
- * the grace period has elapsed AND the live items have converged with cached
278
- * items (e.g., all previously-connected wallets have reconnected).
279
- * A max timeout (6x grace period) prevents infinite hydration.
280
- *
281
- * @param cachedItems - Initial cached items from storage
282
- * @param liveItems$ - Observable of live items as they become available
283
- * @param gracePeriodMs - Time in ms to wait before syncing to live state
284
- * @param mergeFn - Function to merge live and cached items during hydration
285
- * @param isConverged - Optional function to check if live items have caught up to cached items
286
- */
287
- const createBufferCore = (cachedItems, liveItems$, gracePeriodMs, mergeFn, isConverged) => {
146
+ const createBufferCore = (cachedItems, liveItems$, gracePeriodMs, timeoutMs, mergeFn, settled$) => {
288
147
  if (gracePeriodMs <= 0 || cachedItems.length === 0) return liveItems$.pipe((0, rxjs.map)((items) => ({
289
148
  items,
290
149
  isHydrating: false
@@ -292,30 +151,19 @@ const createBufferCore = (cachedItems, liveItems$, gracePeriodMs, mergeFn, isCon
292
151
  return new rxjs.Observable((subscriber) => {
293
152
  const subscriptions = new rxjs.Subscription();
294
153
  const isHydrating$ = new rxjs.BehaviorSubject(true);
295
- const liveWithInitial$ = liveItems$.pipe((0, rxjs.startWith)([]));
296
- if (isConverged) {
297
- const timerFired$ = (0, rxjs.timer)(gracePeriodMs).pipe((0, rxjs.map)(() => true), (0, rxjs.startWith)(false), (0, rxjs.shareReplay)({
298
- bufferSize: 1,
299
- refCount: true
300
- }));
301
- subscriptions.add((0, rxjs.combineLatest)([liveWithInitial$, timerFired$]).pipe((0, rxjs.filter)(([, timerFired]) => timerFired), (0, rxjs.filter)(([liveItems]) => isConverged(liveItems, cachedItems)), (0, rxjs.take)(1)).subscribe(() => {
302
- isHydrating$.next(false);
303
- }));
304
- subscriptions.add((0, rxjs.timer)(gracePeriodMs * 6).subscribe(() => {
305
- if (isHydrating$.value) isHydrating$.next(false);
306
- }));
307
- } else subscriptions.add((0, rxjs.timer)(gracePeriodMs).subscribe(() => {
308
- isHydrating$.next(false);
309
- }));
310
- subscriptions.add((0, rxjs.combineLatest)([liveWithInitial$, isHydrating$]).pipe((0, rxjs.map)(([liveItems, isHydrating]) => {
311
- if (!isHydrating) return {
312
- items: liveItems,
313
- isHydrating: false
314
- };
315
- return {
316
- items: mergeFn(liveItems, cachedItems),
317
- isHydrating: true
318
- };
154
+ const endHydration = () => {
155
+ if (isHydrating$.value) isHydrating$.next(false);
156
+ };
157
+ const graceElapsed$ = (0, rxjs.timer)(gracePeriodMs).pipe((0, rxjs.map)(() => true), (0, rxjs.startWith)(false));
158
+ const ended$ = settled$ ? (0, rxjs.combineLatest)([graceElapsed$, settled$]).pipe((0, rxjs.filter)(([graceElapsed, settled]) => graceElapsed && settled), (0, rxjs.take)(1)) : (0, rxjs.timer)(gracePeriodMs);
159
+ subscriptions.add(ended$.subscribe(endHydration));
160
+ subscriptions.add((0, rxjs.timer)(timeoutMs).subscribe(endHydration));
161
+ subscriptions.add((0, rxjs.combineLatest)([liveItems$.pipe((0, rxjs.startWith)([])), isHydrating$]).pipe((0, rxjs.map)(([liveItems, isHydrating]) => isHydrating ? {
162
+ items: mergeFn(liveItems, cachedItems),
163
+ isHydrating
164
+ } : {
165
+ items: liveItems,
166
+ isHydrating
319
167
  })).subscribe(subscriber));
320
168
  return () => {
321
169
  subscriptions.unsubscribe();
@@ -332,14 +180,15 @@ const createBufferCore = (cachedItems, liveItems$, gracePeriodMs, mergeFn, isCon
332
180
  *
333
181
  * @param cachedItems - Initial cached items from storage
334
182
  * @param liveItems$ - Observable of live items as they become available
335
- * @param gracePeriodMs - Time in ms to wait before syncing to live state
183
+ * @param gracePeriodMs - Minimum time in ms the cached items are shown
184
+ * @param timeoutMs - Upper bound in ms on hydration
336
185
  * @param getKey - Function to extract a unique key from an item
337
- * @param isConverged - Optional function to check if live items have caught up to cached items
186
+ * @param settled$ - Whether the live state has caught up with the cache
338
187
  * @param mergeItem - Optional function to merge a live item with its cached counterpart
339
188
  * @param transformCachedOnly - Optional function to transform cached items not yet in the live stream
340
189
  */
341
- const createHydrationBuffer = (cachedItems, liveItems$, gracePeriodMs, getKey, isConverged, mergeItem, transformCachedOnly) => {
342
- return createBufferCore(cachedItems, liveItems$, gracePeriodMs, (liveItems, cached) => {
190
+ const createHydrationBuffer = (cachedItems, liveItems$, gracePeriodMs, timeoutMs, getKey, settled$, mergeItem, transformCachedOnly) => {
191
+ return createBufferCore(cachedItems, liveItems$, gracePeriodMs, timeoutMs, (liveItems, cached) => {
343
192
  const cachedByKey = new Map(cached.map((item) => [getKey(item), item]));
344
193
  const liveKeys = new Set(liveItems.map(getKey));
345
194
  const mergedItems = liveItems.map((liveItem) => {
@@ -353,7 +202,7 @@ const createHydrationBuffer = (cachedItems, liveItems$, gracePeriodMs, getKey, i
353
202
  mergedItems.push(transformed);
354
203
  }
355
204
  return mergedItems;
356
- }, isConverged);
205
+ }, settled$);
357
206
  };
358
207
  /**
359
208
  * Creates a hydration buffer specifically for accounts, which need special handling.
@@ -364,17 +213,18 @@ const createHydrationBuffer = (cachedItems, liveItems$, gracePeriodMs, getKey, i
364
213
  *
365
214
  * @param cachedAccounts - Initial cached accounts from storage
366
215
  * @param liveAccounts$ - Observable of live accounts as they become available
367
- * @param gracePeriodMs - Time in ms to wait before syncing to live state
216
+ * @param gracePeriodMs - Minimum time in ms the cached accounts are shown
217
+ * @param timeoutMs - Upper bound in ms on hydration
368
218
  * @param getWalletId - Function to extract the wallet ID from an account
369
- * @param isConverged - Optional function to check if live accounts have caught up to cached accounts
219
+ * @param settled$ - Whether the live state has caught up with the cache
370
220
  */
371
- const createAccountHydrationBuffer = (cachedAccounts, liveAccounts$, gracePeriodMs, getWalletId, isConverged) => {
372
- return createBufferCore(cachedAccounts, liveAccounts$, gracePeriodMs, (liveAccounts, cached) => {
221
+ const createAccountHydrationBuffer = (cachedAccounts, liveAccounts$, gracePeriodMs, timeoutMs, getWalletId, settled$) => {
222
+ return createBufferCore(cachedAccounts, liveAccounts$, gracePeriodMs, timeoutMs, (liveAccounts, cached) => {
373
223
  const walletsWithLiveAccounts = new Set(liveAccounts.map(getWalletId));
374
224
  const mergedAccounts = [...liveAccounts];
375
225
  for (const cachedAccount of cached) if (!walletsWithLiveAccounts.has(getWalletId(cachedAccount))) mergedAccounts.push(cachedAccount);
376
226
  return mergedAccounts;
377
- }, isConverged);
227
+ }, settled$);
378
228
  };
379
229
  //#endregion
380
230
  //#region src/utils/logObservable.ts
@@ -389,8 +239,14 @@ const logObservable = (label, opts) => (0, rxjs.tap)((value) => {
389
239
  //#region src/api/accounts.ts
390
240
  const getAccounts$ = (config, wallets) => {
391
241
  return new rxjs.Observable((subscriber) => {
392
- const sources = config.platforms.map((plugin) => plugin.getAccounts$(wallets.pipe((0, rxjs.map)((ws) => ws.filter((w) => require_types.isWalletConnectWallet(w) || w.platform === plugin.platform)))));
393
- const sub = (sources.length ? (0, rxjs.combineLatest)(sources).pipe((0, rxjs.map)((accounts) => accounts.flat().sort(sortAccounts))) : (0, rxjs.of)([])).subscribe(subscriber);
242
+ const sources = config.platforms.map((plugin) => require_platformAccounts.framesOf(plugin.getAccounts$(wallets.pipe((0, rxjs.map)((ws) => ws.filter((w) => require_types.isWalletConnectWallet(w) || w.platform === plugin.platform))))));
243
+ const sub = (sources.length ? (0, rxjs.combineLatest)(sources).pipe((0, rxjs.map)((frames) => ({
244
+ accounts: frames.flatMap((frame) => frame.accounts).sort(sortAccounts),
245
+ answered: frames.flatMap((frame) => frame.answered)
246
+ }))) : (0, rxjs.of)({
247
+ accounts: [],
248
+ answered: []
249
+ })).subscribe(subscriber);
394
250
  return () => {
395
251
  sub.unsubscribe();
396
252
  };
@@ -401,28 +257,41 @@ const getAccounts$ = (config, wallets) => {
401
257
  };
402
258
  //#endregion
403
259
  //#region src/utils/createStore.ts
404
- const createStore = (key, defaultValue, storage = require_iconCache.safeLocalStorage) => {
405
- const subject = new rxjs.BehaviorSubject(getStoredData(key, defaultValue, storage));
406
- let unsubscribeStorage;
407
- if (typeof window !== "undefined" && storage.subscribe) unsubscribeStorage = storage.subscribe(key, (newValue) => {
408
- subject.next(parseData(newValue, defaultValue));
260
+ const createStore = (key, defaultValue, storage = require_iconCache.safeLocalStorage, normalize = (value) => value) => {
261
+ const read = (str) => normalize(parseData(str, defaultValue));
262
+ const syncsAcrossTabs = typeof window !== "undefined" && storage.subscribe !== void 0;
263
+ const subject = new rxjs.BehaviorSubject(read(storage.getItem(key)));
264
+ let observers = 0;
265
+ let stopSync;
266
+ const latestValue = () => {
267
+ if (syncsAcrossTabs && observers === 0) subject.next(read(storage.getItem(key)));
268
+ return subject.getValue();
269
+ };
270
+ const observable = new rxjs.Observable((subscriber) => {
271
+ if (observers === 0 && syncsAcrossTabs) {
272
+ latestValue();
273
+ stopSync = storage.subscribe?.(key, (newValue) => {
274
+ subject.next(read(newValue));
275
+ });
276
+ }
277
+ observers++;
278
+ const subscription = subject.subscribe(subscriber);
279
+ return () => {
280
+ subscription.unsubscribe();
281
+ observers--;
282
+ if (observers === 0) stopSync?.();
283
+ };
409
284
  });
410
285
  const update = (val) => {
411
- setStoredData(key, val, storage);
286
+ persistBestEffort(key, val, storage);
412
287
  subject.next(val);
413
288
  };
414
289
  return {
415
- observable: subject.asObservable(),
290
+ observable,
416
291
  set: (val) => update(val),
417
- mutate: (transform) => update(transform(subject.getValue())),
418
- get: () => structuredClone(subject.getValue()),
419
- /**
420
- * Cleanup subscriptions. Call this when the store is no longer needed.
421
- */
422
- destroy: () => {
423
- unsubscribeStorage?.();
424
- subject.complete();
425
- }
292
+ mutate: (transform) => update(transform(latestValue())),
293
+ get: () => structuredClone(latestValue()),
294
+ destroy: () => subject.complete()
426
295
  };
427
296
  };
428
297
  const parseData = (str, defaultValue) => {
@@ -431,17 +300,23 @@ const parseData = (str, defaultValue) => {
431
300
  } catch {}
432
301
  return defaultValue;
433
302
  };
434
- const getStoredData = (key, defaultValue, storage) => {
435
- const str = storage.getItem(key);
436
- return parseData(str, defaultValue);
437
- };
438
- const setStoredData = (key, val, storage) => {
303
+ const persistBestEffort = (key, val, storage) => {
439
304
  const str = JSON.stringify(val);
440
- storage.setItem(key, str);
305
+ try {
306
+ storage.setItem(key, str);
307
+ } catch {}
441
308
  };
442
309
  //#endregion
443
310
  //#region src/api/compactStore.ts
444
311
  const EMPTY_STORE_DATA = {};
312
+ const migratePreV6StoreData = (data) => {
313
+ if (data?.autoReconnect === void 0) return data;
314
+ const { autoReconnect, ...rest } = data;
315
+ return rest.remembered === void 0 && Array.isArray(autoReconnect) ? {
316
+ ...rest,
317
+ remembered: autoReconnect
318
+ } : rest;
319
+ };
445
320
  const toCompactPolkadotAccountType = (type) => {
446
321
  switch (type) {
447
322
  case "sr25519": return 0;
@@ -497,7 +372,7 @@ const toCompactStore = (data) => {
497
372
  ]);
498
373
  return {
499
374
  v: 1,
500
- r: data.autoReconnect,
375
+ r: data.remembered,
501
376
  w: wallets?.length ? wallets : void 0,
502
377
  a: accounts?.length ? accounts : void 0
503
378
  };
@@ -529,7 +404,7 @@ const fromCompactStore = (data) => {
529
404
  const platform = platformCode != null ? fromCompactPlatform(platformCode) : walletId === "walletconnect" ? void 0 : require_types.parseWalletId(walletId).platform;
530
405
  if (!require_types.isWalletPlatform(platform)) continue;
531
406
  accounts.push({
532
- id: require_errors.getWalletAccountId(walletId, address),
407
+ id: require_platformAccounts.getWalletAccountId(walletId, address),
533
408
  platform,
534
409
  address,
535
410
  name: name ?? void 0,
@@ -540,7 +415,7 @@ const fromCompactStore = (data) => {
540
415
  });
541
416
  }
542
417
  return {
543
- autoReconnect: data.r,
418
+ remembered: data.r,
544
419
  cachedWallets: wallets,
545
420
  cachedAccounts: accounts
546
421
  };
@@ -549,7 +424,7 @@ const decodeStore = (raw, fallback) => {
549
424
  try {
550
425
  const parsed = JSON.parse(raw);
551
426
  if (isCompactStore(parsed)) return fromCompactStore(parsed);
552
- return parsed;
427
+ return migratePreV6StoreData(parsed);
553
428
  } catch {
554
429
  return fallback;
555
430
  }
@@ -617,26 +492,26 @@ const isValidCachedAccount = (value) => {
617
492
  };
618
493
  /**
619
494
  * Creates a kheopskit store with the appropriate storage backend.
620
- * Uses cookieStorage when ssrCookies is provided (for SSR hydration),
621
- * otherwise falls back to safeLocalStorage.
495
+ * Uses the given `storage` when provided, else cookieStorage when ssrCookies is
496
+ * provided (for SSR hydration), otherwise safeLocalStorage.
622
497
  *
623
498
  * @param options - Configuration options for the store
624
499
  */
625
500
  const createKheopskitStore = (options = {}) => {
626
501
  const { ssrCookies, storageKey = DEFAULT_STORAGE_KEY } = options;
627
- const storage = ssrCookies !== void 0 ? createCompactCookieStorage(ssrCookies) : require_iconCache.safeLocalStorage;
628
- const store = createStore(storageKey, DEFAULT_SETTINGS, storage);
629
- const addEnabledWalletId = (walletId) => {
502
+ const storage = options.storage ?? (ssrCookies !== void 0 ? createCompactCookieStorage(ssrCookies) : require_iconCache.safeLocalStorage);
503
+ const store = createStore(storageKey, DEFAULT_SETTINGS, storage, migratePreV6StoreData);
504
+ const rememberWallet = (walletId) => {
630
505
  require_types.parseWalletId(walletId);
631
506
  store.mutate((prev) => ({
632
507
  ...prev,
633
- autoReconnect: (0, lodash_es.uniq)((prev.autoReconnect ?? []).concat(walletId))
508
+ remembered: (0, lodash_es.uniq)((prev.remembered ?? []).concat(walletId))
634
509
  }));
635
510
  };
636
- const removeEnabledWalletId = (walletId) => {
511
+ const forgetWallet = (walletId) => {
637
512
  store.mutate((prev) => ({
638
513
  ...prev,
639
- autoReconnect: (0, lodash_es.uniq)((prev.autoReconnect ?? []).filter((id) => id !== walletId))
514
+ remembered: (0, lodash_es.uniq)((prev.remembered ?? []).filter((id) => id !== walletId))
640
515
  }));
641
516
  };
642
517
  const getCachedState = () => {
@@ -657,10 +532,11 @@ const createKheopskitStore = (options = {}) => {
657
532
  };
658
533
  return {
659
534
  observable: store.observable,
660
- addEnabledWalletId,
661
- removeEnabledWalletId,
535
+ rememberWallet,
536
+ forgetWallet,
662
537
  getCachedState,
663
- setCachedState
538
+ setCachedState,
539
+ destroy: store.destroy
664
540
  };
665
541
  };
666
542
  /**
@@ -680,43 +556,9 @@ const getDefaultStore = () => {
680
556
  return g[DEFAULT_STORE_SYMBOL];
681
557
  };
682
558
  //#endregion
683
- //#region src/api/reconnectPolicy.ts
684
- const createReconnectPolicy = ({ maxAttempts = 3 } = {}) => {
685
- const reconnecting = /* @__PURE__ */ new Set();
686
- const reconnected = /* @__PURE__ */ new Set();
687
- const failedAttempts = /* @__PURE__ */ new Map();
688
- const shouldAttempt = (walletId) => !reconnecting.has(walletId) && !reconnected.has(walletId) && (failedAttempts.get(walletId) ?? 0) < maxAttempts;
689
- /**
690
- * Runs `connect` under the policy. Resolves `false` without connecting when
691
- * an attempt is already in flight, the wallet already reconnected, or it
692
- * exhausted its attempts; resolves `true` on success. A rejection from
693
- * `connect` is recorded as a failed attempt and rethrown for the caller to
694
- * report.
695
- */
696
- const attempt = async (walletId, connect) => {
697
- if (!shouldAttempt(walletId)) return false;
698
- reconnecting.add(walletId);
699
- try {
700
- await connect();
701
- reconnected.add(walletId);
702
- failedAttempts.delete(walletId);
703
- return true;
704
- } catch (err) {
705
- failedAttempts.set(walletId, (failedAttempts.get(walletId) ?? 0) + 1);
706
- throw err;
707
- } finally {
708
- reconnecting.delete(walletId);
709
- }
710
- };
711
- return {
712
- shouldAttempt,
713
- attempt
714
- };
715
- };
716
- //#endregion
717
559
  //#region src/api/wallets.ts
718
560
  const getWallets$ = (config, store) => {
719
- const autoReconnectWalletIds$ = store.observable.pipe((0, rxjs.map)((s) => s.autoReconnect ?? []), (0, rxjs.take)(1), (0, rxjs.shareReplay)({
561
+ const rememberedWalletIds$ = store.observable.pipe((0, rxjs.map)((s) => s.remembered ?? []), (0, rxjs.take)(1), (0, rxjs.shareReplay)({
720
562
  bufferSize: 1,
721
563
  refCount: true
722
564
  }));
@@ -726,20 +568,39 @@ const getWallets$ = (config, store) => {
726
568
  store
727
569
  };
728
570
  const observables = config.platforms.map((plugin) => plugin.getWallets$(ctx));
729
- const platformWallets$ = observables.length ? (0, rxjs.combineLatest)(observables).pipe((0, rxjs.map)((wallets) => wallets.flat())) : (0, rxjs.of)([]);
730
- const wallets$ = (0, rxjs.combineLatest)([platformWallets$, getWalletConnectWallet$(config)]).pipe((0, rxjs.map)(([platformWallets, walletConnect]) => {
731
- return (walletConnect ? [...platformWallets, walletConnect] : platformWallets).sort(sortWallets);
732
- }));
733
571
  const reconnectPolicy = createReconnectPolicy();
734
- const subAutoReconnect = (0, rxjs.combineLatest)([wallets$, autoReconnectWalletIds$]).pipe((0, rxjs.filter)(([, walletIds]) => config.autoReconnect && !!walletIds?.length), (0, rxjs.mergeMap)(([wallets, walletIds]) => wallets.filter((wallet) => walletIds?.includes(wallet.id)))).subscribe(async (wallet) => {
735
- if (wallet.isConnected) return;
572
+ const settleOnUserCall = (wallet) => ({
573
+ ...wallet,
574
+ connect: (options) => {
575
+ reconnectPolicy.settle(wallet.id);
576
+ return wallet.connect(options);
577
+ },
578
+ disconnect: () => {
579
+ reconnectPolicy.settle(wallet.id);
580
+ return wallet.disconnect();
581
+ }
582
+ });
583
+ const platformWallets$ = observables.length ? (0, rxjs.combineLatest)(observables).pipe((0, rxjs.map)((wallets) => wallets.flat())) : (0, rxjs.of)([]);
584
+ const walletConnect$ = config.walletConnect?.getWallet$(ctx);
585
+ const rawWallets$ = (walletConnect$ ? (0, rxjs.combineLatest)([platformWallets$, walletConnect$]).pipe((0, rxjs.map)(([platformWallets, walletConnect]) => walletConnect ? [...platformWallets, walletConnect] : platformWallets)) : platformWallets$).pipe((0, rxjs.map)((all) => all.sort(sortWallets)));
586
+ const wallets$ = rawWallets$.pipe((0, rxjs.map)((wallets) => wallets.map((wallet) => require_types.isInjectedWallet(wallet) ? settleOnUserCall(wallet) : wallet)));
587
+ const reconnect = async (wallet) => {
736
588
  try {
737
589
  await reconnectPolicy.attempt(wallet.id, () => wallet.connect());
738
590
  } catch (err) {
739
- console.error("Failed to reconnect wallet %s", wallet.id, { err });
591
+ if (subscriber.closed) return;
592
+ if (err instanceof require_types.KheopskitError) {
593
+ if (err.code === "USER_REJECTED") store.forgetWallet(wallet.id);
594
+ if (err.code === "USER_REJECTED" || err.code === "ABORTED") reconnectPolicy.settle(wallet.id);
595
+ }
596
+ if (config.debug) console.debug("[kheopskit] reconnect of %s failed", wallet.id, err);
740
597
  }
741
- });
598
+ };
742
599
  const subWallets = wallets$.subscribe(subscriber);
600
+ if (subscriber.closed) return subWallets;
601
+ const subAutoReconnect = (0, rxjs.combineLatest)([rawWallets$, rememberedWalletIds$]).pipe((0, rxjs.filter)(([, walletIds]) => config.autoReconnect !== false && walletIds.length > 0), (0, rxjs.mergeMap)(([wallets, walletIds]) => wallets.filter(require_types.isInjectedWallet).filter((wallet) => walletIds.includes(wallet.id))), (0, rxjs.tap)((wallet) => {
602
+ if (wallet.status === "connected") reconnectPolicy.settle(wallet.id);
603
+ }), (0, rxjs.filter)((wallet) => wallet.status === "disconnected" && reconnectAccepts(config.autoReconnect, wallet))).subscribe(reconnect);
743
604
  return () => {
744
605
  subAutoReconnect.unsubscribe();
745
606
  subWallets.unsubscribe();
@@ -760,33 +621,129 @@ const getKheopskit$ = (config, options = {}) => {
760
621
  });
761
622
  if (kc.debug) console.debug("[kheopskit] config", kc);
762
623
  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.");
763
- const { wallets: cachedWallets, accounts: cachedAccounts } = getHydratedSnapshot(store, kc.platforms, { icons: true });
624
+ const { wallets: cachedWallets, accounts: cachedAccounts } = getHydratedSnapshot(store, kc, { icons: true });
764
625
  if (kc.debug && cachedWallets.length > 0) console.debug("[kheopskit] hydrating from cache:", {
765
626
  wallets: cachedWallets.length,
766
627
  accounts: cachedAccounts.length
767
628
  });
768
629
  return new rxjs.Observable((subscriber) => {
630
+ let latest;
631
+ let closed = false;
632
+ const waiters = /* @__PURE__ */ new Set();
633
+ const liveWaiters = /* @__PURE__ */ new Set();
634
+ const wrapped = /* @__PURE__ */ new WeakMap();
635
+ const abandon = (waiter) => {
636
+ waiters.delete(waiter);
637
+ if (waiter.isConnected) waiter.reject(require_types.abortedError(waiter.walletId, "connect"));
638
+ else waiter.resolve();
639
+ };
640
+ const settleIn = (waiter, state) => {
641
+ const wallet = state.wallets.find((w) => w.id === waiter.walletId);
642
+ if (!wallet || wallet.isConnected === waiter.isConnected) {
643
+ waiters.delete(waiter);
644
+ waiter.resolve();
645
+ }
646
+ };
647
+ const supersede = (walletId, isConnected) => {
648
+ for (const waiter of waiters) if (waiter.walletId === walletId && waiter.isConnected !== isConnected) abandon(waiter);
649
+ };
650
+ const whenState = (walletId, isConnected) => new Promise((resolve, reject) => {
651
+ const waiter = {
652
+ walletId,
653
+ isConnected,
654
+ resolve,
655
+ reject
656
+ };
657
+ waiters.add(waiter);
658
+ if (latest) settleIn(waiter, latest);
659
+ if (closed && waiters.has(waiter)) abandon(waiter);
660
+ });
661
+ const whenLive = (walletId, signal) => new Promise((resolve, reject) => {
662
+ const settle = (finish) => {
663
+ liveWaiters.delete(check);
664
+ signal?.removeEventListener("abort", onAbort);
665
+ finish();
666
+ };
667
+ const onAbort = () => settle(() => reject(require_types.abortedError(walletId, "connect")));
668
+ const check = (state) => {
669
+ if (!state) return onAbort();
670
+ const live = state.wallets.find((w) => w.id === walletId && !w.isPlaceholder);
671
+ if (live) settle(() => resolve(live));
672
+ else if (!state.isHydrating) settle(() => reject(require_types.walletPendingError(walletId)));
673
+ };
674
+ if (closed || signal?.aborted) return onAbort();
675
+ signal?.addEventListener("abort", onAbort, { once: true });
676
+ liveWaiters.add(check);
677
+ if (latest) check(latest);
678
+ });
679
+ const deferToLive = (placeholder) => ({
680
+ ...placeholder,
681
+ connect: async (options) => {
682
+ const live = await whenLive(placeholder.id, options?.signal);
683
+ if (closed) throw require_types.abortedError(placeholder.id, "connect");
684
+ await live.connect(options);
685
+ }
686
+ });
687
+ const settleWithState = (wallet) => {
688
+ const existing = wrapped.get(wallet);
689
+ if (existing) return existing;
690
+ if (wallet.isPlaceholder) {
691
+ const deferred = deferToLive(wallet);
692
+ wrapped.set(wallet, deferred);
693
+ return deferred;
694
+ }
695
+ if (!require_types.isInjectedWallet(wallet)) return wallet;
696
+ const settled = {
697
+ ...wallet,
698
+ connect: async (options) => {
699
+ if (closed) throw require_types.abortedError(wallet.id, "connect");
700
+ await wallet.connect(options);
701
+ supersede(wallet.id, true);
702
+ await whenState(wallet.id, true);
703
+ },
704
+ disconnect: async () => {
705
+ if (closed) throw require_types.abortedError(wallet.id, "disconnect");
706
+ await wallet.disconnect();
707
+ supersede(wallet.id, false);
708
+ await whenState(wallet.id, false);
709
+ }
710
+ };
711
+ wrapped.set(wallet, settled);
712
+ return settled;
713
+ };
769
714
  const liveWallets$ = getWallets$(kc, store);
770
- const liveAccounts$ = getAccounts$(kc, liveWallets$);
771
- const bufferedWallets$ = createHydrationBuffer(cachedWallets, liveWallets$, kc.hydrationGracePeriod, (w) => w.id, (liveWallets, cached) => {
772
- const cachedConnectedIds = new Set(cached.filter((w) => w.isConnected).map((w) => w.id));
773
- if (cachedConnectedIds.size === 0) return true;
774
- return [...cachedConnectedIds].every((id) => liveWallets.some((w) => w.id === id && w.isConnected));
775
- }, (live, cached) => ({
776
- ...cached,
777
- icon: cached.icon || require_iconCache.getCachedIcon(cached.id) || live.icon,
778
- connect: live.connect,
779
- disconnect: live.disconnect
780
- }), (cached) => ({
715
+ const accountFrames$ = getAccounts$(kc, liveWallets$);
716
+ const liveAccounts$ = accountFrames$.pipe((0, rxjs.map)(({ accounts }) => accounts.map(asLive)));
717
+ const isDiscovering$ = require_pageLoad.pageLoaded$.pipe((0, rxjs.switchMap)((loaded) => loaded ? liveWallets$.pipe((0, rxjs.take)(1), (0, rxjs.map)(() => false)) : (0, rxjs.of)(true)), (0, rxjs.shareReplay)({
718
+ bufferSize: 1,
719
+ refCount: true
720
+ }));
721
+ const reconnecting = cachedWallets.filter((w) => w.status === "connecting");
722
+ const hydrationSettled$ = (0, rxjs.combineLatest)([
723
+ liveWallets$,
724
+ accountFrames$.pipe((0, rxjs.startWith)(noAnswerYet)),
725
+ isDiscovering$,
726
+ store.observable.pipe((0, rxjs.map)((s) => s.remembered ?? []), (0, rxjs.take)(1))
727
+ ]).pipe((0, rxjs.map)(([wallets, { accounts, answered }, isDiscovering, remembered]) => !isDiscovering && reconnecting.every((cached) => reconnectSettled(cached, {
728
+ wallets,
729
+ accounts,
730
+ answered,
731
+ cachedAccounts,
732
+ remembered
733
+ }))), (0, rxjs.debounceTime)(0, rxjs.asapScheduler), (0, rxjs.distinctUntilChanged)());
734
+ const bufferedWallets$ = createHydrationBuffer(cachedWallets, liveWallets$.pipe((0, rxjs.map)((wallets) => wallets.map(asLive))), kc.hydrationGracePeriod, kc.hydrationTimeout, (w) => w.id, hydrationSettled$, (live, cached) => {
735
+ const status = sessionRestoring(live, cached) ? "connecting" : live.status;
736
+ return {
737
+ ...live,
738
+ status,
739
+ isConnected: status === "connected",
740
+ icon: cached.icon || require_iconCache.getCachedIcon(cached.id) || live.icon
741
+ };
742
+ }, (cached) => ({
781
743
  ...cached,
782
744
  icon: cached.icon || require_iconCache.getCachedIcon(cached.id) || ""
783
745
  }));
784
- const bufferedAccounts$ = createAccountHydrationBuffer(cachedAccounts, liveAccounts$, kc.hydrationGracePeriod, (a) => a.walletId, (liveAccounts, cached) => {
785
- const cachedWalletIds = new Set(cached.map((a) => a.walletId));
786
- if (cachedWalletIds.size === 0) return true;
787
- const liveWalletIds = new Set(liveAccounts.map((a) => a.walletId));
788
- return [...cachedWalletIds].every((id) => liveWalletIds.has(id));
789
- });
746
+ const bufferedAccounts$ = createAccountHydrationBuffer(cachedAccounts, liveAccounts$, kc.hydrationGracePeriod, kc.hydrationTimeout, (a) => a.walletId, hydrationSettled$);
790
747
  const sharedWallets$ = bufferedWallets$.pipe((0, rxjs.shareReplay)({
791
748
  bufferSize: 1,
792
749
  refCount: true
@@ -797,38 +754,44 @@ const getKheopskit$ = (config, options = {}) => {
797
754
  }));
798
755
  const subscription = (0, rxjs.combineLatest)({
799
756
  wallets: sharedWallets$,
800
- accounts: sharedAccounts$
801
- }).pipe((0, rxjs.map)(({ wallets, accounts }) => {
757
+ accounts: sharedAccounts$,
758
+ isDiscovering: isDiscovering$
759
+ }).pipe((0, rxjs.map)(({ wallets, accounts, isDiscovering }) => {
802
760
  if (kc.debug) console.debug("[kheopskit] hydration state", {
803
761
  walletsHydrating: wallets.isHydrating,
804
762
  accountsHydrating: accounts.isHydrating,
763
+ isDiscovering,
805
764
  walletsConnected: wallets.items.filter((w) => w.isConnected).length,
806
765
  walletsTotal: wallets.items.length
807
766
  });
808
767
  return {
809
768
  config: kc,
810
- wallets: [...wallets.items].sort(sortWallets),
769
+ wallets: [...wallets.items].sort(sortWallets).map(settleWithState),
811
770
  accounts: [...accounts.items].sort(sortAccounts),
812
- isHydrating: wallets.isHydrating || accounts.isHydrating
771
+ isHydrating: wallets.isHydrating || accounts.isHydrating,
772
+ isDiscovering
813
773
  };
774
+ }), (0, rxjs.debounceTime)(0, rxjs.asapScheduler), (0, rxjs.distinctUntilChanged)(statesEqual), (0, rxjs.tap)((state) => {
775
+ latest = state;
776
+ for (const check of liveWaiters) check(state);
777
+ for (const waiter of waiters) settleIn(waiter, state);
814
778
  })).subscribe(subscriber);
815
779
  const persistSub = (0, rxjs.combineLatest)({
816
780
  wallets: sharedWallets$,
817
781
  accounts: sharedAccounts$
818
782
  }).pipe((0, rxjs.filter)(({ wallets, accounts }) => !wallets.isHydrating && !accounts.isHydrating), (0, rxjs.debounceTime)(1e3), (0, rxjs.distinctUntilChanged)((prev, curr) => {
819
- const prevWalletKeys = prev.wallets.items.map(walletPersistKey);
820
- const currWalletKeys = curr.wallets.items.map(walletPersistKey);
783
+ const prevWalletKeys = prev.wallets.items.map(persistedWalletKey);
784
+ const currWalletKeys = curr.wallets.items.map(persistedWalletKey);
821
785
  const prevAccountKeys = prev.accounts.items.map(accountChangeKey);
822
786
  const currAccountKeys = curr.accounts.items.map(accountChangeKey);
823
787
  return arraysEqual(prevWalletKeys, currWalletKeys) && arraysEqual(prevAccountKeys, currAccountKeys);
824
788
  })).subscribe(({ wallets, accounts }) => {
825
- const connectedWalletIds = new Set(wallets.items.filter((w) => w.isConnected).map((w) => w.id));
826
- const relevantAccounts = accounts.items.filter((a) => connectedWalletIds.has(a.walletId));
789
+ const snapshot = persistedSnapshot(wallets.items, accounts.items, store.getCachedState());
827
790
  if (kc.debug) console.debug("[kheopskit] persisting state snapshot:", {
828
- wallets: wallets.items.length,
829
- accounts: relevantAccounts.length
791
+ wallets: snapshot.wallets.length,
792
+ accounts: snapshot.accounts.length
830
793
  });
831
- store.setCachedState(wallets.items.map(require_iconCache.serializeWallet), relevantAccounts.map(require_iconCache.serializeAccount));
794
+ store.setCachedState(snapshot.wallets, snapshot.accounts);
832
795
  const icons = {};
833
796
  for (const wallet of wallets.items) if (wallet.icon) icons[wallet.id] = wallet.icon;
834
797
  require_iconCache.setCachedIcons(icons);
@@ -836,11 +799,11 @@ const getKheopskit$ = (config, options = {}) => {
836
799
  return () => {
837
800
  subscription.unsubscribe();
838
801
  persistSub.unsubscribe();
802
+ closed = true;
803
+ for (const waiter of waiters) abandon(waiter);
804
+ for (const check of liveWaiters) check(void 0);
839
805
  };
840
- }).pipe((0, rxjs.distinctUntilChanged)(statesEqual), (0, rxjs.throttleTime)(16, void 0, {
841
- leading: true,
842
- trailing: true
843
- }), logObservable("kheopskit$", {
806
+ }).pipe(logObservable("kheopskit$", {
844
807
  enabled: kc.debug,
845
808
  printValue: true
846
809
  }), (0, rxjs.shareReplay)({
@@ -848,14 +811,60 @@ const getKheopskit$ = (config, options = {}) => {
848
811
  refCount: true
849
812
  }));
850
813
  };
814
+ const sessionRestoring = (live, cached) => require_types.isWalletConnectWallet(live) && live.status === "disconnected" && cached.status === "connecting";
815
+ const flagged = /* @__PURE__ */ new WeakMap();
816
+ const asLive = (item) => {
817
+ const existing = flagged.get(item);
818
+ if (existing) return existing;
819
+ const live = {
820
+ ...item,
821
+ isPlaceholder: false
822
+ };
823
+ flagged.set(item, live);
824
+ return live;
825
+ };
826
+ const noAnswerYet = {
827
+ accounts: [],
828
+ answered: []
829
+ };
830
+ /**
831
+ * A remembered wallet that reads disconnected without an error is still
832
+ * waiting for its reconnect: getWallets$ emits the wallet list before its
833
+ * connect() flips the status to connecting.
834
+ */
835
+ const reconnectSettled = (cached, live) => {
836
+ const wallet = live.wallets.find((w) => w.id === cached.id);
837
+ if (!wallet) return true;
838
+ const status = sessionRestoring(wallet, cached) ? "connecting" : wallet.status;
839
+ if (status === "connecting") return false;
840
+ if (status === "disconnected") return wallet.error !== void 0 || !live.remembered.includes(cached.id);
841
+ return live.answered.includes(cached.id) || !live.cachedAccounts.some((a) => a.walletId === cached.id) || live.accounts.some((a) => a.walletId === cached.id);
842
+ };
843
+ /**
844
+ * A wallet still connecting keeps its previous cache entry and accounts:
845
+ * written live it would read isConnected: false with no accounts, and the
846
+ * next load would hydrate it as disconnected without placeholders while its
847
+ * reconnect runs.
848
+ */
849
+ const persistedSnapshot = (wallets, accounts, previous) => {
850
+ const connecting = new Set(wallets.filter((w) => w.status === "connecting").map((w) => w.id));
851
+ const connected = new Set(wallets.filter((w) => w.isConnected).map((w) => w.id));
852
+ const previousWallets = new Map(previous.wallets.map((w) => [w.id, w]));
853
+ return {
854
+ wallets: wallets.map((w) => connecting.has(w.id) && previousWallets.get(w.id) || require_iconCache.serializeWallet(w)),
855
+ accounts: [...accounts.filter((a) => connected.has(a.walletId)).map(require_iconCache.serializeAccount), ...previous.accounts.filter((a) => connecting.has(a.walletId))]
856
+ };
857
+ };
851
858
  const arraysEqual = (a, b) => a.length === b.length && a.every((v, i) => v === b[i]);
852
859
  /**
853
- * A signature of an account covering its id plus the platform-specific fields
854
- * that can change without the id changing (Ethereum chainId, Polkadot key type,
855
- * Solana clusters). Used to detect both UI-relevant changes and changes worth
856
- * re-persisting, so the two comparators can't drift apart.
860
+ * A signature of an account covering its id, its name, plus the
861
+ * platform-specific fields that can change without the id changing (Ethereum
862
+ * chainId, Polkadot key type, Solana clusters). Used to detect both UI-relevant
863
+ * changes and changes worth re-persisting, so the two comparators can't drift
864
+ * apart.
857
865
  */
858
- const accountChangeKey = (account) => {
866
+ const accountChangeKey = (account) => `${account.name ?? ""}|${accountPlatformKey(account)}`;
867
+ const accountPlatformKey = (account) => {
859
868
  switch (account.platform) {
860
869
  case "ethereum": return `${account.id}|${account.chainId ?? ""}`;
861
870
  case "polkadot": return `${account.id}|${account.type ?? ""}`;
@@ -868,40 +877,45 @@ const walletPlatforms = (wallet) => require_types.isWalletConnectWallet(wallet)
868
877
  /** Fields persisted to the cache for a wallet (icon is stored separately). */
869
878
  const walletPersistKey = (wallet) => `${wallet.id}|${wallet.isConnected ? 1 : 0}|${wallet.name}`;
870
879
  /**
880
+ * What decides a cache write: the persisted fields plus `status`, because a
881
+ * wallet whose kept entry was written while connecting must be rewritten live
882
+ * once its reconnect fails, and `isConnected` reads false on both sides.
883
+ */
884
+ const persistedWalletKey = (wallet) => `${walletPersistKey(wallet)}|${wallet.status}`;
885
+ /**
871
886
  * UI-significant signature of a wallet: the persisted fields plus icon and the
872
887
  * WalletConnect connector's `platforms` array, which drive what the UI shows but
873
888
  * aren't part of the id (icon and platforms can arrive asynchronously after
874
889
  * connect without isConnected flipping).
875
890
  */
876
- const walletUiKey = (wallet) => `${walletPersistKey(wallet)}|${wallet.icon}|${walletPlatforms(wallet)}`;
891
+ const walletUiKey = (wallet) => `${walletPersistKey(wallet)}|${wallet.status}|${wallet.error?.code ?? ""}|${wallet.icon}|${walletPlatforms(wallet)}`;
877
892
  /**
878
893
  * Equality check for KheopskitState to prevent unnecessary emissions. Compares
879
894
  * the UI-significant signature of every wallet and account, in order.
880
895
  */
881
- 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) => {
896
+ const statesEqual = (a, b) => a.isHydrating === b.isHydrating && a.isDiscovering === b.isDiscovering && a.wallets.length === b.wallets.length && a.accounts.length === b.accounts.length && a.wallets.every((w, i) => {
882
897
  const other = b.wallets[i];
883
- return !!other && walletUiKey(w) === walletUiKey(other);
898
+ return !!other && w.isPlaceholder === other.isPlaceholder && walletUiKey(w) === walletUiKey(other);
884
899
  }) && a.accounts.every((acc, i) => {
885
900
  const other = b.accounts[i];
886
- return !!other && accountChangeKey(acc) === accountChangeKey(other);
901
+ return !!other && acc.isPlaceholder === other.isPlaceholder && accountChangeKey(acc) === accountChangeKey(other);
887
902
  });
888
903
  //#endregion
889
904
  exports.DEFAULT_STORAGE_KEY = DEFAULT_STORAGE_KEY;
890
- exports.KheopskitError = require_errors.KheopskitError;
905
+ exports.KheopskitError = require_types.KheopskitError;
891
906
  exports.clearAllCachedObservables = require_types.clearAllCachedObservables;
892
907
  exports.createKheopskitStore = createKheopskitStore;
893
908
  exports.getDefaultStore = getDefaultStore;
894
909
  exports.getHydratedSnapshot = getHydratedSnapshot;
895
910
  exports.getKheopskit$ = getKheopskit$;
896
- exports.getWalletAccountId = require_errors.getWalletAccountId;
911
+ exports.getWalletAccountId = require_platformAccounts.getWalletAccountId;
897
912
  exports.getWalletId = require_types.getWalletId;
898
913
  exports.isInjectedWallet = require_types.isInjectedWallet;
899
- exports.isValidAddress = require_errors.isValidAddress;
914
+ exports.isValidAddress = require_platformAccounts.isValidAddress;
900
915
  exports.isValidWalletId = require_types.isValidWalletId;
901
916
  exports.isWalletConnectWallet = require_types.isWalletConnectWallet;
902
- exports.parseWalletAccountId = require_errors.parseWalletAccountId;
917
+ exports.parseWalletAccountId = require_platformAccounts.parseWalletAccountId;
903
918
  exports.parseWalletId = require_types.parseWalletId;
904
- exports.resetAppKitCache = resetAppKitCache;
905
919
  exports.resolveConfig = resolveConfig;
906
920
 
907
921
  //# sourceMappingURL=index.js.map