import React , { FunctionComponent , useState } from "react" ; import { PageWithScrollView } from "../../../components/page" ; import { observer } from "mobx-react-lite" ; import { RouteProp , useRoute } from "@react-navigation/native" ; import { RegisterConfig } from "@keplr-wallet/hooks" ; import { useStyle } from "../../../styles" ; import { useSmartNavigation } from "../../../navigation" ; import { Controller , useForm } from "react-hook-form" ; import { TextInput } from "../../../components/input" ; import { StyleSheet , View } from "react-native" ; import { Button } from "../../../components/button" ; import Clipboard from "expo-clipboard" ; import { useStore } from "../../../stores" ; import { BIP44AdvancedButton , useBIP44Option } from "../bip44" ; import { Buffer } from "buffer/" ; const bip39 = require ( "bip39" ) ; function isPrivateKey ( str ) { if ( str . startsWith ( "0x" ) ) { return true ; } if ( str . length === 64 ) { try { return Buffer . from ( str , "hex" ) . length === 32 ; } catch { return false ; } } return false ; } function trimWordsStr ( str ) { str = str . trim ( ) ; const splited = str . split ( /\s+/ ) ; const words = splited . map ( ( word ) => word . trim ( ) ) . filter ( ( word ) => word . trim ( ) . length > 0 ) ; return words . join ( " " ) ; } interface FormData { mnemonic : string ; name : string ; password : string ; confirmPassword : string ; } export const RecoverMnemonicScreen = observer ( ( ) => { const route = useRoute < RouteProp < Record < string , { registerConfig : RegisterConfig ; } > , string > > ( ) ; const style = useStyle ( ) ; const { analyticsStore } = useStore ( ) ; const smartNavigation = useSmartNavigation ( ) ; const registerConfig = route . params . registerConfig ; const bip44Option = useBIP44Option ( ) ; const [ mode ] = useState ( registerConfig . mode ) ; const { control , handleSubmit , setFocus , setValue , getValues , formState : { errors } , } = useForm < FormData > ( ) ; const [ isCreating , setIsCreating ] = useState ( false ) ; const submit = handleSubmit ( async ( ) => { setIsCreating ( true ) ; const mnemonic = trimWordsStr ( getValues ( "mnemonic" ) ) ; if ( ! isPrivateKey ( mnemonic ) ) { await registerConfig . createMnemonic ( getValues ( "name" ) , mnemonic , getValues ( "password" ) , bip44Option . bip44HDPath ) ; } else { const privateKey = Buffer . from ( mnemonic . trim ( ) . replace ( "0x" , "" ) , "hex" ) ; await registerConfig . createPrivateKey ( getValues ( "name" ) , privateKey , getValues ( "password" ) ) ; } analyticsStore . setUserId ( ) ; analyticsStore . setUserProperties ( { registerType : "seed" , } ) ; analyticsStore . logEvent ( "Import account finished" , { accountType : "mnemonic" , } ) ; smartNavigation . reset ( { index : 0 , routes : [ { name : "Register.End" , params : { password : getValues ( "password" ) , } , } , ] , } ) ; } ) ; return ( < PageWithScrollView contentContainerStyle = { style . get ( "flex-grow-1" ) } style = { style . flatten ( [ "padding-x-page" ] ) } > < Controller control = { control } rules = { { required : "Mnemonic is required" , validate : ( value ) => { value = trimWordsStr ( value ) ; if ( ! isPrivateKey ( value ) ) { if ( value . split ( " " ) . length < 8 ) { return "Too short mnemonic" ; } if ( ! bip39 . validateMnemonic ( value ) ) { return "Invalid mnemonic" ; } } else { value = value . replace ( "0x" , "" ) ; if ( value . length !== 64 ) { return "Invalid length of private key" ; } try { if ( Buffer . from ( value , "hex" ) . toString ( "hex" ) . toLowerCase ( ) !== value . toLowerCase ( ) ) { return "Invalid private key" ; } } catch { return "Invalid private key" ; } } } , } } render = { ( { field : { onChange , onBlur , value , ref } } ) => { return ( < TextInput label = "Mnemonic seed" returnKeyType = "next" multiline = { true } numberOfLines = { 4 } inputContainerStyle = { style . flatten ( [ "padding-x-20" , "padding-y-16" , ] ) } bottomInInputContainer = { < View style = { style . flatten ( [ "flex-row" ] ) } > < View style = { style . flatten ( [ "flex-1" ] ) } /> < Button containerStyle = { style . flatten ( [ "height-36" ] ) } style = { style . flatten ( [ "padding-x-12" ] ) } mode = "text" text = "Paste" onPress = { async ( ) => { const text = await Clipboard . getStringAsync ( ) ; if ( text ) { setValue ( "mnemonic" , text , { shouldValidate : true , } ) ; setFocus ( "name" ) ; } } } /> < /View>} style={StyleSheet.flatten([ style . flatten ( [ "h6" , "color-text-black-medium" ] ) , { minHeight : 20 * 4 , textAlignVertical : "top" , } , ] ) } onSubmitEditing = { ( ) => { setFocus ( "name" ) ; } } error = { errors . mnemonic ?. message } onBlur = { onBlur } onChangeText = { onChange } value = { value } ref = { ref } / > ) ; } } name = "mnemonic" defaultValue = "" / > < Controller control = { control } rules = { { required : "Name is required" , } } render = { ( { field : { onChange , onBlur , value , ref } } ) => { return ( < TextInput label = "Wallet nickname" containerStyle = { style . flatten ( [ "padding-bottom-6" ] ) } returnKeyType = { mode === "add" ? "done" : "next" } onSubmitEditing = { ( ) => { if ( mode === "add" ) { submit ( ) ; } if ( mode === "create" ) { setFocus ( "password" ) ; } } } error = { errors . name ?. message } onBlur = { onBlur } onChangeText = { onChange } value = { value } ref = { ref } / > ) ; } } name = "name" defaultValue = "" / > < BIP44AdvancedButton bip44Option = { bip44Option } /> { mode === "create" ? ( < React . Fragment > < Controller control = { control } rules = { { required : "Password is required" , validate : ( value ) => { if ( value . length < 8 ) { return "Password must be longer than 8 characters" ; } } , } } render = { ( { field : { onChange , onBlur , value , ref } } ) => { return ( < TextInput label = "Password" returnKeyType = "next" secureTextEntry = { true } onSubmitEditing = { ( ) => { setFocus ( "confirmPassword" ) ; } } error = { errors . password ?. message } onBlur = { onBlur } onChangeText = { onChange } value = { value } ref = { ref } / > ) ; } } name = "password" defaultValue = "" / > < Controller control = { control } rules = { { required : "Confirm password is required" , validate : ( value ) => { if ( value . length < 8 ) { return "Password must be longer than 8 characters" ; } if ( getValues ( "password" ) !== value ) { return "Password doesn't match" ; } } , } } render = { ( { field : { onChange , onBlur , value , ref } } ) => { return ( < TextInput label = "Confirm password" returnKeyType = "done" secureTextEntry = { true } onSubmitEditing = { ( ) => { submit ( ) ; } } error = { errors . confirmPassword ?. message } onBlur = { onBlur } onChangeText = { onChange } value = { value } ref = { ref } / > ) ; } } name = "confirmPassword" defaultValue = "" / > < /React.Fragment>) : null} < View style = { style . flatten ( [ "flex-1" ] ) } / > < Button text = "Next" size = "large" loading = { isCreating } onPress = { submit } / > { } < View style = { style . flatten ( [ "height-page-pad" ] ) } / > < /PageWithScrollView>); } ) ;