@kheopskit/react 3.0.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # @kheopskit/react
2
+
3
+ React bindings for [Kheopskit](https://github.com/kheopskit/kheopskit) — list
4
+ wallets and accounts across Polkadot, Ethereum and Solana, with injected wallets
5
+ and WalletConnect (Reown AppKit).
6
+
7
+ The framework-agnostic core lives in
8
+ [`@kheopskit/core`](https://www.npmjs.com/package/@kheopskit/core).
9
+ Full docs and the interactive playground:
10
+ https://github.com/kheopskit/kheopskit
11
+
12
+ > **Upgrading from v3?** v4 moves platforms to plugins and makes platform SDKs
13
+ > (and WalletConnect) optional peer dependencies. See
14
+ > [MIGRATING_TO_V4.md](../core/MIGRATING_TO_V4.md).
15
+
16
+ ## Install
17
+
18
+ `rxjs` is always required; every platform SDK (and WalletConnect) is an optional
19
+ peer dependency — install only what you use:
20
+
21
+ ```bash
22
+ pnpm add @kheopskit/core @kheopskit/react rxjs
23
+
24
+ pnpm add polkadot-api # Polkadot
25
+ pnpm add viem mipd # Ethereum
26
+ pnpm add @solana/kit @wallet-standard/app @wallet-standard/base # Solana
27
+ pnpm add @reown/appkit # WalletConnect (optional)
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ Recommended: bind your platform tuple once with `createKheopskit` and get a
33
+ provider plus hooks already typed to those platforms — no generic to repeat.
34
+
35
+ ```ts
36
+ // kheopskit.ts
37
+ import { createKheopskit } from "@kheopskit/react";
38
+ import { polkadot } from "@kheopskit/core/polkadot";
39
+ import { ethereum } from "@kheopskit/core/ethereum";
40
+ import { solana } from "@kheopskit/core/solana";
41
+
42
+ export const { KheopskitProvider, useWallets, useAccounts } = createKheopskit({
43
+ platforms: [polkadot(), ethereum(), solana()],
44
+ autoReconnect: true,
45
+ });
46
+ ```
47
+
48
+ ```tsx
49
+ // app.tsx
50
+ import { KheopskitProvider } from "./kheopskit";
51
+
52
+ export const App = ({ children }: { children: React.ReactNode }) => (
53
+ <KheopskitProvider>{children}</KheopskitProvider>
54
+ );
55
+ ```
56
+
57
+ ```tsx
58
+ // anywhere — accounts/wallets are platform-precise (account.signer / account.client typed)
59
+ import { useWallets, useAccounts } from "./kheopskit";
60
+
61
+ const { wallets, isHydrating } = useWallets();
62
+ const accounts = useAccounts();
63
+ ```
64
+
65
+ ### Without the factory
66
+
67
+ You can also use the `KheopskitProvider` component directly and recover precise
68
+ types with a type argument (React context can't be generic):
69
+
70
+ ```tsx
71
+ import { KheopskitProvider, useWallets } from "@kheopskit/react";
72
+
73
+ const platforms = [polkadot(), ethereum(), solana()] as const;
74
+
75
+ <KheopskitProvider config={{ platforms }}>…</KheopskitProvider>;
76
+
77
+ const { accounts } = useWallets<typeof platforms>();
78
+ ```
79
+
80
+ ## SSR
81
+
82
+ Pass the request cookie header as `ssrCookies` to hydrate wallet state on the
83
+ server without a UI flash:
84
+
85
+ ```tsx
86
+ // Next.js App Router
87
+ const cookieStore = await cookies();
88
+ const ssrCookies = cookieStore
89
+ .getAll()
90
+ .map((c) => `${c.name}=${c.value}`)
91
+ .join("; ");
92
+
93
+ <KheopskitProvider ssrCookies={ssrCookies}>{children}</KheopskitProvider>;
94
+ ```
95
+
96
+ > While `state.isHydrating` is `true`, wallets/accounts are cached placeholders
97
+ > carrying only the base fields — SDK fields (`signer`, `client`,
98
+ > provider/extension handles) are absent until hydration completes. Guard access
99
+ > behind `!isHydrating`.
100
+
101
+ ## License
102
+
103
+ ISC
package/dist/index.d.mts CHANGED
@@ -1,8 +1,47 @@
1
1
  import * as _kheopskit_core from '@kheopskit/core';
2
- import { KheopskitConfig } from '@kheopskit/core';
2
+ import { KheopskitPlatform, KheopskitConfig, KheopskitState } from '@kheopskit/core';
3
3
  import { FC, PropsWithChildren } from 'react';
4
4
 
5
+ type CreateKheopskitConfig<P extends readonly KheopskitPlatform[]> = Omit<Partial<KheopskitConfig<P>>, "platforms"> & {
6
+ /** Platform plugins, e.g. `[polkadot(), solana()]`. Required. */
7
+ platforms: P;
8
+ };
9
+ /**
10
+ * Binds a platform tuple once and returns a `KheopskitProvider` plus hooks
11
+ * (`useWallets`, `useAccounts`) already typed to those platforms — so you don't
12
+ * repeat `useWallets<typeof platforms>()` in every component.
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * // kheopskit.ts
17
+ * export const { KheopskitProvider, useWallets, useAccounts } = createKheopskit({
18
+ * platforms: [polkadot(), ethereum(), solana()],
19
+ * });
20
+ *
21
+ * // anywhere
22
+ * const { accounts } = useWallets(); // accounts are platform-precise, no generic
23
+ * ```
24
+ */
25
+ declare const createKheopskit: <const P extends readonly [KheopskitPlatform, ...KheopskitPlatform[]]>(config: CreateKheopskitConfig<P>) => {
26
+ KheopskitProvider: FC<PropsWithChildren<{
27
+ ssrCookies?: string;
28
+ }>>;
29
+ /** Current state, typed to the bound platform tuple. */
30
+ useWallets: () => _kheopskit_core.KheopskitState<P>;
31
+ /** Current accounts, typed to the bound platform tuple. */
32
+ useAccounts: () => _kheopskit_core.AccountOf<P[number]>[];
33
+ };
34
+
5
35
  type KheopskitProviderProps = PropsWithChildren & {
36
+ /**
37
+ * Kheopskit configuration.
38
+ *
39
+ * @remarks
40
+ * Must be a **referentially stable** value — define it once (module scope, a
41
+ * `useMemo`, or via {@link createKheopskit}) and pass the same reference. A
42
+ * new object literal on every render (`config={{ platforms: [...] }}` inline)
43
+ * recreates the underlying store and re-subscribes each render.
44
+ */
6
45
  config?: Partial<KheopskitConfig>;
7
46
  /**
8
47
  * Cookie string for SSR hydration.
@@ -25,6 +64,22 @@ type KheopskitProviderProps = PropsWithChildren & {
25
64
  };
26
65
  declare const KheopskitProvider: FC<KheopskitProviderProps>;
27
66
 
28
- declare const useWallets: () => _kheopskit_core.KheopskitState;
67
+ /**
68
+ * Convenience hook returning just the accounts from kheopskit state. Pass the
69
+ * platform tuple as a type argument to recover SDK-precise account types —
70
+ * `useAccounts<typeof platforms>()` — or use the pre-typed hook from
71
+ * {@link createKheopskit}.
72
+ */
73
+ declare const useAccounts: <P extends readonly KheopskitPlatform[] = readonly KheopskitPlatform[]>() => _kheopskit_core.AccountOf<P[number]>[];
74
+
75
+ /**
76
+ * Returns the current kheopskit state (wallets, accounts, config, isHydrating).
77
+ *
78
+ * Pass the platform tuple as a type argument to recover SDK-precise account and
79
+ * wallet types — `useWallets<typeof platforms>()`. React contexts can't be
80
+ * generic, so without the argument the state is typed with the base
81
+ * (SDK-free) wallet/account shapes.
82
+ */
83
+ declare const useWallets: <P extends readonly KheopskitPlatform[] = readonly KheopskitPlatform[]>() => KheopskitState<P>;
29
84
 
30
- export { KheopskitProvider, type KheopskitProviderProps, useWallets };
85
+ export { type CreateKheopskitConfig, KheopskitProvider, type KheopskitProviderProps, createKheopskit, useAccounts, useWallets };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,47 @@
1
1
  import * as _kheopskit_core from '@kheopskit/core';
2
- import { KheopskitConfig } from '@kheopskit/core';
2
+ import { KheopskitPlatform, KheopskitConfig, KheopskitState } from '@kheopskit/core';
3
3
  import { FC, PropsWithChildren } from 'react';
4
4
 
5
+ type CreateKheopskitConfig<P extends readonly KheopskitPlatform[]> = Omit<Partial<KheopskitConfig<P>>, "platforms"> & {
6
+ /** Platform plugins, e.g. `[polkadot(), solana()]`. Required. */
7
+ platforms: P;
8
+ };
9
+ /**
10
+ * Binds a platform tuple once and returns a `KheopskitProvider` plus hooks
11
+ * (`useWallets`, `useAccounts`) already typed to those platforms — so you don't
12
+ * repeat `useWallets<typeof platforms>()` in every component.
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * // kheopskit.ts
17
+ * export const { KheopskitProvider, useWallets, useAccounts } = createKheopskit({
18
+ * platforms: [polkadot(), ethereum(), solana()],
19
+ * });
20
+ *
21
+ * // anywhere
22
+ * const { accounts } = useWallets(); // accounts are platform-precise, no generic
23
+ * ```
24
+ */
25
+ declare const createKheopskit: <const P extends readonly [KheopskitPlatform, ...KheopskitPlatform[]]>(config: CreateKheopskitConfig<P>) => {
26
+ KheopskitProvider: FC<PropsWithChildren<{
27
+ ssrCookies?: string;
28
+ }>>;
29
+ /** Current state, typed to the bound platform tuple. */
30
+ useWallets: () => _kheopskit_core.KheopskitState<P>;
31
+ /** Current accounts, typed to the bound platform tuple. */
32
+ useAccounts: () => _kheopskit_core.AccountOf<P[number]>[];
33
+ };
34
+
5
35
  type KheopskitProviderProps = PropsWithChildren & {
36
+ /**
37
+ * Kheopskit configuration.
38
+ *
39
+ * @remarks
40
+ * Must be a **referentially stable** value — define it once (module scope, a
41
+ * `useMemo`, or via {@link createKheopskit}) and pass the same reference. A
42
+ * new object literal on every render (`config={{ platforms: [...] }}` inline)
43
+ * recreates the underlying store and re-subscribes each render.
44
+ */
6
45
  config?: Partial<KheopskitConfig>;
7
46
  /**
8
47
  * Cookie string for SSR hydration.
@@ -25,6 +64,22 @@ type KheopskitProviderProps = PropsWithChildren & {
25
64
  };
26
65
  declare const KheopskitProvider: FC<KheopskitProviderProps>;
27
66
 
28
- declare const useWallets: () => _kheopskit_core.KheopskitState;
67
+ /**
68
+ * Convenience hook returning just the accounts from kheopskit state. Pass the
69
+ * platform tuple as a type argument to recover SDK-precise account types —
70
+ * `useAccounts<typeof platforms>()` — or use the pre-typed hook from
71
+ * {@link createKheopskit}.
72
+ */
73
+ declare const useAccounts: <P extends readonly KheopskitPlatform[] = readonly KheopskitPlatform[]>() => _kheopskit_core.AccountOf<P[number]>[];
74
+
75
+ /**
76
+ * Returns the current kheopskit state (wallets, accounts, config, isHydrating).
77
+ *
78
+ * Pass the platform tuple as a type argument to recover SDK-precise account and
79
+ * wallet types — `useWallets<typeof platforms>()`. React contexts can't be
80
+ * generic, so without the argument the state is typed with the base
81
+ * (SDK-free) wallet/account shapes.
82
+ */
83
+ declare const useWallets: <P extends readonly KheopskitPlatform[] = readonly KheopskitPlatform[]>() => KheopskitState<P>;
29
84
 
30
- export { KheopskitProvider, type KheopskitProviderProps, useWallets };
85
+ export { type CreateKheopskitConfig, KheopskitProvider, type KheopskitProviderProps, createKheopskit, useAccounts, useWallets };
package/dist/index.js CHANGED
@@ -21,12 +21,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  KheopskitProvider: () => KheopskitProvider,
24
+ createKheopskit: () => createKheopskit,
25
+ useAccounts: () => useAccounts,
24
26
  useWallets: () => useWallets
25
27
  });
26
28
  module.exports = __toCommonJS(index_exports);
27
29
 
28
30
  // src/KheopskitProvider.tsx
29
31
  var import_core = require("@kheopskit/core");
32
+ var import_internal = require("@kheopskit/core/internal");
30
33
  var import_react2 = require("react");
31
34
 
32
35
  // src/context.ts
@@ -102,29 +105,29 @@ var KheopskitProvider = ({
102
105
  }
103
106
  const cached = kheopskitStore.getCachedState();
104
107
  return {
105
- wallets: cached.wallets.map(import_core.hydrateWallet),
106
- accounts: cached.accounts.map(import_core.hydrateAccount).filter(
107
- (account) => account.platform !== "polkadot" || resolvedConfig.polkadotAccountTypes.includes(account.type)
108
- ),
108
+ wallets: cached.wallets.map(import_internal.hydrateWallet).sort(import_internal.sortWallets),
109
+ accounts: cached.accounts.filter(
110
+ (account) => (0, import_internal.acceptsCachedAccount)(account, resolvedConfig.platforms)
111
+ ).map(import_internal.hydrateAccount).sort(import_internal.sortAccounts),
109
112
  config: resolvedConfig,
110
113
  isHydrating: true
111
114
  };
112
115
  }, [ssrCookies, kheopskitStore, resolvedConfig]);
113
116
  const initialValue = (0, import_react2.useMemo)(() => {
114
- const enrichedWallets = serverValue.wallets.map((w) => {
115
- if (!w.icon) {
116
- const cachedIcon = (0, import_core.getCachedIcon)(w.id);
117
- if (cachedIcon) {
118
- return { ...w, icon: cachedIcon };
119
- }
120
- }
121
- return w;
122
- });
117
+ const cached = kheopskitStore.getCachedState();
123
118
  return {
124
- ...serverValue,
125
- wallets: enrichedWallets
119
+ wallets: cached.wallets.map(import_internal.hydrateWallet).map((wallet) => {
120
+ if (wallet.icon) return wallet;
121
+ const cachedIcon = (0, import_internal.getCachedIcon)(wallet.id);
122
+ return cachedIcon ? { ...wallet, icon: cachedIcon } : wallet;
123
+ }).sort(import_internal.sortWallets),
124
+ accounts: cached.accounts.filter(
125
+ (account) => (0, import_internal.acceptsCachedAccount)(account, resolvedConfig.platforms)
126
+ ).map(import_internal.hydrateAccount).sort(import_internal.sortAccounts),
127
+ config: resolvedConfig,
128
+ isHydrating: true
126
129
  };
127
- }, [serverValue]);
130
+ }, [kheopskitStore, resolvedConfig]);
128
131
  const store = (0, import_react2.useMemo)(
129
132
  () => createStore(
130
133
  (0, import_core.getKheopskit$)(config, ssrCookies, kheopskitStore),
@@ -153,9 +156,31 @@ var useWallets = () => {
153
156
  throw new Error("useWallets can't be used without a KheopskitProvider");
154
157
  return ctx.state;
155
158
  };
159
+
160
+ // src/createKheopskit.tsx
161
+ var import_jsx_runtime2 = require("react/jsx-runtime");
162
+ var createKheopskit = (config) => {
163
+ const Provider = ({
164
+ children,
165
+ ssrCookies
166
+ }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(KheopskitProvider, { config, ssrCookies, children });
167
+ Provider.displayName = "KheopskitProvider";
168
+ return {
169
+ KheopskitProvider: Provider,
170
+ /** Current state, typed to the bound platform tuple. */
171
+ useWallets: () => useWallets(),
172
+ /** Current accounts, typed to the bound platform tuple. */
173
+ useAccounts: () => useWallets().accounts
174
+ };
175
+ };
176
+
177
+ // src/useAccounts.ts
178
+ var useAccounts = () => useWallets().accounts;
156
179
  // Annotate the CommonJS export names for ESM import in node:
157
180
  0 && (module.exports = {
158
181
  KheopskitProvider,
182
+ createKheopskit,
183
+ useAccounts,
159
184
  useWallets
160
185
  });
161
186
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/KheopskitProvider.tsx","../src/context.ts","../src/createStore.ts","../src/useWallets.ts"],"sourcesContent":["export * from \"./KheopskitProvider\";\nexport * from \"./useWallets\";\n","import {\n\tcreateKheopskitStore,\n\tgetCachedIcon,\n\tgetKheopskit$,\n\thydrateAccount,\n\thydrateWallet,\n\ttype KheopskitConfig,\n\ttype KheopskitState,\n\tresolveConfig,\n} from \"@kheopskit/core\";\nimport {\n\ttype FC,\n\ttype PropsWithChildren,\n\tuseEffect,\n\tuseMemo,\n\tuseSyncExternalStore,\n} from \"react\";\nimport { KheopskitContext } from \"./context\";\nimport { createStore } from \"./createStore\";\n\nexport type KheopskitProviderProps = PropsWithChildren & {\n\tconfig?: Partial<KheopskitConfig>;\n\t/**\n\t * Cookie string for SSR hydration.\n\t * Pass the request cookie header (e.g., from Next.js headers or TanStack Start)\n\t * to hydrate wallet state on the server.\n\t *\n\t * @remarks\n\t * This value should be stable per render to avoid unnecessary store recreation.\n\t * Compute it once in your server component or layout and pass it down.\n\t *\n\t * @example\n\t * ```tsx\n\t * // Next.js App Router\n\t * const cookieStore = await cookies();\n\t * const ssrCookies = cookieStore.getAll().map(c => `${c.name}=${c.value}`).join('; ');\n\t * return <Providers ssrCookies={ssrCookies}>{children}</Providers>\n\t * ```\n\t */\n\tssrCookies?: string;\n};\n\nexport const KheopskitProvider: FC<KheopskitProviderProps> = ({\n\tchildren,\n\tconfig,\n\tssrCookies,\n}) => {\n\tconst resolvedConfig = useMemo(() => resolveConfig(config), [config]);\n\n\t// Create a single store for both reading cached state and powering the observable\n\tconst kheopskitStore = useMemo(\n\t\t() =>\n\t\t\tcreateKheopskitStore({\n\t\t\t\tssrCookies,\n\t\t\t\tstorageKey: resolvedConfig.storageKey,\n\t\t\t}),\n\t\t[ssrCookies, resolvedConfig.storageKey],\n\t);\n\n\t// Read cached state from the store for SSR hydration\n\t// This produces wallets WITHOUT localStorage icons (Ethereum wallets have no icon)\n\t// because localStorage isn't available on server\n\tconst serverValue = useMemo<KheopskitState>(() => {\n\t\tif (ssrCookies === undefined) {\n\t\t\treturn {\n\t\t\t\twallets: [],\n\t\t\t\taccounts: [],\n\t\t\t\tconfig: resolvedConfig,\n\t\t\t\tisHydrating: true,\n\t\t\t};\n\t\t}\n\t\tconst cached = kheopskitStore.getCachedState();\n\t\treturn {\n\t\t\twallets: cached.wallets.map(hydrateWallet),\n\t\t\taccounts: cached.accounts\n\t\t\t\t.map(hydrateAccount)\n\t\t\t\t.filter(\n\t\t\t\t\t(account) =>\n\t\t\t\t\t\taccount.platform !== \"polkadot\" ||\n\t\t\t\t\t\tresolvedConfig.polkadotAccountTypes.includes(account.type),\n\t\t\t\t),\n\t\t\tconfig: resolvedConfig,\n\t\t\tisHydrating: true,\n\t\t};\n\t}, [ssrCookies, kheopskitStore, resolvedConfig]);\n\n\t// Initial value for client includes localStorage icons\n\t// This is what we WANT the client to render, not what server rendered\n\tconst initialValue = useMemo<KheopskitState>(() => {\n\t\t// On client, enrich wallets with localStorage icons\n\t\t// getCachedIcon returns empty on server (no localStorage), so this is safe\n\t\tconst enrichedWallets = serverValue.wallets.map((w) => {\n\t\t\tif (!w.icon) {\n\t\t\t\tconst cachedIcon = getCachedIcon(w.id);\n\t\t\t\tif (cachedIcon) {\n\t\t\t\t\treturn { ...w, icon: cachedIcon };\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn w;\n\t\t});\n\t\treturn {\n\t\t\t...serverValue,\n\t\t\twallets: enrichedWallets,\n\t\t};\n\t}, [serverValue]);\n\n\tconst store = useMemo(\n\t\t() =>\n\t\t\tcreateStore(\n\t\t\t\tgetKheopskit$(config, ssrCookies, kheopskitStore),\n\t\t\t\tinitialValue,\n\t\t\t\tserverValue,\n\t\t\t),\n\t\t[config, ssrCookies, kheopskitStore, initialValue, serverValue],\n\t);\n\n\t// Cleanup store subscriptions when store changes or component unmounts\n\tuseEffect(() => {\n\t\treturn () => store.destroy();\n\t}, [store]);\n\n\tconst state = useSyncExternalStore(\n\t\tstore.subscribe,\n\t\tstore.getSnapshot,\n\t\tstore.getServerSnapshot,\n\t);\n\n\tconst value = useMemo(() => ({ state }), [state]);\n\n\treturn (\n\t\t<KheopskitContext.Provider value={value}>\n\t\t\t{children}\n\t\t</KheopskitContext.Provider>\n\t);\n};\n","import type { KheopskitState } from \"@kheopskit/core\";\nimport { createContext } from \"react\";\n\nexport const KheopskitContext = createContext<{\n\tstate: KheopskitState;\n} | null>(null);\n","import type { Observable, Subscription } from \"rxjs\";\n\nexport const createStore = <T>(\n\tobservable$: Observable<T>,\n\tinitialValue: T,\n\tserverValue?: T,\n) => {\n\t// Use null as sentinel to indicate we haven't received first emission yet\n\tlet latestValue: T | null = null;\n\tlet subscription: Subscription | null = null;\n\tlet subscriberCount = 0;\n\tconst listeners = new Set<(value: T) => void>();\n\n\tconst ensureSubscription = () => {\n\t\tif (!subscription || subscription.closed) {\n\t\t\tsubscription = observable$.subscribe((value) => {\n\t\t\t\tlatestValue = value;\n\t\t\t\tfor (const listener of listeners) {\n\t\t\t\t\tlistener(value);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t};\n\n\t// Start subscription immediately\n\tensureSubscription();\n\n\t// If observable emitted synchronously, use that value\n\t// Otherwise fall back to initialValue\n\tconst getSnapshot = () => latestValue ?? initialValue;\n\n\t/**\n\t * Returns the server-side snapshot for SSR hydration.\n\t * This prevents hydration mismatches by providing a consistent\n\t * value during server rendering. Must return the same value as\n\t * what the server rendered.\n\t */\n\tconst getServerSnapshot = () => serverValue ?? initialValue;\n\n\tconst subscribe = (callback: (value: T) => void) => {\n\t\tsubscriberCount++;\n\t\tlisteners.add(callback);\n\t\t// Ensure observable subscription is active when someone subscribes\n\t\tensureSubscription();\n\n\t\t// Immediately emit current value (BehaviorSubject semantics)\n\t\tcallback(getSnapshot());\n\n\t\treturn () => {\n\t\t\tsubscriberCount--;\n\t\t\tlisteners.delete(callback);\n\t\t\t// Don't close the observable subscription on unsubscribe\n\t\t\t// Let destroy() handle that when the store is truly being disposed\n\t\t};\n\t};\n\n\tconst destroy = () => {\n\t\t// Only unsubscribe if no one is listening\n\t\t// React StrictMode may call destroy and then immediately resubscribe\n\t\tif (subscriberCount === 0 && subscription) {\n\t\t\tsubscription.unsubscribe();\n\t\t\tsubscription = null;\n\t\t}\n\t};\n\n\treturn {\n\t\tgetSnapshot,\n\t\tgetServerSnapshot,\n\t\tsubscribe,\n\t\tdestroy,\n\t};\n};\n","import { useContext } from \"react\";\nimport { KheopskitContext } from \"./context\";\n\nexport const useWallets = () => {\n\tconst ctx = useContext(KheopskitContext);\n\n\t// useEffect(() => {\n\t// console.debug(\n\t// \"useWallets wallets:%s accounts:%s\",\n\t// ctx?.state.wallets.length ?? 0,\n\t// ctx?.state.accounts.length ?? 0,\n\t// );\n\t// }, [ctx?.state]);\n\n\tif (!ctx)\n\t\tthrow new Error(\"useWallets can't be used without a KheopskitProvider\");\n\n\treturn ctx.state;\n};\n"],"mappings":"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","names":["import_react","import_react"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/KheopskitProvider.tsx","../src/context.ts","../src/createStore.ts","../src/useWallets.ts","../src/createKheopskit.tsx","../src/useAccounts.ts"],"sourcesContent":["export * from \"./createKheopskit\";\nexport * from \"./KheopskitProvider\";\nexport * from \"./useAccounts\";\nexport * from \"./useWallets\";\n","import {\n\tcreateKheopskitStore,\n\tgetKheopskit$,\n\ttype KheopskitConfig,\n\ttype KheopskitState,\n\tresolveConfig,\n} from \"@kheopskit/core\";\nimport {\n\tacceptsCachedAccount,\n\tgetCachedIcon,\n\thydrateAccount,\n\thydrateWallet,\n\tsortAccounts,\n\tsortWallets,\n} from \"@kheopskit/core/internal\";\nimport {\n\ttype FC,\n\ttype PropsWithChildren,\n\tuseEffect,\n\tuseMemo,\n\tuseSyncExternalStore,\n} from \"react\";\nimport { KheopskitContext } from \"./context\";\nimport { createStore } from \"./createStore\";\n\nexport type KheopskitProviderProps = PropsWithChildren & {\n\t/**\n\t * Kheopskit configuration.\n\t *\n\t * @remarks\n\t * Must be a **referentially stable** value — define it once (module scope, a\n\t * `useMemo`, or via {@link createKheopskit}) and pass the same reference. A\n\t * new object literal on every render (`config={{ platforms: [...] }}` inline)\n\t * recreates the underlying store and re-subscribes each render.\n\t */\n\tconfig?: Partial<KheopskitConfig>;\n\t/**\n\t * Cookie string for SSR hydration.\n\t * Pass the request cookie header (e.g., from Next.js headers or TanStack Start)\n\t * to hydrate wallet state on the server.\n\t *\n\t * @remarks\n\t * This value should be stable per render to avoid unnecessary store recreation.\n\t * Compute it once in your server component or layout and pass it down.\n\t *\n\t * @example\n\t * ```tsx\n\t * // Next.js App Router\n\t * const cookieStore = await cookies();\n\t * const ssrCookies = cookieStore.getAll().map(c => `${c.name}=${c.value}`).join('; ');\n\t * return <Providers ssrCookies={ssrCookies}>{children}</Providers>\n\t * ```\n\t */\n\tssrCookies?: string;\n};\n\nexport const KheopskitProvider: FC<KheopskitProviderProps> = ({\n\tchildren,\n\tconfig,\n\tssrCookies,\n}) => {\n\tconst resolvedConfig = useMemo(() => resolveConfig(config), [config]);\n\n\t// Create a single store for both reading cached state and powering the observable\n\tconst kheopskitStore = useMemo(\n\t\t() =>\n\t\t\tcreateKheopskitStore({\n\t\t\t\tssrCookies,\n\t\t\t\tstorageKey: resolvedConfig.storageKey,\n\t\t\t}),\n\t\t[ssrCookies, resolvedConfig.storageKey],\n\t);\n\n\t// Read cached state from the store for SSR hydration\n\t// This produces wallets WITHOUT localStorage icons (Ethereum wallets have no icon)\n\t// because localStorage isn't available on server\n\tconst serverValue = useMemo<KheopskitState>(() => {\n\t\tif (ssrCookies === undefined) {\n\t\t\treturn {\n\t\t\t\twallets: [],\n\t\t\t\taccounts: [],\n\t\t\t\tconfig: resolvedConfig,\n\t\t\t\tisHydrating: true,\n\t\t\t};\n\t\t}\n\t\tconst cached = kheopskitStore.getCachedState();\n\t\treturn {\n\t\t\twallets: cached.wallets.map(hydrateWallet).sort(sortWallets),\n\t\t\taccounts: cached.accounts\n\t\t\t\t.filter((account) =>\n\t\t\t\t\tacceptsCachedAccount(account, resolvedConfig.platforms),\n\t\t\t\t)\n\t\t\t\t.map(hydrateAccount)\n\t\t\t\t.sort(sortAccounts),\n\t\t\tconfig: resolvedConfig,\n\t\t\tisHydrating: true,\n\t\t};\n\t}, [ssrCookies, kheopskitStore, resolvedConfig]);\n\n\t// Client-only initial snapshot, read straight from the client cache so a hard\n\t// reload paints the cached wallet/account list on the very first frame instead\n\t// of flashing empty until the live observable produces its first emission —\n\t// which can be asynchronous (e.g. WalletConnect's AppKit is loaded via dynamic\n\t// import, so the underlying combineLatest can't emit synchronously).\n\t//\n\t// We can't derive this from `serverValue`: without SSR cookies that stays empty\n\t// (to keep the server/client hydration markup identical), so the SPA case would\n\t// otherwise render nothing. This snapshot is only ever read on the client via\n\t// getSnapshot, so reading the cache here is safe — and getCachedIcon returns \"\"\n\t// on the server, making the icon enrichment a no-op there.\n\tconst initialValue = useMemo<KheopskitState>(() => {\n\t\tconst cached = kheopskitStore.getCachedState();\n\t\treturn {\n\t\t\twallets: cached.wallets\n\t\t\t\t.map(hydrateWallet)\n\t\t\t\t.map((wallet) => {\n\t\t\t\t\tif (wallet.icon) return wallet;\n\t\t\t\t\tconst cachedIcon = getCachedIcon(wallet.id);\n\t\t\t\t\treturn cachedIcon ? { ...wallet, icon: cachedIcon } : wallet;\n\t\t\t\t})\n\t\t\t\t.sort(sortWallets),\n\t\t\taccounts: cached.accounts\n\t\t\t\t.filter((account) =>\n\t\t\t\t\tacceptsCachedAccount(account, resolvedConfig.platforms),\n\t\t\t\t)\n\t\t\t\t.map(hydrateAccount)\n\t\t\t\t.sort(sortAccounts),\n\t\t\tconfig: resolvedConfig,\n\t\t\tisHydrating: true,\n\t\t};\n\t}, [kheopskitStore, resolvedConfig]);\n\n\tconst store = useMemo(\n\t\t() =>\n\t\t\tcreateStore(\n\t\t\t\tgetKheopskit$(config, ssrCookies, kheopskitStore),\n\t\t\t\tinitialValue,\n\t\t\t\tserverValue,\n\t\t\t),\n\t\t[config, ssrCookies, kheopskitStore, initialValue, serverValue],\n\t);\n\n\t// Cleanup store subscriptions when store changes or component unmounts\n\tuseEffect(() => {\n\t\treturn () => store.destroy();\n\t}, [store]);\n\n\tconst state = useSyncExternalStore(\n\t\tstore.subscribe,\n\t\tstore.getSnapshot,\n\t\tstore.getServerSnapshot,\n\t);\n\n\tconst value = useMemo(() => ({ state }), [state]);\n\n\treturn (\n\t\t<KheopskitContext.Provider value={value}>\n\t\t\t{children}\n\t\t</KheopskitContext.Provider>\n\t);\n};\n","import type { KheopskitState } from \"@kheopskit/core\";\nimport { createContext } from \"react\";\n\nexport const KheopskitContext = createContext<{\n\tstate: KheopskitState;\n} | null>(null);\n","import type { Observable, Subscription } from \"rxjs\";\n\nexport const createStore = <T>(\n\tobservable$: Observable<T>,\n\tinitialValue: T,\n\tserverValue?: T,\n) => {\n\t// Use null as sentinel to indicate we haven't received first emission yet\n\tlet latestValue: T | null = null;\n\tlet subscription: Subscription | null = null;\n\tlet subscriberCount = 0;\n\tconst listeners = new Set<(value: T) => void>();\n\n\tconst ensureSubscription = () => {\n\t\tif (!subscription || subscription.closed) {\n\t\t\tsubscription = observable$.subscribe((value) => {\n\t\t\t\tlatestValue = value;\n\t\t\t\tfor (const listener of listeners) {\n\t\t\t\t\tlistener(value);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t};\n\n\t// Start subscription immediately\n\tensureSubscription();\n\n\t// If observable emitted synchronously, use that value\n\t// Otherwise fall back to initialValue\n\tconst getSnapshot = () => latestValue ?? initialValue;\n\n\t/**\n\t * Returns the server-side snapshot for SSR hydration.\n\t * This prevents hydration mismatches by providing a consistent\n\t * value during server rendering. Must return the same value as\n\t * what the server rendered.\n\t */\n\tconst getServerSnapshot = () => serverValue ?? initialValue;\n\n\tconst subscribe = (callback: (value: T) => void) => {\n\t\tsubscriberCount++;\n\t\tlisteners.add(callback);\n\t\t// Ensure observable subscription is active when someone subscribes\n\t\tensureSubscription();\n\n\t\t// Immediately emit current value (BehaviorSubject semantics)\n\t\tcallback(getSnapshot());\n\n\t\treturn () => {\n\t\t\tsubscriberCount--;\n\t\t\tlisteners.delete(callback);\n\t\t\t// Don't close the observable subscription on unsubscribe\n\t\t\t// Let destroy() handle that when the store is truly being disposed\n\t\t};\n\t};\n\n\tconst destroy = () => {\n\t\t// Only unsubscribe if no one is listening\n\t\t// React StrictMode may call destroy and then immediately resubscribe\n\t\tif (subscriberCount === 0 && subscription) {\n\t\t\tsubscription.unsubscribe();\n\t\t\tsubscription = null;\n\t\t}\n\t};\n\n\treturn {\n\t\tgetSnapshot,\n\t\tgetServerSnapshot,\n\t\tsubscribe,\n\t\tdestroy,\n\t};\n};\n","import type { KheopskitPlatform, KheopskitState } from \"@kheopskit/core\";\nimport { useContext } from \"react\";\nimport { KheopskitContext } from \"./context\";\n\n/**\n * Returns the current kheopskit state (wallets, accounts, config, isHydrating).\n *\n * Pass the platform tuple as a type argument to recover SDK-precise account and\n * wallet types — `useWallets<typeof platforms>()`. React contexts can't be\n * generic, so without the argument the state is typed with the base\n * (SDK-free) wallet/account shapes.\n */\nexport const useWallets = <\n\tP extends readonly KheopskitPlatform[] = readonly KheopskitPlatform[],\n>(): KheopskitState<P> => {\n\tconst ctx = useContext(KheopskitContext);\n\n\tif (!ctx)\n\t\tthrow new Error(\"useWallets can't be used without a KheopskitProvider\");\n\n\treturn ctx.state as unknown as KheopskitState<P>;\n};\n","import type { KheopskitConfig, KheopskitPlatform } from \"@kheopskit/core\";\nimport type { FC, PropsWithChildren } from \"react\";\nimport { KheopskitProvider } from \"./KheopskitProvider\";\nimport { useWallets } from \"./useWallets\";\n\nexport type CreateKheopskitConfig<P extends readonly KheopskitPlatform[]> =\n\tOmit<Partial<KheopskitConfig<P>>, \"platforms\"> & {\n\t\t/** Platform plugins, e.g. `[polkadot(), solana()]`. Required. */\n\t\tplatforms: P;\n\t};\n\n/**\n * Binds a platform tuple once and returns a `KheopskitProvider` plus hooks\n * (`useWallets`, `useAccounts`) already typed to those platforms — so you don't\n * repeat `useWallets<typeof platforms>()` in every component.\n *\n * @example\n * ```tsx\n * // kheopskit.ts\n * export const { KheopskitProvider, useWallets, useAccounts } = createKheopskit({\n * platforms: [polkadot(), ethereum(), solana()],\n * });\n *\n * // anywhere\n * const { accounts } = useWallets(); // accounts are platform-precise, no generic\n * ```\n */\nexport const createKheopskit = <\n\tconst P extends readonly [KheopskitPlatform, ...KheopskitPlatform[]],\n>(\n\tconfig: CreateKheopskitConfig<P>,\n) => {\n\tconst Provider: FC<PropsWithChildren<{ ssrCookies?: string }>> = ({\n\t\tchildren,\n\t\tssrCookies,\n\t}) => (\n\t\t<KheopskitProvider config={config} ssrCookies={ssrCookies}>\n\t\t\t{children}\n\t\t</KheopskitProvider>\n\t);\n\tProvider.displayName = \"KheopskitProvider\";\n\n\treturn {\n\t\tKheopskitProvider: Provider,\n\t\t/** Current state, typed to the bound platform tuple. */\n\t\tuseWallets: () => useWallets<P>(),\n\t\t/** Current accounts, typed to the bound platform tuple. */\n\t\tuseAccounts: () => useWallets<P>().accounts,\n\t};\n};\n","import type { KheopskitPlatform } from \"@kheopskit/core\";\nimport { useWallets } from \"./useWallets\";\n\n/**\n * Convenience hook returning just the accounts from kheopskit state. Pass the\n * platform tuple as a type argument to recover SDK-precise account types —\n * `useAccounts<typeof platforms>()` — or use the pre-typed hook from\n * {@link createKheopskit}.\n */\nexport const useAccounts = <\n\tP extends readonly KheopskitPlatform[] = readonly KheopskitPlatform[],\n>() => useWallets<P>().accounts;\n"],"mappings":"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","names":["import_react","import_react","import_jsx_runtime"]}
package/dist/index.mjs CHANGED
@@ -1,12 +1,17 @@
1
1
  // src/KheopskitProvider.tsx
2
2
  import {
3
3
  createKheopskitStore,
4
- getCachedIcon,
5
4
  getKheopskit$,
6
- hydrateAccount,
7
- hydrateWallet,
8
5
  resolveConfig
9
6
  } from "@kheopskit/core";
7
+ import {
8
+ acceptsCachedAccount,
9
+ getCachedIcon,
10
+ hydrateAccount,
11
+ hydrateWallet,
12
+ sortAccounts,
13
+ sortWallets
14
+ } from "@kheopskit/core/internal";
10
15
  import {
11
16
  useEffect,
12
17
  useMemo,
@@ -86,29 +91,29 @@ var KheopskitProvider = ({
86
91
  }
87
92
  const cached = kheopskitStore.getCachedState();
88
93
  return {
89
- wallets: cached.wallets.map(hydrateWallet),
90
- accounts: cached.accounts.map(hydrateAccount).filter(
91
- (account) => account.platform !== "polkadot" || resolvedConfig.polkadotAccountTypes.includes(account.type)
92
- ),
94
+ wallets: cached.wallets.map(hydrateWallet).sort(sortWallets),
95
+ accounts: cached.accounts.filter(
96
+ (account) => acceptsCachedAccount(account, resolvedConfig.platforms)
97
+ ).map(hydrateAccount).sort(sortAccounts),
93
98
  config: resolvedConfig,
94
99
  isHydrating: true
95
100
  };
96
101
  }, [ssrCookies, kheopskitStore, resolvedConfig]);
97
102
  const initialValue = useMemo(() => {
98
- const enrichedWallets = serverValue.wallets.map((w) => {
99
- if (!w.icon) {
100
- const cachedIcon = getCachedIcon(w.id);
101
- if (cachedIcon) {
102
- return { ...w, icon: cachedIcon };
103
- }
104
- }
105
- return w;
106
- });
103
+ const cached = kheopskitStore.getCachedState();
107
104
  return {
108
- ...serverValue,
109
- wallets: enrichedWallets
105
+ wallets: cached.wallets.map(hydrateWallet).map((wallet) => {
106
+ if (wallet.icon) return wallet;
107
+ const cachedIcon = getCachedIcon(wallet.id);
108
+ return cachedIcon ? { ...wallet, icon: cachedIcon } : wallet;
109
+ }).sort(sortWallets),
110
+ accounts: cached.accounts.filter(
111
+ (account) => acceptsCachedAccount(account, resolvedConfig.platforms)
112
+ ).map(hydrateAccount).sort(sortAccounts),
113
+ config: resolvedConfig,
114
+ isHydrating: true
110
115
  };
111
- }, [serverValue]);
116
+ }, [kheopskitStore, resolvedConfig]);
112
117
  const store = useMemo(
113
118
  () => createStore(
114
119
  getKheopskit$(config, ssrCookies, kheopskitStore),
@@ -137,8 +142,30 @@ var useWallets = () => {
137
142
  throw new Error("useWallets can't be used without a KheopskitProvider");
138
143
  return ctx.state;
139
144
  };
145
+
146
+ // src/createKheopskit.tsx
147
+ import { jsx as jsx2 } from "react/jsx-runtime";
148
+ var createKheopskit = (config) => {
149
+ const Provider = ({
150
+ children,
151
+ ssrCookies
152
+ }) => /* @__PURE__ */ jsx2(KheopskitProvider, { config, ssrCookies, children });
153
+ Provider.displayName = "KheopskitProvider";
154
+ return {
155
+ KheopskitProvider: Provider,
156
+ /** Current state, typed to the bound platform tuple. */
157
+ useWallets: () => useWallets(),
158
+ /** Current accounts, typed to the bound platform tuple. */
159
+ useAccounts: () => useWallets().accounts
160
+ };
161
+ };
162
+
163
+ // src/useAccounts.ts
164
+ var useAccounts = () => useWallets().accounts;
140
165
  export {
141
166
  KheopskitProvider,
167
+ createKheopskit,
168
+ useAccounts,
142
169
  useWallets
143
170
  };
144
171
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/KheopskitProvider.tsx","../src/context.ts","../src/createStore.ts","../src/useWallets.ts"],"sourcesContent":["import {\n\tcreateKheopskitStore,\n\tgetCachedIcon,\n\tgetKheopskit$,\n\thydrateAccount,\n\thydrateWallet,\n\ttype KheopskitConfig,\n\ttype KheopskitState,\n\tresolveConfig,\n} from \"@kheopskit/core\";\nimport {\n\ttype FC,\n\ttype PropsWithChildren,\n\tuseEffect,\n\tuseMemo,\n\tuseSyncExternalStore,\n} from \"react\";\nimport { KheopskitContext } from \"./context\";\nimport { createStore } from \"./createStore\";\n\nexport type KheopskitProviderProps = PropsWithChildren & {\n\tconfig?: Partial<KheopskitConfig>;\n\t/**\n\t * Cookie string for SSR hydration.\n\t * Pass the request cookie header (e.g., from Next.js headers or TanStack Start)\n\t * to hydrate wallet state on the server.\n\t *\n\t * @remarks\n\t * This value should be stable per render to avoid unnecessary store recreation.\n\t * Compute it once in your server component or layout and pass it down.\n\t *\n\t * @example\n\t * ```tsx\n\t * // Next.js App Router\n\t * const cookieStore = await cookies();\n\t * const ssrCookies = cookieStore.getAll().map(c => `${c.name}=${c.value}`).join('; ');\n\t * return <Providers ssrCookies={ssrCookies}>{children}</Providers>\n\t * ```\n\t */\n\tssrCookies?: string;\n};\n\nexport const KheopskitProvider: FC<KheopskitProviderProps> = ({\n\tchildren,\n\tconfig,\n\tssrCookies,\n}) => {\n\tconst resolvedConfig = useMemo(() => resolveConfig(config), [config]);\n\n\t// Create a single store for both reading cached state and powering the observable\n\tconst kheopskitStore = useMemo(\n\t\t() =>\n\t\t\tcreateKheopskitStore({\n\t\t\t\tssrCookies,\n\t\t\t\tstorageKey: resolvedConfig.storageKey,\n\t\t\t}),\n\t\t[ssrCookies, resolvedConfig.storageKey],\n\t);\n\n\t// Read cached state from the store for SSR hydration\n\t// This produces wallets WITHOUT localStorage icons (Ethereum wallets have no icon)\n\t// because localStorage isn't available on server\n\tconst serverValue = useMemo<KheopskitState>(() => {\n\t\tif (ssrCookies === undefined) {\n\t\t\treturn {\n\t\t\t\twallets: [],\n\t\t\t\taccounts: [],\n\t\t\t\tconfig: resolvedConfig,\n\t\t\t\tisHydrating: true,\n\t\t\t};\n\t\t}\n\t\tconst cached = kheopskitStore.getCachedState();\n\t\treturn {\n\t\t\twallets: cached.wallets.map(hydrateWallet),\n\t\t\taccounts: cached.accounts\n\t\t\t\t.map(hydrateAccount)\n\t\t\t\t.filter(\n\t\t\t\t\t(account) =>\n\t\t\t\t\t\taccount.platform !== \"polkadot\" ||\n\t\t\t\t\t\tresolvedConfig.polkadotAccountTypes.includes(account.type),\n\t\t\t\t),\n\t\t\tconfig: resolvedConfig,\n\t\t\tisHydrating: true,\n\t\t};\n\t}, [ssrCookies, kheopskitStore, resolvedConfig]);\n\n\t// Initial value for client includes localStorage icons\n\t// This is what we WANT the client to render, not what server rendered\n\tconst initialValue = useMemo<KheopskitState>(() => {\n\t\t// On client, enrich wallets with localStorage icons\n\t\t// getCachedIcon returns empty on server (no localStorage), so this is safe\n\t\tconst enrichedWallets = serverValue.wallets.map((w) => {\n\t\t\tif (!w.icon) {\n\t\t\t\tconst cachedIcon = getCachedIcon(w.id);\n\t\t\t\tif (cachedIcon) {\n\t\t\t\t\treturn { ...w, icon: cachedIcon };\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn w;\n\t\t});\n\t\treturn {\n\t\t\t...serverValue,\n\t\t\twallets: enrichedWallets,\n\t\t};\n\t}, [serverValue]);\n\n\tconst store = useMemo(\n\t\t() =>\n\t\t\tcreateStore(\n\t\t\t\tgetKheopskit$(config, ssrCookies, kheopskitStore),\n\t\t\t\tinitialValue,\n\t\t\t\tserverValue,\n\t\t\t),\n\t\t[config, ssrCookies, kheopskitStore, initialValue, serverValue],\n\t);\n\n\t// Cleanup store subscriptions when store changes or component unmounts\n\tuseEffect(() => {\n\t\treturn () => store.destroy();\n\t}, [store]);\n\n\tconst state = useSyncExternalStore(\n\t\tstore.subscribe,\n\t\tstore.getSnapshot,\n\t\tstore.getServerSnapshot,\n\t);\n\n\tconst value = useMemo(() => ({ state }), [state]);\n\n\treturn (\n\t\t<KheopskitContext.Provider value={value}>\n\t\t\t{children}\n\t\t</KheopskitContext.Provider>\n\t);\n};\n","import type { KheopskitState } from \"@kheopskit/core\";\nimport { createContext } from \"react\";\n\nexport const KheopskitContext = createContext<{\n\tstate: KheopskitState;\n} | null>(null);\n","import type { Observable, Subscription } from \"rxjs\";\n\nexport const createStore = <T>(\n\tobservable$: Observable<T>,\n\tinitialValue: T,\n\tserverValue?: T,\n) => {\n\t// Use null as sentinel to indicate we haven't received first emission yet\n\tlet latestValue: T | null = null;\n\tlet subscription: Subscription | null = null;\n\tlet subscriberCount = 0;\n\tconst listeners = new Set<(value: T) => void>();\n\n\tconst ensureSubscription = () => {\n\t\tif (!subscription || subscription.closed) {\n\t\t\tsubscription = observable$.subscribe((value) => {\n\t\t\t\tlatestValue = value;\n\t\t\t\tfor (const listener of listeners) {\n\t\t\t\t\tlistener(value);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t};\n\n\t// Start subscription immediately\n\tensureSubscription();\n\n\t// If observable emitted synchronously, use that value\n\t// Otherwise fall back to initialValue\n\tconst getSnapshot = () => latestValue ?? initialValue;\n\n\t/**\n\t * Returns the server-side snapshot for SSR hydration.\n\t * This prevents hydration mismatches by providing a consistent\n\t * value during server rendering. Must return the same value as\n\t * what the server rendered.\n\t */\n\tconst getServerSnapshot = () => serverValue ?? initialValue;\n\n\tconst subscribe = (callback: (value: T) => void) => {\n\t\tsubscriberCount++;\n\t\tlisteners.add(callback);\n\t\t// Ensure observable subscription is active when someone subscribes\n\t\tensureSubscription();\n\n\t\t// Immediately emit current value (BehaviorSubject semantics)\n\t\tcallback(getSnapshot());\n\n\t\treturn () => {\n\t\t\tsubscriberCount--;\n\t\t\tlisteners.delete(callback);\n\t\t\t// Don't close the observable subscription on unsubscribe\n\t\t\t// Let destroy() handle that when the store is truly being disposed\n\t\t};\n\t};\n\n\tconst destroy = () => {\n\t\t// Only unsubscribe if no one is listening\n\t\t// React StrictMode may call destroy and then immediately resubscribe\n\t\tif (subscriberCount === 0 && subscription) {\n\t\t\tsubscription.unsubscribe();\n\t\t\tsubscription = null;\n\t\t}\n\t};\n\n\treturn {\n\t\tgetSnapshot,\n\t\tgetServerSnapshot,\n\t\tsubscribe,\n\t\tdestroy,\n\t};\n};\n","import { useContext } from \"react\";\nimport { KheopskitContext } from \"./context\";\n\nexport const useWallets = () => {\n\tconst ctx = useContext(KheopskitContext);\n\n\t// useEffect(() => {\n\t// console.debug(\n\t// \"useWallets wallets:%s accounts:%s\",\n\t// ctx?.state.wallets.length ?? 0,\n\t// ctx?.state.accounts.length ?? 0,\n\t// );\n\t// }, [ctx?.state]);\n\n\tif (!ctx)\n\t\tthrow new Error(\"useWallets can't be used without a KheopskitProvider\");\n\n\treturn ctx.state;\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/KheopskitProvider.tsx","../src/context.ts","../src/createStore.ts","../src/useWallets.ts","../src/createKheopskit.tsx","../src/useAccounts.ts"],"sourcesContent":["import {\n\tcreateKheopskitStore,\n\tgetKheopskit$,\n\ttype KheopskitConfig,\n\ttype KheopskitState,\n\tresolveConfig,\n} from \"@kheopskit/core\";\nimport {\n\tacceptsCachedAccount,\n\tgetCachedIcon,\n\thydrateAccount,\n\thydrateWallet,\n\tsortAccounts,\n\tsortWallets,\n} from \"@kheopskit/core/internal\";\nimport {\n\ttype FC,\n\ttype PropsWithChildren,\n\tuseEffect,\n\tuseMemo,\n\tuseSyncExternalStore,\n} from \"react\";\nimport { KheopskitContext } from \"./context\";\nimport { createStore } from \"./createStore\";\n\nexport type KheopskitProviderProps = PropsWithChildren & {\n\t/**\n\t * Kheopskit configuration.\n\t *\n\t * @remarks\n\t * Must be a **referentially stable** value — define it once (module scope, a\n\t * `useMemo`, or via {@link createKheopskit}) and pass the same reference. A\n\t * new object literal on every render (`config={{ platforms: [...] }}` inline)\n\t * recreates the underlying store and re-subscribes each render.\n\t */\n\tconfig?: Partial<KheopskitConfig>;\n\t/**\n\t * Cookie string for SSR hydration.\n\t * Pass the request cookie header (e.g., from Next.js headers or TanStack Start)\n\t * to hydrate wallet state on the server.\n\t *\n\t * @remarks\n\t * This value should be stable per render to avoid unnecessary store recreation.\n\t * Compute it once in your server component or layout and pass it down.\n\t *\n\t * @example\n\t * ```tsx\n\t * // Next.js App Router\n\t * const cookieStore = await cookies();\n\t * const ssrCookies = cookieStore.getAll().map(c => `${c.name}=${c.value}`).join('; ');\n\t * return <Providers ssrCookies={ssrCookies}>{children}</Providers>\n\t * ```\n\t */\n\tssrCookies?: string;\n};\n\nexport const KheopskitProvider: FC<KheopskitProviderProps> = ({\n\tchildren,\n\tconfig,\n\tssrCookies,\n}) => {\n\tconst resolvedConfig = useMemo(() => resolveConfig(config), [config]);\n\n\t// Create a single store for both reading cached state and powering the observable\n\tconst kheopskitStore = useMemo(\n\t\t() =>\n\t\t\tcreateKheopskitStore({\n\t\t\t\tssrCookies,\n\t\t\t\tstorageKey: resolvedConfig.storageKey,\n\t\t\t}),\n\t\t[ssrCookies, resolvedConfig.storageKey],\n\t);\n\n\t// Read cached state from the store for SSR hydration\n\t// This produces wallets WITHOUT localStorage icons (Ethereum wallets have no icon)\n\t// because localStorage isn't available on server\n\tconst serverValue = useMemo<KheopskitState>(() => {\n\t\tif (ssrCookies === undefined) {\n\t\t\treturn {\n\t\t\t\twallets: [],\n\t\t\t\taccounts: [],\n\t\t\t\tconfig: resolvedConfig,\n\t\t\t\tisHydrating: true,\n\t\t\t};\n\t\t}\n\t\tconst cached = kheopskitStore.getCachedState();\n\t\treturn {\n\t\t\twallets: cached.wallets.map(hydrateWallet).sort(sortWallets),\n\t\t\taccounts: cached.accounts\n\t\t\t\t.filter((account) =>\n\t\t\t\t\tacceptsCachedAccount(account, resolvedConfig.platforms),\n\t\t\t\t)\n\t\t\t\t.map(hydrateAccount)\n\t\t\t\t.sort(sortAccounts),\n\t\t\tconfig: resolvedConfig,\n\t\t\tisHydrating: true,\n\t\t};\n\t}, [ssrCookies, kheopskitStore, resolvedConfig]);\n\n\t// Client-only initial snapshot, read straight from the client cache so a hard\n\t// reload paints the cached wallet/account list on the very first frame instead\n\t// of flashing empty until the live observable produces its first emission —\n\t// which can be asynchronous (e.g. WalletConnect's AppKit is loaded via dynamic\n\t// import, so the underlying combineLatest can't emit synchronously).\n\t//\n\t// We can't derive this from `serverValue`: without SSR cookies that stays empty\n\t// (to keep the server/client hydration markup identical), so the SPA case would\n\t// otherwise render nothing. This snapshot is only ever read on the client via\n\t// getSnapshot, so reading the cache here is safe — and getCachedIcon returns \"\"\n\t// on the server, making the icon enrichment a no-op there.\n\tconst initialValue = useMemo<KheopskitState>(() => {\n\t\tconst cached = kheopskitStore.getCachedState();\n\t\treturn {\n\t\t\twallets: cached.wallets\n\t\t\t\t.map(hydrateWallet)\n\t\t\t\t.map((wallet) => {\n\t\t\t\t\tif (wallet.icon) return wallet;\n\t\t\t\t\tconst cachedIcon = getCachedIcon(wallet.id);\n\t\t\t\t\treturn cachedIcon ? { ...wallet, icon: cachedIcon } : wallet;\n\t\t\t\t})\n\t\t\t\t.sort(sortWallets),\n\t\t\taccounts: cached.accounts\n\t\t\t\t.filter((account) =>\n\t\t\t\t\tacceptsCachedAccount(account, resolvedConfig.platforms),\n\t\t\t\t)\n\t\t\t\t.map(hydrateAccount)\n\t\t\t\t.sort(sortAccounts),\n\t\t\tconfig: resolvedConfig,\n\t\t\tisHydrating: true,\n\t\t};\n\t}, [kheopskitStore, resolvedConfig]);\n\n\tconst store = useMemo(\n\t\t() =>\n\t\t\tcreateStore(\n\t\t\t\tgetKheopskit$(config, ssrCookies, kheopskitStore),\n\t\t\t\tinitialValue,\n\t\t\t\tserverValue,\n\t\t\t),\n\t\t[config, ssrCookies, kheopskitStore, initialValue, serverValue],\n\t);\n\n\t// Cleanup store subscriptions when store changes or component unmounts\n\tuseEffect(() => {\n\t\treturn () => store.destroy();\n\t}, [store]);\n\n\tconst state = useSyncExternalStore(\n\t\tstore.subscribe,\n\t\tstore.getSnapshot,\n\t\tstore.getServerSnapshot,\n\t);\n\n\tconst value = useMemo(() => ({ state }), [state]);\n\n\treturn (\n\t\t<KheopskitContext.Provider value={value}>\n\t\t\t{children}\n\t\t</KheopskitContext.Provider>\n\t);\n};\n","import type { KheopskitState } from \"@kheopskit/core\";\nimport { createContext } from \"react\";\n\nexport const KheopskitContext = createContext<{\n\tstate: KheopskitState;\n} | null>(null);\n","import type { Observable, Subscription } from \"rxjs\";\n\nexport const createStore = <T>(\n\tobservable$: Observable<T>,\n\tinitialValue: T,\n\tserverValue?: T,\n) => {\n\t// Use null as sentinel to indicate we haven't received first emission yet\n\tlet latestValue: T | null = null;\n\tlet subscription: Subscription | null = null;\n\tlet subscriberCount = 0;\n\tconst listeners = new Set<(value: T) => void>();\n\n\tconst ensureSubscription = () => {\n\t\tif (!subscription || subscription.closed) {\n\t\t\tsubscription = observable$.subscribe((value) => {\n\t\t\t\tlatestValue = value;\n\t\t\t\tfor (const listener of listeners) {\n\t\t\t\t\tlistener(value);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t};\n\n\t// Start subscription immediately\n\tensureSubscription();\n\n\t// If observable emitted synchronously, use that value\n\t// Otherwise fall back to initialValue\n\tconst getSnapshot = () => latestValue ?? initialValue;\n\n\t/**\n\t * Returns the server-side snapshot for SSR hydration.\n\t * This prevents hydration mismatches by providing a consistent\n\t * value during server rendering. Must return the same value as\n\t * what the server rendered.\n\t */\n\tconst getServerSnapshot = () => serverValue ?? initialValue;\n\n\tconst subscribe = (callback: (value: T) => void) => {\n\t\tsubscriberCount++;\n\t\tlisteners.add(callback);\n\t\t// Ensure observable subscription is active when someone subscribes\n\t\tensureSubscription();\n\n\t\t// Immediately emit current value (BehaviorSubject semantics)\n\t\tcallback(getSnapshot());\n\n\t\treturn () => {\n\t\t\tsubscriberCount--;\n\t\t\tlisteners.delete(callback);\n\t\t\t// Don't close the observable subscription on unsubscribe\n\t\t\t// Let destroy() handle that when the store is truly being disposed\n\t\t};\n\t};\n\n\tconst destroy = () => {\n\t\t// Only unsubscribe if no one is listening\n\t\t// React StrictMode may call destroy and then immediately resubscribe\n\t\tif (subscriberCount === 0 && subscription) {\n\t\t\tsubscription.unsubscribe();\n\t\t\tsubscription = null;\n\t\t}\n\t};\n\n\treturn {\n\t\tgetSnapshot,\n\t\tgetServerSnapshot,\n\t\tsubscribe,\n\t\tdestroy,\n\t};\n};\n","import type { KheopskitPlatform, KheopskitState } from \"@kheopskit/core\";\nimport { useContext } from \"react\";\nimport { KheopskitContext } from \"./context\";\n\n/**\n * Returns the current kheopskit state (wallets, accounts, config, isHydrating).\n *\n * Pass the platform tuple as a type argument to recover SDK-precise account and\n * wallet types — `useWallets<typeof platforms>()`. React contexts can't be\n * generic, so without the argument the state is typed with the base\n * (SDK-free) wallet/account shapes.\n */\nexport const useWallets = <\n\tP extends readonly KheopskitPlatform[] = readonly KheopskitPlatform[],\n>(): KheopskitState<P> => {\n\tconst ctx = useContext(KheopskitContext);\n\n\tif (!ctx)\n\t\tthrow new Error(\"useWallets can't be used without a KheopskitProvider\");\n\n\treturn ctx.state as unknown as KheopskitState<P>;\n};\n","import type { KheopskitConfig, KheopskitPlatform } from \"@kheopskit/core\";\nimport type { FC, PropsWithChildren } from \"react\";\nimport { KheopskitProvider } from \"./KheopskitProvider\";\nimport { useWallets } from \"./useWallets\";\n\nexport type CreateKheopskitConfig<P extends readonly KheopskitPlatform[]> =\n\tOmit<Partial<KheopskitConfig<P>>, \"platforms\"> & {\n\t\t/** Platform plugins, e.g. `[polkadot(), solana()]`. Required. */\n\t\tplatforms: P;\n\t};\n\n/**\n * Binds a platform tuple once and returns a `KheopskitProvider` plus hooks\n * (`useWallets`, `useAccounts`) already typed to those platforms — so you don't\n * repeat `useWallets<typeof platforms>()` in every component.\n *\n * @example\n * ```tsx\n * // kheopskit.ts\n * export const { KheopskitProvider, useWallets, useAccounts } = createKheopskit({\n * platforms: [polkadot(), ethereum(), solana()],\n * });\n *\n * // anywhere\n * const { accounts } = useWallets(); // accounts are platform-precise, no generic\n * ```\n */\nexport const createKheopskit = <\n\tconst P extends readonly [KheopskitPlatform, ...KheopskitPlatform[]],\n>(\n\tconfig: CreateKheopskitConfig<P>,\n) => {\n\tconst Provider: FC<PropsWithChildren<{ ssrCookies?: string }>> = ({\n\t\tchildren,\n\t\tssrCookies,\n\t}) => (\n\t\t<KheopskitProvider config={config} ssrCookies={ssrCookies}>\n\t\t\t{children}\n\t\t</KheopskitProvider>\n\t);\n\tProvider.displayName = \"KheopskitProvider\";\n\n\treturn {\n\t\tKheopskitProvider: Provider,\n\t\t/** Current state, typed to the bound platform tuple. */\n\t\tuseWallets: () => useWallets<P>(),\n\t\t/** Current accounts, typed to the bound platform tuple. */\n\t\tuseAccounts: () => useWallets<P>().accounts,\n\t};\n};\n","import type { KheopskitPlatform } from \"@kheopskit/core\";\nimport { useWallets } from \"./useWallets\";\n\n/**\n * Convenience hook returning just the accounts from kheopskit state. Pass the\n * platform tuple as a type argument to recover SDK-precise account types —\n * `useAccounts<typeof platforms>()` — or use the pre-typed hook from\n * {@link createKheopskit}.\n */\nexport const useAccounts = <\n\tP extends readonly KheopskitPlatform[] = readonly KheopskitPlatform[],\n>() => useWallets<P>().accounts;\n"],"mappings":";AAAA;AAAA,EACC;AAAA,EACA;AAAA,EAGA;AAAA,OACM;AACP;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP;AAAA,EAGC;AAAA,EACA;AAAA,EACA;AAAA,OACM;;;ACpBP,SAAS,qBAAqB;AAEvB,IAAM,mBAAmB,cAEtB,IAAI;;;ACHP,IAAM,cAAc,CAC1B,aACA,cACA,gBACI;AAEJ,MAAI,cAAwB;AAC5B,MAAI,eAAoC;AACxC,MAAI,kBAAkB;AACtB,QAAM,YAAY,oBAAI,IAAwB;AAE9C,QAAM,qBAAqB,MAAM;AAChC,QAAI,CAAC,gBAAgB,aAAa,QAAQ;AACzC,qBAAe,YAAY,UAAU,CAAC,UAAU;AAC/C,sBAAc;AACd,mBAAW,YAAY,WAAW;AACjC,mBAAS,KAAK;AAAA,QACf;AAAA,MACD,CAAC;AAAA,IACF;AAAA,EACD;AAGA,qBAAmB;AAInB,QAAM,cAAc,MAAM,eAAe;AAQzC,QAAM,oBAAoB,MAAM,eAAe;AAE/C,QAAM,YAAY,CAAC,aAAiC;AACnD;AACA,cAAU,IAAI,QAAQ;AAEtB,uBAAmB;AAGnB,aAAS,YAAY,CAAC;AAEtB,WAAO,MAAM;AACZ;AACA,gBAAU,OAAO,QAAQ;AAAA,IAG1B;AAAA,EACD;AAEA,QAAM,UAAU,MAAM;AAGrB,QAAI,oBAAoB,KAAK,cAAc;AAC1C,mBAAa,YAAY;AACzB,qBAAe;AAAA,IAChB;AAAA,EACD;AAEA,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;;;AFqFE;AApGK,IAAM,oBAAgD,CAAC;AAAA,EAC7D;AAAA,EACA;AAAA,EACA;AACD,MAAM;AACL,QAAM,iBAAiB,QAAQ,MAAM,cAAc,MAAM,GAAG,CAAC,MAAM,CAAC;AAGpE,QAAM,iBAAiB;AAAA,IACtB,MACC,qBAAqB;AAAA,MACpB;AAAA,MACA,YAAY,eAAe;AAAA,IAC5B,CAAC;AAAA,IACF,CAAC,YAAY,eAAe,UAAU;AAAA,EACvC;AAKA,QAAM,cAAc,QAAwB,MAAM;AACjD,QAAI,eAAe,QAAW;AAC7B,aAAO;AAAA,QACN,SAAS,CAAC;AAAA,QACV,UAAU,CAAC;AAAA,QACX,QAAQ;AAAA,QACR,aAAa;AAAA,MACd;AAAA,IACD;AACA,UAAM,SAAS,eAAe,eAAe;AAC7C,WAAO;AAAA,MACN,SAAS,OAAO,QAAQ,IAAI,aAAa,EAAE,KAAK,WAAW;AAAA,MAC3D,UAAU,OAAO,SACf;AAAA,QAAO,CAAC,YACR,qBAAqB,SAAS,eAAe,SAAS;AAAA,MACvD,EACC,IAAI,cAAc,EAClB,KAAK,YAAY;AAAA,MACnB,QAAQ;AAAA,MACR,aAAa;AAAA,IACd;AAAA,EACD,GAAG,CAAC,YAAY,gBAAgB,cAAc,CAAC;AAa/C,QAAM,eAAe,QAAwB,MAAM;AAClD,UAAM,SAAS,eAAe,eAAe;AAC7C,WAAO;AAAA,MACN,SAAS,OAAO,QACd,IAAI,aAAa,EACjB,IAAI,CAAC,WAAW;AAChB,YAAI,OAAO,KAAM,QAAO;AACxB,cAAM,aAAa,cAAc,OAAO,EAAE;AAC1C,eAAO,aAAa,EAAE,GAAG,QAAQ,MAAM,WAAW,IAAI;AAAA,MACvD,CAAC,EACA,KAAK,WAAW;AAAA,MAClB,UAAU,OAAO,SACf;AAAA,QAAO,CAAC,YACR,qBAAqB,SAAS,eAAe,SAAS;AAAA,MACvD,EACC,IAAI,cAAc,EAClB,KAAK,YAAY;AAAA,MACnB,QAAQ;AAAA,MACR,aAAa;AAAA,IACd;AAAA,EACD,GAAG,CAAC,gBAAgB,cAAc,CAAC;AAEnC,QAAM,QAAQ;AAAA,IACb,MACC;AAAA,MACC,cAAc,QAAQ,YAAY,cAAc;AAAA,MAChD;AAAA,MACA;AAAA,IACD;AAAA,IACD,CAAC,QAAQ,YAAY,gBAAgB,cAAc,WAAW;AAAA,EAC/D;AAGA,YAAU,MAAM;AACf,WAAO,MAAM,MAAM,QAAQ;AAAA,EAC5B,GAAG,CAAC,KAAK,CAAC;AAEV,QAAM,QAAQ;AAAA,IACb,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,EACP;AAEA,QAAM,QAAQ,QAAQ,OAAO,EAAE,MAAM,IAAI,CAAC,KAAK,CAAC;AAEhD,SACC,oBAAC,iBAAiB,UAAjB,EAA0B,OACzB,UACF;AAEF;;;AG/JA,SAAS,kBAAkB;AAWpB,IAAM,aAAa,MAEA;AACzB,QAAM,MAAM,WAAW,gBAAgB;AAEvC,MAAI,CAAC;AACJ,UAAM,IAAI,MAAM,sDAAsD;AAEvE,SAAO,IAAI;AACZ;;;ACeE,gBAAAA,YAAA;AATK,IAAM,kBAAkB,CAG9B,WACI;AACJ,QAAM,WAA2D,CAAC;AAAA,IACjE;AAAA,IACA;AAAA,EACD,MACC,gBAAAA,KAAC,qBAAkB,QAAgB,YACjC,UACF;AAED,WAAS,cAAc;AAEvB,SAAO;AAAA,IACN,mBAAmB;AAAA;AAAA,IAEnB,YAAY,MAAM,WAAc;AAAA;AAAA,IAEhC,aAAa,MAAM,WAAc,EAAE;AAAA,EACpC;AACD;;;ACxCO,IAAM,cAAc,MAEpB,WAAc,EAAE;","names":["jsx"]}
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@kheopskit/react",
3
- "version": "3.0.1",
3
+ "version": "4.0.0",
4
4
  "description": "",
5
5
  "private": false,
6
6
  "files": [
7
- "dist"
7
+ "dist",
8
+ "README.md"
8
9
  ],
9
10
  "main": "./dist/index.js",
10
11
  "module": "./dist/index.mjs",
@@ -27,7 +28,7 @@
27
28
  },
28
29
  "license": "ISC",
29
30
  "peerDependencies": {
30
- "@kheopskit/core": "^1.0.1",
31
+ "@kheopskit/core": "^4.0.0",
31
32
  "react": ">=18.0.0",
32
33
  "react-dom": ">=18.0.0",
33
34
  "rxjs": ">=7.0.0"
@@ -38,7 +39,7 @@
38
39
  "react": "^19.2.7",
39
40
  "react-dom": "^19.2.7",
40
41
  "rxjs": "^7.8.2",
41
- "@kheopskit/core": "1.0.1"
42
+ "@kheopskit/core": "4.0.0"
42
43
  },
43
44
  "tsup": {
44
45
  "entry": [
@@ -56,7 +57,7 @@
56
57
  "target": "es2020"
57
58
  },
58
59
  "scripts": {
59
- "test": "echo \"Error: no test specified\" && exit 1",
60
+ "test": "vitest run --root=../.. packages/react",
60
61
  "dev": "tsup --watch",
61
62
  "build": "tsup",
62
63
  "clean": "rm -rf ./dist && rm -rf ./node_modules",