feat(vault): show mnemonic flow will show at first time ; or user reset vault state;

This commit is contained in:
Ada
2026-02-01 11:02:14 -08:00
parent 7b8511f080
commit 8e6c621f7b
4 changed files with 150 additions and 27 deletions

View File

@@ -25,6 +25,8 @@ import { VaultAsset, VaultAssetType, Heir } from '../types';
import BiometricModal from '../components/common/BiometricModal';
import { useAuth } from '../context/AuthContext';
import { useVaultAssets } from '../hooks/useVaultAssets';
import { VAULT_STORAGE_KEYS } from '../config';
import { mnemonicToEntropy, splitSecret, serializeShare } from '../utils/sss';
// Asset type configuration with nautical theme
const assetTypeConfig: Record<VaultAssetType, { icon: string; iconType: 'ionicons' | 'feather' | 'material' | 'fontawesome5'; label: string }> = {
@@ -168,6 +170,7 @@ export default function VaultScreen() {
const [accountProvider, setAccountProvider] = useState<'bank' | 'steam' | 'facebook' | 'custom'>('bank');
const [showMnemonic, setShowMnemonic] = useState(false);
const [showLegacyAssignCta, setShowLegacyAssignCta] = useState(false);
const [hasS0, setHasS0] = useState<boolean | null>(null);
const [mnemonicWords, setMnemonicWords] = useState<string[]>([]);
const [mnemonicParts, setMnemonicParts] = useState<string[][]>([]);
const [mnemonicStep, setMnemonicStep] = useState<1 | 2 | 3 | 4 | 5>(1);
@@ -185,15 +188,31 @@ export default function VaultScreen() {
const mnemonicRef = useRef<View>(null);
const progressTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
// Detect S0 (TEE/SE): if present, later open shows biometric only; if not, mnemonic flow
useEffect(() => {
if (!isUnlocked) {
const timer = setTimeout(() => {
setShowBiometric(true);
}, 500);
return () => clearTimeout(timer);
}
let cancelled = false;
AsyncStorage.getItem(VAULT_STORAGE_KEYS.SHARE_DEVICE)
.then((v) => {
if (!cancelled) setHasS0(!!v);
})
.catch(() => {
if (!cancelled) setHasS0(false);
});
return () => { cancelled = true; };
}, []);
// Only when S0 exists and vault not unlocked: show biometric after short delay.
// When hasS0 is false or null, never show biometric — go straight to mnemonic flow.
useEffect(() => {
if (hasS0 !== true) {
setShowBiometric(false);
return;
}
if (isUnlocked) return;
const timer = setTimeout(() => setShowBiometric(true), 500);
return () => clearTimeout(timer);
}, [isUnlocked, hasS0]);
useEffect(() => {
if (isUnlocked) {
Animated.timing(fadeAnim, {
@@ -335,28 +354,49 @@ export default function VaultScreen() {
const handleHeirDecision = (share: boolean) => {
// Placeholder for future heir flow
setShowMnemonic(false);
setIsUnlocked(true);
finishMnemonicThenShowBiometric();
};
const handleSubmitAssignment = () => {
// Placeholder for submitting assignment to API
setShowMnemonic(false);
setIsUnlocked(true);
finishMnemonicThenShowBiometric();
};
const handleHeirYes = () => {
setShowMnemonic(false);
setIsUnlocked(true);
finishMnemonicThenShowBiometric();
setShowLegacyAssignCta(true);
};
const handleHeirNo = () => {
setShowMnemonic(false);
setIsUnlocked(true);
finishMnemonicThenShowBiometric();
setShowLegacyAssignCta(true);
};
/** After mnemonic flow: persist S0 (simulated TEE/SE via AsyncStorage), then show biometric; unlock on success */
const finishMnemonicThenShowBiometric = async () => {
try {
const wordList = bip39.wordlists.english;
const entropy = mnemonicToEntropy(mnemonicWords, wordList);
const shares = splitSecret(entropy);
const s0 = shares[0]; // device share (S0)
// S0 is stored in AsyncStorage under SHARE_DEVICE — app-level storage, not hardware TEE/SE
await AsyncStorage.setItem(VAULT_STORAGE_KEYS.SHARE_DEVICE, serializeShare(s0));
await AsyncStorage.setItem(VAULT_STORAGE_KEYS.INITIALIZED, '1');
setHasS0(true);
setShowMnemonic(false);
setShowBiometric(true);
} catch (e) {
if (__DEV__) console.warn('finishMnemonicThenShowBiometric', e);
setShowMnemonic(false);
setShowBiometric(true);
}
};
const handleBiometricSuccess = () => {
setShowBiometric(false);
setIsUnlocked(true);
};
const handleOpenLegacyAssign = () => {
setSelectedHeir(null);
setSelectedHeirAsset(null);
@@ -888,7 +928,7 @@ export default function VaultScreen() {
<TouchableOpacity
style={styles.unlockButton}
onPress={() => setShowBiometric(true)}
onPress={hasS0 ? () => setShowBiometric(true) : handleUnlock}
activeOpacity={0.8}
>
<LinearGradient
@@ -897,8 +937,10 @@ export default function VaultScreen() {
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
>
<Ionicons name="finger-print" size={20} color={colors.vault.background} />
<Text style={styles.unlockButtonText}>Captain's Verification</Text>
<Ionicons name={hasS0 ? 'finger-print' : 'key'} size={20} color={colors.vault.background} />
<Text style={styles.unlockButtonText}>
{hasS0 === true ? "Captain's Verification" : hasS0 === false ? 'Enter Vault' : 'Loading…'}
</Text>
</LinearGradient>
</TouchableOpacity>
</View>
@@ -906,8 +948,8 @@ export default function VaultScreen() {
</LinearGradient>
<BiometricModal
visible={showBiometric}
onSuccess={handleUnlock}
visible={hasS0 === true && showBiometric}
onSuccess={handleBiometricSuccess}
onCancel={() => setShowBiometric(false)}
title="Enter the Vault"
message="Verify your identity to access your treasures"