@spacesops/wdk-react-native-core 1.0.0-beta.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (275) hide show
  1. package/README.md +551 -0
  2. package/dist/__mocks__/react.d.ts +40 -0
  3. package/dist/__mocks__/react.js +33 -0
  4. package/dist/__mocks__/react.js.map +1 -0
  5. package/dist/__mocks__/secureStorage.d.ts +26 -0
  6. package/dist/__mocks__/secureStorage.js +80 -0
  7. package/dist/__mocks__/secureStorage.js.map +1 -0
  8. package/dist/__tests__/hooks/useBalance.test.d.ts +6 -0
  9. package/dist/__tests__/hooks/useBalance.test.js +217 -0
  10. package/dist/__tests__/hooks/useBalance.test.js.map +1 -0
  11. package/dist/__tests__/hooks/useWallet.test.d.ts +6 -0
  12. package/dist/__tests__/hooks/useWallet.test.js +105 -0
  13. package/dist/__tests__/hooks/useWallet.test.js.map +1 -0
  14. package/dist/__tests__/hooks/useWalletManager.test.d.ts +6 -0
  15. package/dist/__tests__/hooks/useWalletManager.test.js +193 -0
  16. package/dist/__tests__/hooks/useWalletManager.test.js.map +1 -0
  17. package/dist/__tests__/hooks/useWdkApp.test.d.ts +6 -0
  18. package/dist/__tests__/hooks/useWdkApp.test.js +55 -0
  19. package/dist/__tests__/hooks/useWdkApp.test.js.map +1 -0
  20. package/dist/__tests__/hooks/useWorklet.test.d.ts +6 -0
  21. package/dist/__tests__/hooks/useWorklet.test.js +168 -0
  22. package/dist/__tests__/hooks/useWorklet.test.js.map +1 -0
  23. package/dist/__tests__/provider/WdkAppProvider.test.d.ts +6 -0
  24. package/dist/__tests__/provider/WdkAppProvider.test.js +57 -0
  25. package/dist/__tests__/provider/WdkAppProvider.test.js.map +1 -0
  26. package/dist/__tests__/services/accountService.test.d.ts +6 -0
  27. package/dist/__tests__/services/accountService.test.js +216 -0
  28. package/dist/__tests__/services/accountService.test.js.map +1 -0
  29. package/dist/__tests__/services/addressService.test.d.ts +6 -0
  30. package/dist/__tests__/services/addressService.test.js +246 -0
  31. package/dist/__tests__/services/addressService.test.js.map +1 -0
  32. package/dist/__tests__/services/balanceService.test.d.ts +6 -0
  33. package/dist/__tests__/services/balanceService.test.js +333 -0
  34. package/dist/__tests__/services/balanceService.test.js.map +1 -0
  35. package/dist/__tests__/services/walletSetupService.test.d.ts +6 -0
  36. package/dist/__tests__/services/walletSetupService.test.js +304 -0
  37. package/dist/__tests__/services/walletSetupService.test.js.map +1 -0
  38. package/dist/__tests__/services/walletSwitchingService.test.d.ts +6 -0
  39. package/dist/__tests__/services/walletSwitchingService.test.js +209 -0
  40. package/dist/__tests__/services/walletSwitchingService.test.js.map +1 -0
  41. package/dist/__tests__/services/workletLifecycleService.test.d.ts +6 -0
  42. package/dist/__tests__/services/workletLifecycleService.test.js +451 -0
  43. package/dist/__tests__/services/workletLifecycleService.test.js.map +1 -0
  44. package/dist/__tests__/setup.d.ts +7 -0
  45. package/dist/__tests__/setup.js +49 -0
  46. package/dist/__tests__/setup.js.map +1 -0
  47. package/dist/__tests__/storage/mmkvStorage.test.d.ts +6 -0
  48. package/dist/__tests__/storage/mmkvStorage.test.js +213 -0
  49. package/dist/__tests__/storage/mmkvStorage.test.js.map +1 -0
  50. package/dist/__tests__/store/walletStore.test.d.ts +4 -0
  51. package/dist/__tests__/store/walletStore.test.js +86 -0
  52. package/dist/__tests__/store/walletStore.test.js.map +1 -0
  53. package/dist/__tests__/store/workletStore.test.d.ts +4 -0
  54. package/dist/__tests__/store/workletStore.test.js +255 -0
  55. package/dist/__tests__/store/workletStore.test.js.map +1 -0
  56. package/dist/__tests__/types/hrpc.test.d.ts +4 -0
  57. package/dist/__tests__/types/hrpc.test.js +99 -0
  58. package/dist/__tests__/types/hrpc.test.js.map +1 -0
  59. package/dist/__tests__/utils/balanceUtils.test.d.ts +4 -0
  60. package/dist/__tests__/utils/balanceUtils.test.js +133 -0
  61. package/dist/__tests__/utils/balanceUtils.test.js.map +1 -0
  62. package/dist/__tests__/utils/errorHandling.test.d.ts +6 -0
  63. package/dist/__tests__/utils/errorHandling.test.js +119 -0
  64. package/dist/__tests__/utils/errorHandling.test.js.map +1 -0
  65. package/dist/__tests__/utils/errorUtils.test.d.ts +4 -0
  66. package/dist/__tests__/utils/errorUtils.test.js +133 -0
  67. package/dist/__tests__/utils/errorUtils.test.js.map +1 -0
  68. package/dist/__tests__/utils/initializationState.test.d.ts +6 -0
  69. package/dist/__tests__/utils/initializationState.test.js +243 -0
  70. package/dist/__tests__/utils/initializationState.test.js.map +1 -0
  71. package/dist/__tests__/utils/jsonUtils.test.d.ts +4 -0
  72. package/dist/__tests__/utils/jsonUtils.test.js +118 -0
  73. package/dist/__tests__/utils/jsonUtils.test.js.map +1 -0
  74. package/dist/__tests__/utils/mmkvKeyManager.test.d.ts +6 -0
  75. package/dist/__tests__/utils/mmkvKeyManager.test.js +158 -0
  76. package/dist/__tests__/utils/mmkvKeyManager.test.js.map +1 -0
  77. package/dist/__tests__/utils/mnemonicUtils.test.d.ts +4 -0
  78. package/dist/__tests__/utils/mnemonicUtils.test.js +40 -0
  79. package/dist/__tests__/utils/mnemonicUtils.test.js.map +1 -0
  80. package/dist/__tests__/utils/raceConditions.test.d.ts +6 -0
  81. package/dist/__tests__/utils/raceConditions.test.js +239 -0
  82. package/dist/__tests__/utils/raceConditions.test.js.map +1 -0
  83. package/dist/__tests__/utils/result.test.d.ts +4 -0
  84. package/dist/__tests__/utils/result.test.js +66 -0
  85. package/dist/__tests__/utils/result.test.js.map +1 -0
  86. package/dist/__tests__/utils/schemas.test.d.ts +4 -0
  87. package/dist/__tests__/utils/schemas.test.js +66 -0
  88. package/dist/__tests__/utils/schemas.test.js.map +1 -0
  89. package/dist/__tests__/utils/storeHelpers.test.d.ts +4 -0
  90. package/dist/__tests__/utils/storeHelpers.test.js +227 -0
  91. package/dist/__tests__/utils/storeHelpers.test.js.map +1 -0
  92. package/dist/__tests__/utils/typeGuards.test.d.ts +4 -0
  93. package/dist/__tests__/utils/typeGuards.test.js +175 -0
  94. package/dist/__tests__/utils/typeGuards.test.js.map +1 -0
  95. package/dist/__tests__/utils/validation.test.d.ts +4 -0
  96. package/dist/__tests__/utils/validation.test.js +126 -0
  97. package/dist/__tests__/utils/validation.test.js.map +1 -0
  98. package/dist/__tests__/utils/walletUtils.test.d.ts +4 -0
  99. package/dist/__tests__/utils/walletUtils.test.js +156 -0
  100. package/dist/__tests__/utils/walletUtils.test.js.map +1 -0
  101. package/dist/hooks/useBalance.d.ts +170 -0
  102. package/dist/hooks/useBalance.js +442 -0
  103. package/dist/hooks/useBalance.js.map +1 -0
  104. package/dist/hooks/useWallet.d.ts +43 -0
  105. package/dist/hooks/useWallet.js +193 -0
  106. package/dist/hooks/useWallet.js.map +1 -0
  107. package/dist/hooks/useWalletManager.d.ts +100 -0
  108. package/dist/hooks/useWalletManager.js +526 -0
  109. package/dist/hooks/useWalletManager.js.map +1 -0
  110. package/dist/hooks/useWdkApp.d.ts +66 -0
  111. package/dist/hooks/useWdkApp.js +74 -0
  112. package/dist/hooks/useWdkApp.js.map +1 -0
  113. package/dist/hooks/useWorklet.d.ts +66 -0
  114. package/dist/hooks/useWorklet.js +42 -0
  115. package/dist/hooks/useWorklet.js.map +1 -0
  116. package/dist/index.d.ts +33 -0
  117. package/dist/index.js +36 -0
  118. package/dist/index.js.map +1 -0
  119. package/dist/provider/WdkAppProvider.d.ts +112 -0
  120. package/dist/provider/WdkAppProvider.js +558 -0
  121. package/dist/provider/WdkAppProvider.js.map +1 -0
  122. package/dist/services/accountService.d.ts +52 -0
  123. package/dist/services/accountService.js +119 -0
  124. package/dist/services/accountService.js.map +1 -0
  125. package/dist/services/addressService.d.ts +22 -0
  126. package/dist/services/addressService.js +110 -0
  127. package/dist/services/addressService.js.map +1 -0
  128. package/dist/services/balanceService.d.ts +114 -0
  129. package/dist/services/balanceService.js +208 -0
  130. package/dist/services/balanceService.js.map +1 -0
  131. package/dist/services/walletSetupService.d.ts +217 -0
  132. package/dist/services/walletSetupService.js +535 -0
  133. package/dist/services/walletSetupService.js.map +1 -0
  134. package/dist/services/walletSwitchingService.d.ts +48 -0
  135. package/dist/services/walletSwitchingService.js +143 -0
  136. package/dist/services/walletSwitchingService.js.map +1 -0
  137. package/dist/services/workletLifecycleService.d.ts +117 -0
  138. package/dist/services/workletLifecycleService.js +433 -0
  139. package/dist/services/workletLifecycleService.js.map +1 -0
  140. package/dist/storage/mmkvStorage.d.ts +44 -0
  141. package/dist/storage/mmkvStorage.js +348 -0
  142. package/dist/storage/mmkvStorage.js.map +1 -0
  143. package/dist/store/walletStore.d.ts +130 -0
  144. package/dist/store/walletStore.js +204 -0
  145. package/dist/store/walletStore.js.map +1 -0
  146. package/dist/store/workletStore.d.ts +121 -0
  147. package/dist/store/workletStore.js +256 -0
  148. package/dist/store/workletStore.js.map +1 -0
  149. package/dist/types/hrpc.d.ts +60 -0
  150. package/dist/types/hrpc.js +26 -0
  151. package/dist/types/hrpc.js.map +1 -0
  152. package/dist/types.d.ts +168 -0
  153. package/dist/types.js +7 -0
  154. package/dist/types.js.map +1 -0
  155. package/dist/utils/balanceUtils.d.ts +30 -0
  156. package/dist/utils/balanceUtils.js +86 -0
  157. package/dist/utils/balanceUtils.js.map +1 -0
  158. package/dist/utils/constants.d.ts +66 -0
  159. package/dist/utils/constants.js +73 -0
  160. package/dist/utils/constants.js.map +1 -0
  161. package/dist/utils/errorHandling.d.ts +47 -0
  162. package/dist/utils/errorHandling.js +54 -0
  163. package/dist/utils/errorHandling.js.map +1 -0
  164. package/dist/utils/errorUtils.d.ts +89 -0
  165. package/dist/utils/errorUtils.js +264 -0
  166. package/dist/utils/errorUtils.js.map +1 -0
  167. package/dist/utils/initializationState.d.ts +129 -0
  168. package/dist/utils/initializationState.js +220 -0
  169. package/dist/utils/initializationState.js.map +1 -0
  170. package/dist/utils/jsonUtils.d.ts +21 -0
  171. package/dist/utils/jsonUtils.js +67 -0
  172. package/dist/utils/jsonUtils.js.map +1 -0
  173. package/dist/utils/logger.d.ts +27 -0
  174. package/dist/utils/logger.js +50 -0
  175. package/dist/utils/logger.js.map +1 -0
  176. package/dist/utils/mmkvKeyManager.d.ts +57 -0
  177. package/dist/utils/mmkvKeyManager.js +214 -0
  178. package/dist/utils/mmkvKeyManager.js.map +1 -0
  179. package/dist/utils/mnemonicUtils.d.ts +17 -0
  180. package/dist/utils/mnemonicUtils.js +28 -0
  181. package/dist/utils/mnemonicUtils.js.map +1 -0
  182. package/dist/utils/operationMutex.d.ts +69 -0
  183. package/dist/utils/operationMutex.js +150 -0
  184. package/dist/utils/operationMutex.js.map +1 -0
  185. package/dist/utils/result.d.ts +34 -0
  186. package/dist/utils/result.js +43 -0
  187. package/dist/utils/result.js.map +1 -0
  188. package/dist/utils/schemas.d.ts +365 -0
  189. package/dist/utils/schemas.js +152 -0
  190. package/dist/utils/schemas.js.map +1 -0
  191. package/dist/utils/storeHelpers.d.ts +112 -0
  192. package/dist/utils/storeHelpers.js +198 -0
  193. package/dist/utils/storeHelpers.js.map +1 -0
  194. package/dist/utils/typeGuards.d.ts +70 -0
  195. package/dist/utils/typeGuards.js +95 -0
  196. package/dist/utils/typeGuards.js.map +1 -0
  197. package/dist/utils/validation.d.ts +53 -0
  198. package/dist/utils/validation.js +152 -0
  199. package/dist/utils/validation.js.map +1 -0
  200. package/dist/utils/walletStateHelpers.d.ts +62 -0
  201. package/dist/utils/walletStateHelpers.js +81 -0
  202. package/dist/utils/walletStateHelpers.js.map +1 -0
  203. package/dist/utils/walletUtils.d.ts +38 -0
  204. package/dist/utils/walletUtils.js +87 -0
  205. package/dist/utils/walletUtils.js.map +1 -0
  206. package/package.json +79 -0
  207. package/src/__mocks__/react.ts +34 -0
  208. package/src/__mocks__/secureStorage.ts +87 -0
  209. package/src/__tests__/hooks/useBalance.test.ts +286 -0
  210. package/src/__tests__/hooks/useWallet.test.ts +122 -0
  211. package/src/__tests__/hooks/useWalletManager.test.ts +271 -0
  212. package/src/__tests__/hooks/useWdkApp.test.tsx +59 -0
  213. package/src/__tests__/hooks/useWorklet.test.ts +195 -0
  214. package/src/__tests__/provider/WdkAppProvider.test.tsx +67 -0
  215. package/src/__tests__/services/accountService.test.ts +337 -0
  216. package/src/__tests__/services/addressService.test.ts +312 -0
  217. package/src/__tests__/services/balanceService.test.ts +411 -0
  218. package/src/__tests__/services/walletSetupService.test.ts +463 -0
  219. package/src/__tests__/services/walletSwitchingService.test.ts +258 -0
  220. package/src/__tests__/services/workletLifecycleService.test.ts +514 -0
  221. package/src/__tests__/setup.ts +54 -0
  222. package/src/__tests__/storage/mmkvStorage.test.ts +325 -0
  223. package/src/__tests__/store/walletStore.test.ts +101 -0
  224. package/src/__tests__/store/workletStore.test.ts +329 -0
  225. package/src/__tests__/types/hrpc.test.ts +125 -0
  226. package/src/__tests__/utils/balanceUtils.test.ts +151 -0
  227. package/src/__tests__/utils/errorHandling.test.ts +147 -0
  228. package/src/__tests__/utils/errorUtils.test.ts +170 -0
  229. package/src/__tests__/utils/initializationState.test.ts +337 -0
  230. package/src/__tests__/utils/jsonUtils.test.ts +131 -0
  231. package/src/__tests__/utils/mmkvKeyManager.test.ts +205 -0
  232. package/src/__tests__/utils/mnemonicUtils.test.ts +48 -0
  233. package/src/__tests__/utils/raceConditions.test.ts +292 -0
  234. package/src/__tests__/utils/result.test.ts +73 -0
  235. package/src/__tests__/utils/schemas.test.ts +82 -0
  236. package/src/__tests__/utils/storeHelpers.test.ts +323 -0
  237. package/src/__tests__/utils/typeGuards.test.ts +209 -0
  238. package/src/__tests__/utils/validation.test.ts +152 -0
  239. package/src/__tests__/utils/walletUtils.test.ts +195 -0
  240. package/src/hooks/useBalance.ts +597 -0
  241. package/src/hooks/useWallet.ts +288 -0
  242. package/src/hooks/useWalletManager.ts +654 -0
  243. package/src/hooks/useWdkApp.ts +78 -0
  244. package/src/hooks/useWorklet.ts +109 -0
  245. package/src/index.ts +112 -0
  246. package/src/provider/WdkAppProvider.tsx +764 -0
  247. package/src/services/accountService.ts +134 -0
  248. package/src/services/addressService.ts +125 -0
  249. package/src/services/balanceService.ts +291 -0
  250. package/src/services/walletSetupService.ts +695 -0
  251. package/src/services/walletSwitchingService.ts +157 -0
  252. package/src/services/workletLifecycleService.ts +561 -0
  253. package/src/storage/mmkvStorage.ts +417 -0
  254. package/src/store/walletStore.ts +301 -0
  255. package/src/store/workletStore.ts +340 -0
  256. package/src/types/hrpc.ts +78 -0
  257. package/src/types.ts +188 -0
  258. package/src/utils/balanceUtils.ts +96 -0
  259. package/src/utils/constants.ts +84 -0
  260. package/src/utils/errorHandling.md +163 -0
  261. package/src/utils/errorHandling.ts +61 -0
  262. package/src/utils/errorUtils.ts +300 -0
  263. package/src/utils/initializationState.ts +242 -0
  264. package/src/utils/jsonUtils.ts +75 -0
  265. package/src/utils/logger.ts +54 -0
  266. package/src/utils/mmkvKeyManager.ts +256 -0
  267. package/src/utils/mnemonicUtils.ts +35 -0
  268. package/src/utils/operationMutex.ts +181 -0
  269. package/src/utils/result.ts +57 -0
  270. package/src/utils/schemas.ts +190 -0
  271. package/src/utils/storeHelpers.ts +236 -0
  272. package/src/utils/typeGuards.ts +129 -0
  273. package/src/utils/validation.ts +174 -0
  274. package/src/utils/walletStateHelpers.ts +104 -0
  275. package/src/utils/walletUtils.ts +107 -0
@@ -0,0 +1,301 @@
1
+ /**
2
+ * Wallet Store - Source of Truth for Wallet Data
3
+ *
4
+ * IMPORTANT: This is the ONLY place where addresses and balances are actually stored.
5
+ *
6
+ * ## Store Boundaries
7
+ *
8
+ * **walletStore** (this file):
9
+ * - Wallet addresses: { [walletId]: { [network]: { [accountIndex]: address } } }
10
+ * - Wallet balances: { [walletId]: { [network]: { [accountIndex]: { [tokenAddress]: balance } } } }
11
+ * - Address loading states: { [walletId]: { [network-accountIndex]: boolean } }
12
+ * - Balance loading states: { [walletId]: { [network-accountIndex-tokenAddress]: boolean } }
13
+ * - Last balance update timestamps: { [walletId]: { [network]: { [accountIndex]: timestamp } } }
14
+ * - Account list: { [walletId]: Array of account info }
15
+ * - Wallet list: Array of wallet info (multiple wallets)
16
+ * - Active wallet ID: Currently active wallet identifier
17
+ *
18
+ * Note: Loading states for wallet list operations are managed locally in hooks
19
+ * (useWalletManager) since they're ephemeral and only used within those hooks.
20
+ *
21
+ * **workletStore** (workletStore.ts):
22
+ * - Worklet lifecycle state (isWorkletStarted, isInitialized, etc.)
23
+ * - Worklet runtime instances (worklet, hrpc, ipc)
24
+ * - Worklet configuration
25
+ *
26
+ * ## Separation of Concerns
27
+ *
28
+ * - **walletStore**: Manages wallet data (addresses, balances) - derived/computed from worklet
29
+ * - **workletStore**: Manages worklet runtime and lifecycle
30
+ *
31
+ * These stores are intentionally separate to:
32
+ * 1. Prevent cross-contamination of lifecycle and data concerns
33
+ * 2. Allow independent persistence strategies
34
+ * 3. Enable clear boundaries for testing and debugging
35
+ *
36
+ * ## Important Notes
37
+ *
38
+ * - Addresses: Stored in Zustand (derived/computed state, deterministic, no refetching needed)
39
+ * - Balances: Stored in Zustand (single source of truth), use TanStack Query via useBalance() hook for fetching
40
+ * - Zustand is the single source of truth for balances
41
+ * - TanStack Query reads from Zustand (initialData) and updates Zustand after fetch
42
+ * - No sync logic needed - TanStack Query directly updates the source of truth
43
+ * - NEVER store worklet lifecycle state in walletStore
44
+ * - NEVER store worklet runtime instances in walletStore
45
+ * - All operations are handled by focused services (AddressService, BalanceService), not the store itself
46
+ */
47
+
48
+ // External packages
49
+ import { create } from 'zustand'
50
+ import { persist, createJSONStorage, devtools } from 'zustand/middleware'
51
+
52
+ // Local imports
53
+ import type {
54
+ WalletAddressesByWallet,
55
+ WalletBalancesByWallet,
56
+ BalanceLoadingStates,
57
+ } from '../types'
58
+ import { createMMKVStorageAdapter } from '../storage/mmkvStorage'
59
+ import { log, logError } from '../utils/logger'
60
+
61
+ export interface WalletLoadingStates {
62
+ [key: string]: boolean
63
+ }
64
+
65
+ export interface AccountInfo {
66
+ /** Account index (0-based) */
67
+ accountIndex: number
68
+ /** Account address for each network */
69
+ addresses: Record<string, string>
70
+ }
71
+
72
+ export interface WalletInfo {
73
+ /** Wallet identifier (e.g., user email) */
74
+ identifier: string
75
+ /** Whether wallet exists in secure storage */
76
+ exists: boolean
77
+ /** Whether this wallet is currently active/initialized */
78
+ isActive: boolean
79
+ }
80
+
81
+ /**
82
+ * Wallet loading state - tracks the lifecycle of loading a specific wallet
83
+ * This is the single source of truth for wallet loading state
84
+ */
85
+ export type WalletLoadingState =
86
+ | { type: 'not_loaded' }
87
+ | { type: 'checking'; identifier: string }
88
+ | { type: 'loading'; identifier: string; walletExists: boolean }
89
+ | { type: 'ready'; identifier: string }
90
+ | { type: 'error'; identifier: string | null; error: Error }
91
+
92
+ export interface WalletState {
93
+ // SOURCE OF TRUTH - addresses stored ONLY here (per-wallet)
94
+ addresses: WalletAddressesByWallet // walletId -> addresses
95
+ walletLoading: Record<string, WalletLoadingStates> // walletId -> loading states
96
+ // SOURCE OF TRUTH - balances stored ONLY here (per-wallet)
97
+ balances: WalletBalancesByWallet // walletId -> balances
98
+ // Maps walletId -> "network-accountIndex-tokenAddress" -> boolean
99
+ balanceLoading: Record<string, BalanceLoadingStates> // walletId -> loading states
100
+ lastBalanceUpdate: Record<string, Record<string, Record<number, number>>> // walletId -> network -> accountIndex -> timestamp
101
+ // Account list management (per-wallet)
102
+ accountList: Record<string, AccountInfo[]> // walletId -> account list
103
+ // Wallet list management
104
+ walletList: WalletInfo[]
105
+ activeWalletId: string | null
106
+ // SOURCE OF TRUTH - wallet loading state (replaces React reducer)
107
+ walletLoadingState: WalletLoadingState
108
+ // Operation mutex - prevents concurrent wallet operations
109
+ isOperationInProgress: boolean
110
+ currentOperation: string | null // Description of current operation
111
+ }
112
+
113
+ export type WalletStore = WalletState
114
+
115
+ type WalletStoreInstance = ReturnType<ReturnType<typeof create<WalletStore>>>
116
+
117
+ const initialState: WalletState = {
118
+ addresses: {}, // walletId -> addresses
119
+ walletLoading: {}, // walletId -> loading states
120
+ balances: {}, // walletId -> balances
121
+ balanceLoading: {}, // walletId -> loading states
122
+ lastBalanceUpdate: {}, // walletId -> network -> accountIndex -> timestamp
123
+ accountList: {}, // walletId -> account list
124
+ walletList: [],
125
+ activeWalletId: null,
126
+ walletLoadingState: { type: 'not_loaded' },
127
+ isOperationInProgress: false,
128
+ currentOperation: null,
129
+ }
130
+
131
+ const defaultStorageAdapter = createMMKVStorageAdapter()
132
+
133
+ let walletStoreInstance: WalletStoreInstance | null = null
134
+
135
+ /**
136
+ * Creates singleton wallet store instance.
137
+ * All operations are handled by focused services (AddressService, BalanceService), not the store itself.
138
+ */
139
+ export function createWalletStore(): WalletStoreInstance {
140
+ if (walletStoreInstance) {
141
+ return walletStoreInstance
142
+ }
143
+
144
+ const store = create<WalletStore>()(
145
+ devtools(
146
+ persist(
147
+ (set, get) => ({
148
+ ...initialState,
149
+ }),
150
+ {
151
+ name: 'wallet-storage',
152
+ storage: createJSONStorage(() => defaultStorageAdapter),
153
+ partialize: (state: WalletState) => ({
154
+ addresses: state.addresses,
155
+ balances: state.balances,
156
+ balanceLoading: {},
157
+ lastBalanceUpdate: state.lastBalanceUpdate,
158
+ accountList: state.accountList,
159
+ walletList: state.walletList,
160
+ activeWalletId: state.activeWalletId,
161
+ // Don't persist loading state or operation mutex - these are runtime-only
162
+ }),
163
+ onRehydrateStorage: () => {
164
+ return (state: WalletState | undefined) => {
165
+ if (state) {
166
+ log('🔄 Rehydrating wallet state - resetting loading states')
167
+ state.walletLoading = {}
168
+ state.balanceLoading = {}
169
+ // Reset runtime-only state
170
+ state.walletLoadingState = { type: 'not_loaded' }
171
+ state.isOperationInProgress = false
172
+ state.currentOperation = null
173
+ }
174
+ }
175
+ },
176
+ }
177
+ ),
178
+ { name: 'WalletStore' }
179
+ )
180
+ )
181
+
182
+ walletStoreInstance = store
183
+ return store
184
+ }
185
+
186
+ export function getWalletStore() {
187
+ return createWalletStore()
188
+ }
189
+
190
+ /**
191
+ * Wallet state actions - helper functions for updating wallet loading state
192
+ * These provide a clean API for state transitions with validation
193
+ */
194
+
195
+ /**
196
+ * Validate state transition
197
+ * Returns true if transition is valid, false otherwise
198
+ */
199
+ function isValidStateTransition(
200
+ current: WalletLoadingState,
201
+ next: WalletLoadingState['type']
202
+ ): boolean {
203
+ // Allow reset from any state
204
+ if (next === 'not_loaded') return true
205
+
206
+ // Allow error from any state
207
+ if (next === 'error') return true
208
+
209
+ // After early returns, TypeScript narrows the type, but we need to check all transitions
210
+ // Use type assertion to tell TypeScript that 'not_loaded' and 'error' are still possible
211
+ // (even though they're handled by early returns, they're valid from all states)
212
+ const nextType = next as WalletLoadingState['type']
213
+
214
+ switch (current.type) {
215
+ case 'not_loaded':
216
+ return nextType === 'checking' || nextType === 'loading'
217
+ case 'checking':
218
+ return nextType === 'loading' || nextType === 'error'
219
+ case 'loading':
220
+ return nextType === 'ready' || nextType === 'error'
221
+ case 'ready':
222
+ return nextType === 'not_loaded' || nextType === 'loading' || nextType === 'checking'
223
+ case 'error':
224
+ return nextType === 'not_loaded' || nextType === 'checking' || nextType === 'loading'
225
+ default:
226
+ return false
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Update wallet loading state with validation
232
+ * Throws error if transition is invalid
233
+ * Logs state transitions for debugging
234
+ */
235
+ export function updateWalletLoadingState(
236
+ state: WalletState,
237
+ newState: WalletLoadingState
238
+ ): Partial<WalletState> {
239
+ // Log state transition for debugging
240
+ if (state.walletLoadingState.type !== newState.type) {
241
+ log(`[WalletState] Transition: ${state.walletLoadingState.type} -> ${newState.type}`, {
242
+ from: state.walletLoadingState,
243
+ to: newState,
244
+ })
245
+ }
246
+
247
+ if (!isValidStateTransition(state.walletLoadingState, newState.type)) {
248
+ const error = new Error(
249
+ `Invalid state transition from ${state.walletLoadingState.type} to ${newState.type}`
250
+ )
251
+ logError(`⚠️ Invalid state transition: ${error.message}`, {
252
+ currentState: state.walletLoadingState,
253
+ attemptedState: newState,
254
+ })
255
+ // In development, throw error. In production, log and allow transition
256
+ // Note: __DEV__ is a React Native global, may not be available in all environments
257
+ if (typeof __DEV__ !== 'undefined' && __DEV__) {
258
+ throw error
259
+ }
260
+ }
261
+
262
+ return { walletLoadingState: newState }
263
+ }
264
+
265
+ /**
266
+ * Get wallet identifier from loading state
267
+ */
268
+ export function getWalletIdFromLoadingState(state: WalletLoadingState): string | null {
269
+ switch (state.type) {
270
+ case 'checking':
271
+ case 'loading':
272
+ case 'ready':
273
+ return state.identifier
274
+ case 'error':
275
+ return state.identifier
276
+ case 'not_loaded':
277
+ return null
278
+ }
279
+ }
280
+
281
+ /**
282
+ * Check if wallet is in a loading state
283
+ */
284
+ export function isWalletLoadingState(state: WalletLoadingState): boolean {
285
+ return state.type === 'checking' || state.type === 'loading'
286
+ }
287
+
288
+ /**
289
+ * Check if wallet is ready
290
+ */
291
+ export function isWalletReadyState(state: WalletLoadingState): boolean {
292
+ return state.type === 'ready'
293
+ }
294
+
295
+ /**
296
+ * Check if wallet is in error state
297
+ */
298
+ export function isWalletErrorState(state: WalletLoadingState): boolean {
299
+ return state.type === 'error'
300
+ }
301
+
@@ -0,0 +1,340 @@
1
+ /**
2
+ * Worklet Store - Source of Truth for Worklet Lifecycle
3
+ *
4
+ * This store manages worklet lifecycle state (initialization, configuration, runtime instances).
5
+ *
6
+ * ## Store Boundaries
7
+ *
8
+ * **workletStore** (this file):
9
+ * - Worklet lifecycle state (isWorkletStarted, isInitialized, isLoading)
10
+ * - Worklet runtime instances (worklet, hrpc, ipc)
11
+ * - Worklet configuration (networkConfigs)
12
+ * - Worklet initialization results (workletStartResult, wdkInitResult)
13
+ * - Encrypted credentials in memory (encryptedSeed, encryptionKey) - for active wallet
14
+ * - Multi-wallet credential cache (credentialsCache) - for multiple wallets
15
+ *
16
+ * **walletStore** (walletStore.ts):
17
+ * - Wallet data (addresses, balances)
18
+ * - Wallet loading states
19
+ * - Balance loading states
20
+ * - Last balance update timestamps
21
+ *
22
+ * ## Separation of Concerns
23
+ *
24
+ * - **workletStore**: Manages the worklet runtime and its lifecycle
25
+ * - **walletStore**: Manages wallet data derived from the worklet
26
+ *
27
+ * These stores are intentionally separate to:
28
+ * 1. Prevent cross-contamination of lifecycle and data concerns
29
+ * 2. Allow independent persistence strategies
30
+ * 3. Enable clear boundaries for testing and debugging
31
+ *
32
+ * ## Important Notes
33
+ *
34
+ * - NEVER store wallet data (addresses, balances) in workletStore
35
+ * - NEVER store worklet lifecycle state in walletStore
36
+ * - All worklet state is runtime-only - state resets completely on app restart
37
+ * - Worklets must be recreated when the app restarts
38
+ * - Encrypted credentials are runtime-only (loaded from secure storage when needed)
39
+ * - All operations are handled by WorkletLifecycleService, not the store itself
40
+ */
41
+
42
+ // External packages
43
+ import { create } from 'zustand'
44
+ import { devtools } from 'zustand/middleware'
45
+ import { Worklet } from '@spacesops/react-native-bare-kit'
46
+ import { HRPC } from '@spacesops/pear-wrk-wdk'
47
+ import type { WorkletStartResponse } from '@spacesops/pear-wrk-wdk/types/rpc'
48
+
49
+ // Local imports
50
+ import type {
51
+ NetworkConfigs,
52
+ } from '../types'
53
+ import { log } from '../utils/logger'
54
+
55
+ /**
56
+ * Maximum number of credentials to cache before evicting least recently used
57
+ * This prevents unbounded memory growth while maintaining performance
58
+ * Recommended: 10-20 wallets for typical use cases
59
+ *
60
+ * Cache Management:
61
+ * - Size Limit: When cache exceeds MAX_CREDENTIALS_CACHE_SIZE, LRU entries are evicted
62
+ * - TTL (Time To Live): Each credential has an expiration timestamp (default: 5 minutes)
63
+ * - Automatic Expiration: Expired credentials are removed on access
64
+ * - LRU Eviction: Least recently used credentials are evicted when size limit is reached
65
+ *
66
+ * These safeguards ensure:
67
+ * 1. Memory usage remains bounded
68
+ * 2. Credentials don't persist indefinitely in memory
69
+ * 3. Frequently used wallets remain cached for better UX
70
+ */
71
+ const MAX_CREDENTIALS_CACHE_SIZE = 15
72
+
73
+ /**
74
+ * Cached credentials interface for multi-wallet support
75
+ * Credentials are cached in workletStore to avoid repeated biometric prompts
76
+ */
77
+ export interface CachedCredentials {
78
+ encryptionKey?: string
79
+ encryptedSeed?: string
80
+ encryptedEntropy?: string
81
+ expiresAt: number // TTL timestamp
82
+ }
83
+
84
+ /**
85
+ * Access tracking for LRU eviction
86
+ * Tracks when each credential was last accessed
87
+ */
88
+ const credentialsAccessOrder = new Map<string, number>()
89
+ let credentialsAccessCounter = 0
90
+
91
+ export interface WorkletState {
92
+ worklet: Worklet | null
93
+ hrpc: HRPC | null
94
+ ipc: unknown | null
95
+ isWorkletStarted: boolean
96
+ isInitialized: boolean
97
+ isLoading: boolean
98
+ error: string | null
99
+ encryptedSeed: string | null
100
+ encryptionKey: string | null
101
+ // seedPhrase is never stored - we only use encrypted approach
102
+ // encryptedEntropy is stored in secure storage but not in runtime state
103
+ // It's only needed when retrieving mnemonic, so it's loaded from secure storage on demand
104
+ networkConfigs: NetworkConfigs | null
105
+ workletStartResult: WorkletStartResponse | null
106
+ wdkInitResult: { status?: string | null } | null
107
+ // Multi-wallet credential cache (replaces static Map in WalletSetupService)
108
+ // Each entry has a TTL (expiresAt timestamp) and is automatically evicted when:
109
+ // 1. Cache size exceeds MAX_CREDENTIALS_CACHE_SIZE (LRU eviction)
110
+ // 2. Credential is accessed after expiration (automatic cleanup)
111
+ credentialsCache: Record<string, CachedCredentials>
112
+ // Cache TTL configuration (time to live in milliseconds)
113
+ // Default: 5 minutes - credentials expire after this time
114
+ credentialsCacheTTL: number
115
+ }
116
+
117
+ export type WorkletStore = WorkletState
118
+
119
+ type WorkletStoreInstance = ReturnType<ReturnType<typeof create<WorkletStore>>>
120
+
121
+ const initialState: WorkletState = {
122
+ worklet: null,
123
+ hrpc: null,
124
+ ipc: null,
125
+ isWorkletStarted: false,
126
+ isInitialized: false,
127
+ isLoading: false,
128
+ error: null,
129
+ encryptedSeed: null,
130
+ encryptionKey: null,
131
+ networkConfigs: null,
132
+ workletStartResult: null,
133
+ wdkInitResult: null,
134
+ credentialsCache: {},
135
+ credentialsCacheTTL: 5 * 60 * 1000, // 5 minutes
136
+ }
137
+
138
+ let workletStoreInstance: WorkletStoreInstance | null = null
139
+
140
+ /**
141
+ * Creates singleton worklet store instance.
142
+ *
143
+ * This store is runtime-only - all state resets on app restart.
144
+ * All operations are handled by WorkletLifecycleService, not the store itself.
145
+ */
146
+ export function createWorkletStore(): WorkletStoreInstance {
147
+ if (workletStoreInstance) {
148
+ return workletStoreInstance
149
+ }
150
+
151
+ const store = create<WorkletStore>()(
152
+ devtools(
153
+ () => ({
154
+ ...initialState,
155
+ }),
156
+ { name: 'WorkletStore' }
157
+ )
158
+ )
159
+
160
+ workletStoreInstance = store
161
+ return store
162
+ }
163
+
164
+ export function getWorkletStore() {
165
+ return createWorkletStore()
166
+ }
167
+
168
+ /**
169
+ * Evict least recently used credentials from cache when limit is reached
170
+ * Logs eviction for monitoring
171
+ */
172
+ function evictLRUCredentials(): void {
173
+ const store = getWorkletStore()
174
+ const state = store.getState()
175
+ const cacheSize = Object.keys(state.credentialsCache).length
176
+
177
+ if (cacheSize < MAX_CREDENTIALS_CACHE_SIZE) {
178
+ return
179
+ }
180
+
181
+ // Find the least recently used credential
182
+ let oldestIdentifier: string | null = null
183
+ let oldestAccess = Infinity
184
+
185
+ for (const [identifier, accessTime] of credentialsAccessOrder.entries()) {
186
+ if (accessTime < oldestAccess && state.credentialsCache[identifier]) {
187
+ oldestAccess = accessTime
188
+ oldestIdentifier = identifier
189
+ }
190
+ }
191
+
192
+ // Remove the least recently used credential
193
+ if (oldestIdentifier !== null) {
194
+ const { [oldestIdentifier]: _, ...rest } = state.credentialsCache
195
+ credentialsAccessOrder.delete(oldestIdentifier)
196
+ store.setState({ credentialsCache: rest })
197
+ log(`[WorkletStore] Evicted LRU credentials cache entry: ${oldestIdentifier} (cache size: ${cacheSize}/${MAX_CREDENTIALS_CACHE_SIZE})`)
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Log cache size if it exceeds warning threshold (50% of max)
203
+ * Useful for monitoring cache growth in production
204
+ */
205
+ function logCacheSizeIfNeeded(cacheSize: number): void {
206
+ const warningThreshold = Math.ceil(MAX_CREDENTIALS_CACHE_SIZE * 0.5)
207
+ if (cacheSize >= warningThreshold) {
208
+ log(`[WorkletStore] Credentials cache size: ${cacheSize}/${MAX_CREDENTIALS_CACHE_SIZE} (${Math.round(cacheSize / MAX_CREDENTIALS_CACHE_SIZE * 100)}% full)`)
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Get cached credentials for a wallet identifier
214
+ * Returns null if not cached or expired
215
+ * Updates access time for LRU tracking
216
+ */
217
+ export function getCachedCredentials(identifier: string): CachedCredentials | null {
218
+ const store = getWorkletStore()
219
+ const state = store.getState()
220
+ const cached = state.credentialsCache[identifier]
221
+
222
+ if (!cached) return null
223
+
224
+ // Check expiration
225
+ if (Date.now() > cached.expiresAt) {
226
+ // Remove expired entry
227
+ const { [identifier]: _, ...rest } = state.credentialsCache
228
+ credentialsAccessOrder.delete(identifier)
229
+ store.setState({
230
+ credentialsCache: rest,
231
+ })
232
+ return null
233
+ }
234
+
235
+ // Update access time for LRU tracking
236
+ credentialsAccessCounter++
237
+ credentialsAccessOrder.set(identifier, credentialsAccessCounter)
238
+
239
+ return cached
240
+ }
241
+
242
+ /**
243
+ * Set cached credentials for a wallet identifier
244
+ * Evicts LRU entries if cache size limit is reached
245
+ * Logs cache size for monitoring
246
+ */
247
+ export function setCachedCredentials(
248
+ identifier: string,
249
+ credentials: Partial<CachedCredentials>
250
+ ): void {
251
+ const store = getWorkletStore()
252
+ const state = store.getState()
253
+ const existing = state.credentialsCache[identifier] || {}
254
+
255
+ // Evict LRU credentials if needed before adding new entry
256
+ // Check if we're adding a new entry (not just updating existing)
257
+ const isNewEntry = !state.credentialsCache[identifier]
258
+ if (isNewEntry) {
259
+ evictLRUCredentials()
260
+ }
261
+
262
+ // Update access time for LRU tracking
263
+ credentialsAccessCounter++
264
+ credentialsAccessOrder.set(identifier, credentialsAccessCounter)
265
+
266
+ store.setState({
267
+ credentialsCache: {
268
+ ...state.credentialsCache,
269
+ [identifier]: {
270
+ ...existing,
271
+ ...credentials,
272
+ expiresAt: Date.now() + state.credentialsCacheTTL,
273
+ }
274
+ }
275
+ })
276
+
277
+ // Log cache size for monitoring (after state update)
278
+ const newCacheSize = Object.keys(store.getState().credentialsCache).length
279
+ logCacheSizeIfNeeded(newCacheSize)
280
+ }
281
+
282
+ /**
283
+ * Clear credentials cache for a specific wallet or all wallets
284
+ * Also clears access tracking
285
+ */
286
+ export function clearCredentialsCache(identifier?: string): void {
287
+ const store = getWorkletStore()
288
+ const state = store.getState()
289
+
290
+ if (identifier) {
291
+ // Clear specific wallet
292
+ const { [identifier]: _, ...rest } = state.credentialsCache
293
+ credentialsAccessOrder.delete(identifier)
294
+ store.setState({ credentialsCache: rest })
295
+ } else {
296
+ // Clear all
297
+ credentialsAccessOrder.clear()
298
+ credentialsAccessCounter = 0
299
+ store.setState({ credentialsCache: {} })
300
+ }
301
+ }
302
+
303
+ /**
304
+ * Clear all sensitive data from memory (active wallet credentials + cache)
305
+ * This should be called when sensitive data is no longer needed
306
+ * to minimize exposure in memory dumps or debugging
307
+ *
308
+ * Note: In JavaScript, we cannot overwrite memory. Setting to null and clearing
309
+ * references allows garbage collection, but the old values may remain in memory
310
+ * until GC runs. This is a JavaScript limitation.
311
+ */
312
+ export function clearAllSensitiveData(): void {
313
+ const store = getWorkletStore()
314
+ credentialsAccessOrder.clear()
315
+ credentialsAccessCounter = 0
316
+ store.setState({
317
+ encryptedSeed: null,
318
+ encryptionKey: null,
319
+ credentialsCache: {},
320
+ })
321
+ }
322
+
323
+ /**
324
+ * Reset the worklet store instance (useful for testing)
325
+ * Also resets access tracking
326
+ */
327
+ export function resetWorkletStore(): void {
328
+ credentialsAccessOrder.clear()
329
+ credentialsAccessCounter = 0
330
+ workletStoreInstance = null
331
+ }
332
+
333
+ /**
334
+ * Get current credentials cache size
335
+ * Useful for monitoring and debugging
336
+ */
337
+ export function getCredentialsCacheSize(): number {
338
+ const store = getWorkletStore()
339
+ return Object.keys(store.getState().credentialsCache).length
340
+ }