@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,157 @@
1
+ /**
2
+ * Wallet Switching Service
3
+ *
4
+ * Handles wallet switching operations: checking wallet existence, loading credentials,
5
+ * and initializing WDK with the new wallet.
6
+ *
7
+ * Architecture:
8
+ * - This service is the single source of truth for wallet switching logic
9
+ * - Used by useWallet hook for automatic wallet switching
10
+ * - Can be used by other components that need to switch wallets programmatically
11
+ */
12
+
13
+ import { WalletSetupService } from './walletSetupService'
14
+ import { WorkletLifecycleService } from './workletLifecycleService'
15
+ import { getWalletStore } from '../store/walletStore'
16
+ import { updateWalletLoadingState, getWalletIdFromLoadingState } from '../store/walletStore'
17
+ import { withOperationMutex } from '../utils/operationMutex'
18
+ import { normalizeError } from '../utils/errorUtils'
19
+ import { log, logError } from '../utils/logger'
20
+
21
+ /**
22
+ * Wallet Switching Service
23
+ *
24
+ * Provides methods for switching between wallets.
25
+ */
26
+ export class WalletSwitchingService {
27
+ /**
28
+ * Switch to a wallet by identifier
29
+ *
30
+ * This method:
31
+ * 1. Checks if the wallet exists (fail fast)
32
+ * 2. Ensures worklet is started
33
+ * 3. Loads credentials for the wallet
34
+ * 4. Initializes WDK with the credentials
35
+ * 5. Updates activeWalletId in the store
36
+ *
37
+ * @param walletId - Wallet identifier to switch to
38
+ * @param options - Optional configuration
39
+ * @param options.autoStartWorklet - If true, start worklet if not started (default: false)
40
+ * @throws Error if wallet doesn't exist or switching fails
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * await WalletSwitchingService.switchToWallet('user@example.com')
45
+ * ```
46
+ */
47
+ static async switchToWallet(
48
+ walletId: string,
49
+ options?: { autoStartWorklet?: boolean }
50
+ ): Promise<void> {
51
+ return withOperationMutex(`switchToWallet:${walletId}`, async () => {
52
+ const walletStore = getWalletStore()
53
+ const activeWalletId = walletStore.getState().activeWalletId
54
+
55
+ // Check if already on this wallet
56
+ if (activeWalletId === walletId) {
57
+ log('[WalletSwitchingService] Already on wallet', { walletId })
58
+ return
59
+ }
60
+
61
+ try {
62
+ // Update loading state
63
+ walletStore.setState((prev) => updateWalletLoadingState(prev, {
64
+ type: 'loading',
65
+ identifier: walletId,
66
+ walletExists: true,
67
+ }))
68
+
69
+ // Check if wallet exists first (fail fast)
70
+ const walletExists = await WalletSetupService.hasWallet(walletId)
71
+ if (!walletExists) {
72
+ throw new Error(`Wallet with identifier "${walletId}" does not exist`)
73
+ }
74
+
75
+ // Ensure worklet is started
76
+ await WorkletLifecycleService.ensureWorkletStarted(undefined, {
77
+ autoStart: options?.autoStartWorklet ?? false,
78
+ })
79
+
80
+ // Note: We don't clear previous wallet's credentials cache when switching
81
+ // The LRU eviction policy in workletStore will handle cache management
82
+ // This allows users to switch back to recently used wallets without re-authentication
83
+ if (activeWalletId !== null && activeWalletId !== walletId) {
84
+ log('[WalletSwitchingService] Switching wallets', {
85
+ from: activeWalletId,
86
+ to: walletId,
87
+ })
88
+ // Cache is managed by LRU eviction - no need to clear here
89
+ }
90
+
91
+ // Load credentials for the wallet
92
+ const credentials = await WalletSetupService.loadExistingWallet(walletId)
93
+
94
+ // Switch worklet to this wallet
95
+ await WorkletLifecycleService.initializeWDK({
96
+ encryptionKey: credentials.encryptionKey,
97
+ encryptedSeed: credentials.encryptedSeed,
98
+ })
99
+
100
+ // Update activeWalletId in store and mark as ready
101
+ walletStore.setState((prev) => ({
102
+ ...updateWalletLoadingState(prev, {
103
+ type: 'ready',
104
+ identifier: walletId,
105
+ }),
106
+ activeWalletId: walletId,
107
+ }))
108
+ log('[WalletSwitchingService] Successfully switched to wallet', { walletId })
109
+ } catch (error) {
110
+ // Cleanup state on error
111
+ const normalizedError = normalizeError(error, false, {
112
+ component: 'WalletSwitchingService',
113
+ operation: 'switchToWallet',
114
+ walletId,
115
+ })
116
+ logError('[WalletSwitchingService] Failed to switch wallet, cleaning up state', normalizedError)
117
+
118
+ walletStore.setState((prev) => {
119
+ const currentWalletId = getWalletIdFromLoadingState(prev.walletLoadingState)
120
+ // Only update error state if we were loading this wallet
121
+ if (currentWalletId === walletId || prev.walletLoadingState.type === 'loading') {
122
+ return updateWalletLoadingState(prev, {
123
+ type: 'error',
124
+ identifier: walletId,
125
+ error: normalizedError,
126
+ })
127
+ }
128
+ return prev
129
+ })
130
+
131
+ throw normalizedError
132
+ }
133
+ })
134
+ }
135
+
136
+ /**
137
+ * Check if a wallet can be switched to
138
+ *
139
+ * @param walletId - Wallet identifier to check
140
+ * @returns Promise resolving to true if wallet exists and can be switched to
141
+ */
142
+ static async canSwitchToWallet(walletId: string): Promise<boolean> {
143
+ try {
144
+ return await WalletSetupService.hasWallet(walletId)
145
+ } catch (error) {
146
+ // Log error but don't throw - this is a non-critical check
147
+ const normalizedError = normalizeError(error, false, {
148
+ component: 'WalletSwitchingService',
149
+ operation: 'canSwitchToWallet',
150
+ walletId,
151
+ })
152
+ logError('[WalletSwitchingService] Failed to check wallet:', normalizedError)
153
+ return false
154
+ }
155
+ }
156
+ }
157
+
@@ -0,0 +1,561 @@
1
+ /**
2
+ * Worklet Lifecycle Service
3
+ *
4
+ * Handles worklet lifecycle operations: starting, initializing, and cleaning up worklets.
5
+ * This service is focused solely on worklet lifecycle management.
6
+ */
7
+
8
+ import { HRPC } from '@spacesops/pear-wrk-wdk'
9
+ import { Worklet } from '@spacesops/react-native-bare-kit'
10
+
11
+ import { getWalletStore } from '../store/walletStore'
12
+ import { getWorkletStore } from '../store/workletStore'
13
+ import { asExtendedHRPC } from '../types/hrpc'
14
+ import { DEFAULT_MNEMONIC_WORD_COUNT } from '../utils/constants'
15
+ import { handleServiceError } from '../utils/errorHandling'
16
+ import { normalizeError } from '../utils/errorUtils'
17
+ import { log, logWarn } from '../utils/logger'
18
+ import { isInitialized as isWorkletInitialized } from '../utils/storeHelpers'
19
+ import type { NetworkConfigs } from '../types'
20
+ import type { WorkletState } from '../store/workletStore'
21
+
22
+ /**
23
+ * Extended HRPC type that may have a cleanup method
24
+ */
25
+ interface HRPCWithCleanup extends HRPC {
26
+ cleanup?: () => Promise<void> | void
27
+ }
28
+
29
+ /**
30
+ * Extended Worklet type that may have cleanup methods
31
+ */
32
+ interface WorkletWithCleanup extends Worklet {
33
+ cleanup?: () => Promise<void> | void
34
+ destroy?: () => Promise<void> | void
35
+ stop?: () => Promise<void> | void
36
+ }
37
+
38
+ /**
39
+ * Type guard to check if HRPC has cleanup method
40
+ */
41
+ function hasHRPCCleanup(hrpc: HRPC): hrpc is HRPCWithCleanup {
42
+ return 'cleanup' in hrpc && typeof (hrpc as Record<string, unknown>).cleanup === 'function'
43
+ }
44
+
45
+ /**
46
+ * Type guard to check if Worklet has cleanup methods
47
+ */
48
+ function hasWorkletCleanup(worklet: Worklet): worklet is WorkletWithCleanup {
49
+ const w = worklet as unknown as Record<string, unknown>
50
+ return (
51
+ (typeof w.cleanup === 'function') ||
52
+ (typeof w.destroy === 'function') ||
53
+ (typeof w.stop === 'function')
54
+ )
55
+ }
56
+
57
+ /**
58
+ * Worklet Lifecycle Service
59
+ *
60
+ * Provides methods for managing worklet lifecycle: start, initialize, cleanup, reset.
61
+ */
62
+ export class WorkletLifecycleService {
63
+ /**
64
+ * Cleanup a resource by trying cleanup methods in order
65
+ * Handles cleanup gracefully, continuing even if individual steps fail
66
+ */
67
+ private static async cleanupResource(
68
+ resource: HRPC | Worklet | null,
69
+ cleanupMethods: string[]
70
+ ): Promise<void> {
71
+ if (!resource) return
72
+
73
+ const r = resource as unknown as Record<string, unknown>
74
+ const method = cleanupMethods.find((m) => typeof r[m] === 'function')
75
+ if (method) {
76
+ try {
77
+ await (r[method] as () => Promise<void> | void)()
78
+ } catch (error) {
79
+ logWarn(`Error calling ${method} on resource:`, error)
80
+ }
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Cleanup worklet resources (HRPC and Worklet instances)
86
+ * Handles cleanup gracefully, continuing even if individual steps fail
87
+ */
88
+ private static async cleanupWorkletResources(
89
+ hrpc: HRPC | null,
90
+ worklet: Worklet | null
91
+ ): Promise<void> {
92
+ try {
93
+ // Cleanup HRPC if it has a cleanup method
94
+ await this.cleanupResource(hrpc, ['cleanup'])
95
+
96
+ // Cleanup worklet - try cleanup, destroy, or stop in that order
97
+ await this.cleanupResource(worklet, ['cleanup', 'destroy', 'stop'])
98
+ } catch (error) {
99
+ logWarn('Error cleaning up worklet resources:', error)
100
+ // Continue even if cleanup fails
101
+ }
102
+ }
103
+ /**
104
+ * Start the worklet with network configurations
105
+ */
106
+ static async startWorklet(
107
+ networkConfigs: NetworkConfigs
108
+ ): Promise<void> {
109
+ const store = getWorkletStore()
110
+ const state = store.getState()
111
+
112
+ if (state.isLoading) {
113
+ logWarn('Worklet initialization already in progress')
114
+ return
115
+ }
116
+
117
+ if (state.isWorkletStarted) {
118
+ log('Worklet already started')
119
+ return
120
+ }
121
+
122
+ try {
123
+ store.setState({
124
+ error: null,
125
+ isLoading: true,
126
+ })
127
+
128
+ // Cleanup existing worklet if present
129
+ const { worklet: existingWorklet, hrpc: existingHrpc } = store.getState()
130
+ if (existingWorklet || existingHrpc) {
131
+ await this.cleanupWorkletResources(existingHrpc, existingWorklet)
132
+ }
133
+
134
+ const worklet = new Worklet()
135
+
136
+ // Dynamic import of @spacesops/pear-wrk-wdk bundle
137
+ const pearWrkWdk = await import('@spacesops/pear-wrk-wdk')
138
+ const bundle = (pearWrkWdk as { bundle?: unknown }).bundle
139
+
140
+ if (!bundle) {
141
+ throw new Error('Failed to load @spacesops/pear-wrk-wdk bundle')
142
+ }
143
+
144
+ // Bundle file (mobile bundle for React Native) - worklet.start expects bundle parameter
145
+ ;(worklet.start as (path: string, bundle: unknown) => void)('/wdk-worklet.bundle', bundle)
146
+
147
+ const { IPC } = worklet
148
+
149
+ if (!IPC) {
150
+ throw new Error('IPC not available from worklet')
151
+ }
152
+
153
+ const hrpcInstance = new HRPC(IPC)
154
+
155
+ const result = await hrpcInstance.workletStart({
156
+ config: JSON.stringify(networkConfigs),
157
+ })
158
+
159
+ store.setState({
160
+ worklet,
161
+ hrpc: hrpcInstance,
162
+ ipc: IPC,
163
+ isWorkletStarted: true,
164
+ isLoading: false,
165
+ networkConfigs,
166
+ workletStartResult: result,
167
+ error: null,
168
+ })
169
+ } catch (error) {
170
+ this.handleErrorWithStateUpdate(
171
+ error,
172
+ 'startWorklet',
173
+ (normalizedError) => ({
174
+ error: normalizedError.message,
175
+ isLoading: false,
176
+ worklet: null,
177
+ hrpc: null,
178
+ ipc: null,
179
+ isWorkletStarted: false,
180
+ })
181
+ )
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Ensure worklet is started, starting it if needed
187
+ *
188
+ * @param networkConfigs - Network configs (required if autoStart=true)
189
+ * @param options - Options
190
+ * @param options.autoStart - If true, start worklet if not started (default: false)
191
+ * @throws Error if worklet not started and autoStart=false or networkConfigs not provided
192
+ */
193
+ static async ensureWorkletStarted(
194
+ networkConfigs?: NetworkConfigs,
195
+ options?: { autoStart?: boolean }
196
+ ): Promise<void> {
197
+ const store = getWorkletStore()
198
+ const state = store.getState()
199
+
200
+ if (state.isWorkletStarted) {
201
+ return // Already started
202
+ }
203
+
204
+ const autoStart = options?.autoStart ?? false
205
+ if (!autoStart || !networkConfigs) {
206
+ throw new Error('Worklet must be started before this operation')
207
+ }
208
+
209
+ await this.startWorklet(networkConfigs)
210
+ }
211
+
212
+ /**
213
+ * Initialize WDK with encrypted seed (ONLY encrypted approach)
214
+ */
215
+ static async initializeWDK(
216
+ options: { encryptionKey: string; encryptedSeed: string }
217
+ ): Promise<void> {
218
+ const store = getWorkletStore()
219
+ const state = store.getState()
220
+
221
+ if (!state.isWorkletStarted) {
222
+ throw new Error('Worklet must be started before initializing WDK')
223
+ }
224
+
225
+ if (
226
+ state.isInitialized &&
227
+ state.encryptionKey === options.encryptionKey &&
228
+ state.encryptedSeed === options.encryptedSeed
229
+ ) {
230
+ log('WDK already initialized with the same encrypted seed')
231
+ return
232
+ }
233
+
234
+ try {
235
+ store.setState({
236
+ error: null,
237
+ isLoading: true,
238
+ })
239
+
240
+ // Get HRPC directly from store instead of using requireExtendedHRPC()
241
+ // requireExtendedHRPC() requires isInitialized to be true, but we're setting it here
242
+ const currentState = store.getState()
243
+ if (!currentState.hrpc) {
244
+ throw new Error('HRPC instance not available. Worklet may not be fully started.')
245
+ }
246
+ const extendedHrpc = asExtendedHRPC(currentState.hrpc)
247
+ const result = await extendedHrpc.initializeWDK({
248
+ encryptionKey: options.encryptionKey,
249
+ encryptedSeed: options.encryptedSeed,
250
+ config: JSON.stringify(currentState.networkConfigs || {}),
251
+ })
252
+
253
+ // NEVER store seed phrase
254
+ // Extract status from result
255
+ const wdkInitResult = this.extractWdkInitResult(result)
256
+
257
+ store.setState({
258
+ isInitialized: true,
259
+ isLoading: false,
260
+ encryptedSeed: options.encryptedSeed,
261
+ encryptionKey: options.encryptionKey,
262
+ wdkInitResult,
263
+ error: null,
264
+ })
265
+ } catch (error) {
266
+ this.handleErrorWithStateUpdate(
267
+ error,
268
+ 'initializeWDK',
269
+ (normalizedError) => ({
270
+ error: normalizedError.message,
271
+ isLoading: false,
272
+ isInitialized: false,
273
+ })
274
+ )
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Generate entropy and encrypt (for creating new wallets)
280
+ */
281
+ static async generateEntropyAndEncrypt(
282
+ wordCount: 12 | 24 = DEFAULT_MNEMONIC_WORD_COUNT
283
+ ): Promise<{
284
+ encryptionKey: string
285
+ encryptedSeedBuffer: string
286
+ encryptedEntropyBuffer: string
287
+ }> {
288
+ const store = getWorkletStore()
289
+ const state = store.getState()
290
+
291
+ if (!state.isWorkletStarted) {
292
+ throw new Error('Worklet must be started before generating entropy')
293
+ }
294
+
295
+ try {
296
+ // Get HRPC directly from store instead of using requireExtendedHRPC()
297
+ // These methods may be called before WDK is initialized
298
+ const currentState = store.getState()
299
+ if (!currentState.hrpc) {
300
+ throw new Error('HRPC instance not available. Worklet may not be fully started.')
301
+ }
302
+ const extendedHrpc = asExtendedHRPC(currentState.hrpc)
303
+ const result = await extendedHrpc.generateEntropyAndEncrypt({
304
+ wordCount,
305
+ })
306
+
307
+ return {
308
+ encryptionKey: result.encryptionKey,
309
+ encryptedSeedBuffer: result.encryptedSeedBuffer,
310
+ encryptedEntropyBuffer: result.encryptedEntropyBuffer,
311
+ }
312
+ } catch (error) {
313
+ this.handleAndThrowError(error, 'generateEntropyAndEncrypt', 'Failed to generate entropy')
314
+ }
315
+ }
316
+
317
+ /**
318
+ * Get mnemonic from encrypted entropy (for display purposes only - never stored)
319
+ */
320
+ static async getMnemonicFromEntropy(
321
+ encryptedEntropy: string,
322
+ encryptionKey: string
323
+ ): Promise<{
324
+ mnemonic: string
325
+ }> {
326
+ const store = getWorkletStore()
327
+ const state = store.getState()
328
+
329
+ if (!state.isWorkletStarted) {
330
+ throw new Error('Worklet must be started before getting mnemonic')
331
+ }
332
+
333
+ try {
334
+ // Get HRPC directly from store instead of using requireExtendedHRPC()
335
+ // These methods may be called before WDK is initialized
336
+ const currentState = store.getState()
337
+ if (!currentState.hrpc) {
338
+ throw new Error('HRPC instance not available. Worklet may not be fully started.')
339
+ }
340
+ const extendedHrpc = asExtendedHRPC(currentState.hrpc)
341
+
342
+ const result = await extendedHrpc.getMnemonicFromEntropy({
343
+ encryptedEntropy,
344
+ encryptionKey,
345
+ })
346
+
347
+ return {
348
+ mnemonic: result.mnemonic,
349
+ }
350
+ } catch (error) {
351
+ this.handleAndThrowError(error, 'getMnemonicFromEntropy', 'Failed to get mnemonic')
352
+ }
353
+ }
354
+
355
+ /**
356
+ * Get seed and entropy from mnemonic phrase (for importing existing wallets)
357
+ */
358
+ static async getSeedAndEntropyFromMnemonic(
359
+ mnemonic: string
360
+ ): Promise<{
361
+ encryptionKey: string
362
+ encryptedSeedBuffer: string
363
+ encryptedEntropyBuffer: string
364
+ }> {
365
+ const store = getWorkletStore()
366
+ const state = store.getState()
367
+
368
+ if (!state.isWorkletStarted) {
369
+ throw new Error('Worklet must be started before getting seed and entropy from mnemonic')
370
+ }
371
+
372
+ try {
373
+ // Get HRPC directly from store instead of using requireExtendedHRPC()
374
+ // These methods may be called before WDK is initialized
375
+ const currentState = store.getState()
376
+ if (!currentState.hrpc) {
377
+ throw new Error('HRPC instance not available. Worklet may not be fully started.')
378
+ }
379
+ const extendedHrpc = asExtendedHRPC(currentState.hrpc)
380
+ const result = await extendedHrpc.getSeedAndEntropyFromMnemonic({
381
+ mnemonic,
382
+ })
383
+
384
+ return {
385
+ encryptionKey: result.encryptionKey,
386
+ encryptedSeedBuffer: result.encryptedSeedBuffer,
387
+ encryptedEntropyBuffer: result.encryptedEntropyBuffer,
388
+ }
389
+ } catch (error) {
390
+ this.handleAndThrowError(
391
+ error,
392
+ 'getSeedAndEntropyFromMnemonic',
393
+ 'Failed to get seed and entropy from mnemonic'
394
+ )
395
+ }
396
+ }
397
+
398
+ /**
399
+ * Initialize both worklet and WDK in one call (convenience method) - ONLY encrypted
400
+ */
401
+ static async initializeWorklet(
402
+ options: {
403
+ encryptionKey: string
404
+ encryptedSeed: string
405
+ networkConfigs: NetworkConfigs
406
+ }
407
+ ): Promise<void> {
408
+ // Convenience method that does both steps - ONLY encrypted approach
409
+ await this.startWorklet(options.networkConfigs)
410
+ await this.initializeWDK({
411
+ encryptionKey: options.encryptionKey,
412
+ encryptedSeed: options.encryptedSeed,
413
+ })
414
+ }
415
+
416
+ /**
417
+ * Handle error for methods that throw with a message prefix
418
+ * Normalizes error, logs it, and throws a new error with operation context
419
+ */
420
+ private static handleAndThrowError(
421
+ error: unknown,
422
+ operation: string,
423
+ errorMessagePrefix: string
424
+ ): never {
425
+ const normalizedError = normalizeError(error, false, {
426
+ component: 'WorkletLifecycleService',
427
+ operation,
428
+ })
429
+ handleServiceError(error, 'WorkletLifecycleService', operation)
430
+ throw new Error(`${errorMessagePrefix}: ${normalizedError.message}`)
431
+ }
432
+
433
+ /**
434
+ * Handle error for methods that update store state
435
+ * Normalizes error, updates store state, and re-throws
436
+ */
437
+ private static handleErrorWithStateUpdate(
438
+ error: unknown,
439
+ operation: string,
440
+ stateUpdate: (normalizedError: Error) => Partial<WorkletState>
441
+ ): never {
442
+ const normalizedError = normalizeError(error, false, {
443
+ component: 'WorkletLifecycleService',
444
+ operation,
445
+ })
446
+ const store = getWorkletStore()
447
+ store.setState(stateUpdate(normalizedError))
448
+ handleServiceError(error, 'WorkletLifecycleService', operation)
449
+ throw normalizedError
450
+ }
451
+
452
+ /**
453
+ * Extract WDK initialization result status
454
+ * Safely extracts status from result object
455
+ */
456
+ private static extractWdkInitResult(result: unknown): { status?: string | null } | null {
457
+ if (result && typeof result === 'object' && 'status' in result) {
458
+ const status = (result as { status?: unknown }).status
459
+ if (status === null || status === undefined || typeof status === 'string') {
460
+ return { status: status ?? null }
461
+ }
462
+ }
463
+ return null
464
+ }
465
+
466
+ /**
467
+ * Reset both worklet and wallet stores
468
+ */
469
+ private static resetStores(): void {
470
+ const workletStore = getWorkletStore()
471
+ const walletStore = getWalletStore()
472
+
473
+ workletStore.setState({
474
+ worklet: null,
475
+ hrpc: null,
476
+ ipc: null,
477
+ isWorkletStarted: false,
478
+ isInitialized: false,
479
+ isLoading: false,
480
+ error: null,
481
+ encryptedSeed: null,
482
+ encryptionKey: null,
483
+ networkConfigs: null,
484
+ workletStartResult: null,
485
+ wdkInitResult: null,
486
+ })
487
+ walletStore.setState({
488
+ addresses: {},
489
+ walletLoading: {},
490
+ balances: {},
491
+ balanceLoading: {},
492
+ lastBalanceUpdate: {},
493
+ })
494
+ }
495
+
496
+ /**
497
+ * Cleanup worklet resources
498
+ * Clears only addresses, seed, and WDK instance - does NOT terminate the worklet
499
+ * The worklet continues running for faster re-initialization
500
+ */
501
+ static async cleanup(): Promise<void> {
502
+ const workletStore = getWorkletStore()
503
+ const walletStore = getWalletStore()
504
+
505
+ // Clear only sensitive data - addresses, seed, and WDK instance
506
+ // Do NOT terminate worklet, hrpc, or ipc - keep them running
507
+ workletStore.setState({
508
+ encryptedSeed: null,
509
+ encryptionKey: null,
510
+ isInitialized: false,
511
+ wdkInitResult: null,
512
+ })
513
+
514
+ // Clear addresses from wallet store
515
+ walletStore.setState({
516
+ addresses: {},
517
+ })
518
+ }
519
+
520
+ /**
521
+ * Reset worklet state (synchronous)
522
+ * Clears only addresses, seed, and WDK instance - does NOT terminate the worklet
523
+ * The worklet continues running for faster re-initialization
524
+ * For async cleanup, use cleanup() instead
525
+ */
526
+ static reset(): void {
527
+ const workletStore = getWorkletStore()
528
+ const walletStore = getWalletStore()
529
+
530
+ // Clear only sensitive data - addresses, seed, and WDK instance
531
+ // Do NOT terminate worklet, hrpc, or ipc - keep them running
532
+ workletStore.setState({
533
+ encryptedSeed: null,
534
+ encryptionKey: null,
535
+ isInitialized: false,
536
+ wdkInitResult: null,
537
+ })
538
+
539
+ // Clear addresses from wallet store
540
+ walletStore.setState({
541
+ addresses: {},
542
+ })
543
+ }
544
+
545
+ /**
546
+ * Clear error state
547
+ */
548
+ static clearError(): void {
549
+ const store = getWorkletStore()
550
+ store.setState({ error: null })
551
+ }
552
+
553
+ /**
554
+ * Check if wallet is initialized
555
+ * Returns true if worklet is started and WDK is initialized
556
+ */
557
+ static isInitialized(): boolean {
558
+ return isWorkletInitialized()
559
+ }
560
+ }
561
+