@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,695 @@
1
+ import type { SecureStorage } from '@tetherto/wdk-react-native-secure-storage'
2
+
3
+ import {
4
+ getWorkletStore,
5
+ getCachedCredentials,
6
+ setCachedCredentials,
7
+ clearCredentialsCache as clearWorkletCredentialsCache,
8
+ type CachedCredentials
9
+ } from '../store/workletStore'
10
+ import { WorkletLifecycleService } from './workletLifecycleService'
11
+ import { DEFAULT_MNEMONIC_WORD_COUNT } from '../utils/constants'
12
+ import { log, logError } from '../utils/logger'
13
+ import type { NetworkConfigs } from '../types'
14
+
15
+ /**
16
+ * Wallet setup service
17
+ * Handles creating new wallets and loading existing wallets with biometric authentication
18
+ * Caches credentials in ephemeral memory to avoid repeated biometric prompts
19
+ *
20
+ * ## Biometric Authentication Lifecycle
21
+ *
22
+ * ### When Biometric Authentication is Required
23
+ *
24
+ * 1. **Creating a new wallet** (`createNewWallet`)
25
+ * - Always requires biometric authentication before wallet creation
26
+ * - Ensures only authorized users can create wallets
27
+ * - Credentials are cached after successful authentication
28
+ *
29
+ * 2. **Loading existing wallet** (`loadExistingWallet`)
30
+ * - Requires biometric authentication if credentials are not cached
31
+ * - If credentials are in cache, authentication is skipped (better UX)
32
+ * - Cache is checked first before prompting for biometrics
33
+ *
34
+ * 3. **Importing wallet from mnemonic** (`initializeFromMnemonic`)
35
+ * - Always requires biometric authentication before import
36
+ * - Ensures only authorized users can import wallets
37
+ * - Credentials are cached after successful authentication
38
+ *
39
+ * ### Credential Caching Behavior
40
+ *
41
+ * - **Cache Location**: Ephemeral memory only (Map<string, CachedCredentials>)
42
+ * - Cache is keyed by `identifier` (user email/ID) for multi-wallet support
43
+ * - Cache persists for the lifetime of the app session
44
+ * - Cache is cleared when app is terminated (not persisted to disk)
45
+ *
46
+ * - **Cache Contents**:
47
+ * - `encryptionKey`: Cached after first retrieval (most sensitive, requires biometrics)
48
+ * - `encryptedSeed`: Cached after first retrieval
49
+ * - `encryptedEntropy`: Cached after first retrieval
50
+ *
51
+ * - **Cache Benefits**:
52
+ * - Avoids repeated biometric prompts during the same app session
53
+ * - Improves UX for operations that need credentials multiple times
54
+ * - Reduces security surface (fewer biometric prompts = fewer attack vectors)
55
+ *
56
+ * ### When Cache is Cleared
57
+ *
58
+ * Cache is automatically cleared in the following scenarios:
59
+ *
60
+ * 1. **Explicit cache clearing** (`clearCredentialsCache`)
61
+ * - Called when wallet data is corrupted or invalid
62
+ * - Called during wallet deletion
63
+ * - Can be called manually for security (e.g., on logout)
64
+ *
65
+ * 2. **App termination**
66
+ * - Cache is in-memory only, so it's cleared when app closes
67
+ * - Next app launch will require biometric authentication again
68
+ *
69
+ * 3. **Wallet deletion** (`deleteWallet`)
70
+ * - Cache for the deleted wallet identifier is cleared
71
+ * - Other wallet caches remain intact
72
+ *
73
+ * ### Security Considerations
74
+ *
75
+ * - **Cache Lifetime**: Ephemeral (in-memory only, cleared on app termination)
76
+ * - **Cache Scope**: Per-identifier (multi-wallet support)
77
+ * - **Cache Invalidation**: Manual via `clearCredentialsCache()` or automatic on app termination
78
+ * - **Biometric Prompt Frequency**:
79
+ * - First access per app session: Always required
80
+ * - Subsequent accesses in same session: Cached (no prompt)
81
+ * - After app restart: Always required again
82
+ *
83
+ * ### Best Practices
84
+ *
85
+ * 1. **Clear cache on logout**: Call `clearCredentialsCache(identifier)` when user logs out
86
+ * 2. **Clear cache on errors**: Clear cache if decryption fails (may indicate corrupted data)
87
+ * 3. **Don't persist cache**: Cache is intentionally in-memory only for security
88
+ * 4. **Handle cache misses**: Always handle the case where cache is empty (will prompt for biometrics)
89
+ *
90
+ * ### Example Usage
91
+ *
92
+ * ```typescript
93
+ * // Create new wallet (always requires biometrics)
94
+ * await WalletSetupService.createNewWallet(networkConfigs, walletId)
95
+ *
96
+ * // Load existing wallet (biometrics only if not cached)
97
+ * const credentials = await WalletSetupService.loadExistingWallet(walletId)
98
+ *
99
+ * // Clear cache (e.g., on logout)
100
+ * WalletSetupService.clearCredentialsCache(walletId)
101
+ * ```
102
+ */
103
+ export class WalletSetupService {
104
+
105
+ /**
106
+ * SecureStorage singleton instance
107
+ * Set by WdkAppProvider during initialization
108
+ */
109
+ private static secureStorageInstance: SecureStorage | null = null
110
+
111
+ /**
112
+ * Set the secureStorage singleton instance
113
+ * Called by WdkAppProvider during initialization
114
+ * Also exposed publicly for testing purposes
115
+ *
116
+ * @param secureStorage - SecureStorage instance to use
117
+ * @param allowOverwrite - If false, warns when overwriting existing instance (default: true)
118
+ */
119
+ static setSecureStorage(secureStorage: SecureStorage, allowOverwrite: boolean = true): void {
120
+ if (this.secureStorageInstance && !allowOverwrite) {
121
+ log('⚠️ SecureStorage already set. This may indicate multiple WdkAppProviders are mounted.', {
122
+ hasExisting: !!this.secureStorageInstance
123
+ })
124
+ } else if (this.secureStorageInstance) {
125
+ log('⚠️ SecureStorage being overwritten. This may indicate multiple WdkAppProviders are mounted.')
126
+ }
127
+
128
+ this.secureStorageInstance = secureStorage
129
+ log('✅ SecureStorage singleton set in WalletSetupService')
130
+ }
131
+
132
+ /**
133
+ * Get the secureStorage singleton instance
134
+ * Throws error if not initialized (should only happen if called before WdkAppProvider mounts)
135
+ */
136
+ private static getSecureStorage(): SecureStorage {
137
+ if (!this.secureStorageInstance) {
138
+ throw new Error('SecureStorage not initialized. Ensure WdkAppProvider is mounted.')
139
+ }
140
+ return this.secureStorageInstance
141
+ }
142
+
143
+ /**
144
+ * Check if secureStorage is initialized
145
+ * Useful for testing or debugging
146
+ */
147
+ static isSecureStorageInitialized(): boolean {
148
+ return this.secureStorageInstance !== null
149
+ }
150
+
151
+ /**
152
+ * Get cache key for walletId
153
+ */
154
+ private static getCacheKey(walletId?: string): string {
155
+ return walletId || 'default'
156
+ }
157
+
158
+ /**
159
+ * Cache credentials after retrieval
160
+ */
161
+ private static cacheCredentials(
162
+ walletId: string | undefined,
163
+ encryptionKey?: string,
164
+ encryptedSeed?: string,
165
+ encryptedEntropy?: string
166
+ ): void {
167
+ const cacheKey = this.getCacheKey(walletId)
168
+ const existing = getCachedCredentials(cacheKey) || {}
169
+
170
+ // Use workletStore functions for all credential caching
171
+ setCachedCredentials(cacheKey, {
172
+ ...existing,
173
+ ...(encryptionKey && { encryptionKey }),
174
+ ...(encryptedSeed && { encryptedSeed }),
175
+ ...(encryptedEntropy && { encryptedEntropy }),
176
+ })
177
+
178
+ log('Credentials cached', { hasWalletId: !!walletId })
179
+ }
180
+
181
+ /**
182
+ * Generic helper to retrieve a credential value (checks cache first, then secureStorage)
183
+ */
184
+ private static async getCredential<T extends 'encryptionKey' | 'encryptedSeed' | 'encryptedEntropy'>(
185
+ walletId: string | undefined,
186
+ credentialType: T,
187
+ fetchFn: (walletId?: string) => Promise<string | null>,
188
+ cacheKey: keyof CachedCredentials
189
+ ): Promise<string | null> {
190
+ const secureStorage = this.getSecureStorage()
191
+ const cacheKeyStr = this.getCacheKey(walletId)
192
+ const cached = getCachedCredentials(cacheKeyStr)
193
+
194
+ if (cached?.[cacheKey]) {
195
+ const logMessage = credentialType === 'encryptionKey'
196
+ ? '✅ Encryption key retrieved from cache (no biometrics needed)'
197
+ : `✅ ${credentialType} retrieved from cache`
198
+ log(logMessage)
199
+ return cached[cacheKey] as string
200
+ }
201
+
202
+ if (credentialType === 'encryptionKey') {
203
+ log('Encryption key not in cache, fetching from secureStorage...')
204
+ }
205
+
206
+ const value = await fetchFn(walletId)
207
+
208
+ if (value) {
209
+ // Cache it for future use
210
+ if (credentialType === 'encryptionKey') {
211
+ this.cacheCredentials(walletId, value)
212
+ } else if (credentialType === 'encryptedSeed') {
213
+ this.cacheCredentials(walletId, undefined, value)
214
+ } else {
215
+ this.cacheCredentials(walletId, undefined, undefined, value)
216
+ }
217
+ }
218
+
219
+ return value
220
+ }
221
+
222
+ /**
223
+ * Validate that encrypted data can be decrypted with the encryption key
224
+ * Attempts to initialize WDK with the provided credentials to verify compatibility
225
+ *
226
+ * @param networkConfigs - Network configurations for worklet
227
+ * @param encryptionKey - Encryption key to test
228
+ * @param encryptedSeed - Encrypted seed to test
229
+ * @param encryptedEntropy - Optional encrypted entropy to test
230
+ * @throws Error if validation fails (decryption fails)
231
+ */
232
+ private static async validateEncryptionCompatibility(
233
+ networkConfigs: NetworkConfigs,
234
+ encryptionKey: string,
235
+ encryptedSeed: string,
236
+ encryptedEntropy?: string
237
+ ): Promise<void> {
238
+ const store = getWorkletStore()
239
+
240
+ // Ensure worklet is started
241
+ if (!store.getState().isWorkletStarted) {
242
+ await WorkletLifecycleService.startWorklet(networkConfigs)
243
+ }
244
+
245
+ const wasInitialized = store.getState().isInitialized
246
+ const previousEncryptionKey = store.getState().encryptionKey
247
+ const previousEncryptedSeed = store.getState().encryptedSeed
248
+
249
+ try {
250
+ // Attempt to initialize WDK with the credentials
251
+ // This will fail if the encryption key cannot decrypt the seed
252
+ await WorkletLifecycleService.initializeWDK({
253
+ encryptionKey,
254
+ encryptedSeed,
255
+ })
256
+
257
+ log('✅ Encryption compatibility validation passed')
258
+ } catch (error) {
259
+ const errorMessage = error instanceof Error ? error.message : String(error)
260
+ const isDecryptionError =
261
+ errorMessage.toLowerCase().includes('decryption failed') ||
262
+ errorMessage.toLowerCase().includes('failed to decrypt')
263
+
264
+ if (isDecryptionError) {
265
+ log('❌ Encryption compatibility validation failed - decryption error detected')
266
+ throw new Error(
267
+ `Failed to validate encryption compatibility: The encryption key cannot decrypt the encrypted seed. ` +
268
+ `This indicates corrupted or mismatched wallet data. Error: ${errorMessage}`
269
+ )
270
+ }
271
+
272
+ throw error
273
+ } finally {
274
+ // Restore previous state if wallet was already initialized
275
+ // Only restore if we're not overwriting with the same credentials
276
+ if (wasInitialized &&
277
+ (previousEncryptionKey !== encryptionKey || previousEncryptedSeed !== encryptedSeed)) {
278
+ try {
279
+ if (previousEncryptionKey && previousEncryptedSeed) {
280
+ await WorkletLifecycleService.initializeWDK({
281
+ encryptionKey: previousEncryptionKey,
282
+ encryptedSeed: previousEncryptedSeed,
283
+ })
284
+ } else {
285
+ WorkletLifecycleService.reset()
286
+ }
287
+ } catch (restoreError) {
288
+ log('⚠️ Failed to restore previous wallet state after validation, resetting', restoreError)
289
+ WorkletLifecycleService.reset()
290
+ }
291
+ }
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Create a new wallet
297
+ * Generates entropy, encrypts it, and stores credentials securely
298
+ * Requires biometric authentication to ensure authorized wallet creation
299
+ */
300
+ static async createNewWallet(
301
+ networkConfigs: NetworkConfigs,
302
+ walletId?: string
303
+ ): Promise<{
304
+ encryptionKey: string
305
+ encryptedSeed: string
306
+ }> {
307
+ const store = getWorkletStore()
308
+ const secureStorage = this.getSecureStorage()
309
+
310
+ // Step 1: Require biometric authentication before creating wallet
311
+ log('🔐 Creating new wallet - biometric authentication required...')
312
+ try {
313
+ const authenticated = await secureStorage.authenticate()
314
+ if (!authenticated) {
315
+ throw new Error('Biometric authentication required to create wallet')
316
+ }
317
+ } catch (error) {
318
+ // Re-throw authentication errors so they can be properly handled by the UI
319
+ // This includes AuthenticationError from secure storage
320
+ log('❌ Biometric authentication failed', error)
321
+ throw error
322
+ }
323
+
324
+ // Step 2: Start worklet
325
+ if (!store.getState().isWorkletStarted) {
326
+ await WorkletLifecycleService.startWorklet(networkConfigs)
327
+ }
328
+
329
+ // Step 3: Generate entropy and encrypt
330
+ const result = await WorkletLifecycleService.generateEntropyAndEncrypt(DEFAULT_MNEMONIC_WORD_COUNT)
331
+
332
+ // Step 4: Validate encryption compatibility before saving to keychain
333
+ log('🔍 Validating encryption compatibility before saving to keychain...')
334
+ try {
335
+ await this.validateEncryptionCompatibility(
336
+ networkConfigs,
337
+ result.encryptionKey,
338
+ result.encryptedSeedBuffer,
339
+ result.encryptedEntropyBuffer
340
+ )
341
+ } catch (error) {
342
+ log('❌ Encryption validation failed - aborting wallet creation', error)
343
+ // Reset worklet state on validation failure
344
+ WorkletLifecycleService.reset()
345
+ throw error
346
+ }
347
+
348
+ // Step 5: Store credentials securely with walletId for multi-wallet support
349
+ try {
350
+ await secureStorage.setEncryptionKey(result.encryptionKey, walletId)
351
+ await secureStorage.setEncryptedSeed(result.encryptedSeedBuffer, walletId)
352
+ await secureStorage.setEncryptedEntropy(result.encryptedEntropyBuffer, walletId)
353
+ } catch (error) {
354
+ // Clean up any partial writes (deleteWallet is idempotent)
355
+ try {
356
+ await secureStorage.deleteWallet(walletId)
357
+ log('[WalletSetupService] Cleaned up partial wallet creation after storage failure')
358
+ } catch (cleanupError) {
359
+ logError('[WalletSetupService] Failed to cleanup partial wallet creation:', cleanupError)
360
+ }
361
+ throw error
362
+ }
363
+
364
+ this.cacheCredentials(
365
+ walletId,
366
+ result.encryptionKey,
367
+ result.encryptedSeedBuffer,
368
+ result.encryptedEntropyBuffer
369
+ )
370
+
371
+ log('✅ New wallet created and stored securely')
372
+
373
+ return {
374
+ encryptionKey: result.encryptionKey,
375
+ encryptedSeed: result.encryptedSeedBuffer,
376
+ }
377
+ }
378
+
379
+ /**
380
+ * Load existing wallet from secure storage
381
+ * Checks cache first, only requires biometric authentication if not cached
382
+ */
383
+ static async loadExistingWallet(
384
+ walletId?: string
385
+ ): Promise<{
386
+ encryptionKey: string
387
+ encryptedSeed: string
388
+ }> {
389
+ const secureStorage = this.getSecureStorage()
390
+ const cacheKey = this.getCacheKey(walletId)
391
+ const cached = getCachedCredentials(cacheKey)
392
+
393
+ // Check if all required credentials are cached
394
+ if (cached?.encryptionKey && cached?.encryptedSeed) {
395
+ log('✅ Wallet loaded from cache (no biometrics needed)')
396
+ return {
397
+ encryptionKey: cached.encryptionKey,
398
+ encryptedSeed: cached.encryptedSeed,
399
+ }
400
+ }
401
+
402
+ log('🔓 Loading existing wallet - biometric authentication required...')
403
+
404
+ // Get encrypted seed first (doesn't require biometrics)
405
+ const encryptedSeed = await secureStorage.getEncryptedSeed(walletId)
406
+
407
+ // Try to get encryption key from cache first
408
+ let encryptionKey = cached?.encryptionKey
409
+
410
+ // If not in cache, get from secureStorage (will trigger biometrics)
411
+ if (!encryptionKey) {
412
+ log('Encryption key not in cache, fetching from secureStorage...')
413
+ try {
414
+ const allEncrypted = await secureStorage.getAllEncrypted(walletId)
415
+ encryptionKey = allEncrypted.encryptionKey || undefined
416
+ } catch (error) {
417
+ throw error
418
+ }
419
+ } else {
420
+ log('Using cached encryption key (no biometrics needed)')
421
+ }
422
+
423
+ if (!encryptionKey) {
424
+ throw new Error('Encryption key not found. Authentication may have failed or wallet does not exist.')
425
+ }
426
+
427
+ if (!encryptedSeed) {
428
+ throw new Error('Encrypted seed not found. Authentication may have failed or wallet does not exist.')
429
+ }
430
+
431
+ // Cache credentials for future use
432
+ this.cacheCredentials(walletId, encryptionKey, encryptedSeed)
433
+
434
+ log('✅ Wallet loaded successfully from secure storage')
435
+ return {
436
+ encryptionKey,
437
+ encryptedSeed,
438
+ }
439
+ }
440
+
441
+ /**
442
+ * Check if a wallet exists
443
+ */
444
+ static async hasWallet(walletId?: string): Promise<boolean> {
445
+ const secureStorage = this.getSecureStorage()
446
+ const result = await secureStorage.hasWallet(walletId)
447
+ return result
448
+ }
449
+
450
+ /**
451
+ * Initialize WDK from an existing mnemonic phrase
452
+ * Converts mnemonic to encrypted seed and entropy, stores them securely, and initializes WDK
453
+ * Requires biometric authentication to ensure authorized wallet import
454
+ */
455
+ static async initializeFromMnemonic(
456
+ networkConfigs: NetworkConfigs,
457
+ mnemonic: string,
458
+ walletId?: string
459
+ ): Promise<{
460
+ encryptionKey: string
461
+ encryptedSeed: string
462
+ encryptedEntropy: string
463
+ }> {
464
+ const store = getWorkletStore()
465
+ const secureStorage = this.getSecureStorage()
466
+
467
+ // Step 1: Require biometric authentication before importing wallet
468
+ log('🔐 Importing wallet from mnemonic - biometric authentication required...')
469
+ try {
470
+ const authenticated = await secureStorage.authenticate()
471
+ if (!authenticated) {
472
+ throw new Error('Biometric authentication required to import wallet')
473
+ }
474
+ } catch (error) {
475
+ // Re-throw authentication errors so they can be properly handled by the UI
476
+ // This includes AuthenticationError from secure storage
477
+ log('❌ Biometric authentication failed', error)
478
+ throw error
479
+ }
480
+
481
+ // Step 2: Start worklet
482
+ if (!store.getState().isWorkletStarted) {
483
+ await WorkletLifecycleService.startWorklet(networkConfigs)
484
+ }
485
+
486
+ // Step 3: Get seed and entropy from mnemonic
487
+ const result = await WorkletLifecycleService.getSeedAndEntropyFromMnemonic(mnemonic)
488
+
489
+ // Step 4: Validate encryption compatibility before saving to keychain
490
+ log('🔍 Validating encryption compatibility before saving to keychain...')
491
+ try {
492
+ await this.validateEncryptionCompatibility(
493
+ networkConfigs,
494
+ result.encryptionKey,
495
+ result.encryptedSeedBuffer,
496
+ result.encryptedEntropyBuffer
497
+ )
498
+ } catch (error) {
499
+ log('❌ Encryption validation failed - aborting wallet import', error)
500
+ // Reset worklet state on validation failure
501
+ WorkletLifecycleService.reset()
502
+ throw error
503
+ }
504
+
505
+ // Step 5: Store credentials securely with walletId for multi-wallet support
506
+ try {
507
+ await secureStorage.setEncryptionKey(result.encryptionKey, walletId)
508
+ await secureStorage.setEncryptedSeed(result.encryptedSeedBuffer, walletId)
509
+ await secureStorage.setEncryptedEntropy(result.encryptedEntropyBuffer, walletId)
510
+ } catch (error) {
511
+ // Clean up any partial writes (deleteWallet is idempotent)
512
+ try {
513
+ await secureStorage.deleteWallet(walletId)
514
+ log('[WalletSetupService] Cleaned up partial wallet import after storage failure')
515
+ } catch (cleanupError) {
516
+ logError('[WalletSetupService] Failed to cleanup partial wallet import:', cleanupError)
517
+ }
518
+ throw error
519
+ }
520
+
521
+ this.cacheCredentials(
522
+ walletId,
523
+ result.encryptionKey,
524
+ result.encryptedSeedBuffer,
525
+ result.encryptedEntropyBuffer
526
+ )
527
+
528
+ // Step 6: Initialize WDK with the credentials
529
+ await WorkletLifecycleService.initializeWDK({
530
+ encryptionKey: result.encryptionKey,
531
+ encryptedSeed: result.encryptedSeedBuffer,
532
+ })
533
+
534
+ log('✅ Wallet imported from mnemonic and stored securely')
535
+
536
+ return {
537
+ encryptionKey: result.encryptionKey,
538
+ encryptedSeed: result.encryptedSeedBuffer,
539
+ encryptedEntropy: result.encryptedEntropyBuffer,
540
+ }
541
+ }
542
+
543
+ /**
544
+ * Initialize WDK with wallet credentials
545
+ */
546
+ static async initializeWDK(
547
+ networkConfigs: NetworkConfigs,
548
+ credentials: {
549
+ encryptionKey: string
550
+ encryptedSeed: string
551
+ }
552
+ ): Promise<void> {
553
+ const store = getWorkletStore()
554
+
555
+ // Ensure worklet is started
556
+ if (!store.getState().isWorkletStarted) {
557
+ log('Starting worklet...')
558
+ await WorkletLifecycleService.startWorklet(networkConfigs)
559
+ log('Worklet started')
560
+ }
561
+
562
+ // Initialize WDK
563
+ await WorkletLifecycleService.initializeWDK(credentials)
564
+ }
565
+
566
+ /**
567
+ * Complete wallet initialization flow
568
+ * Either creates a new wallet or loads an existing one
569
+ */
570
+ static async initializeWallet(
571
+ networkConfigs: NetworkConfigs,
572
+ options: {
573
+ createNew?: boolean
574
+ walletId?: string
575
+ }
576
+ ): Promise<void> {
577
+ // Remove the early return check - WorkletLifecycleService.initializeWDK() will handle
578
+ // checking if credentials match (it already has the correct logic to skip if same credentials)
579
+
580
+ let credentials: { encryptionKey: string; encryptedSeed: string }
581
+
582
+ if (options.createNew) {
583
+ credentials = await this.createNewWallet(networkConfigs, options.walletId)
584
+ } else {
585
+ credentials = await this.loadExistingWallet(options.walletId)
586
+ }
587
+
588
+ // Initialize WDK with credentials
589
+ await this.initializeWDK(networkConfigs, credentials)
590
+ }
591
+
592
+ /**
593
+ * Delete wallet and clear all data
594
+ *
595
+ * @param identifier - Optional identifier for multi-wallet support. If provided, deletes wallet for that identifier.
596
+ * If not provided, deletes the default wallet.
597
+ */
598
+ static async deleteWallet(
599
+ walletId?: string
600
+ ): Promise<void> {
601
+ const secureStorage = this.getSecureStorage()
602
+
603
+ // Clear secure storage for the specified walletId
604
+ await secureStorage.deleteWallet(walletId)
605
+
606
+ // Reset store state
607
+ WorkletLifecycleService.reset()
608
+
609
+ // Clear credentials cache
610
+ this.clearCredentialsCache(walletId)
611
+ }
612
+
613
+ /**
614
+ * Get encryption key (checks cache first, then secureStorage with biometrics)
615
+ */
616
+ static async getEncryptionKey(
617
+ walletId?: string
618
+ ): Promise<string | null> {
619
+ const secureStorage = this.getSecureStorage()
620
+ return this.getCredential(
621
+ walletId,
622
+ 'encryptionKey',
623
+ async (id) => {
624
+ const allEncrypted = await secureStorage.getAllEncrypted(id)
625
+ return allEncrypted.encryptionKey || null
626
+ },
627
+ 'encryptionKey'
628
+ )
629
+ }
630
+
631
+ /**
632
+ * Get encrypted seed (checks cache first, then secureStorage)
633
+ */
634
+ static async getEncryptedSeed(
635
+ walletId?: string
636
+ ): Promise<string | null> {
637
+ const secureStorage = this.getSecureStorage()
638
+ return this.getCredential(
639
+ walletId,
640
+ 'encryptedSeed',
641
+ (id) => secureStorage.getEncryptedSeed(id),
642
+ 'encryptedSeed'
643
+ )
644
+ }
645
+
646
+ /**
647
+ * Get encrypted entropy (checks cache first, then secureStorage)
648
+ */
649
+ static async getEncryptedEntropy(
650
+ walletId?: string
651
+ ): Promise<string | null> {
652
+ const secureStorage = this.getSecureStorage()
653
+ return this.getCredential(
654
+ walletId,
655
+ 'encryptedEntropy',
656
+ (id) => secureStorage.getEncryptedEntropy(id),
657
+ 'encryptedEntropy'
658
+ )
659
+ }
660
+
661
+ /**
662
+ * Get mnemonic phrase from wallet
663
+ * Retrieves encrypted entropy and encryption key, then decrypts to get mnemonic
664
+ *
665
+ * @param walletId - Optional walletId for multi-wallet support
666
+ * @returns Promise<string | null> - The mnemonic phrase or null if not found
667
+ */
668
+ static async getMnemonic(
669
+ walletId?: string
670
+ ): Promise<string | null> {
671
+ const encryptedEntropy = await this.getEncryptedEntropy(walletId)
672
+ const encryptionKey = await this.getEncryptionKey(walletId)
673
+
674
+ if (!encryptedEntropy || !encryptionKey) {
675
+ return null
676
+ }
677
+
678
+ const result = await WorkletLifecycleService.getMnemonicFromEntropy(
679
+ encryptedEntropy,
680
+ encryptionKey
681
+ )
682
+
683
+ return result.mnemonic || null
684
+ }
685
+
686
+ /**
687
+ * Clear all cached credentials
688
+ * Should be called on logout or app background for security
689
+ */
690
+ static clearCredentialsCache(walletId?: string): void {
691
+ clearWorkletCredentialsCache(walletId ? this.getCacheKey(walletId) : undefined)
692
+ log('Credentials cache cleared', { walletId })
693
+ }
694
+ }
695
+