@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.
- package/README.md +551 -0
- package/dist/__mocks__/react.d.ts +40 -0
- package/dist/__mocks__/react.js +33 -0
- package/dist/__mocks__/react.js.map +1 -0
- package/dist/__mocks__/secureStorage.d.ts +26 -0
- package/dist/__mocks__/secureStorage.js +80 -0
- package/dist/__mocks__/secureStorage.js.map +1 -0
- package/dist/__tests__/hooks/useBalance.test.d.ts +6 -0
- package/dist/__tests__/hooks/useBalance.test.js +217 -0
- package/dist/__tests__/hooks/useBalance.test.js.map +1 -0
- package/dist/__tests__/hooks/useWallet.test.d.ts +6 -0
- package/dist/__tests__/hooks/useWallet.test.js +105 -0
- package/dist/__tests__/hooks/useWallet.test.js.map +1 -0
- package/dist/__tests__/hooks/useWalletManager.test.d.ts +6 -0
- package/dist/__tests__/hooks/useWalletManager.test.js +193 -0
- package/dist/__tests__/hooks/useWalletManager.test.js.map +1 -0
- package/dist/__tests__/hooks/useWdkApp.test.d.ts +6 -0
- package/dist/__tests__/hooks/useWdkApp.test.js +55 -0
- package/dist/__tests__/hooks/useWdkApp.test.js.map +1 -0
- package/dist/__tests__/hooks/useWorklet.test.d.ts +6 -0
- package/dist/__tests__/hooks/useWorklet.test.js +168 -0
- package/dist/__tests__/hooks/useWorklet.test.js.map +1 -0
- package/dist/__tests__/provider/WdkAppProvider.test.d.ts +6 -0
- package/dist/__tests__/provider/WdkAppProvider.test.js +57 -0
- package/dist/__tests__/provider/WdkAppProvider.test.js.map +1 -0
- package/dist/__tests__/services/accountService.test.d.ts +6 -0
- package/dist/__tests__/services/accountService.test.js +216 -0
- package/dist/__tests__/services/accountService.test.js.map +1 -0
- package/dist/__tests__/services/addressService.test.d.ts +6 -0
- package/dist/__tests__/services/addressService.test.js +246 -0
- package/dist/__tests__/services/addressService.test.js.map +1 -0
- package/dist/__tests__/services/balanceService.test.d.ts +6 -0
- package/dist/__tests__/services/balanceService.test.js +333 -0
- package/dist/__tests__/services/balanceService.test.js.map +1 -0
- package/dist/__tests__/services/walletSetupService.test.d.ts +6 -0
- package/dist/__tests__/services/walletSetupService.test.js +304 -0
- package/dist/__tests__/services/walletSetupService.test.js.map +1 -0
- package/dist/__tests__/services/walletSwitchingService.test.d.ts +6 -0
- package/dist/__tests__/services/walletSwitchingService.test.js +209 -0
- package/dist/__tests__/services/walletSwitchingService.test.js.map +1 -0
- package/dist/__tests__/services/workletLifecycleService.test.d.ts +6 -0
- package/dist/__tests__/services/workletLifecycleService.test.js +451 -0
- package/dist/__tests__/services/workletLifecycleService.test.js.map +1 -0
- package/dist/__tests__/setup.d.ts +7 -0
- package/dist/__tests__/setup.js +49 -0
- package/dist/__tests__/setup.js.map +1 -0
- package/dist/__tests__/storage/mmkvStorage.test.d.ts +6 -0
- package/dist/__tests__/storage/mmkvStorage.test.js +213 -0
- package/dist/__tests__/storage/mmkvStorage.test.js.map +1 -0
- package/dist/__tests__/store/walletStore.test.d.ts +4 -0
- package/dist/__tests__/store/walletStore.test.js +86 -0
- package/dist/__tests__/store/walletStore.test.js.map +1 -0
- package/dist/__tests__/store/workletStore.test.d.ts +4 -0
- package/dist/__tests__/store/workletStore.test.js +255 -0
- package/dist/__tests__/store/workletStore.test.js.map +1 -0
- package/dist/__tests__/types/hrpc.test.d.ts +4 -0
- package/dist/__tests__/types/hrpc.test.js +99 -0
- package/dist/__tests__/types/hrpc.test.js.map +1 -0
- package/dist/__tests__/utils/balanceUtils.test.d.ts +4 -0
- package/dist/__tests__/utils/balanceUtils.test.js +133 -0
- package/dist/__tests__/utils/balanceUtils.test.js.map +1 -0
- package/dist/__tests__/utils/errorHandling.test.d.ts +6 -0
- package/dist/__tests__/utils/errorHandling.test.js +119 -0
- package/dist/__tests__/utils/errorHandling.test.js.map +1 -0
- package/dist/__tests__/utils/errorUtils.test.d.ts +4 -0
- package/dist/__tests__/utils/errorUtils.test.js +133 -0
- package/dist/__tests__/utils/errorUtils.test.js.map +1 -0
- package/dist/__tests__/utils/initializationState.test.d.ts +6 -0
- package/dist/__tests__/utils/initializationState.test.js +243 -0
- package/dist/__tests__/utils/initializationState.test.js.map +1 -0
- package/dist/__tests__/utils/jsonUtils.test.d.ts +4 -0
- package/dist/__tests__/utils/jsonUtils.test.js +118 -0
- package/dist/__tests__/utils/jsonUtils.test.js.map +1 -0
- package/dist/__tests__/utils/mmkvKeyManager.test.d.ts +6 -0
- package/dist/__tests__/utils/mmkvKeyManager.test.js +158 -0
- package/dist/__tests__/utils/mmkvKeyManager.test.js.map +1 -0
- package/dist/__tests__/utils/mnemonicUtils.test.d.ts +4 -0
- package/dist/__tests__/utils/mnemonicUtils.test.js +40 -0
- package/dist/__tests__/utils/mnemonicUtils.test.js.map +1 -0
- package/dist/__tests__/utils/raceConditions.test.d.ts +6 -0
- package/dist/__tests__/utils/raceConditions.test.js +239 -0
- package/dist/__tests__/utils/raceConditions.test.js.map +1 -0
- package/dist/__tests__/utils/result.test.d.ts +4 -0
- package/dist/__tests__/utils/result.test.js +66 -0
- package/dist/__tests__/utils/result.test.js.map +1 -0
- package/dist/__tests__/utils/schemas.test.d.ts +4 -0
- package/dist/__tests__/utils/schemas.test.js +66 -0
- package/dist/__tests__/utils/schemas.test.js.map +1 -0
- package/dist/__tests__/utils/storeHelpers.test.d.ts +4 -0
- package/dist/__tests__/utils/storeHelpers.test.js +227 -0
- package/dist/__tests__/utils/storeHelpers.test.js.map +1 -0
- package/dist/__tests__/utils/typeGuards.test.d.ts +4 -0
- package/dist/__tests__/utils/typeGuards.test.js +175 -0
- package/dist/__tests__/utils/typeGuards.test.js.map +1 -0
- package/dist/__tests__/utils/validation.test.d.ts +4 -0
- package/dist/__tests__/utils/validation.test.js +126 -0
- package/dist/__tests__/utils/validation.test.js.map +1 -0
- package/dist/__tests__/utils/walletUtils.test.d.ts +4 -0
- package/dist/__tests__/utils/walletUtils.test.js +156 -0
- package/dist/__tests__/utils/walletUtils.test.js.map +1 -0
- package/dist/hooks/useBalance.d.ts +170 -0
- package/dist/hooks/useBalance.js +442 -0
- package/dist/hooks/useBalance.js.map +1 -0
- package/dist/hooks/useWallet.d.ts +43 -0
- package/dist/hooks/useWallet.js +193 -0
- package/dist/hooks/useWallet.js.map +1 -0
- package/dist/hooks/useWalletManager.d.ts +100 -0
- package/dist/hooks/useWalletManager.js +526 -0
- package/dist/hooks/useWalletManager.js.map +1 -0
- package/dist/hooks/useWdkApp.d.ts +66 -0
- package/dist/hooks/useWdkApp.js +74 -0
- package/dist/hooks/useWdkApp.js.map +1 -0
- package/dist/hooks/useWorklet.d.ts +66 -0
- package/dist/hooks/useWorklet.js +42 -0
- package/dist/hooks/useWorklet.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/provider/WdkAppProvider.d.ts +112 -0
- package/dist/provider/WdkAppProvider.js +558 -0
- package/dist/provider/WdkAppProvider.js.map +1 -0
- package/dist/services/accountService.d.ts +52 -0
- package/dist/services/accountService.js +119 -0
- package/dist/services/accountService.js.map +1 -0
- package/dist/services/addressService.d.ts +22 -0
- package/dist/services/addressService.js +110 -0
- package/dist/services/addressService.js.map +1 -0
- package/dist/services/balanceService.d.ts +114 -0
- package/dist/services/balanceService.js +208 -0
- package/dist/services/balanceService.js.map +1 -0
- package/dist/services/walletSetupService.d.ts +217 -0
- package/dist/services/walletSetupService.js +535 -0
- package/dist/services/walletSetupService.js.map +1 -0
- package/dist/services/walletSwitchingService.d.ts +48 -0
- package/dist/services/walletSwitchingService.js +143 -0
- package/dist/services/walletSwitchingService.js.map +1 -0
- package/dist/services/workletLifecycleService.d.ts +117 -0
- package/dist/services/workletLifecycleService.js +433 -0
- package/dist/services/workletLifecycleService.js.map +1 -0
- package/dist/storage/mmkvStorage.d.ts +44 -0
- package/dist/storage/mmkvStorage.js +348 -0
- package/dist/storage/mmkvStorage.js.map +1 -0
- package/dist/store/walletStore.d.ts +130 -0
- package/dist/store/walletStore.js +204 -0
- package/dist/store/walletStore.js.map +1 -0
- package/dist/store/workletStore.d.ts +121 -0
- package/dist/store/workletStore.js +256 -0
- package/dist/store/workletStore.js.map +1 -0
- package/dist/types/hrpc.d.ts +60 -0
- package/dist/types/hrpc.js +26 -0
- package/dist/types/hrpc.js.map +1 -0
- package/dist/types.d.ts +168 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/balanceUtils.d.ts +30 -0
- package/dist/utils/balanceUtils.js +86 -0
- package/dist/utils/balanceUtils.js.map +1 -0
- package/dist/utils/constants.d.ts +66 -0
- package/dist/utils/constants.js +73 -0
- package/dist/utils/constants.js.map +1 -0
- package/dist/utils/errorHandling.d.ts +47 -0
- package/dist/utils/errorHandling.js +54 -0
- package/dist/utils/errorHandling.js.map +1 -0
- package/dist/utils/errorUtils.d.ts +89 -0
- package/dist/utils/errorUtils.js +264 -0
- package/dist/utils/errorUtils.js.map +1 -0
- package/dist/utils/initializationState.d.ts +129 -0
- package/dist/utils/initializationState.js +220 -0
- package/dist/utils/initializationState.js.map +1 -0
- package/dist/utils/jsonUtils.d.ts +21 -0
- package/dist/utils/jsonUtils.js +67 -0
- package/dist/utils/jsonUtils.js.map +1 -0
- package/dist/utils/logger.d.ts +27 -0
- package/dist/utils/logger.js +50 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/mmkvKeyManager.d.ts +57 -0
- package/dist/utils/mmkvKeyManager.js +214 -0
- package/dist/utils/mmkvKeyManager.js.map +1 -0
- package/dist/utils/mnemonicUtils.d.ts +17 -0
- package/dist/utils/mnemonicUtils.js +28 -0
- package/dist/utils/mnemonicUtils.js.map +1 -0
- package/dist/utils/operationMutex.d.ts +69 -0
- package/dist/utils/operationMutex.js +150 -0
- package/dist/utils/operationMutex.js.map +1 -0
- package/dist/utils/result.d.ts +34 -0
- package/dist/utils/result.js +43 -0
- package/dist/utils/result.js.map +1 -0
- package/dist/utils/schemas.d.ts +365 -0
- package/dist/utils/schemas.js +152 -0
- package/dist/utils/schemas.js.map +1 -0
- package/dist/utils/storeHelpers.d.ts +112 -0
- package/dist/utils/storeHelpers.js +198 -0
- package/dist/utils/storeHelpers.js.map +1 -0
- package/dist/utils/typeGuards.d.ts +70 -0
- package/dist/utils/typeGuards.js +95 -0
- package/dist/utils/typeGuards.js.map +1 -0
- package/dist/utils/validation.d.ts +53 -0
- package/dist/utils/validation.js +152 -0
- package/dist/utils/validation.js.map +1 -0
- package/dist/utils/walletStateHelpers.d.ts +62 -0
- package/dist/utils/walletStateHelpers.js +81 -0
- package/dist/utils/walletStateHelpers.js.map +1 -0
- package/dist/utils/walletUtils.d.ts +38 -0
- package/dist/utils/walletUtils.js +87 -0
- package/dist/utils/walletUtils.js.map +1 -0
- package/package.json +79 -0
- package/src/__mocks__/react.ts +34 -0
- package/src/__mocks__/secureStorage.ts +87 -0
- package/src/__tests__/hooks/useBalance.test.ts +286 -0
- package/src/__tests__/hooks/useWallet.test.ts +122 -0
- package/src/__tests__/hooks/useWalletManager.test.ts +271 -0
- package/src/__tests__/hooks/useWdkApp.test.tsx +59 -0
- package/src/__tests__/hooks/useWorklet.test.ts +195 -0
- package/src/__tests__/provider/WdkAppProvider.test.tsx +67 -0
- package/src/__tests__/services/accountService.test.ts +337 -0
- package/src/__tests__/services/addressService.test.ts +312 -0
- package/src/__tests__/services/balanceService.test.ts +411 -0
- package/src/__tests__/services/walletSetupService.test.ts +463 -0
- package/src/__tests__/services/walletSwitchingService.test.ts +258 -0
- package/src/__tests__/services/workletLifecycleService.test.ts +514 -0
- package/src/__tests__/setup.ts +54 -0
- package/src/__tests__/storage/mmkvStorage.test.ts +325 -0
- package/src/__tests__/store/walletStore.test.ts +101 -0
- package/src/__tests__/store/workletStore.test.ts +329 -0
- package/src/__tests__/types/hrpc.test.ts +125 -0
- package/src/__tests__/utils/balanceUtils.test.ts +151 -0
- package/src/__tests__/utils/errorHandling.test.ts +147 -0
- package/src/__tests__/utils/errorUtils.test.ts +170 -0
- package/src/__tests__/utils/initializationState.test.ts +337 -0
- package/src/__tests__/utils/jsonUtils.test.ts +131 -0
- package/src/__tests__/utils/mmkvKeyManager.test.ts +205 -0
- package/src/__tests__/utils/mnemonicUtils.test.ts +48 -0
- package/src/__tests__/utils/raceConditions.test.ts +292 -0
- package/src/__tests__/utils/result.test.ts +73 -0
- package/src/__tests__/utils/schemas.test.ts +82 -0
- package/src/__tests__/utils/storeHelpers.test.ts +323 -0
- package/src/__tests__/utils/typeGuards.test.ts +209 -0
- package/src/__tests__/utils/validation.test.ts +152 -0
- package/src/__tests__/utils/walletUtils.test.ts +195 -0
- package/src/hooks/useBalance.ts +597 -0
- package/src/hooks/useWallet.ts +288 -0
- package/src/hooks/useWalletManager.ts +654 -0
- package/src/hooks/useWdkApp.ts +78 -0
- package/src/hooks/useWorklet.ts +109 -0
- package/src/index.ts +112 -0
- package/src/provider/WdkAppProvider.tsx +764 -0
- package/src/services/accountService.ts +134 -0
- package/src/services/addressService.ts +125 -0
- package/src/services/balanceService.ts +291 -0
- package/src/services/walletSetupService.ts +695 -0
- package/src/services/walletSwitchingService.ts +157 -0
- package/src/services/workletLifecycleService.ts +561 -0
- package/src/storage/mmkvStorage.ts +417 -0
- package/src/store/walletStore.ts +301 -0
- package/src/store/workletStore.ts +340 -0
- package/src/types/hrpc.ts +78 -0
- package/src/types.ts +188 -0
- package/src/utils/balanceUtils.ts +96 -0
- package/src/utils/constants.ts +84 -0
- package/src/utils/errorHandling.md +163 -0
- package/src/utils/errorHandling.ts +61 -0
- package/src/utils/errorUtils.ts +300 -0
- package/src/utils/initializationState.ts +242 -0
- package/src/utils/jsonUtils.ts +75 -0
- package/src/utils/logger.ts +54 -0
- package/src/utils/mmkvKeyManager.ts +256 -0
- package/src/utils/mnemonicUtils.ts +35 -0
- package/src/utils/operationMutex.ts +181 -0
- package/src/utils/result.ts +57 -0
- package/src/utils/schemas.ts +190 -0
- package/src/utils/storeHelpers.ts +236 -0
- package/src/utils/typeGuards.ts +129 -0
- package/src/utils/validation.ts +174 -0
- package/src/utils/walletStateHelpers.ts +104 -0
- package/src/utils/walletUtils.ts +107 -0
|
@@ -0,0 +1,764 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WdkAppProvider
|
|
3
|
+
*
|
|
4
|
+
* App-level orchestration provider that manages initialization state.
|
|
5
|
+
* Provides app-level status and initialization state via useWdkApp() hook.
|
|
6
|
+
*
|
|
7
|
+
* Responsibilities:
|
|
8
|
+
* - Manages worklet initialization state
|
|
9
|
+
* - Manages wallet initialization state
|
|
10
|
+
* - Provides combined app status (isReady, isInitializing, error)
|
|
11
|
+
* - Sets up QueryClientProvider for TanStack Query
|
|
12
|
+
* - Sets up SecureStorage
|
|
13
|
+
* - Handles AppState listeners for security
|
|
14
|
+
*
|
|
15
|
+
* Architecture:
|
|
16
|
+
* - Worklet state: Managed by workletStore (global, initialized once)
|
|
17
|
+
* - Wallet state: Managed by separate state machine (per-identifier, can load multiple)
|
|
18
|
+
* - Combined status: Derived from both worklet and wallet states
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import React, { createContext, useCallback, useEffect, useMemo, useRef } from 'react'
|
|
22
|
+
import { AppState, type AppStateStatus } from 'react-native'
|
|
23
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
24
|
+
|
|
25
|
+
import { createSecureStorage } from '@tetherto/wdk-react-native-secure-storage'
|
|
26
|
+
|
|
27
|
+
import { useWalletManager } from '../hooks/useWalletManager'
|
|
28
|
+
import { useWorklet } from '../hooks/useWorklet'
|
|
29
|
+
import { getWalletStore } from '../store/walletStore'
|
|
30
|
+
import type { WalletStore } from '../store/walletStore'
|
|
31
|
+
import {
|
|
32
|
+
updateWalletLoadingState,
|
|
33
|
+
getWalletIdFromLoadingState,
|
|
34
|
+
isWalletLoadingState,
|
|
35
|
+
isWalletErrorState,
|
|
36
|
+
} from '../store/walletStore'
|
|
37
|
+
import {
|
|
38
|
+
shouldResetToNotLoaded,
|
|
39
|
+
getWalletSwitchDecision,
|
|
40
|
+
shouldMarkWalletAsReady,
|
|
41
|
+
shouldHandleError,
|
|
42
|
+
} from '../utils/walletStateHelpers'
|
|
43
|
+
import { clearAllSensitiveData } from '../store/workletStore'
|
|
44
|
+
import { WalletSetupService } from '../services/walletSetupService'
|
|
45
|
+
import { WorkletLifecycleService } from '../services/workletLifecycleService'
|
|
46
|
+
import { normalizeError } from '../utils/errorUtils'
|
|
47
|
+
import { log, logError } from '../utils/logger'
|
|
48
|
+
import { validateNetworkConfigs, validateTokenConfigs } from '../utils/validation'
|
|
49
|
+
import { DEFAULT_QUERY_STALE_TIME_MS, DEFAULT_QUERY_GC_TIME_MS } from '../utils/constants'
|
|
50
|
+
import { InitializationStatus, AppStatus, isAppReadyStatus, isAppInProgressStatus, getCombinedStatus, getWorkletStatus } from '../utils/initializationState'
|
|
51
|
+
import type { NetworkConfigs, TokenConfigs } from '../types'
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Context state exposed to consumers
|
|
57
|
+
*
|
|
58
|
+
* Purpose: App-level initialization state only. For wallet operations, use useWallet().
|
|
59
|
+
* For wallet lifecycle (create, load, import, delete), use useWalletManager().
|
|
60
|
+
*
|
|
61
|
+
* API Design:
|
|
62
|
+
* - `status`: Combined status for convenience (most common use case: "is app ready?")
|
|
63
|
+
* - `workletState`: Separate worklet state for flexibility (check worklet-specific conditions)
|
|
64
|
+
* - `walletState`: Separate wallet state for flexibility (check wallet-specific conditions)
|
|
65
|
+
*
|
|
66
|
+
* For simple cases, use `status`. For advanced cases needing granular control,
|
|
67
|
+
* use `workletState` and `walletState` directly.
|
|
68
|
+
*/
|
|
69
|
+
export interface WdkAppContextValue {
|
|
70
|
+
/**
|
|
71
|
+
* Combined app status (convenience helper)
|
|
72
|
+
* Derived from workletState and walletState.
|
|
73
|
+
* Use this for common "is app ready?" checks.
|
|
74
|
+
*/
|
|
75
|
+
status: AppStatus
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Worklet initialization status (global, initialized once)
|
|
79
|
+
* Use this when you need to check worklet initialization state specifically.
|
|
80
|
+
*/
|
|
81
|
+
workletStatus: InitializationStatus
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Worklet state (global, initialized once)
|
|
85
|
+
* Use this when you need to check worklet-specific conditions.
|
|
86
|
+
*/
|
|
87
|
+
workletState: {
|
|
88
|
+
/** Worklet is ready (started, not loading, no error) */
|
|
89
|
+
isReady: boolean
|
|
90
|
+
/** Worklet is currently loading */
|
|
91
|
+
isLoading: boolean
|
|
92
|
+
/** Worklet error message (null if no error) */
|
|
93
|
+
error: string | null
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Wallet state (per-identifier, can load multiple wallets)
|
|
98
|
+
* Use this when you need to check wallet-specific conditions.
|
|
99
|
+
*/
|
|
100
|
+
walletState: {
|
|
101
|
+
/** Current wallet state */
|
|
102
|
+
status: 'not_loaded' | 'checking' | 'loading' | 'ready' | 'error'
|
|
103
|
+
/** Wallet identifier being operated on (null if not_loaded) */
|
|
104
|
+
identifier: string | null
|
|
105
|
+
/** Wallet error (null if no error) */
|
|
106
|
+
error: Error | null
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Initialization in progress (convenience getter, equivalent to isInProgressStatus(status)) */
|
|
110
|
+
isInitializing: boolean
|
|
111
|
+
/** App is ready (convenience getter, equivalent to isReadyStatus(status)) */
|
|
112
|
+
isReady: boolean
|
|
113
|
+
|
|
114
|
+
/** Currently active wallet identifier from walletStore (null if no wallet is loaded) */
|
|
115
|
+
activeWalletId: string | null
|
|
116
|
+
/** Wallet identifier being loaded (transient, only during loading operations) */
|
|
117
|
+
loadingWalletId: string | null
|
|
118
|
+
/** Whether the wallet being loaded exists in secure storage (null = not checked yet, true = exists, false = doesn't exist) */
|
|
119
|
+
walletExists: boolean | null
|
|
120
|
+
/** Initialization error if any (worklet or wallet error) - convenience getter */
|
|
121
|
+
error: Error | null
|
|
122
|
+
|
|
123
|
+
/** Retry initialization after an error */
|
|
124
|
+
retry: () => void
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const WdkAppContext = createContext<WdkAppContextValue | null>(null)
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Provider props
|
|
131
|
+
*/
|
|
132
|
+
export interface WdkAppProviderProps {
|
|
133
|
+
/** Network configurations */
|
|
134
|
+
networkConfigs: NetworkConfigs
|
|
135
|
+
/** Token configurations for balance fetching */
|
|
136
|
+
tokenConfigs: TokenConfigs
|
|
137
|
+
/** Enable automatic wallet initialization on app restart (default: true) */
|
|
138
|
+
enableAutoInitialization?: boolean
|
|
139
|
+
/**
|
|
140
|
+
* Current user's identifier (typically email)
|
|
141
|
+
* Auto-initialization will NOT proceed if:
|
|
142
|
+
* - currentUserId is undefined/null (user identity not yet confirmed)
|
|
143
|
+
* - currentUserId doesn't match activeWalletId (wrong user's wallet)
|
|
144
|
+
*
|
|
145
|
+
* This prevents initializing with wrong user's wallet during account switches
|
|
146
|
+
*/
|
|
147
|
+
currentUserId?: string | null
|
|
148
|
+
/** Child components (app content) */
|
|
149
|
+
children: React.ReactNode
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* WdkAppProvider - Orchestrates WDK initialization flow
|
|
154
|
+
*
|
|
155
|
+
* Composes useWorklet and useWalletManager hooks into a unified initialization flow.
|
|
156
|
+
* Automatically fetches balances when wallet is ready.
|
|
157
|
+
*/
|
|
158
|
+
/**
|
|
159
|
+
* Create QueryClient singleton for TanStack Query
|
|
160
|
+
* This is created once and reused across the app lifecycle
|
|
161
|
+
*/
|
|
162
|
+
const queryClient = new QueryClient({
|
|
163
|
+
defaultOptions: {
|
|
164
|
+
queries: {
|
|
165
|
+
retry: 1,
|
|
166
|
+
staleTime: DEFAULT_QUERY_STALE_TIME_MS,
|
|
167
|
+
gcTime: DEFAULT_QUERY_GC_TIME_MS,
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
// Custom deep equality for walletLoadingState comparison
|
|
173
|
+
// Defined outside component to prevent recreation on every render
|
|
174
|
+
const deepEqualityFn = (a: any, b: any) => {
|
|
175
|
+
if (a === b) return true
|
|
176
|
+
if (!a || !b) return false
|
|
177
|
+
if (typeof a !== 'object' || typeof b !== 'object') return a === b
|
|
178
|
+
|
|
179
|
+
const keysA = Object.keys(a)
|
|
180
|
+
const keysB = Object.keys(b)
|
|
181
|
+
if (keysA.length !== keysB.length) return false
|
|
182
|
+
|
|
183
|
+
for (const key of keysA) {
|
|
184
|
+
if (!keysB.includes(key)) return false
|
|
185
|
+
if (a[key] !== b[key]) return false
|
|
186
|
+
}
|
|
187
|
+
return true
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function WdkAppProvider({
|
|
191
|
+
networkConfigs,
|
|
192
|
+
tokenConfigs,
|
|
193
|
+
enableAutoInitialization = true,
|
|
194
|
+
currentUserId,
|
|
195
|
+
children,
|
|
196
|
+
}: WdkAppProviderProps) {
|
|
197
|
+
// Create secureStorage singleton and register synchronously so child effects
|
|
198
|
+
// (e.g. useWallet wallet switching) can access it on the first paint.
|
|
199
|
+
const secureStorage = useMemo(() => {
|
|
200
|
+
const storage = createSecureStorage()
|
|
201
|
+
WalletSetupService.setSecureStorage(storage)
|
|
202
|
+
return storage
|
|
203
|
+
}, [])
|
|
204
|
+
|
|
205
|
+
// Clear sensitive data on mount AND when app goes to background
|
|
206
|
+
// This ensures biometrics are always required on app restart or foreground
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
// CRITICAL: Clear cache on mount to handle true app restarts (not hot reloads)
|
|
209
|
+
// When app is killed and reopened:
|
|
210
|
+
// - walletStore rehydrates with 'not_loaded' state
|
|
211
|
+
// - But in dev mode with hot reload, JS context persists and cache remains
|
|
212
|
+
// - Clearing here ensures cache is empty even in dev mode
|
|
213
|
+
log('[WdkAppProvider] Clearing credentials cache on mount (app restart)')
|
|
214
|
+
clearAllSensitiveData()
|
|
215
|
+
|
|
216
|
+
const appStateRef = { current: AppState.currentState }
|
|
217
|
+
|
|
218
|
+
const subscription = AppState.addEventListener('change', (nextAppState: AppStateStatus) => {
|
|
219
|
+
const previousState = appStateRef.current
|
|
220
|
+
appStateRef.current = nextAppState
|
|
221
|
+
|
|
222
|
+
// When going to background: clear cache and mark wallet for re-authentication
|
|
223
|
+
if ((nextAppState === 'background' || nextAppState === 'inactive') && previousState === 'active') {
|
|
224
|
+
log('[WdkAppProvider] App going to background - clearing sensitive data and marking for re-auth')
|
|
225
|
+
clearAllSensitiveData()
|
|
226
|
+
|
|
227
|
+
// Reset wallet state to trigger re-authentication on foreground
|
|
228
|
+
// This ensures biometrics are required when app comes back
|
|
229
|
+
// CRITICAL: Only reset if wallet is 'ready' - don't interrupt 'loading' or 'checking' states
|
|
230
|
+
// Interrupting these states would cancel biometric authentication in progress
|
|
231
|
+
const currentState = walletStore.getState()
|
|
232
|
+
const currentStateType = currentState.walletLoadingState.type
|
|
233
|
+
|
|
234
|
+
if (currentStateType === 'ready' && currentState.activeWalletId) {
|
|
235
|
+
log('[WdkAppProvider] Resetting wallet state to trigger biometrics on foreground')
|
|
236
|
+
walletStore.setState((prev) => updateWalletLoadingState(prev, {
|
|
237
|
+
type: 'not_loaded'
|
|
238
|
+
}))
|
|
239
|
+
} else if (currentStateType === 'loading' || currentStateType === 'checking') {
|
|
240
|
+
log('[WdkAppProvider] Preserving wallet loading state during background transition', {
|
|
241
|
+
currentState: currentStateType,
|
|
242
|
+
})
|
|
243
|
+
// Do not reset - allow biometric authentication to complete
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// When coming to foreground: wallet will auto-initialize with biometrics
|
|
248
|
+
// (handled by the consolidated effect below)
|
|
249
|
+
if (nextAppState === 'active' && (previousState === 'background' || previousState === 'inactive')) {
|
|
250
|
+
log('[WdkAppProvider] App coming to foreground - auto-initialization will trigger biometrics')
|
|
251
|
+
}
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
return () => subscription.remove()
|
|
255
|
+
}, [])
|
|
256
|
+
|
|
257
|
+
// Validate props on mount and when props change
|
|
258
|
+
useEffect(() => {
|
|
259
|
+
try {
|
|
260
|
+
validateNetworkConfigs(networkConfigs)
|
|
261
|
+
validateTokenConfigs(tokenConfigs)
|
|
262
|
+
} catch (error) {
|
|
263
|
+
const err = normalizeError(error, true, { component: 'WdkAppProvider', operation: 'propsValidation' })
|
|
264
|
+
logError('[WdkAppProvider] Invalid props:', err)
|
|
265
|
+
// Always throw validation errors - they indicate programming errors
|
|
266
|
+
throw err
|
|
267
|
+
}
|
|
268
|
+
}, [networkConfigs, tokenConfigs])
|
|
269
|
+
|
|
270
|
+
// Worklet state - read from workletStore via hook
|
|
271
|
+
const workletHookState = useWorklet()
|
|
272
|
+
const {
|
|
273
|
+
isWorkletStarted,
|
|
274
|
+
isInitialized: isWorkletInitialized,
|
|
275
|
+
isLoading: isWorkletLoading,
|
|
276
|
+
error: workletError,
|
|
277
|
+
} = workletHookState
|
|
278
|
+
|
|
279
|
+
// Wallet state - read from walletStore (single source of truth)
|
|
280
|
+
const walletStore = getWalletStore()
|
|
281
|
+
|
|
282
|
+
// Subscribe to primitive values directly
|
|
283
|
+
const activeWalletId = walletStore((state: WalletStore) => state.activeWalletId)
|
|
284
|
+
|
|
285
|
+
// For walletLoadingState, use a ref to manually check equality and prevent unnecessary re-renders
|
|
286
|
+
const walletLoadingStateRef = useRef(walletStore.getState().walletLoadingState)
|
|
287
|
+
const [walletLoadingState, setWalletLoadingState] = React.useState(walletStore.getState().walletLoadingState)
|
|
288
|
+
|
|
289
|
+
useEffect(() => {
|
|
290
|
+
const unsubscribe = walletStore.subscribe((state: WalletStore) => {
|
|
291
|
+
const newState = state.walletLoadingState
|
|
292
|
+
// Only update if content actually changed (deep equality check)
|
|
293
|
+
if (!deepEqualityFn(walletLoadingStateRef.current, newState)) {
|
|
294
|
+
walletLoadingStateRef.current = newState
|
|
295
|
+
setWalletLoadingState(newState)
|
|
296
|
+
}
|
|
297
|
+
})
|
|
298
|
+
return unsubscribe
|
|
299
|
+
}, [walletStore])
|
|
300
|
+
|
|
301
|
+
const walletAddresses = walletStore((state: WalletStore) =>
|
|
302
|
+
state.activeWalletId ? state.addresses[state.activeWalletId] : undefined
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
// Hooks for wallet operations
|
|
306
|
+
const {
|
|
307
|
+
initializeWallet,
|
|
308
|
+
hasWallet,
|
|
309
|
+
error: walletManagerError,
|
|
310
|
+
} = useWalletManager()
|
|
311
|
+
|
|
312
|
+
// Store initializeWallet in a ref to avoid it being a dependency of the effect
|
|
313
|
+
// This breaks the infinite loop: effect runs → component re-renders → initializeWallet recreated → effect runs again
|
|
314
|
+
const initializeWalletRef = useRef(initializeWallet)
|
|
315
|
+
useEffect(() => {
|
|
316
|
+
initializeWalletRef.current = initializeWallet
|
|
317
|
+
}, [initializeWallet])
|
|
318
|
+
|
|
319
|
+
// Track authentication errors to prevent infinite retry loops
|
|
320
|
+
// When biometric authentication fails, we shouldn't automatically retry
|
|
321
|
+
const authErrorRef = useRef<string | null>(null)
|
|
322
|
+
|
|
323
|
+
// Derive isWalletInitializing from walletLoadingState (single source of truth)
|
|
324
|
+
const isWalletInitializing = useMemo(() => {
|
|
325
|
+
return isWalletLoadingState(walletLoadingState)
|
|
326
|
+
}, [walletLoadingState])
|
|
327
|
+
|
|
328
|
+
// Worklet state object (exposed separately for flexibility)
|
|
329
|
+
const workletState = useMemo(() => ({
|
|
330
|
+
isReady: isWorkletStarted && !isWorkletLoading && !workletError,
|
|
331
|
+
isLoading: isWorkletLoading,
|
|
332
|
+
error: workletError,
|
|
333
|
+
}), [isWorkletStarted, isWorkletLoading, workletError])
|
|
334
|
+
|
|
335
|
+
// Wallet state object (exposed separately for flexibility)
|
|
336
|
+
const walletStateObject = useMemo(() => ({
|
|
337
|
+
status: (walletLoadingState.type === 'not_loaded' ? 'not_loaded' :
|
|
338
|
+
walletLoadingState.type === 'checking' ? 'checking' :
|
|
339
|
+
walletLoadingState.type === 'loading' ? 'loading' :
|
|
340
|
+
walletLoadingState.type === 'ready' ? 'ready' :
|
|
341
|
+
'error') as 'not_loaded' | 'checking' | 'loading' | 'ready' | 'error',
|
|
342
|
+
identifier: getWalletIdFromLoadingState(walletLoadingState),
|
|
343
|
+
error: walletLoadingState.type === 'error' ? walletLoadingState.error : null,
|
|
344
|
+
}), [walletLoadingState])
|
|
345
|
+
|
|
346
|
+
// Worklet initialization status (worklet-specific)
|
|
347
|
+
const workletStatus = useMemo(() => {
|
|
348
|
+
return getWorkletStatus({
|
|
349
|
+
isWorkletStarted,
|
|
350
|
+
isLoading: isWorkletLoading,
|
|
351
|
+
error: workletError,
|
|
352
|
+
})
|
|
353
|
+
}, [isWorkletStarted, isWorkletLoading, workletError])
|
|
354
|
+
|
|
355
|
+
// Combined app status - derived from both worklet and wallet states (convenience)
|
|
356
|
+
const status = useMemo(() => {
|
|
357
|
+
return getCombinedStatus(
|
|
358
|
+
{
|
|
359
|
+
isWorkletStarted,
|
|
360
|
+
isLoading: isWorkletLoading,
|
|
361
|
+
error: workletError,
|
|
362
|
+
},
|
|
363
|
+
walletLoadingState
|
|
364
|
+
)
|
|
365
|
+
}, [isWorkletStarted, isWorkletLoading, workletError, walletLoadingState])
|
|
366
|
+
|
|
367
|
+
// Automatically initialize worklet when component mounts
|
|
368
|
+
useEffect(() => {
|
|
369
|
+
log('[WdkAppProvider] Checking initialization conditions', {
|
|
370
|
+
isWorkletInitialized,
|
|
371
|
+
isWorkletLoading,
|
|
372
|
+
isWorkletStarted,
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
// Skip if worklet is loading
|
|
376
|
+
if (isWorkletLoading) {
|
|
377
|
+
log('[WdkAppProvider] Initialization skipped', { reason: 'already loading' })
|
|
378
|
+
return
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// If worklet is already started/initialized, nothing to do
|
|
382
|
+
if (isWorkletStarted || isWorkletInitialized) {
|
|
383
|
+
log('[WdkAppProvider] Worklet already started, ready to load wallets')
|
|
384
|
+
return
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
let cancelled = false
|
|
388
|
+
|
|
389
|
+
const initializeWorklet = async () => {
|
|
390
|
+
try {
|
|
391
|
+
log('[WdkAppProvider] Starting worklet initialization...')
|
|
392
|
+
await WorkletLifecycleService.startWorklet(networkConfigs)
|
|
393
|
+
if (!cancelled) {
|
|
394
|
+
log('[WdkAppProvider] Worklet started successfully')
|
|
395
|
+
}
|
|
396
|
+
} catch (error) {
|
|
397
|
+
if (!cancelled) {
|
|
398
|
+
const err = normalizeError(error, true, {
|
|
399
|
+
component: 'WdkAppProvider',
|
|
400
|
+
operation: 'workletInitialization',
|
|
401
|
+
})
|
|
402
|
+
logError('[WdkAppProvider] Failed to initialize worklet:', error)
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
initializeWorklet()
|
|
408
|
+
|
|
409
|
+
// Cleanup function to prevent state updates if component unmounts
|
|
410
|
+
return () => {
|
|
411
|
+
cancelled = true
|
|
412
|
+
}
|
|
413
|
+
}, [isWorkletInitialized, isWorkletLoading, isWorkletStarted])
|
|
414
|
+
// Note: networkConfigs removed from deps - it's a prop that should be stable for app lifetime
|
|
415
|
+
// and doesn't need to trigger worklet re-initialization
|
|
416
|
+
|
|
417
|
+
// Consolidated effect: Sync wallet loading state with activeWalletId, addresses, and errors
|
|
418
|
+
//
|
|
419
|
+
// IMPORTANT: This effect MUST remain consolidated to prevent race conditions.
|
|
420
|
+
// Multiple interdependent state changes (activeWalletId, addresses, loadingState, errors)
|
|
421
|
+
// must be evaluated atomically in a single effect. Splitting into multiple effects would
|
|
422
|
+
// allow them to run with stale state, causing race conditions.
|
|
423
|
+
//
|
|
424
|
+
// State synchronization logic:
|
|
425
|
+
// 1. If activeWalletId is cleared, reset to not_loaded
|
|
426
|
+
// 2. If activeWalletId changes, handle wallet switch (check if addresses exist)
|
|
427
|
+
// 3. If wallet has addresses but state is not_loaded, transition to loading first
|
|
428
|
+
// 4. If wallet is loading and addresses appear, mark as ready (initialization complete)
|
|
429
|
+
// 5. If error occurs, update error state (only if tracking the correct wallet)
|
|
430
|
+
//
|
|
431
|
+
// All conditions are checked in order with early returns to ensure atomic evaluation.
|
|
432
|
+
useEffect(() => {
|
|
433
|
+
// EARLY EXIT: Skip automatic wallet initialization if disabled (e.g., when logged out)
|
|
434
|
+
if (!enableAutoInitialization) {
|
|
435
|
+
// Clear authentication error flag when auto-init is disabled (e.g., logout)
|
|
436
|
+
if (authErrorRef.current) {
|
|
437
|
+
log('[WdkAppProvider] Clearing authentication error flag - auto-init disabled')
|
|
438
|
+
authErrorRef.current = null
|
|
439
|
+
}
|
|
440
|
+
return
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// VALIDATION 1: User identity must be confirmed before auto-initialization
|
|
444
|
+
// If currentUserId is undefined/null, we don't know which user is logged in yet
|
|
445
|
+
// Wait until user identity is confirmed to prevent wrong wallet initialization
|
|
446
|
+
if (currentUserId === undefined || currentUserId === null) {
|
|
447
|
+
log('[WdkAppProvider] Waiting for user identity confirmation before auto-init', {
|
|
448
|
+
hasActiveWalletId: !!activeWalletId,
|
|
449
|
+
})
|
|
450
|
+
return
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// VALIDATION 2: If activeWalletId doesn't match currentUserId, set it to correct user
|
|
454
|
+
// This allows useOnboarding to initialize the correct wallet without interference
|
|
455
|
+
if (activeWalletId !== currentUserId) {
|
|
456
|
+
log('[WdkAppProvider] Setting activeWalletId to current user', {
|
|
457
|
+
activeWalletId,
|
|
458
|
+
currentUserId,
|
|
459
|
+
})
|
|
460
|
+
|
|
461
|
+
// Set activeWalletId to current user - let useOnboarding handle initialization
|
|
462
|
+
walletStore.setState({
|
|
463
|
+
activeWalletId: currentUserId,
|
|
464
|
+
})
|
|
465
|
+
|
|
466
|
+
// Clear auth error flag to allow fresh authentication
|
|
467
|
+
if (authErrorRef.current) {
|
|
468
|
+
authErrorRef.current = null
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Return and let the effect re-run with correct activeWalletId
|
|
472
|
+
return
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// EARLY EXIT: Skip if we have an authentication error to prevent infinite retry loop
|
|
476
|
+
// Authentication errors (biometric failures) require user intervention, not automatic retry
|
|
477
|
+
if (authErrorRef.current) {
|
|
478
|
+
log('[WdkAppProvider] Skipping auto-initialization due to authentication error', {
|
|
479
|
+
error: authErrorRef.current,
|
|
480
|
+
})
|
|
481
|
+
return
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const currentWalletId = getWalletIdFromLoadingState(walletLoadingState)
|
|
485
|
+
const hasAddresses = !!(walletAddresses && Object.keys(walletAddresses).length > 0)
|
|
486
|
+
|
|
487
|
+
// Handle activeWalletId cleared
|
|
488
|
+
if (shouldResetToNotLoaded(activeWalletId, walletLoadingState)) {
|
|
489
|
+
log('[WdkAppProvider] Active wallet cleared, resetting wallet state')
|
|
490
|
+
// Clear authentication error flag when wallet is reset
|
|
491
|
+
if (authErrorRef.current) {
|
|
492
|
+
log('[WdkAppProvider] Clearing authentication error flag on wallet reset')
|
|
493
|
+
authErrorRef.current = null
|
|
494
|
+
}
|
|
495
|
+
walletStore.setState((prev) => updateWalletLoadingState(prev, { type: 'not_loaded' }))
|
|
496
|
+
return
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Ensure activeWalletId is not null before proceeding (shouldn't happen after check above, but TypeScript needs it)
|
|
500
|
+
if (!activeWalletId) {
|
|
501
|
+
return
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Handle wallet switching (activeWalletId changed to different wallet)
|
|
505
|
+
const switchDecision = getWalletSwitchDecision(currentWalletId, activeWalletId, hasAddresses)
|
|
506
|
+
if (switchDecision.shouldSwitch) {
|
|
507
|
+
log('[WdkAppProvider] Active wallet changed', {
|
|
508
|
+
from: currentWalletId,
|
|
509
|
+
to: activeWalletId,
|
|
510
|
+
hasAddresses,
|
|
511
|
+
isWorkletStarted,
|
|
512
|
+
shouldMarkReady: switchDecision.shouldMarkReady,
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
// When switching to a wallet, trigger proper initialization with biometrics
|
|
516
|
+
if (isWorkletStarted) {
|
|
517
|
+
// Skip if wallet is already initializing to prevent duplicate biometric prompts
|
|
518
|
+
if (isWalletInitializing) {
|
|
519
|
+
log('[WdkAppProvider] Skipping wallet switch initialization - already in progress', {
|
|
520
|
+
activeWalletId,
|
|
521
|
+
walletLoadingState: walletLoadingState.type,
|
|
522
|
+
})
|
|
523
|
+
return
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
log('[WdkAppProvider] Wallet switch detected - triggering initialization', {
|
|
527
|
+
activeWalletId,
|
|
528
|
+
hasAddresses,
|
|
529
|
+
})
|
|
530
|
+
|
|
531
|
+
// Check if wallet already exists before deciding to create new or load existing
|
|
532
|
+
;(async () => {
|
|
533
|
+
try {
|
|
534
|
+
const walletExists = await hasWallet(activeWalletId)
|
|
535
|
+
const shouldCreateNew = !walletExists
|
|
536
|
+
|
|
537
|
+
log('[WdkAppProvider] Wallet existence check', {
|
|
538
|
+
activeWalletId,
|
|
539
|
+
walletExists,
|
|
540
|
+
shouldCreateNew,
|
|
541
|
+
})
|
|
542
|
+
|
|
543
|
+
// Call initializeWallet to trigger biometrics and properly load the wallet
|
|
544
|
+
// This will transition state to 'checking' immediately, preventing duplicate calls
|
|
545
|
+
await initializeWalletRef.current({ createNew: shouldCreateNew, walletId: activeWalletId })
|
|
546
|
+
log('[WdkAppProvider] Wallet initialized successfully after switch')
|
|
547
|
+
} catch (error) {
|
|
548
|
+
logError('[WdkAppProvider] Failed to initialize wallet after switch:', error)
|
|
549
|
+
// Error will be handled by the error handling logic below
|
|
550
|
+
}
|
|
551
|
+
})()
|
|
552
|
+
} else {
|
|
553
|
+
// Worklet not started yet - reset to not_loaded and wait
|
|
554
|
+
walletStore.setState((prev) => updateWalletLoadingState(prev, {
|
|
555
|
+
type: 'not_loaded'
|
|
556
|
+
}))
|
|
557
|
+
}
|
|
558
|
+
// This effect will run again when isWorkletStarted becomes true
|
|
559
|
+
return
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Handle case where addresses exist but state is not_loaded (from persistence/app restart)
|
|
563
|
+
// This means wallet was cached but worklet is not initialized with credentials
|
|
564
|
+
// We need to trigger proper initialization with biometrics
|
|
565
|
+
// Only trigger if we're not already in the process of loading (checking/loading state)
|
|
566
|
+
if (walletLoadingState.type === 'not_loaded' && hasAddresses && activeWalletId && isWorkletStarted) {
|
|
567
|
+
// Skip if wallet is already initializing to prevent duplicate biometric prompts
|
|
568
|
+
if (isWalletInitializing) {
|
|
569
|
+
log('[WdkAppProvider] Skipping cached wallet initialization - already in progress', {
|
|
570
|
+
activeWalletId,
|
|
571
|
+
walletLoadingState: walletLoadingState.type,
|
|
572
|
+
})
|
|
573
|
+
return
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
log('[WdkAppProvider] Cached wallet detected on restart - triggering initialization with biometrics', {
|
|
577
|
+
activeWalletId,
|
|
578
|
+
hasAddresses,
|
|
579
|
+
isWorkletStarted,
|
|
580
|
+
isWorkletInitialized,
|
|
581
|
+
walletLoadingState: walletLoadingState.type
|
|
582
|
+
})
|
|
583
|
+
|
|
584
|
+
// Check if wallet already exists before deciding to create new or load existing
|
|
585
|
+
;(async () => {
|
|
586
|
+
try {
|
|
587
|
+
const walletExists = await hasWallet(activeWalletId)
|
|
588
|
+
const shouldCreateNew = !walletExists
|
|
589
|
+
|
|
590
|
+
log('[WdkAppProvider] Wallet existence check', {
|
|
591
|
+
activeWalletId,
|
|
592
|
+
walletExists,
|
|
593
|
+
shouldCreateNew,
|
|
594
|
+
})
|
|
595
|
+
|
|
596
|
+
// Call initializeWallet to trigger biometrics and properly load the wallet
|
|
597
|
+
// This will transition state to 'checking' immediately, preventing duplicate calls
|
|
598
|
+
// Then it will go through: checking -> loading -> ready
|
|
599
|
+
await initializeWalletRef.current({ createNew: shouldCreateNew, walletId: activeWalletId })
|
|
600
|
+
log('[WdkAppProvider] Wallet initialized successfully from cache')
|
|
601
|
+
} catch (error) {
|
|
602
|
+
logError('[WdkAppProvider] Failed to initialize wallet from cache:', error)
|
|
603
|
+
// Error will be handled by the error handling logic below
|
|
604
|
+
}
|
|
605
|
+
})()
|
|
606
|
+
|
|
607
|
+
return
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// Handle case where activeWalletId exists but no addresses (after logout/app restart)
|
|
611
|
+
// This means the user logged in but addresses were cleared during logout
|
|
612
|
+
// We need to trigger initialization to load the wallet
|
|
613
|
+
if (walletLoadingState.type === 'not_loaded' && !hasAddresses && activeWalletId && isWorkletStarted) {
|
|
614
|
+
// Skip if wallet is already initializing to prevent duplicate biometric prompts
|
|
615
|
+
if (isWalletInitializing) {
|
|
616
|
+
log('[WdkAppProvider] Skipping wallet initialization - already in progress', {
|
|
617
|
+
activeWalletId,
|
|
618
|
+
walletLoadingState: walletLoadingState.type,
|
|
619
|
+
})
|
|
620
|
+
return
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
log('[WdkAppProvider] Active wallet detected without addresses - triggering initialization', {
|
|
624
|
+
activeWalletId,
|
|
625
|
+
hasAddresses,
|
|
626
|
+
isWorkletStarted,
|
|
627
|
+
walletLoadingState: walletLoadingState.type
|
|
628
|
+
})
|
|
629
|
+
|
|
630
|
+
// Check if wallet already exists before deciding to create new or load existing
|
|
631
|
+
;(async () => {
|
|
632
|
+
try {
|
|
633
|
+
const walletExists = await hasWallet(activeWalletId)
|
|
634
|
+
const shouldCreateNew = !walletExists
|
|
635
|
+
|
|
636
|
+
log('[WdkAppProvider] Wallet existence check', {
|
|
637
|
+
activeWalletId,
|
|
638
|
+
walletExists,
|
|
639
|
+
shouldCreateNew,
|
|
640
|
+
})
|
|
641
|
+
|
|
642
|
+
// Call initializeWallet to trigger biometrics and properly load the wallet
|
|
643
|
+
// This will transition state to 'checking' immediately, preventing duplicate calls
|
|
644
|
+
await initializeWalletRef.current({ createNew: shouldCreateNew, walletId: activeWalletId })
|
|
645
|
+
log('[WdkAppProvider] Wallet initialized successfully')
|
|
646
|
+
} catch (error) {
|
|
647
|
+
logError('[WdkAppProvider] Failed to initialize wallet:', error)
|
|
648
|
+
// Error will be handled by the error handling logic below
|
|
649
|
+
}
|
|
650
|
+
})()
|
|
651
|
+
|
|
652
|
+
return
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// Handle ready state transitions
|
|
656
|
+
if (shouldMarkWalletAsReady(walletLoadingState, hasAddresses, currentWalletId, activeWalletId, isWorkletInitialized)) {
|
|
657
|
+
log('[WdkAppProvider] Wallet ready', { activeWalletId })
|
|
658
|
+
walletStore.setState((prev) => updateWalletLoadingState(prev, {
|
|
659
|
+
type: 'ready',
|
|
660
|
+
identifier: activeWalletId
|
|
661
|
+
}))
|
|
662
|
+
return
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// Handle errors from useWalletManager
|
|
666
|
+
if (shouldHandleError(walletManagerError, currentWalletId, activeWalletId, walletLoadingState)) {
|
|
667
|
+
log('[WdkAppProvider] Wallet operation error detected', {
|
|
668
|
+
activeWalletId,
|
|
669
|
+
error: walletManagerError
|
|
670
|
+
})
|
|
671
|
+
const error = new Error(walletManagerError!)
|
|
672
|
+
|
|
673
|
+
// Check if this is an authentication error (biometric failure)
|
|
674
|
+
const errorMessage = walletManagerError?.toLowerCase() || ''
|
|
675
|
+
const isAuthError =
|
|
676
|
+
errorMessage.includes('authentication') ||
|
|
677
|
+
errorMessage.includes('biometric') ||
|
|
678
|
+
errorMessage.includes('user cancel')
|
|
679
|
+
|
|
680
|
+
if (isAuthError && !authErrorRef.current) {
|
|
681
|
+
log('[WdkAppProvider] Authentication error detected - preventing auto-retry', {
|
|
682
|
+
error: walletManagerError,
|
|
683
|
+
})
|
|
684
|
+
authErrorRef.current = walletManagerError || 'Authentication failed'
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
walletStore.setState((prev) => updateWalletLoadingState(prev, {
|
|
688
|
+
type: 'error',
|
|
689
|
+
identifier: activeWalletId,
|
|
690
|
+
error
|
|
691
|
+
}))
|
|
692
|
+
}
|
|
693
|
+
}, [enableAutoInitialization, currentUserId, activeWalletId, walletLoadingState, walletAddresses, walletManagerError, isWalletInitializing, isWorkletStarted, isWorkletInitialized])
|
|
694
|
+
// Note: walletStore removed from deps - it's a singleton that never changes
|
|
695
|
+
// Note: initializeWallet removed from deps and accessed via ref to prevent infinite loop
|
|
696
|
+
|
|
697
|
+
// Retry initialization
|
|
698
|
+
const retry = useCallback(() => {
|
|
699
|
+
log('[WdkAppProvider] Retrying initialization...')
|
|
700
|
+
// Clear authentication error flag to allow retry
|
|
701
|
+
if (authErrorRef.current) {
|
|
702
|
+
log('[WdkAppProvider] Clearing authentication error flag for retry')
|
|
703
|
+
authErrorRef.current = null
|
|
704
|
+
}
|
|
705
|
+
if (isWalletErrorState(walletLoadingState)) {
|
|
706
|
+
walletStore.setState((prev) => updateWalletLoadingState(prev, { type: 'not_loaded' }))
|
|
707
|
+
}
|
|
708
|
+
}, [walletLoadingState])
|
|
709
|
+
// Note: walletStore removed from deps - it's a singleton that never changes
|
|
710
|
+
|
|
711
|
+
// Convenience getters
|
|
712
|
+
const isInitializing = useMemo(() => isAppInProgressStatus(status), [status])
|
|
713
|
+
const isReady = useMemo(() => isAppReadyStatus(status), [status])
|
|
714
|
+
|
|
715
|
+
// Get wallet error from wallet state
|
|
716
|
+
const walletError = walletLoadingState.type === 'error' ? walletLoadingState.error : null
|
|
717
|
+
const initializationError = workletError ? new Error(workletError) : walletError
|
|
718
|
+
|
|
719
|
+
// Get walletExists from wallet state
|
|
720
|
+
const walletExists = useMemo(() => {
|
|
721
|
+
if (walletLoadingState.type === 'loading') {
|
|
722
|
+
return walletLoadingState.walletExists
|
|
723
|
+
}
|
|
724
|
+
if (walletLoadingState.type === 'ready') {
|
|
725
|
+
return true // Wallet is loaded, so it exists
|
|
726
|
+
}
|
|
727
|
+
return null
|
|
728
|
+
}, [walletLoadingState])
|
|
729
|
+
|
|
730
|
+
// Loading wallet ID (transient, during loading operations)
|
|
731
|
+
const loadingWalletId = useMemo(() => {
|
|
732
|
+
if (walletLoadingState.type === 'checking' || walletLoadingState.type === 'loading') {
|
|
733
|
+
return walletLoadingState.identifier
|
|
734
|
+
}
|
|
735
|
+
return null
|
|
736
|
+
}, [walletLoadingState])
|
|
737
|
+
|
|
738
|
+
const contextValue: WdkAppContextValue = useMemo(
|
|
739
|
+
() => ({
|
|
740
|
+
status,
|
|
741
|
+
workletStatus,
|
|
742
|
+
workletState,
|
|
743
|
+
walletState: walletStateObject,
|
|
744
|
+
isInitializing,
|
|
745
|
+
isReady,
|
|
746
|
+
activeWalletId,
|
|
747
|
+
loadingWalletId,
|
|
748
|
+
walletExists,
|
|
749
|
+
error: initializationError,
|
|
750
|
+
retry,
|
|
751
|
+
}),
|
|
752
|
+
[status, workletStatus, workletState, walletStateObject, isInitializing, isReady, activeWalletId, loadingWalletId, walletExists, initializationError, retry]
|
|
753
|
+
)
|
|
754
|
+
|
|
755
|
+
return (
|
|
756
|
+
<QueryClientProvider client={queryClient}>
|
|
757
|
+
<WdkAppContext.Provider value={contextValue}>{children}</WdkAppContext.Provider>
|
|
758
|
+
</QueryClientProvider>
|
|
759
|
+
)
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// Export context for use by the useWdkApp hook
|
|
763
|
+
export { WdkAppContext }
|
|
764
|
+
|