Skip to content

Commit

Permalink
restore - pin setup (#426)
Browse files Browse the repository at this point in the history
* `Encrypted Wallet` - restore (#420)

* restore wallet followed with pin/biometric after input words

* minor update e2e

* recover tests

Co-authored-by: Ivan Lee <[email protected]>
  • Loading branch information
thedoublejay and ivan-zynesis authored Aug 5, 2021
1 parent 0ef3536 commit 5df6e2d
Show file tree
Hide file tree
Showing 10 changed files with 86 additions and 40 deletions.
2 changes: 1 addition & 1 deletion app/components/CreateWalletStepIndicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export function CreateWalletStepIndicator (props: StepIndicatorProps): JSX.Eleme
<StepNode step={1} current={current} />
{following()}
</View>
<View style={[tailwind('flex-row justify-between w-10/12 mt-1 font-medium')]}>
<View style={[tailwind('flex-row justify-between w-10/12 mt-1 font-medium pl-2')]}>
{descriptions()}
</View>
</View>
Expand Down
7 changes: 6 additions & 1 deletion app/components/OceanInterface/OceanInterface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,11 @@ export function OceanInterface (): JSX.Element | null {
})
broadcastTransaction(transaction.tx, client)
.then(async () => {
setTxUrl(getTransactionUrl(transaction.tx.txId))
try {
setTxUrl(getTransactionUrl(transaction.tx.txId))
} catch (e) {
Logging.error(e)
}
setTx({
...transaction,
title: translate('screens/OceanInterface', 'Waiting for confirmation')
Expand All @@ -133,6 +137,7 @@ export function OceanInterface (): JSX.Element | null {
.catch((e: Error) => {
const errMsg = `${e.message}. Txid: ${transaction.tx.txId}`
setError(new Error(errMsg))
Logging.error(e)
})
.finally(() => {
dispatch(ocean.actions.popTransaction())
Expand Down
8 changes: 4 additions & 4 deletions app/contexts/DeFiScanContext.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useContext, useMemo, createContext } from 'react'
import React, { createContext, useContext, useMemo } from 'react'
import { EnvironmentNetwork } from '../environment'
import { useNetworkContext } from './NetworkContext'

Expand Down Expand Up @@ -31,20 +31,20 @@ export function DeFiScanProvider (props: React.PropsWithChildren<any>): JSX.Elem
}

function getTxURLByNetwork (network: EnvironmentNetwork, txid: string): string {
const baseUrl = new URL(`https://defiscan.live/transactions/${txid}`)
let baseUrl = `https://defiscan.live/transactions/${txid}`

switch (network) {
case EnvironmentNetwork.MainNet:
// no-op: network param not required for MainNet
break

case EnvironmentNetwork.TestNet:
baseUrl.searchParams.set('network', EnvironmentNetwork.TestNet)
baseUrl = `${baseUrl}?network=${EnvironmentNetwork.TestNet}`
break

case EnvironmentNetwork.LocalPlayground:
case EnvironmentNetwork.RemotePlayground:
baseUrl.searchParams.set('network', EnvironmentNetwork.RemotePlayground)
baseUrl = `${baseUrl}?network=${EnvironmentNetwork.RemotePlayground}`
break
}

Expand Down
4 changes: 4 additions & 0 deletions app/screens/WalletNavigator/WalletNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { VerifyMnemonicWallet } from './screens/CreateWallet/VerifyMnemonicWalle
import { Onboarding } from './screens/Onboarding'
import { RestoreMnemonicWallet } from './screens/RestoreWallet/RestoreMnemonicWallet'

type PinCreationType = 'create' | 'restore'

export interface WalletParamList {
WalletOnboardingScreen: undefined
CreateMnemonicWallet: undefined
Expand All @@ -24,10 +26,12 @@ export interface WalletParamList {
PinCreation: {
pinLength: 4 | 6
words: string[]
type: PinCreationType
}
PinConfirmation: {
pin: string
words: string[]
type: PinCreationType
}

[key: string]: undefined | object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import { ActivityIndicator, ScrollView } from 'react-native'
import { Logging } from '../../../../api'
import { MnemonicEncrypted } from '../../../../api/wallet'
import { Text, View } from '../../../../components'
import { CREATE_STEPS, CreateWalletStepIndicator } from '../../../../components/CreateWalletStepIndicator'
import {
CREATE_STEPS,
CreateWalletStepIndicator,
RESTORE_STEPS
} from '../../../../components/CreateWalletStepIndicator'
import { PinTextInput } from '../../../../components/PinTextInput'
import { useNetworkContext } from '../../../../contexts/NetworkContext'
import { useWalletPersistenceContext } from '../../../../contexts/WalletPersistenceContext'
Expand All @@ -16,7 +20,7 @@ type Props = StackScreenProps<WalletParamList, 'PinConfirmation'>

export function PinConfirmation ({ route }: Props): JSX.Element {
const { network } = useNetworkContext()
const { pin, words } = route.params
const { pin, words, type } = route.params
const { setWallet } = useWalletPersistenceContext()
const [newPin, setNewPin] = useState('')

Expand All @@ -26,6 +30,7 @@ export function PinConfirmation ({ route }: Props): JSX.Element {
function verifyPin (input: string): void {
if (input.length !== pin.length) return
if (input !== pin) {
setNewPin('')
setInvalid(true)
return
} else {
Expand All @@ -46,8 +51,8 @@ export function PinConfirmation ({ route }: Props): JSX.Element {
return (
<ScrollView style={tailwind('w-full flex-1 flex-col bg-white')}>
<CreateWalletStepIndicator
current={3}
steps={CREATE_STEPS}
current={type === 'create' ? 3 : 2}
steps={type === 'create' ? CREATE_STEPS : RESTORE_STEPS}
style={tailwind('py-4 px-1')}
/>
<View style={tailwind('px-6 py-4 mb-6')}>
Expand All @@ -73,7 +78,7 @@ export function PinConfirmation ({ route }: Props): JSX.Element {
}
{
invalid && (
<Text style={tailwind('text-center text-error font-semibold text-sm')}>
<Text testID='wrong_passcode_text' style={tailwind('text-center text-error font-semibold text-sm')}>
{translate('screens/PinConfirmation', 'Wrong passcode entered')}
</Text>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import React, { useState } from 'react'
import { ScrollView } from 'react-native'
import { Text, View } from '../../../../components'
import { Button } from '../../../../components/Button'
import { CREATE_STEPS, CreateWalletStepIndicator } from '../../../../components/CreateWalletStepIndicator'
import {
CREATE_STEPS,
CreateWalletStepIndicator,
RESTORE_STEPS
} from '../../../../components/CreateWalletStepIndicator'
import { PinTextInput } from '../../../../components/PinTextInput'
import { tailwind } from '../../../../tailwind'
import { translate } from '../../../../translations'
Expand All @@ -15,20 +19,23 @@ type Props = StackScreenProps<WalletParamList, 'PinCreation'>

export function PinCreationScreen ({ route }: Props): JSX.Element {
const navigation = useNavigation<NavigationProp<WalletParamList>>()
const { pinLength, words } = route.params
const { pinLength, words, type } = route.params
const [newPin, setNewPin] = useState('')

return (
<ScrollView style={tailwind('w-full flex-1 flex-col bg-white')}>
<ScrollView
testID='screen_create_pin'
style={tailwind('w-full flex-1 flex-col bg-white')}
>
<CreateWalletStepIndicator
current={3}
steps={CREATE_STEPS}
current={type === 'create' ? 3 : 2}
steps={type === 'create' ? CREATE_STEPS : RESTORE_STEPS}
style={tailwind('py-4 px-1')}
/>
<View style={tailwind('px-6 py-4 mb-12')}>
<Text
style={tailwind('text-center font-semibold')}
>{translate('screens/PinCreation', 'Well done! Your wallet is created. Keep your wallet private and secure by creating a passcode for it.')}
>{translate('screens/PinCreation', `Well done! Your wallet is ${type === 'create' ? 'created' : 'restored'}. Keep your wallet private and secure by creating a passcode for it.`)}
</Text>
</View>
<PinTextInput cellCount={6} testID='pin_input' value={newPin} onChange={setNewPin} />
Expand All @@ -45,7 +52,7 @@ export function PinCreationScreen ({ route }: Props): JSX.Element {
title='create-pin'
disabled={newPin.length !== pinLength}
onPress={() => {
navigation.navigate('PinConfirmation', { words, pin: newPin })
navigation.navigate('PinConfirmation', { words, pin: newPin, type })
}}
/>
</ScrollView>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,17 @@ export function VerifyMnemonicWallet ({ route, navigation }: Props): JSX.Element
setRandomWords([...randomWords])
}, [JSON.stringify(recoveryWords)])

function navigateToPinCreation (): void {
navigation.navigate('PinCreation', {
pinLength: HARDCODED_PIN_LENGTH,
words: recoveryWords,
type: 'create'
})
}

function onVerify (): void {
if (recoveryWords.join(' ') === selectedWords.join(' ')) {
navigation.navigate('PinCreation', {
pinLength: HARDCODED_PIN_LENGTH,
words: recoveryWords
})
navigateToPinCreation()
} else {
if (Platform.OS === 'web') {
navigation.navigate('CreateMnemonicWallet')
Expand All @@ -74,10 +79,7 @@ export function VerifyMnemonicWallet ({ route, navigation }: Props): JSX.Element

function debugBypass (): void {
if (getEnvironment().debug) {
navigation.navigate('PinCreation', {
pinLength: HARDCODED_PIN_LENGTH,
words: recoveryWords
})
navigateToPinCreation()
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
import { validateMnemonicSentence } from '@defichain/jellyfish-wallet-mnemonic'
import { NavigationProp, useNavigation } from '@react-navigation/native'
import * as React from 'react'
import { createRef, useEffect, useState } from 'react'
import { Controller, useForm } from 'react-hook-form'
import { Alert, TextInput } from 'react-native'
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'
import { MnemonicUnprotected } from '../../../../api/wallet/provider/mnemonic_unprotected'
import { Text, View } from '../../../../components'
import { Button } from '../../../../components/Button'
import { CreateWalletStepIndicator, RESTORE_STEPS } from '../../../../components/CreateWalletStepIndicator'
import { SectionTitle } from '../../../../components/SectionTitle'
import { useNetworkContext } from '../../../../contexts/NetworkContext'
import { useWalletPersistenceContext } from '../../../../contexts/WalletPersistenceContext'
import { tailwind } from '../../../../tailwind'
import { translate } from '../../../../translations'
import LoadingScreen from '../../../LoadingNavigator/LoadingScreen'
import { WalletParamList } from '../../WalletNavigator'

export function RestoreMnemonicWallet (): JSX.Element {
const { network } = useNetworkContext()
const { setWallet } = useWalletPersistenceContext()
const navigation = useNavigation<NavigationProp<WalletParamList>>()
const { control, formState: { isValid }, getValues } = useForm({ mode: 'onChange' })
const [recoveryWords] = useState<number[]>(Array.from(Array(24), (v, i) => i + 1))
const [isSubmitting, setIsSubmitting] = useState(false)
Expand All @@ -38,7 +37,11 @@ export function RestoreMnemonicWallet (): JSX.Element {
const words = Object.values(getValues())
if (isValid && validateMnemonicSentence(words)) {
setIsSubmitting(false)
await setWallet(MnemonicUnprotected.toData(words, network))
navigation.navigate('PinCreation', {
words,
pinLength: 6,
type: 'restore'
})
} else {
setIsSubmitting(false)
Alert.alert(
Expand All @@ -52,16 +55,23 @@ export function RestoreMnemonicWallet (): JSX.Element {
}

return (
<KeyboardAwareScrollView style={tailwind('bg-gray-100')}>
<View style={tailwind('justify-center p-4 bg-white')}>
<KeyboardAwareScrollView style={tailwind('bg-white')}>
<CreateWalletStepIndicator
current={1}
steps={RESTORE_STEPS}
style={tailwind('py-4 px-1')}
/>
<View style={tailwind('justify-center p-4')}>
<Text style={tailwind('font-medium text-sm text-gray-500 text-center')}>
{translate('screens/RestoreWallet', 'Please provide your 24 recovery words to regain access to your wallet.')}
</Text>
</View>
<SectionTitle
text={translate('screens/RestoreWallet', 'ENTER THE CORRECT WORD')}
testID='recover_title'
/>
<View style={tailwind('bg-gray-100')}>
<SectionTitle
text={translate('screens/RestoreWallet', 'ENTER THE CORRECT WORD')}
testID='recover_title'
/>
</View>
{
recoveryWords.map((order) => (
<Controller
Expand All @@ -80,6 +90,8 @@ export function RestoreMnemonicWallet (): JSX.Element {
placeholderTextColor={`${invalid && isTouched ? 'rgba(255, 0, 0, 1)' : 'rgba(0, 0, 0, 0.4)'}`}
style={tailwind(`flex-grow p-4 pl-0 ${invalid && isTouched ? 'text-error' : 'text-black'}`)}
autoCapitalize='none'
autoCorrect={false}
autoCompleteType='off'
value={value}
onBlur={onBlur}
onChangeText={onChange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@ context('wallet/createmnemonic', () => {
cy.getByTestID('verify_words_button').click()
})

it('should be able to set pincode', function () {
it('should be able to verify and set pincode', function () {
cy.getByTestID('pin_input').type('000000')
cy.getByTestID('create_pin_button').click()
cy.getByTestID('pin_confirm_input').type('777777').wait(5000)
cy.getByTestID('wrong_passcode_text').should('exist')
cy.getByTestID('pin_confirm_input').type('000000')
})

Expand All @@ -58,7 +60,10 @@ context('wallet/createmnemonic', () => {
cy.getByTestID(`recover_word_${index + 1}`).should('have.css', 'color', 'rgb(0, 0, 0)')
})
cy.getByTestID('recover_wallet_button').should('not.have.attr', 'disabled')
cy.getByTestID('recover_wallet_button').click().wait(2000)
cy.getByTestID('recover_wallet_button').click()
cy.getByTestID('pin_input').type('000000')
cy.getByTestID('create_pin_button').click()
cy.getByTestID('pin_confirm_input').type('000000')
cy.getByTestID('balances_list').should('exist')
})
})
6 changes: 6 additions & 0 deletions cypress/integration/functional/onboarding/restore.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ context('wallet/recover', () => {
it('should be able to submit form', function () {
cy.getByTestID('recover_wallet_button').should('not.have.attr', 'disabled')
cy.getByTestID('recover_wallet_button').click().wait(2000)
})

it('should be able to set pincode', function () {
cy.getByTestID('pin_input').type('000000')
cy.getByTestID('create_pin_button').click()
cy.getByTestID('pin_confirm_input').type('000000')
cy.getByTestID('balances_list').should('exist')
})
})

0 comments on commit 5df6e2d

Please sign in to comment.