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