// Copyright 2026 Tether Operations Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* useWdkApp Hook
*
* Hook to access the WDK's top-level initialization state.
* It provides a simple state object to determine if the WDK is ready, loading, locked, etc.
* Must be used within WdkAppProvider.
*
* ## Status meanings
*
* - `INITIALIZING` - The worklet hasn't started yet, or there isn't enough
* information yet to report anything more specific (e.g. the worklet has
* started but no identity or wallets are known - nothing has been
* created/restored/unlocked this session, and nothing has told the SDK
* who the user is).
* - `REINITIALIZING` - The worklet is being manually reinitialized via
* `reinitializeWdk()`.
* - `NO_WALLET` - No wallet exists at all - confirmed empty, so this is a
* genuinely fresh device/user. Safe to route to onboarding
* (create/restore).
* - `LOCKED` - No wallet is currently unlocked. `walletId` is present when a
* specific wallet is targeted (e.g. `unlock()`/`switchWallet()` was called
* and is still mid-decrypt) and absent when a wallet is only known to
* exist but none is currently targeted (e.g. right after `lock()`).
* Either way, the correct action is the same: show your own unlock flow -
* treat `walletId` as an optional hint for labeling that flow, not as a
* signal to show a different screen.
* - `READY` - A wallet is fully unlocked and its identity has been confirmed
* to match what's actually loaded. Safe to render the main app.
* - `ERROR` - Something failed - inspect `error` for details. Can originate
* from either the worklet layer or a wallet operation (create/unlock/etc).
*
* @example
* ```tsx
* import { useWdkApp } from '@tetherto/wdk-react-native-core'
*
* function App() {
* const { state } = useWdkApp()
*
* switch (state.status) {
* case 'INITIALIZING':
* return
*
* case 'LOCKED':
* // walletId is an optional hint, not a different flow - fall back to
* // whatever wallet your own session logic already knows about.
* return
*
* case 'NO_WALLET':
* return
*
* case 'READY':
* return
*
* case 'ERROR':
* return
*
* default:
* return
* }
* }
* ```
*/
import { useCallback, useContext } from 'react'
import { WdkAppContext } from '../provider/WdkAppProvider'
import type { WdkAppContextValue } from '../provider/WdkAppProvider'
import { getWorkletStore } from '../store/workletStore'
import { WorkletLifecycleService } from '../services/workletLifecycleService'
import { log, logError } from '../utils/logger'
import { withOperationMutex } from '../utils/operationMutex'
import { createResolvablePromise } from '../utils/promise'
export interface UseWdkAppResult extends WdkAppContextValue {
reinitializeWdk: () => Promise
resetWallets: (blockchains: string[]) => Promise
}
/**
* Hook to access WdkAppProvider context
*
* @returns WdkApp context value with initialization state
* @throws Error if used outside WdkAppProvider
*/
export function useWdkApp(): UseWdkAppResult {
const context = useContext(WdkAppContext)
if (!context) {
throw new Error('useWdkApp must be used within WdkAppProvider')
}
const reinitializeWdk = useCallback(async () => {
return withOperationMutex('reinitializeWdk', async () => {
const ws = getWorkletStore().getState()
if (
!ws.isWorkletStarted ||
!ws.isInitialized ||
ws.isLoading
) {
log('[useWdkApp] Manual WDK reinit skipped due to state:', {
isWorkletStarted: ws.isWorkletStarted,
isInitialized: ws.isInitialized,
isLoading: ws.isLoading,
})
return
}
const workletStore = getWorkletStore();
log('[useWdkApp] Manually reinitialize WDK')
try {
workletStore.setState({
isInitialized: false,
isReinitialized: true,
wdkInitResult: null,
isWorkletInitializedPromise: createResolvablePromise(),
});
await WorkletLifecycleService.initializeWDK();
log('[useWdkApp] Manual WDK reinitialization done');
} catch (e) {
logError('[useWdkApp] Manual WDK reinitialization failed', e);
workletStore.setState({ isLoading: false });
}
})
}, [])
const resetWallets = useCallback(async (blockchains: string[]) => {
return withOperationMutex('reinitializeWdk', async () => {
const ws = getWorkletStore().getState()
if (
!ws.isWorkletStarted ||
!ws.isInitialized ||
ws.isLoading
) {
log('[useWdkApp] Reset wallets skipped due to state:', {
isWorkletStarted: ws.isWorkletStarted,
isInitialized: ws.isInitialized,
isLoading: ws.isLoading,
})
return
}
const workletStore = getWorkletStore()
log('[useWdkApp] Resetting wallets for blockchains:', blockchains)
try {
workletStore.setState({
isWorkletInitializedPromise: createResolvablePromise(),
})
await WorkletLifecycleService.resetWallets(blockchains)
log('[useWdkApp] Wallet reset done')
} catch (e) {
logError('[useWdkApp] Wallet reset failed', e)
workletStore.setState({ isLoading: false })
}
})
}, [])
return {
...context,
reinitializeWdk,
resetWallets,
}
}