import React, { useEffect, useMemo, useRef, useState } from 'react'; import { RouteProp, useRoute } from '@react-navigation/native'; import { ActivityIndicator, FlatList, LayoutAnimation, StyleSheet, View } from 'react-native'; import IdleTimerManager from 'react-native-idle-timer'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; import { BlueButtonLink, BlueFormLabel, BlueSpacing10, BlueSpacing20, BlueText } from '../../BlueComponents'; import { HDSegwitBech32Wallet, WatchOnlyWallet } from '../../class'; import startImport from '../../class/wallet-import'; import presentAlert from '../../components/Alert'; import Button from '../../components/Button'; import SafeArea from '../../components/SafeArea'; import { useTheme } from '../../components/themes'; import WalletToImport from '../../components/WalletToImport'; import prompt from '../../helpers/prompt'; import loc from '../../loc'; import { useExtendedNavigation } from '../../hooks/useExtendedNavigation'; import { useStorage } from '../../hooks/context/useStorage'; import { AddWalletStackParamList } from '../../navigation/AddWalletStack'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { THDWalletForWatchOnly, TWallet } from '../../class/wallets/types'; import { navigate } from '../../NavigationService'; type RouteProps = RouteProp; type NavigationProp = NativeStackNavigationProp; type TReturn = { cancelled?: boolean; stopped?: boolean; wallets: TWallet[]; }; type ImportTask = { promise: Promise; stop: () => void; }; const ImportWalletDiscovery: React.FC = () => { const navigation = useExtendedNavigation(); const { colors } = useTheme(); const route = useRoute(); const { importText, askPassphrase, searchAccounts } = route.params; const task = useRef(null); const { addAndSaveWallet } = useStorage(); const [loading, setLoading] = useState(true); const [wallets, setWallets] = useState>([]); const [password, setPassword] = useState(); const [selected, setSelected] = useState(0); const [progress, setProgress] = useState(); const importing = useRef(false); const bip39 = useMemo(() => { const hd = new HDSegwitBech32Wallet(); hd.setSecret(importText); return hd.validateMnemonic(); }, [importText]); const stylesHook = StyleSheet.create({ root: { backgroundColor: colors.elevated, }, center: { backgroundColor: colors.elevated, }, }); const saveWallet = (wallet: TWallet) => { if (importing.current) return; importing.current = true; addAndSaveWallet(wallet); navigate('WalletsList'); }; useEffect(() => { const onProgress = (data: string) => setProgress(data); const onWallet = (wallet: TWallet | THDWalletForWatchOnly) => { LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); const id = wallet.getID(); let subtitle: string | undefined; try { // For watch-only wallets, display the descriptor or xpub if (wallet.type === WatchOnlyWallet.type) { if (wallet.isHd() && wallet.getSecret()) { subtitle = wallet.getSecret(); // Display descriptor } else { subtitle = wallet.getAddress(); // Display address } } else { subtitle = (wallet as THDWalletForWatchOnly).getDerivationPath?.(); } } catch (e) {} setWallets(w => [...w, { wallet, subtitle: subtitle || '', id }]); }; const onPassword = async (title: string, subtitle: string) => { try { const pass = await prompt(title, subtitle); setPassword(pass); return pass; } catch (e: any) { if (e.message === 'Cancel Pressed') { navigation.goBack(); } throw e; } }; IdleTimerManager.setIdleTimerDisabled(true); task.current = startImport(importText, askPassphrase, searchAccounts, onProgress, onWallet, onPassword); task.current.promise .then(({ cancelled, wallets: w }) => { if (cancelled) return; if (w.length === 1) saveWallet(w[0]); // Instantly save wallet if only one has been discovered if (w.length === 0) { triggerHapticFeedback(HapticFeedbackTypes.ImpactLight); } }) .catch(e => { console.warn('import error', e); presentAlert({ title: 'Import error', message: e.message }); }) .finally(() => { LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); setLoading(false); IdleTimerManager.setIdleTimerDisabled(false); }); return () => { if (task.current) { task.current.stop(); } }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const handleCustomDerivation = () => { if (task.current) { task.current.stop(); } navigation.navigate('ImportCustomDerivationPath', { importText, password }); }; const renderItem = ({ item, index }: { item: { wallet: TWallet; subtitle: string; id: string }; index: number }) => ( { setSelected(index); triggerHapticFeedback(HapticFeedbackTypes.Selection); }} /> ); const keyExtractor = (w: { id: string }) => w.id; const ListHeaderComponent = useMemo( () => ( <> {wallets.length > 0 ? ( <> {loc.wallets.import_discovery_subtitle} ) : null} ), [wallets.length], ); const ListEmptyComponent = useMemo( () => ( {loc.wallets.import_discovery_no_wallets} ), [], ); return ( {loading && ( <> {progress} )} {bip39 && ( )}