From f0d0bfe19f3e197940b5649fff8e7496c610acfd Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 22 Jul 2021 15:01:41 +0800 Subject: [PATCH 01/48] quick save --- app/components/PinInput.tsx | 52 +++++++++++++++++++ .../WalletNavigator/WalletNavigator.tsx | 9 ++++ .../screens/PinInputScreen.tsx | 13 +++++ .../screens/WalletOnboarding.tsx | 3 +- 4 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 app/components/PinInput.tsx create mode 100644 app/screens/WalletNavigator/screens/PinInputScreen.tsx diff --git a/app/components/PinInput.tsx b/app/components/PinInput.tsx new file mode 100644 index 0000000000..3b2dd52a30 --- /dev/null +++ b/app/components/PinInput.tsx @@ -0,0 +1,52 @@ +import { MaterialIcons } from '@expo/vector-icons' +import React, { useState, useRef } from 'react' +import { TextInput, TouchableOpacity } from 'react-native' +import tailwind from 'tailwind-rn' +import { View } from '.' + +interface PinInputOptions { + length: 6 // should be easy to support 4-8 numeric, fix it to 6 first + onChange: (text: string) => void +} + +export function PinInput ({ length }: PinInputOptions): JSX.Element { + const [text, setText] = useState('') + const _textInput = useRef(null) + + const digitBoxes = (): JSX.Element => { + console.log('length', length) + const arr = [] + for (let i = 0; i < length; i++) { + let child: JSX.Element | null = null + if (text.length > i) { + child = + } + arr.push( + + {child} + + ) + } + return ( + {arr} + ) + } + + return ( + _textInput.current?.focus()}> + {digitBoxes()} + { + _textInput.current = ref + ref?.focus() + }} + style={tailwind('opacity-0 h-0')} + keyboardType='numeric' + secureTextEntry + autoFocus + maxLength={length} + onChangeText={txt => setText(txt)} + /> + + ) +} diff --git a/app/screens/WalletNavigator/WalletNavigator.tsx b/app/screens/WalletNavigator/WalletNavigator.tsx index d3af110eac..38d1e2cf3f 100644 --- a/app/screens/WalletNavigator/WalletNavigator.tsx +++ b/app/screens/WalletNavigator/WalletNavigator.tsx @@ -8,6 +8,7 @@ import { WalletMnemonicCreate } from './screens/WalletMnemonicCreate' import { WalletMnemonicCreateVerify } from './screens/WalletMnemonicCreateVerify' import { WalletMnemonicRestore } from './screens/WalletMnemonicRestore' import { WalletOnboarding } from './screens/WalletOnboarding' +import { PinInputScreen } from './screens/PinInputScreen' export interface WalletParamList { WalletOnboardingScreen: undefined @@ -16,6 +17,7 @@ export interface WalletParamList { words: string[] } WalletMnemonicRestore: undefined + PinCreation: undefined [key: string]: undefined | object } @@ -71,6 +73,13 @@ export function WalletNavigator (): JSX.Element { headerBackTitleVisible: false }} /> + ) diff --git a/app/screens/WalletNavigator/screens/PinInputScreen.tsx b/app/screens/WalletNavigator/screens/PinInputScreen.tsx new file mode 100644 index 0000000000..49ef4bc047 --- /dev/null +++ b/app/screens/WalletNavigator/screens/PinInputScreen.tsx @@ -0,0 +1,13 @@ +import React from 'react' +import { PinInput } from '../../../components/PinInput' + +// type Props = StackScreenProps + +export function PinInputScreen (): JSX.Element { + return ( + { console.log(val) }} + /> + ) +} diff --git a/app/screens/WalletNavigator/screens/WalletOnboarding.tsx b/app/screens/WalletNavigator/screens/WalletOnboarding.tsx index 86f0b60016..9f124a3749 100644 --- a/app/screens/WalletNavigator/screens/WalletOnboarding.tsx +++ b/app/screens/WalletNavigator/screens/WalletOnboarding.tsx @@ -33,7 +33,8 @@ export function WalletOnboarding (): JSX.Element { navigator.navigate('WalletMnemonicCreate')} + // onPress={() => navigator.navigate('WalletMnemonicCreate')} + onPress={() => navigator.navigate('PinCreation', { length: 6 })} text='Create new mnemonic wallet' icon='account-balance-wallet' /> From 1a25c4d3b2fb82841efc2d4b2824ec4c9daa91d0 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 22 Jul 2021 19:58:32 +0800 Subject: [PATCH 02/48] completed create pin page completed verify pin (pending some text and styling) --- app/components/CreateWalletStepIndicator.tsx | 80 +++++++++++++ app/components/PinInput.tsx | 23 ++-- .../WalletNavigator/WalletNavigator.tsx | 9 +- .../.WalletMnemonicCreateVerify.tsx.swp | Bin 0 -> 12288 bytes .../screens/PinCreationScreen.tsx | 107 ++++++++++++++++++ .../screens/PinInputScreen.tsx | 13 --- .../screens/WalletMnemonicCreateVerify.tsx | 14 +++ .../screens/WalletOnboarding.tsx | 8 +- 8 files changed, 225 insertions(+), 29 deletions(-) create mode 100644 app/components/CreateWalletStepIndicator.tsx create mode 100644 app/screens/WalletNavigator/screens/.WalletMnemonicCreateVerify.tsx.swp create mode 100644 app/screens/WalletNavigator/screens/PinCreationScreen.tsx delete mode 100644 app/screens/WalletNavigator/screens/PinInputScreen.tsx diff --git a/app/components/CreateWalletStepIndicator.tsx b/app/components/CreateWalletStepIndicator.tsx new file mode 100644 index 0000000000..47c03a4a91 --- /dev/null +++ b/app/components/CreateWalletStepIndicator.tsx @@ -0,0 +1,80 @@ +import React from 'react' +import { StyleProp, ViewStyle } from 'react-native' +import { Text, View } from '.' +import { PrimaryColor } from '../constants/Theme' +import { tailwind } from '../tailwind' + +interface StepIndicatorProps { + current: number + total?: number + steps?: string[] + style?: StyleProp +} + +/** + * @param props + * @param {number} props.total throw error if not fulfill (3 < total < 6), optional when props.steps provided + * @param {number} props.current throw error if not fulfill (0 < current <= total) + * @param {string[]} props.steps description displayed for each step + * @returns {JSX.Element} + */ +export function CreateWalletStepIndicator (props: StepIndicatorProps): JSX.Element { + const { current, total, style: containerViewStyle, steps = [] } = props + if (total === undefined && steps.length === 0) { + throw Error('Invalid prop for CreateWalletStepIndicator') + } + + const totalStep = total ?? steps.length + if (totalStep < 3 || totalStep > 5 || current <= 0 || current > totalStep) { + throw Error('Invalid prop for CreateWalletStepIndicator') + } + + function following (): JSX.Element[] { + const arr: JSX.Element[] = [] + for (let i = 1; i < totalStep; i++) { + arr.push(= i + 1 ? PrimaryColor : '#FFCDEF' }]} />) + arr.push() + } + return arr + } + + function descriptions (): JSX.Element[] { + const arr: JSX.Element[] = [] + for (let i = 0; i < steps.length; i++) { + const textColor = current === i + 1 ? PrimaryColor : 'gray' + arr.push( + + {steps[i]} + + ) + } + return arr + } + + return ( + + + + {following()} + + + {descriptions()} + + + ) +} + +function StepNode (props: { step: number, isActive: boolean }): JSX.Element { + return ( + + + {props.step} + + + ) +} diff --git a/app/components/PinInput.tsx b/app/components/PinInput.tsx index 3b2dd52a30..3727d0514a 100644 --- a/app/components/PinInput.tsx +++ b/app/components/PinInput.tsx @@ -1,20 +1,24 @@ import { MaterialIcons } from '@expo/vector-icons' -import React, { useState, useRef } from 'react' +import React, { useState, useRef, useEffect } from 'react' + import { TextInput, TouchableOpacity } from 'react-native' import tailwind from 'tailwind-rn' import { View } from '.' interface PinInputOptions { - length: 6 // should be easy to support 4-8 numeric, fix it to 6 first + length: 4 | 6 // should be easy to support 4-8 numeric, fix it to 4 or 6 first onChange: (text: string) => void } -export function PinInput ({ length }: PinInputOptions): JSX.Element { +export function PinInput ({ length, onChange }: PinInputOptions): JSX.Element { const [text, setText] = useState('') const _textInput = useRef(null) + useEffect(() => { + _textInput.current?.focus() + }, [_textInput]) + const digitBoxes = (): JSX.Element => { - console.log('length', length) const arr = [] for (let i = 0; i < length; i++) { let child: JSX.Element | null = null @@ -36,16 +40,17 @@ export function PinInput ({ length }: PinInputOptions): JSX.Element { _textInput.current?.focus()}> {digitBoxes()} { - _textInput.current = ref - ref?.focus() - }} + ref={ref => { _textInput.current = ref }} style={tailwind('opacity-0 h-0')} keyboardType='numeric' secureTextEntry autoFocus maxLength={length} - onChangeText={txt => setText(txt)} + onChangeText={txt => { + console.log('pininput raw: ', txt) + setText(txt) + onChange(txt) + }} /> ) diff --git a/app/screens/WalletNavigator/WalletNavigator.tsx b/app/screens/WalletNavigator/WalletNavigator.tsx index 38d1e2cf3f..308e51e4d8 100644 --- a/app/screens/WalletNavigator/WalletNavigator.tsx +++ b/app/screens/WalletNavigator/WalletNavigator.tsx @@ -8,7 +8,7 @@ import { WalletMnemonicCreate } from './screens/WalletMnemonicCreate' import { WalletMnemonicCreateVerify } from './screens/WalletMnemonicCreateVerify' import { WalletMnemonicRestore } from './screens/WalletMnemonicRestore' import { WalletOnboarding } from './screens/WalletOnboarding' -import { PinInputScreen } from './screens/PinInputScreen' +import { PinCreationScreen } from './screens/PinCreationScreen' export interface WalletParamList { WalletOnboardingScreen: undefined @@ -17,7 +17,10 @@ export interface WalletParamList { words: string[] } WalletMnemonicRestore: undefined - PinCreation: undefined + PinCreation: { + words: string[] + pinLength: 4 | 6 + } [key: string]: undefined | object } @@ -75,7 +78,7 @@ export function WalletNavigator (): JSX.Element { /> vu}NqF?YhHZvJ=Im9703Z|?3j zpTG3g{p}lTXAXZ^FW9#EJ<%Sxs_lrr+dk%=CsdmUK|8WTA!O9PWty(@i96uR4;R{Z zq!{_qwKqe-l{gTg+mBl+Iyv*HRY%1@#X!QqIrjA0>WaR<@Z=Np=|{g-5mPZxF;Fp3 zF;Fp3F;Fp3F;Fp3G4OxHfC?U9_^Ho6l=?hKKOb25Outqi6$2Fm6$2Fm6$2Fm6$2Fm z6$2Fm6$2Fm6$2Fm|AP#0hq0$%_dEIrKmPwe`~CmRPce2MI08DrUp~p$o4^Ek9QgA& z#(o8S7x)hFEN~h4>n9ld32+~H3AhDZ0N#CsvA+Yq2EGkk0lox00elX44EPN2-ouRj z4)`hXeP9BNfB|3wJ>X&B?S~lqJ@6agJ|KV%;2$4n>~Fw3!0W)Pz$?HeZ~=J#V~o88 z{1A8r7y?JYF0c)30at)$fyaTjKg!q}z-z!&;2iM!M;N;cd>yzAya0S2`0az>1o%1d z8t?<42RsS<;=_#n2>2fGGB5*d;1S?m{Nu}8z|VkJfgW%bxCnd}co6s#^!X#8b^3$W z>Oa0_jHnI}b=ohAxaafGd2Zsnjw=TTt~lN>eY$tw4t>unx9QfK{@5P!o+ow#Zo4YZ z#wRP@6ep_OLUgcoWBclD+PS{9vwMAeGhN+`)`KZhu_wAy#a-{%m5x;lnom>Tm#W$G zJ%<#=G>?a_61DoQSh=_(^5r#O4)#J3McrvH4tNx84uySBdzxL2@OWGr_RPx4O1a;c z>3m-%XGqGOsfmY$jics83jMKkghPE#oDl3AMNL~sB|^G8jueieWM6xOW)Qj~9>%r$ zQL%1kkx7=@TQ5dw%I77}EpG)fP~P%sR6Bxs_K#<(_Dt}m$% zwuRH6z!SR9)^t&T zki-;>!@!S3qj2GLnKPlDIYZ-4Q9zbe)>+eBCFI5+UL=rI+cFaBIY;Nw z2w$HUh)UNFyEQ&m{s6BgwY$C13CJ$VNb-y9HHR(%Hw)%9+2O*L!h>_$$h`$y}y@`g)CK_`8JK6}7uSL^R z0i53X!n8yMeUVmYcUn+zU-3{`YYnRD5X)D{LWihSlZmo2Q`CCxdJ?24XSF(N&q7~X z>1t*AiXq3%OY`}0MqVAJmYq=*ktk*tM9J3Wv8OjlokE(-^5&Bl&2l)BPOM1Lq4BOk zSuE&y8Ak#PDbeZqK6V4SVX1H|v@iX!w9RgDYS~QUXq~1>)mukl9`!`nF!gjD2_GVJ zXjWgR7xrIjZFxdxGU_50PDIG0m=(MQuDmAXKn>UFQZAH^)M%YbB{ySGCTBvip|#Rm zVb%6rY0iS8wbn@Mu-hkVAk>zexS=n#Ay&QRh~9WmHzRUSOK%Tk!F2szbehc>fmWY; zks%9Sk-&ZWL?p*Ccky;3RC0dR%1CVCSS#pGTCZ@$t#UTk6Ed{HC5KygeOFpFLiFjB zEl()A5?6Q>OS_ORdz~@f?1lcwjYLO#rMD>h)A_oYw=ukt(cS*pYmu-z@MzAG66v#Fn`>?HS6OqG_^g}y;~xo?npMJzd;xMBO?M +type STEP = 'CREATE' | 'VERIFY' + +export function PinCreationScreen ({ navigation, route }: Props): JSX.Element { + const { pinLength, words } = route.params + const [step, setStep] = useState('CREATE') + const [newPin, setNewPin] = useState('') + const [verifyPin, setVerifyPin] = useState('') + const { setWallet } = useWalletManagementContext() + + // inherit from MnemonicVerify screen + // TODO(@ivan-zynesis): encrypt seed + function onPinVerified (): void { + setWallet(Mnemonic.createWalletData(words)) + .catch(e => console.log(e)) + } + + return ( + + { + step === 'CREATE' ? ( + { setNewPin(val) }} + onComplete={() => setStep('VERIFY')} + value={newPin} + /> + ) : null + } + { + step === 'VERIFY' ? ( + { + setVerifyPin(val) + if (newPin === val) onPinVerified() + }} + /> + ) : null + } + + ) +} + +function CreatePin (props: { value: string, pinLength: 4 | 6, onChange: (pin: string) => void, onComplete: () => void }): JSX.Element { + return ( + <> + + + {translate('screens/PinCreation', 'Secure your wallet')} + + + {translate('screens/PinCreation', 'Well done! You answered correctly. Now let\'s make your wallet safe by creating a passcode. Don\'t share your passcode to anyone.')} + + + + {translate('screens/PinCreation', 'Create a passcode for your wallet')} + + + + {translate('screens/PinCreation', 'CREATE PASSCODE')} + + + ) +} + +function VerifyPin (props: { value: string, pinLength: 4 | 6, onChange: (pin: string) => void, error: boolean }): JSX.Element { + return ( + <> + + { + (props.error) ? ( + {translate('screens/PinCreation', 'Wrong passcode entered')} + ) : null + } + + ) +} diff --git a/app/screens/WalletNavigator/screens/PinInputScreen.tsx b/app/screens/WalletNavigator/screens/PinInputScreen.tsx deleted file mode 100644 index 49ef4bc047..0000000000 --- a/app/screens/WalletNavigator/screens/PinInputScreen.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react' -import { PinInput } from '../../../components/PinInput' - -// type Props = StackScreenProps - -export function PinInputScreen (): JSX.Element { - return ( - { console.log(val) }} - /> - ) -} diff --git a/app/screens/WalletNavigator/screens/WalletMnemonicCreateVerify.tsx b/app/screens/WalletNavigator/screens/WalletMnemonicCreateVerify.tsx index 87419ee0b4..a6303de57f 100644 --- a/app/screens/WalletNavigator/screens/WalletMnemonicCreateVerify.tsx +++ b/app/screens/WalletNavigator/screens/WalletMnemonicCreateVerify.tsx @@ -1,3 +1,4 @@ +import { NavigationProp, useNavigation } from '@react-navigation/native' import { StackScreenProps } from '@react-navigation/stack' import * as React from 'react' import { useState } from 'react' @@ -5,12 +6,14 @@ import { KeyboardAvoidingView, ScrollView, TouchableOpacity } from 'react-native import { Mnemonic } from '../../../api/wallet/mnemonic' import { Text, TextInput, View } from '../../../components' import { useWalletManagementContext } from '../../../contexts/WalletManagementContext' +import { getEnvironment } from '../../../environment' import { tailwind } from '../../../tailwind' import { WalletParamList } from '../WalletNavigator' type Props = StackScreenProps export function WalletMnemonicCreateVerify ({ route }: Props): JSX.Element { + const navigation = useNavigation>() const actualWords = route.params.words const enteredWords: string[] = [] @@ -25,6 +28,15 @@ export function WalletMnemonicCreateVerify ({ route }: Props): JSX.Element { } } + function bypassCheck (): void { + if (getEnvironment().debug) { + navigation.navigate('PinCreation', { + words: actualWords, + pinLength: 6 + }) + } + } + function MnemonicWordInputRow (props: { index: number, word: string }): JSX.Element { const [valid, setValid] = useState(true) @@ -78,6 +90,8 @@ export function WalletMnemonicCreateVerify ({ route }: Props): JSX.Element { VERIFY MNEMONIC diff --git a/app/screens/WalletNavigator/screens/WalletOnboarding.tsx b/app/screens/WalletNavigator/screens/WalletOnboarding.tsx index 9f124a3749..19208f4e92 100644 --- a/app/screens/WalletNavigator/screens/WalletOnboarding.tsx +++ b/app/screens/WalletNavigator/screens/WalletOnboarding.tsx @@ -1,4 +1,4 @@ -import { useNavigation } from '@react-navigation/native' +import { NavigationProp, useNavigation } from '@react-navigation/native' import * as React from 'react' import { ScrollView, TouchableOpacity } from 'react-native' import { Mnemonic } from '../../../api/wallet/mnemonic' @@ -8,10 +8,11 @@ import { useWalletManagementContext } from '../../../contexts/WalletManagementCo import { getEnvironment } from '../../../environment' import { tailwind } from '../../../tailwind' import { translate } from '../../../translations' +import { WalletParamList } from '../WalletNavigator' export function WalletOnboarding (): JSX.Element { const { setWallet } = useWalletManagementContext() - const navigator = useNavigation() + const navigator = useNavigation>() const onDebugPress = getEnvironment().debug ? async () => { await setWallet(Mnemonic.createWalletDataAbandon23()) @@ -33,8 +34,7 @@ export function WalletOnboarding (): JSX.Element { navigator.navigate('WalletMnemonicCreate')} - onPress={() => navigator.navigate('PinCreation', { length: 6 })} + onPress={() => navigator.navigate('WalletMnemonicCreate')} text='Create new mnemonic wallet' icon='account-balance-wallet' /> From e584da492a7720e8268f880c0ade7f0aabfeeea1 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 23 Jul 2021 11:12:29 +0800 Subject: [PATCH 03/48] quick save --- app/components/PinInput.tsx | 1 - .../WalletNavigator/WalletNavigator.tsx | 16 +++- .../.WalletMnemonicCreateVerify.tsx.swp | Bin 12288 -> 0 bytes .../screens/PinConfirmation.tsx | 55 ++++++++++++ .../screens/PinCreationScreen.tsx | 80 ++++-------------- app/translations/index.ts | 6 +- 6 files changed, 87 insertions(+), 71 deletions(-) delete mode 100644 app/screens/WalletNavigator/screens/.WalletMnemonicCreateVerify.tsx.swp create mode 100644 app/screens/WalletNavigator/screens/PinConfirmation.tsx diff --git a/app/components/PinInput.tsx b/app/components/PinInput.tsx index 3727d0514a..5a17122afc 100644 --- a/app/components/PinInput.tsx +++ b/app/components/PinInput.tsx @@ -47,7 +47,6 @@ export function PinInput ({ length, onChange }: PinInputOptions): JSX.Element { autoFocus maxLength={length} onChangeText={txt => { - console.log('pininput raw: ', txt) setText(txt) onChange(txt) }} diff --git a/app/screens/WalletNavigator/WalletNavigator.tsx b/app/screens/WalletNavigator/WalletNavigator.tsx index 308e51e4d8..9d6e253adb 100644 --- a/app/screens/WalletNavigator/WalletNavigator.tsx +++ b/app/screens/WalletNavigator/WalletNavigator.tsx @@ -8,7 +8,8 @@ import { WalletMnemonicCreate } from './screens/WalletMnemonicCreate' import { WalletMnemonicCreateVerify } from './screens/WalletMnemonicCreateVerify' import { WalletMnemonicRestore } from './screens/WalletMnemonicRestore' import { WalletOnboarding } from './screens/WalletOnboarding' -import { PinCreationScreen } from './screens/PinCreationScreen' +import { PinCreation } from './screens/PinCreationScreen' +import { PinConfirmation } from './screens/PinConfirmation' export interface WalletParamList { WalletOnboardingScreen: undefined @@ -21,6 +22,10 @@ export interface WalletParamList { words: string[] pinLength: 4 | 6 } + PinConfirmation: { + words: string[] + pin: string + } [key: string]: undefined | object } @@ -78,7 +83,14 @@ export function WalletNavigator (): JSX.Element { /> + vu}NqF?YhHZvJ=Im9703Z|?3j zpTG3g{p}lTXAXZ^FW9#EJ<%Sxs_lrr+dk%=CsdmUK|8WTA!O9PWty(@i96uR4;R{Z zq!{_qwKqe-l{gTg+mBl+Iyv*HRY%1@#X!QqIrjA0>WaR<@Z=Np=|{g-5mPZxF;Fp3 zF;Fp3F;Fp3F;Fp3G4OxHfC?U9_^Ho6l=?hKKOb25Outqi6$2Fm6$2Fm6$2Fm6$2Fm z6$2Fm6$2Fm6$2Fm|AP#0hq0$%_dEIrKmPwe`~CmRPce2MI08DrUp~p$o4^Ek9QgA& z#(o8S7x)hFEN~h4>n9ld32+~H3AhDZ0N#CsvA+Yq2EGkk0lox00elX44EPN2-ouRj z4)`hXeP9BNfB|3wJ>X&B?S~lqJ@6agJ|KV%;2$4n>~Fw3!0W)Pz$?HeZ~=J#V~o88 z{1A8r7y?JYF0c)30at)$fyaTjKg!q}z-z!&;2iM!M;N;cd>yzAya0S2`0az>1o%1d z8t?<42RsS<;=_#n2>2fGGB5*d;1S?m{Nu}8z|VkJfgW%bxCnd}co6s#^!X#8b^3$W z>Oa0_jHnI}b=ohAxaafGd2Zsnjw=TTt~lN>eY$tw4t>unx9QfK{@5P!o+ow#Zo4YZ z#wRP@6ep_OLUgcoWBclD+PS{9vwMAeGhN+`)`KZhu_wAy#a-{%m5x;lnom>Tm#W$G zJ%<#=G>?a_61DoQSh=_(^5r#O4)#J3McrvH4tNx84uySBdzxL2@OWGr_RPx4O1a;c z>3m-%XGqGOsfmY$jics83jMKkghPE#oDl3AMNL~sB|^G8jueieWM6xOW)Qj~9>%r$ zQL%1kkx7=@TQ5dw%I77}EpG)fP~P%sR6Bxs_K#<(_Dt}m$% zwuRH6z!SR9)^t&T zki-;>!@!S3qj2GLnKPlDIYZ-4Q9zbe)>+eBCFI5+UL=rI+cFaBIY;Nw z2w$HUh)UNFyEQ&m{s6BgwY$C13CJ$VNb-y9HHR(%Hw)%9+2O*L!h>_$$h`$y}y@`g)CK_`8JK6}7uSL^R z0i53X!n8yMeUVmYcUn+zU-3{`YYnRD5X)D{LWihSlZmo2Q`CCxdJ?24XSF(N&q7~X z>1t*AiXq3%OY`}0MqVAJmYq=*ktk*tM9J3Wv8OjlokE(-^5&Bl&2l)BPOM1Lq4BOk zSuE&y8Ak#PDbeZqK6V4SVX1H|v@iX!w9RgDYS~QUXq~1>)mukl9`!`nF!gjD2_GVJ zXjWgR7xrIjZFxdxGU_50PDIG0m=(MQuDmAXKn>UFQZAH^)M%YbB{ySGCTBvip|#Rm zVb%6rY0iS8wbn@Mu-hkVAk>zexS=n#Ay&QRh~9WmHzRUSOK%Tk!F2szbehc>fmWY; zks%9Sk-&ZWL?p*Ccky;3RC0dR%1CVCSS#pGTCZ@$t#UTk6Ed{HC5KygeOFpFLiFjB zEl()A5?6Q>OS_ORdz~@f?1lcwjYLO#rMD>h)A_oYw=ukt(cS*pYmu-z@MzAG66v#Fn`>?HS6OqG_^g}y;~xo?npMJzd;xMBO?M +const MAX_ALLOWED_ATTEMPT = 3 + +export function PinConfirmation ({ route }: Props): JSX.Element { + const navigation = useNavigation() + const { pin, words } = route.params + const { setWallet } = useWalletManagementContext() + const [invalid, setInvalid] = useState(false) + + if (![4, 6].includes(pin.length)) throw new Error('Unexpected pin length') + + // inherit from MnemonicVerify screen + // TODO(@ivan-zynesis): encrypt seed + function verifyPin (input: string): void { + if (input.length !== pin.length) return + if (input !== pin) { + setInvalid(true) + return + } + + setWallet(Mnemonic.createWalletData(words)) + .catch(e => console.log(e)) + } + + return ( + + + { + (invalid) ? ( + + {translate('screens/PinConfirmation', 'Wrong passcode entered')} + + ) : null + } + + ) +} diff --git a/app/screens/WalletNavigator/screens/PinCreationScreen.tsx b/app/screens/WalletNavigator/screens/PinCreationScreen.tsx index bf50bc8ace..2f49b0a920 100644 --- a/app/screens/WalletNavigator/screens/PinCreationScreen.tsx +++ b/app/screens/WalletNavigator/screens/PinCreationScreen.tsx @@ -1,65 +1,25 @@ import { MaterialIcons } from '@expo/vector-icons' +import { NavigationProp, useNavigation } from '@react-navigation/native' import { StackScreenProps } from '@react-navigation/stack' import React, { useState } from 'react' +import { ScrollView } from 'react-native' import tailwind from 'tailwind-rn' -import { Mnemonic } from '../../../api/wallet/mnemonic' import { Text, View } from '../../../components' import { CreateWalletStepIndicator } from '../../../components/CreateWalletStepIndicator' import { PinInput } from '../../../components/PinInput' import { PrimaryButton } from '../../../components/PrimaryButton' -import { useWalletManagementContext } from '../../../contexts/WalletManagementContext' import { translate } from '../../../translations' import { WalletParamList } from '../WalletNavigator' type Props = StackScreenProps -type STEP = 'CREATE' | 'VERIFY' -export function PinCreationScreen ({ navigation, route }: Props): JSX.Element { +export function PinCreation ({ route }: Props): JSX.Element { + const navigation = useNavigation>() const { pinLength, words } = route.params - const [step, setStep] = useState('CREATE') const [newPin, setNewPin] = useState('') - const [verifyPin, setVerifyPin] = useState('') - const { setWallet } = useWalletManagementContext() - // inherit from MnemonicVerify screen - // TODO(@ivan-zynesis): encrypt seed - function onPinVerified (): void { - setWallet(Mnemonic.createWalletData(words)) - .catch(e => console.log(e)) - } - - return ( - - { - step === 'CREATE' ? ( - { setNewPin(val) }} - onComplete={() => setStep('VERIFY')} - value={newPin} - /> - ) : null - } - { - step === 'VERIFY' ? ( - { - setVerifyPin(val) - if (newPin === val) onPinVerified() - }} - /> - ) : null - } - - ) -} - -function CreatePin (props: { value: string, pinLength: 4 | 6, onChange: (pin: string) => void, onComplete: () => void }): JSX.Element { return ( - <> + {translate('screens/PinCreation', 'Create a passcode for your wallet')} setNewPin(val)} /> - + { + navigation.navigate('PinConfirmation', { words, pin: newPin }) + }} + > {translate('screens/PinCreation', 'CREATE PASSCODE')} - - ) -} - -function VerifyPin (props: { value: string, pinLength: 4 | 6, onChange: (pin: string) => void, error: boolean }): JSX.Element { - return ( - <> - - { - (props.error) ? ( - {translate('screens/PinCreation', 'Wrong passcode entered')} - ) : null - } - + ) } diff --git a/app/translations/index.ts b/app/translations/index.ts index bb3c0a1c49..d504c2b2ae 100644 --- a/app/translations/index.ts +++ b/app/translations/index.ts @@ -1,5 +1,5 @@ import * as Localization from 'expo-localization' -import i18n from 'i18n-js' +import i18n, { TranslateOptions } from 'i18n-js' import { languages } from './languages' /** @@ -48,11 +48,11 @@ export function initI18n (): void { * @param path translation path, can follow file location * @param text english text for internationalisation */ -export function translate (path: string, text: string): string { +export function translate (path: string, text: string, options?: TranslateOptions): string { if (!init) { initI18n() } - const translation = i18n.translate(`${path}.${text}`) + const translation = i18n.translate(`${path}.${text}`, options) if (translation !== null && translation !== undefined && translation !== '') { return translation } else { From 3d7e31d611e979377a4b1563c18877ee2a4af979 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 23 Jul 2021 15:15:21 +0800 Subject: [PATCH 04/48] complete pin creations UI implementation --- app/components/CreateWalletStepIndicator.tsx | 6 +++--- .../screens/PinConfirmation.tsx | 18 ++++++++---------- .../screens/WalletMnemonicCreate.tsx | 4 ++-- .../screens/WalletMnemonicCreateVerify.tsx | 2 +- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/app/components/CreateWalletStepIndicator.tsx b/app/components/CreateWalletStepIndicator.tsx index 47c03a4a91..b282e4f712 100644 --- a/app/components/CreateWalletStepIndicator.tsx +++ b/app/components/CreateWalletStepIndicator.tsx @@ -32,8 +32,8 @@ export function CreateWalletStepIndicator (props: StepIndicatorProps): JSX.Eleme function following (): JSX.Element[] { const arr: JSX.Element[] = [] for (let i = 1; i < totalStep; i++) { - arr.push(= i + 1 ? PrimaryColor : '#FFCDEF' }]} />) - arr.push() + arr.push(= i + 1 ? PrimaryColor : '#FFCDEF' }]} />) + arr.push() } return arr } @@ -43,7 +43,7 @@ export function CreateWalletStepIndicator (props: StepIndicatorProps): JSX.Eleme for (let i = 0; i < steps.length; i++) { const textColor = current === i + 1 ? PrimaryColor : 'gray' arr.push( - + {steps[i]} ) diff --git a/app/screens/WalletNavigator/screens/PinConfirmation.tsx b/app/screens/WalletNavigator/screens/PinConfirmation.tsx index 1583fc375d..b804700f79 100644 --- a/app/screens/WalletNavigator/screens/PinConfirmation.tsx +++ b/app/screens/WalletNavigator/screens/PinConfirmation.tsx @@ -1,23 +1,19 @@ -import { MaterialIcons } from '@expo/vector-icons' -import { NavigationProp, useNavigation } from '@react-navigation/native' import { StackScreenProps } from '@react-navigation/stack' import React, { useState } from 'react' import { ScrollView } from 'react-native' import tailwind from 'tailwind-rn' import { Mnemonic } from '../../../api/wallet/mnemonic' -import { Text, View } from '../../../components' -import { CreateWalletStepIndicator } from '../../../components/CreateWalletStepIndicator' +import { Text } from '../../../components' import { PinInput } from '../../../components/PinInput' -import { PrimaryButton } from '../../../components/PrimaryButton' +import { useNetworkContext } from '../../../contexts/NetworkContext' import { useWalletManagementContext } from '../../../contexts/WalletManagementContext' import { translate } from '../../../translations' import { WalletParamList } from '../WalletNavigator' type Props = StackScreenProps -const MAX_ALLOWED_ATTEMPT = 3 export function PinConfirmation ({ route }: Props): JSX.Element { - const navigation = useNavigation() + const { network } = useNetworkContext() const { pin, words } = route.params const { setWallet } = useWalletManagementContext() const [invalid, setInvalid] = useState(false) @@ -33,19 +29,21 @@ export function PinConfirmation ({ route }: Props): JSX.Element { return } - setWallet(Mnemonic.createWalletData(words)) + setWallet(Mnemonic.createWalletData(words, network)) .catch(e => console.log(e)) } return ( - + + {translate('screens/PinConfirmation', 'Verify your passcode')} + {translate('screens/PinConfirmation', 'Enter your passcode again to verify')} { (invalid) ? ( - + {translate('screens/PinConfirmation', 'Wrong passcode entered')} ) : null diff --git a/app/screens/WalletNavigator/screens/WalletMnemonicCreate.tsx b/app/screens/WalletNavigator/screens/WalletMnemonicCreate.tsx index e8e595c1f0..8942f67c21 100644 --- a/app/screens/WalletNavigator/screens/WalletMnemonicCreate.tsx +++ b/app/screens/WalletNavigator/screens/WalletMnemonicCreate.tsx @@ -1,4 +1,4 @@ -import { generateMnemonic } from '@defichain/jellyfish-wallet-mnemonic' +import { generateMnemonicWords } from '@defichain/jellyfish-wallet-mnemonic' import { StackScreenProps } from '@react-navigation/stack' import * as Random from 'expo-random' import * as React from 'react' @@ -11,7 +11,7 @@ import { WalletParamList } from '../WalletNavigator' type Props = StackScreenProps export function WalletMnemonicCreate ({ navigation }: Props): JSX.Element { - const words = generateMnemonic(24, numOfBytes => { + const words = generateMnemonicWords(24, numOfBytes => { const bytes = Random.getRandomBytes(numOfBytes) return Buffer.from(bytes) }) diff --git a/app/screens/WalletNavigator/screens/WalletMnemonicCreateVerify.tsx b/app/screens/WalletNavigator/screens/WalletMnemonicCreateVerify.tsx index ceba9f9197..ed9a40f6ae 100644 --- a/app/screens/WalletNavigator/screens/WalletMnemonicCreateVerify.tsx +++ b/app/screens/WalletNavigator/screens/WalletMnemonicCreateVerify.tsx @@ -92,7 +92,7 @@ export function WalletMnemonicCreateVerify ({ route }: Props): JSX.Element { From e3d96be7d1819bcb80f2a9c3b281033e12803bcf Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 23 Jul 2021 16:34:34 +0800 Subject: [PATCH 05/48] update OceanInterface queue api --- .../OceanInterface/OceanInterface.tsx | 6 +++++- .../screens/Balances/ConvertScreen.tsx | 20 ++++++------------- .../screens/Balances/screens/SendScreen.tsx | 19 ++++++++---------- .../screens/Dex/DexConfirmAddLiquidity.tsx | 16 +++++---------- .../screens/Dex/DexRemoveLiquidity.tsx | 15 +++++--------- .../screens/Dex/PoolSwap/PoolSwapScreen.tsx | 10 +++++----- app/store/ocean.ts | 7 ++++--- 7 files changed, 38 insertions(+), 55 deletions(-) diff --git a/app/components/OceanInterface/OceanInterface.tsx b/app/components/OceanInterface/OceanInterface.tsx index 4f49abd00e..c0080bb260 100644 --- a/app/components/OceanInterface/OceanInterface.tsx +++ b/app/components/OceanInterface/OceanInterface.tsx @@ -6,6 +6,7 @@ import { ActivityIndicator, Animated, Linking, TouchableOpacity, View } from 're import { useDispatch, useSelector } from 'react-redux' import { Text } from '..' import { Logging } from '../../api/logging' +import { useWallet } from '../../contexts/WalletContext' import { useWhaleApiClient } from '../../contexts/WhaleContext' import { RootState } from '../../store' import { firstTransactionSelector, ocean, OceanTransaction } from '../../store/ocean' @@ -44,6 +45,9 @@ export function OceanInterface (): JSX.Element | null { const dispatch = useDispatch() const client = useWhaleApiClient() + // Require fixes to support more than 1 WalletAcount + const whaleWalletAccount = useWallet().get(0) + // store const { height, err: e } = useSelector((state: RootState) => state.ocean) const transaction = useSelector((state: RootState) => firstTransactionSelector(state.ocean)) @@ -67,7 +71,7 @@ export function OceanInterface (): JSX.Element | null { ...transaction, broadcasted: false }) - transaction.signer() + transaction.sign(whaleWalletAccount) .then(async signedTx => { setTxid(signedTx.txId) await broadcastTransaction(signedTx, client) diff --git a/app/screens/AppNavigator/screens/Balances/ConvertScreen.tsx b/app/screens/AppNavigator/screens/Balances/ConvertScreen.tsx index 7b6889760c..5165f445fa 100644 --- a/app/screens/AppNavigator/screens/Balances/ConvertScreen.tsx +++ b/app/screens/AppNavigator/screens/Balances/ConvertScreen.tsx @@ -14,7 +14,6 @@ import { Logging } from '../../../../api/logging' import { Text, TextInput, View } from '../../../../components' import { getTokenIcon } from '../../../../components/icons/tokens/_index' import { PrimaryButton } from '../../../../components/PrimaryButton' -import { useWallet } from '../../../../contexts/WalletContext' import { useTokensAPI } from '../../../../hooks/wallet/TokensAPI' import { RootState } from '../../../../store' import { hasTxQueued, ocean } from '../../../../store/ocean' @@ -31,6 +30,7 @@ interface ConversionIO extends AddressToken { } export function ConvertScreen (props: Props): JSX.Element { + const dispatch = useDispatch() // global state const tokens = useTokensAPI() const hasPendingJob = useSelector((state: RootState) => hasTxQueued(state.ocean)) @@ -41,9 +41,6 @@ export function ConvertScreen (props: Props): JSX.Element { const [amount, setAmount] = useState('0') - const account = useWallet().get(0) - const dispatch = useDispatch() - useEffect(() => { const [source, target] = getDFIBalances(mode, tokens) setSourceToken(source) @@ -60,7 +57,6 @@ export function ConvertScreen (props: Props): JSX.Element { const convert = (): void => { if (hasPendingJob) return constructSignedConversionAndSend( - account, props.route.params.mode, new BigNumber(amount), dispatch @@ -243,13 +239,10 @@ function canConvert (amount: string, balance: string): boolean { return new BigNumber(balance).gte(amount) && !(new BigNumber(amount).isZero()) } -async function constructSignedConversionAndSend (account: WhaleWalletAccount, mode: ConversionMode, - amount: BigNumber, dispatch: Dispatch): Promise { - const builder = account.withTransactionBuilder() - - const script = await account.getScript() - - const signer = async (): Promise => { +async function constructSignedConversionAndSend (mode: ConversionMode, amount: BigNumber, dispatch: Dispatch): Promise { + const signer = async (account: WhaleWalletAccount): Promise => { + const builder = account.withTransactionBuilder() + const script = await account.getScript() let signed: TransactionSegWit if (mode === 'utxosToAccount') { signed = await builder.account.utxosToAccount({ @@ -273,8 +266,7 @@ async function constructSignedConversionAndSend (account: WhaleWalletAccount, mo } dispatch(ocean.actions.queueTransaction({ - signer, - broadcasted: false, + sign: signer, title: `${translate('screens/ConvertScreen', 'Converting DFI')}` })) } diff --git a/app/screens/AppNavigator/screens/Balances/screens/SendScreen.tsx b/app/screens/AppNavigator/screens/Balances/screens/SendScreen.tsx index f901f2addf..f5697f8bf2 100644 --- a/app/screens/AppNavigator/screens/Balances/screens/SendScreen.tsx +++ b/app/screens/AppNavigator/screens/Balances/screens/SendScreen.tsx @@ -2,6 +2,7 @@ import { DeFiAddress } from '@defichain/jellyfish-address' import { NetworkName } from '@defichain/jellyfish-network' import { CTransactionSegWit, TransactionSegWit } from '@defichain/jellyfish-transaction' import { AddressToken } from '@defichain/whale-api-client/dist/api/address' +import { WhaleWalletAccount } from '@defichain/whale-api-wallet' import { MaterialIcons } from '@expo/vector-icons' import { StackScreenProps } from '@react-navigation/stack' import BigNumber from 'bignumber.js' @@ -12,13 +13,11 @@ import NumberFormat from 'react-number-format' import { useDispatch, useSelector } from 'react-redux' import { Dispatch } from 'redux' import { Logging } from '../../../../../api/logging' -import { Wallet } from '../../../../../api/wallet' import { Text, TextInput } from '../../../../../components' import { getTokenIcon } from '../../../../../components/icons/tokens/_index' import { PrimaryButton } from '../../../../../components/PrimaryButton' import { SectionTitle } from '../../../../../components/SectionTitle' import { useNetworkContext } from '../../../../../contexts/NetworkContext' -import { useWallet } from '../../../../../contexts/WalletContext' import { useWhaleApiClient } from '../../../../../contexts/WhaleContext' import { RootState } from '../../../../../store' import { hasTxQueued, ocean } from '../../../../../store/ocean' @@ -39,14 +38,14 @@ async function send ({ token, amount, networkName -}: SendForm, wallet: Wallet, dispatch: Dispatch): Promise { +}: SendForm, dispatch: Dispatch): Promise { try { - const account = wallet.get(0) - const script = await account.getScript() - const builder = account.withTransactionBuilder() const to = DeFiAddress.from(networkName, address).getScript() - const signer = async (): Promise => { + const signer = async (account: WhaleWalletAccount): Promise => { + const script = await account.getScript() + const builder = account.withTransactionBuilder() + let signed: TransactionSegWit if (token.symbol === 'DFI') { signed = await builder.utxo.send(amount, to, script) @@ -60,8 +59,7 @@ async function send ({ } dispatch(ocean.actions.queueTransaction({ - signer, - broadcasted: false, + sign: signer, title: `${translate('screens/SendScreen', 'Sending')} ${token.symbol}` })) } catch (e) { @@ -74,7 +72,6 @@ type Props = StackScreenProps export function SendScreen ({ route, navigation }: Props): JSX.Element { const { networkName } = useNetworkContext() - const wallet = useWallet() const client = useWhaleApiClient() const [token] = useState(route.params.token) const { control, setValue, formState: { isValid }, getValues, trigger } = useForm({ mode: 'onChange' }) @@ -99,7 +96,7 @@ export function SendScreen ({ route, navigation }: Props): JSX.Element { token, amount: new BigNumber(values.amount), networkName - }, wallet, dispatch) + }, dispatch) } setIsSubmitting(false) } diff --git a/app/screens/AppNavigator/screens/Dex/DexConfirmAddLiquidity.tsx b/app/screens/AppNavigator/screens/Dex/DexConfirmAddLiquidity.tsx index ae9e390db7..388e4325af 100644 --- a/app/screens/AppNavigator/screens/Dex/DexConfirmAddLiquidity.tsx +++ b/app/screens/AppNavigator/screens/Dex/DexConfirmAddLiquidity.tsx @@ -12,7 +12,6 @@ import { Dispatch } from 'redux' import { Logging } from '../../../../api/logging' import { Text, View } from '../../../../components' import { PrimaryButton } from '../../../../components/PrimaryButton' -import { useWallet } from '../../../../contexts/WalletContext' import { RootState } from '../../../../store' import { hasTxQueued, ocean } from '../../../../store/ocean' import { tailwind } from '../../../../tailwind' @@ -44,14 +43,11 @@ export function ConfirmAddLiquidityScreen (props: Props): JSX.Element { const aToBRate = new BigNumber(tokenB.reserve).div(tokenA.reserve) const bToARate = new BigNumber(tokenA.reserve).div(tokenB.reserve) const lmTokenAmount = percentage.times(totalLiquidity) - - const account = useWallet().get(0) const dispatch = useDispatch() const addLiquidity = useCallback(() => { if (hasPendingJob) return constructSignedAddLiqAndSend( - account, { tokenAId: Number(tokenA.id), tokenAAmount, @@ -159,15 +155,14 @@ function ConfirmButton (props: { disabled?: boolean, onPress: () => void }): JSX ) } -async function constructSignedAddLiqAndSend (account: WhaleWalletAccount, +async function constructSignedAddLiqAndSend ( addLiqForm: { tokenAId: number, tokenAAmount: BigNumber, tokenBId: number, tokenBAmount: BigNumber }, dispatch: Dispatch ): Promise { - const builder = account.withTransactionBuilder() - - const script = await account.getScript() + const signer = async (account: WhaleWalletAccount): Promise => { + const builder = account.withTransactionBuilder() + const script = await account.getScript() - const signer = async (): Promise => { const addLiq = { from: [{ script, @@ -184,8 +179,7 @@ async function constructSignedAddLiqAndSend (account: WhaleWalletAccount, } dispatch(ocean.actions.queueTransaction({ - signer, - broadcasted: false, + sign: signer, title: `${translate('screens/ConfirmLiquidity', 'Adding Liquidity')}` })) } diff --git a/app/screens/AppNavigator/screens/Dex/DexRemoveLiquidity.tsx b/app/screens/AppNavigator/screens/Dex/DexRemoveLiquidity.tsx index dbd1d4b122..963024db07 100644 --- a/app/screens/AppNavigator/screens/Dex/DexRemoveLiquidity.tsx +++ b/app/screens/AppNavigator/screens/Dex/DexRemoveLiquidity.tsx @@ -15,7 +15,6 @@ import { Logging } from '../../../../api/logging' import { Text, View } from '../../../../components' import { getTokenIcon } from '../../../../components/icons/tokens/_index' import { PrimaryButton } from '../../../../components/PrimaryButton' -import { useWallet } from '../../../../contexts/WalletContext' import { useTokensAPI } from '../../../../hooks/wallet/TokensAPI' import { RootState } from '../../../../store' import { hasTxQueued, ocean } from '../../../../store/ocean' @@ -55,13 +54,11 @@ export function RemoveLiquidityScreen (props: Props): JSX.Element { setPercentage(new BigNumber(percentage).toFixed(2)) }, [percentage]) - const account = useWallet().get(0) const dispatch = useDispatch() const removeLiquidity = useCallback(() => { if (hasPendingJob) return constructSignedRemoveLiqAndSend( - account, Number(pair.id), amount, dispatch @@ -191,12 +188,11 @@ function ContinueButton (props: { enabled: boolean, onPress: () => void }): JSX. ) } -async function constructSignedRemoveLiqAndSend (account: WhaleWalletAccount, tokenId: number, - amount: BigNumber, dispatch: Dispatch): Promise { - const builder = account.withTransactionBuilder() - const script = await account.getScript() +async function constructSignedRemoveLiqAndSend (tokenId: number, amount: BigNumber, dispatch: Dispatch): Promise { + const signer = async (account: WhaleWalletAccount): Promise => { + const builder = account.withTransactionBuilder() + const script = await account.getScript() - const signer = async (): Promise => { const removeLiq = { script, tokenId, @@ -207,8 +203,7 @@ async function constructSignedRemoveLiqAndSend (account: WhaleWalletAccount, tok } dispatch(ocean.actions.queueTransaction({ - signer, - broadcasted: false, + sign: signer, title: `${translate('screens/RemoveLiquidity', 'Removing Liquidity')}` })) } diff --git a/app/screens/AppNavigator/screens/Dex/PoolSwap/PoolSwapScreen.tsx b/app/screens/AppNavigator/screens/Dex/PoolSwap/PoolSwapScreen.tsx index 9b6594fdc4..bab125ff1a 100644 --- a/app/screens/AppNavigator/screens/Dex/PoolSwap/PoolSwapScreen.tsx +++ b/app/screens/AppNavigator/screens/Dex/PoolSwap/PoolSwapScreen.tsx @@ -308,11 +308,12 @@ async function constructSignedSwapAndSend ( dexForm: DexForm, dispatch: Dispatch ): Promise { - const builder = account.withTransactionBuilder() - const maxPrice = dexForm.fromAmount.div(dexForm.toAmount) - const script = await account.getScript() + const signer = async (): Promise => { + const builder = account.withTransactionBuilder() + const script = await account.getScript() + const swap: PoolSwap = { fromScript: script, toScript: script, @@ -326,8 +327,7 @@ async function constructSignedSwapAndSend ( } dispatch(ocean.actions.queueTransaction({ - signer, - broadcasted: false, + sign: signer, title: `${translate('screens/PoolSwapScreen', 'Swapping Token')}` })) } diff --git a/app/store/ocean.ts b/app/store/ocean.ts index 68d25a9e66..f5af75f51d 100644 --- a/app/store/ocean.ts +++ b/app/store/ocean.ts @@ -1,9 +1,10 @@ import { CTransactionSegWit } from '@defichain/jellyfish-transaction' +import { WhaleWalletAccount } from '@defichain/whale-api-wallet' import { createSelector, createSlice, PayloadAction } from '@reduxjs/toolkit' export interface OceanTransaction { broadcasted: boolean - signer: () => Promise + sign: (account: WhaleWalletAccount) => Promise title?: string } @@ -26,8 +27,8 @@ export const ocean = createSlice({ setHeight: (state, action: PayloadAction) => { state.height = action.payload }, - queueTransaction: (state, action: PayloadAction) => { - state.transactions = [...state.transactions, action.payload] + queueTransaction: (state, action: PayloadAction>) => { + state.transactions = [...state.transactions, { ...action.payload, broadcasted: false }] }, setError: (state, action: PayloadAction) => { state.err = action.payload From 08f8f754e78be967f64beaf1f2af9418313de2b0 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 23 Jul 2021 16:37:53 +0800 Subject: [PATCH 06/48] update unit test --- app/store/ocean.test.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/store/ocean.test.ts b/app/store/ocean.test.ts index 586984234a..f8b2211afc 100644 --- a/app/store/ocean.test.ts +++ b/app/store/ocean.test.ts @@ -1,6 +1,7 @@ import { CTransactionSegWit } from "@defichain/jellyfish-transaction"; +import { WhaleWalletAccount } from '@defichain/whale-api-wallet'; import { SmartBuffer } from "smart-buffer"; -import { ocean, OceanState } from './ocean' +import { ocean, OceanState, OceanTransaction } from './ocean' describe('ocean reducer', () => { let initialState: OceanState @@ -24,8 +25,8 @@ describe('ocean reducer', () => { it('should handle queueTransaction and popTransaction', () => { const v2 = '020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff050393700500ffffffff038260498a040000001976a9143db7aeb218455b697e94f6ff00c548e72221231d88ac7e67ce1d0000000017a914dd7730517e0e4969b4e43677ff5bee682e53420a870000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000' const buffer = SmartBuffer.fromBuffer(Buffer.from(v2, 'hex')) - const signed = new CTransactionSegWit(buffer) - const payload = { title: 'Sending', broadcasted: false, signed } + const sign = async () => new CTransactionSegWit(buffer) + const payload: Omit = { title: 'Sending', sign } const addedTransaction = ocean.reducer(initialState, ocean.actions.queueTransaction(payload)); expect(addedTransaction).toStrictEqual({ transactions: [payload], err: undefined, height: 49 }) const actual = ocean.reducer(addedTransaction, ocean.actions.queueTransaction(payload)); From 216d0d845127a9c6bdd458d7394db7f84244e92e Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 23 Jul 2021 16:56:03 +0800 Subject: [PATCH 07/48] fix outdated jellyfish mnemonic usage --- .../screens/.WalletMnemonicCreate.tsx.swp | Bin 0 -> 4096 bytes .../screens/WalletMnemonicCreate.tsx | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 app/screens/WalletNavigator/screens/.WalletMnemonicCreate.tsx.swp diff --git a/app/screens/WalletNavigator/screens/.WalletMnemonicCreate.tsx.swp b/app/screens/WalletNavigator/screens/.WalletMnemonicCreate.tsx.swp new file mode 100644 index 0000000000000000000000000000000000000000..460cc74269257e350052417e5e486c38918d7fe7 GIT binary patch literal 4096 zcmYc?2=nw+u+TGP00IF929Ba%30F?DGGur&Fl3e`=7EF>0T}^V9y{U zoXYD!2I?m#W~b_>XO`%vq^4!+mnY`rq?YI>78K|gCl{rr<`wIQgN6JO%QDjwOY)1* z#C`KpbMx~ulbwrF6H8L{N{TCR+BYgO8UmvsfE)t648}%=1|Sh-B}D~cp-^PWQTAvE ejE2By2#kinXb6mkz-S1JhQMeDjE2B44*>w;JTIvL literal 0 HcmV?d00001 diff --git a/app/screens/WalletNavigator/screens/WalletMnemonicCreate.tsx b/app/screens/WalletNavigator/screens/WalletMnemonicCreate.tsx index e8e595c1f0..8942f67c21 100644 --- a/app/screens/WalletNavigator/screens/WalletMnemonicCreate.tsx +++ b/app/screens/WalletNavigator/screens/WalletMnemonicCreate.tsx @@ -1,4 +1,4 @@ -import { generateMnemonic } from '@defichain/jellyfish-wallet-mnemonic' +import { generateMnemonicWords } from '@defichain/jellyfish-wallet-mnemonic' import { StackScreenProps } from '@react-navigation/stack' import * as Random from 'expo-random' import * as React from 'react' @@ -11,7 +11,7 @@ import { WalletParamList } from '../WalletNavigator' type Props = StackScreenProps export function WalletMnemonicCreate ({ navigation }: Props): JSX.Element { - const words = generateMnemonic(24, numOfBytes => { + const words = generateMnemonicWords(24, numOfBytes => { const bytes = Random.getRandomBytes(numOfBytes) return Buffer.from(bytes) }) From 87e01760acec77d8a7ee24b5b8aa88a9ea6e6924 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 23 Jul 2021 17:03:24 +0800 Subject: [PATCH 08/48] derive account from wallet context when only needed --- app/components/OceanInterface/OceanInterface.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/components/OceanInterface/OceanInterface.tsx b/app/components/OceanInterface/OceanInterface.tsx index c0080bb260..8a81a07973 100644 --- a/app/components/OceanInterface/OceanInterface.tsx +++ b/app/components/OceanInterface/OceanInterface.tsx @@ -44,9 +44,7 @@ async function broadcastTransaction (tx: CTransactionSegWit, client: WhaleApiCli export function OceanInterface (): JSX.Element | null { const dispatch = useDispatch() const client = useWhaleApiClient() - - // Require fixes to support more than 1 WalletAcount - const whaleWalletAccount = useWallet().get(0) + const walletContext = useWallet() // store const { height, err: e } = useSelector((state: RootState) => state.ocean) @@ -71,7 +69,7 @@ export function OceanInterface (): JSX.Element | null { ...transaction, broadcasted: false }) - transaction.sign(whaleWalletAccount) + transaction.sign(walletContext.get(0)) .then(async signedTx => { setTxid(signedTx.txId) await broadcastTransaction(signedTx, client) @@ -90,7 +88,7 @@ export function OceanInterface (): JSX.Element | null { }) .finally(() => dispatch(ocean.actions.popTransaction())) // remove the job as soon as completion } - }, [transaction]) + }, [transaction, walletContext]) if (tx === undefined) { return null From 57a1888bace0f123020cbbcb4461bb59a5a87ea6 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 23 Jul 2021 17:54:52 +0800 Subject: [PATCH 09/48] add wallet context mock for oceaninterface unit test --- app/components/OceanInterface/OceanInterface.test.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/components/OceanInterface/OceanInterface.test.tsx b/app/components/OceanInterface/OceanInterface.test.tsx index f955313824..2e6c9cea4d 100644 --- a/app/components/OceanInterface/OceanInterface.test.tsx +++ b/app/components/OceanInterface/OceanInterface.test.tsx @@ -8,6 +8,12 @@ import { RootState } from "../../store"; import { ocean } from "../../store/ocean"; import { OceanInterface } from "./OceanInterface"; +jest.mock('../../contexts/WalletContext', () => ({ + useWallet: jest.fn().mockReturnValue({ + get: jest.fn() + }) +})) + describe('oceanInterface', () => { it('should match snapshot with error', async () => { const initialState: Partial = { @@ -40,7 +46,7 @@ describe('oceanInterface', () => { height: 49, transactions: [{ broadcasted: false, - signer: async () => signed + sign: async () => signed }] } }; From a44f76a65f59f9597904645bde0bef6c6adad278 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 23 Jul 2021 18:06:30 +0800 Subject: [PATCH 10/48] fix test, redux store auto append broadcast status on queue --- app/store/ocean.test.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/store/ocean.test.ts b/app/store/ocean.test.ts index f8b2211afc..1c1ffde0dc 100644 --- a/app/store/ocean.test.ts +++ b/app/store/ocean.test.ts @@ -28,11 +28,17 @@ describe('ocean reducer', () => { const sign = async () => new CTransactionSegWit(buffer) const payload: Omit = { title: 'Sending', sign } const addedTransaction = ocean.reducer(initialState, ocean.actions.queueTransaction(payload)); - expect(addedTransaction).toStrictEqual({ transactions: [payload], err: undefined, height: 49 }) + expect(addedTransaction).toStrictEqual({ transactions: [{ + ...payload, + broadcasted: false + }], err: undefined, height: 49 }) const actual = ocean.reducer(addedTransaction, ocean.actions.queueTransaction(payload)); const pop = ocean.reducer(actual, ocean.actions.popTransaction()); - expect(pop).toStrictEqual({ transactions: [payload], err: undefined, height: 49 }) + expect(pop).toStrictEqual({ transactions: [{ + ...payload, + broadcasted: false + }], err: undefined, height: 49 }) const removed = ocean.reducer(pop, ocean.actions.popTransaction()); expect(removed).toStrictEqual({ transactions: [], err: undefined, height: 49 }) }) From 46ec1cf0bbc74a57cc2d580be2ef49a76d3e8e46 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 23 Jul 2021 19:43:18 +0800 Subject: [PATCH 11/48] install rn scrypt, implement native ScryptProvider --- app/api/scrypt.native.ts | 36 +++++++ app/api/scrypt.ts | 3 + app/api/wallet/persistence.ts | 3 +- app/api/wallet/provider/mnemonic_encrypted.ts | 35 +++++++ .../screens/PinConfirmation.tsx | 5 +- .../screens/WalletMnemonicCreateVerify.tsx | 16 ++-- package-lock.json | 95 +++++++++++++++++++ package.json | 2 + 8 files changed, 185 insertions(+), 10 deletions(-) create mode 100644 app/api/scrypt.native.ts create mode 100644 app/api/scrypt.ts create mode 100644 app/api/wallet/provider/mnemonic_encrypted.ts diff --git a/app/api/scrypt.native.ts b/app/api/scrypt.native.ts new file mode 100644 index 0000000000..f4bce17166 --- /dev/null +++ b/app/api/scrypt.native.ts @@ -0,0 +1,36 @@ +import rnScrypt from 'react-native-scrypt' +import { ScryptProvider, ScryptParams, Scrypt } from '@defichain/jellyfish-wallet-encrypted' + +const DEFAULT_SCRYPT_PARAMS: ScryptParams = { + N: 16384, + r: 8, + p: 8 +} + +class NativeScryptModule implements ScryptProvider { + passphraseToKey (nfcUtf8: string, salt: Buffer, desiredKeyLen: number): Buffer { + let result: Buffer | null = null + let err: Error | null = null + rnScrypt( + Buffer.from(nfcUtf8, 'ascii'), + salt, + DEFAULT_SCRYPT_PARAMS.N, + DEFAULT_SCRYPT_PARAMS.r, + DEFAULT_SCRYPT_PARAMS.p, + desiredKeyLen, + 'buffer' + ) + .then(buffer => { result = buffer }) + .catch((e: Error) => { err = e }) + + const start = Date.now() + while (true) { + if (result !== null) { + return result + } + if (err !== null || (Date.now() > start + 10000)) throw new Error() + } + } +} + +export const scrypt = new Scrypt(new NativeScryptModule()) diff --git a/app/api/scrypt.ts b/app/api/scrypt.ts new file mode 100644 index 0000000000..55870c5612 --- /dev/null +++ b/app/api/scrypt.ts @@ -0,0 +1,3 @@ +import { SimpleScryptsy, Scrypt } from '@defichain/jellyfish-wallet-encrypted' + +export const scrypt = new Scrypt(new SimpleScryptsy()) diff --git a/app/api/wallet/persistence.ts b/app/api/wallet/persistence.ts index c86f8eca1a..b14874e509 100644 --- a/app/api/wallet/persistence.ts +++ b/app/api/wallet/persistence.ts @@ -1,7 +1,8 @@ import { getItem, setItem } from '../storage' export enum WalletType { - MNEMONIC_UNPROTECTED = 'MNEMONIC_UNPROTECTED' + MNEMONIC_UNPROTECTED = 'MNEMONIC_UNPROTECTED', + MNEMONIC_ENCRYPTED = 'MNEMONIC_ENCRYPTED' } export interface WalletPersistenceData { diff --git a/app/api/wallet/provider/mnemonic_encrypted.ts b/app/api/wallet/provider/mnemonic_encrypted.ts new file mode 100644 index 0000000000..8e159c2cad --- /dev/null +++ b/app/api/wallet/provider/mnemonic_encrypted.ts @@ -0,0 +1,35 @@ +import { MnemonicHdNodeProvider, MnemonicProviderData } from '@defichain/jellyfish-wallet-mnemonic' +import { EnvironmentNetwork } from '../../../environment' +import { getBip32Option } from '../network' +import { WalletPersistenceData, WalletType } from '../persistence' + +function initProvider (data: WalletPersistenceData, network: EnvironmentNetwork): MnemonicHdNodeProvider { + if (data.type !== WalletType.MNEMONIC_UNPROTECTED || data.version !== 'v1') { + throw new Error('Unexpected WalletPersistenceData') + } + + const options = getBip32Option(network) + return MnemonicHdNodeProvider.fromData(data.raw, options) +} + +function toData (mnemonic: string[], network: EnvironmentNetwork): WalletPersistenceData { + const options = getBip32Option(network) + const data = MnemonicHdNodeProvider.wordsToData(mnemonic, options) + + return { + version: 'v1', + type: WalletType.MNEMONIC_UNPROTECTED, + raw: data + } +} + +export const MnemonicUnprotected = { + initProvider, + toData, + /** + * Convenience Abandon23 Art on Playground Network Data + */ + Abandon23Playground: toData([ + 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'art' + ], EnvironmentNetwork.LocalPlayground) +} diff --git a/app/screens/WalletNavigator/screens/PinConfirmation.tsx b/app/screens/WalletNavigator/screens/PinConfirmation.tsx index b804700f79..b243562671 100644 --- a/app/screens/WalletNavigator/screens/PinConfirmation.tsx +++ b/app/screens/WalletNavigator/screens/PinConfirmation.tsx @@ -2,7 +2,7 @@ import { StackScreenProps } from '@react-navigation/stack' import React, { useState } from 'react' import { ScrollView } from 'react-native' import tailwind from 'tailwind-rn' -import { Mnemonic } from '../../../api/wallet/mnemonic' +import { MnemonicUnprotected } from '../../../api/wallet/provider/mnemonic_unprotected' import { Text } from '../../../components' import { PinInput } from '../../../components/PinInput' import { useNetworkContext } from '../../../contexts/NetworkContext' @@ -28,8 +28,7 @@ export function PinConfirmation ({ route }: Props): JSX.Element { setInvalid(true) return } - - setWallet(Mnemonic.createWalletData(words, network)) + setWallet(MnemonicUnprotected.toData(words, network)) .catch(e => console.log(e)) } diff --git a/app/screens/WalletNavigator/screens/WalletMnemonicCreateVerify.tsx b/app/screens/WalletNavigator/screens/WalletMnemonicCreateVerify.tsx index 37004ab4ed..274e9d05cd 100644 --- a/app/screens/WalletNavigator/screens/WalletMnemonicCreateVerify.tsx +++ b/app/screens/WalletNavigator/screens/WalletMnemonicCreateVerify.tsx @@ -3,10 +3,10 @@ import { StackScreenProps } from '@react-navigation/stack' import * as React from 'react' import { useState } from 'react' import { KeyboardAvoidingView, ScrollView, TouchableOpacity } from 'react-native' -import { MnemonicUnprotected } from '../../../api/wallet/provider/mnemonic_unprotected' +// import { MnemonicUnprotected } from '../../../api/wallet/provider/mnemonic_unprotected' import { Text, TextInput, View } from '../../../components' -import { useNetworkContext } from '../../../contexts/NetworkContext' -import { useWalletManagementContext } from '../../../contexts/WalletManagementContext' +// import { useNetworkContext } from '../../../contexts/NetworkContext' +// import { useWalletManagementContext } from '../../../contexts/WalletManagementContext' import { getEnvironment } from '../../../environment' import { tailwind } from '../../../tailwind' import { WalletParamList } from '../WalletNavigator' @@ -19,12 +19,16 @@ export function WalletMnemonicCreateVerify ({ route }: Props): JSX.Element { const enteredWords: string[] = [] const [valid, setValid] = useState(true) - const { network } = useNetworkContext() - const { setWallet } = useWalletManagementContext() + // const { network } = useNetworkContext() + // const { setWallet } = useWalletManagementContext() async function onVerify (): Promise { if (actualWords.join(' ') === enteredWords.join(' ')) { - await setWallet(MnemonicUnprotected.toData(enteredWords, network)) + // await setWallet(MnemonicUnprotected.toData(enteredWords, network)) + navigation.navigate('PinCreation', { + words: actualWords, + pinLength: 6 + }) } else { setValid(false) } diff --git a/package-lock.json b/package-lock.json index af82cb5fcb..5af46d007e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@defichain/jellyfish-transaction": ">=0.29.0", "@defichain/jellyfish-transaction-builder": ">=0.29.0", "@defichain/jellyfish-wallet": ">=0.29.0", + "@defichain/jellyfish-wallet-encrypted": ">=0.29.0", "@defichain/jellyfish-wallet-mnemonic": ">=0.29.0", "@defichain/playground-api-client": ">=0.6.0", "@defichain/whale-api-client": ">=0.5.10", @@ -54,6 +55,7 @@ "react-native-qrcode-svg": "^6.1.1", "react-native-safe-area-context": "3.2.0", "react-native-screens": "~3.4.0", + "react-native-scrypt": "^1.2.1", "react-native-svg": "12.1.1", "react-native-web": "~0.13.12", "react-number-format": "^4.6.4", @@ -2117,6 +2119,15 @@ "bignumber.js": "^9.0.1" } }, + "node_modules/@defichain/jellyfish-wallet-encrypted": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet-encrypted/-/jellyfish-wallet-encrypted-0.29.0.tgz", + "integrity": "sha512-Fo9ppgeP0IBdocqj0wnQPW/Q7X7PPsZXrORgNikvwdkTrSfN/yRUZioGKI+c3nziOR80OWdUkwGSdKYHu0/9Rw==", + "dependencies": { + "@defichain/jellyfish-wallet-mnemonic": "^0.29.0", + "scryptsy": "^2.1.0" + } + }, "node_modules/@defichain/jellyfish-wallet-mnemonic": { "version": "0.29.0", "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet-mnemonic/-/jellyfish-wallet-mnemonic-0.29.0.tgz", @@ -31846,6 +31857,46 @@ "react-native": "*" } }, + "node_modules/react-native-scrypt": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/react-native-scrypt/-/react-native-scrypt-1.2.1.tgz", + "integrity": "sha512-y03UtSmpY6RtdNB4Ne3yxe7xFNl2BYQMB59p+/iJGch+9eJ05Lw8DObX9dOA/bldlCljtP3KsCiZmf/rwrzeSA==", + "dependencies": { + "@types/node": "^14.14.35", + "buffer": "^5.6.0" + }, + "peerDependencies": { + "react-native": "*" + } + }, + "node_modules/react-native-scrypt/node_modules/@types/node": { + "version": "14.17.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.5.tgz", + "integrity": "sha512-bjqH2cX/O33jXT/UmReo2pM7DIJREPMnarixbQ57DOOzzFaI6D2+IcwaJQaJpv0M1E9TIhPCYVxrkcityLjlqA==" + }, + "node_modules/react-native-scrypt/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/react-native-svg": { "version": "12.1.1", "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-12.1.1.tgz", @@ -33269,6 +33320,11 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/scryptsy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scryptsy/-/scryptsy-2.1.0.tgz", + "integrity": "sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w==" + }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -42105,6 +42161,15 @@ "@defichain/jellyfish-transaction": "^0.29.0" } }, + "@defichain/jellyfish-wallet-encrypted": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet-encrypted/-/jellyfish-wallet-encrypted-0.29.0.tgz", + "integrity": "sha512-Fo9ppgeP0IBdocqj0wnQPW/Q7X7PPsZXrORgNikvwdkTrSfN/yRUZioGKI+c3nziOR80OWdUkwGSdKYHu0/9Rw==", + "requires": { + "@defichain/jellyfish-wallet-mnemonic": "^0.29.0", + "scryptsy": "^2.1.0" + } + }, "@defichain/jellyfish-wallet-mnemonic": { "version": "0.29.0", "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet-mnemonic/-/jellyfish-wallet-mnemonic-0.29.0.tgz", @@ -65649,6 +65714,31 @@ "warn-once": "^0.1.0" } }, + "react-native-scrypt": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/react-native-scrypt/-/react-native-scrypt-1.2.1.tgz", + "integrity": "sha512-y03UtSmpY6RtdNB4Ne3yxe7xFNl2BYQMB59p+/iJGch+9eJ05Lw8DObX9dOA/bldlCljtP3KsCiZmf/rwrzeSA==", + "requires": { + "@types/node": "^14.14.35", + "buffer": "^5.6.0" + }, + "dependencies": { + "@types/node": { + "version": "14.17.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.5.tgz", + "integrity": "sha512-bjqH2cX/O33jXT/UmReo2pM7DIJREPMnarixbQ57DOOzzFaI6D2+IcwaJQaJpv0M1E9TIhPCYVxrkcityLjlqA==" + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + } + } + }, "react-native-svg": { "version": "12.1.1", "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-12.1.1.tgz", @@ -66674,6 +66764,11 @@ "ajv-keywords": "^3.5.2" } }, + "scryptsy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scryptsy/-/scryptsy-2.1.0.tgz", + "integrity": "sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w==" + }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", diff --git a/package.json b/package.json index a78265935b..21c2fce6d7 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@defichain/jellyfish-transaction": ">=0.29.0", "@defichain/jellyfish-transaction-builder": ">=0.29.0", "@defichain/jellyfish-wallet": ">=0.29.0", + "@defichain/jellyfish-wallet-encrypted": ">=0.29.0", "@defichain/jellyfish-wallet-mnemonic": ">=0.29.0", "@defichain/playground-api-client": ">=0.6.0", "@defichain/whale-api-client": ">=0.5.10", @@ -64,6 +65,7 @@ "react-native-qrcode-svg": "^6.1.1", "react-native-safe-area-context": "3.2.0", "react-native-screens": "~3.4.0", + "react-native-scrypt": "^1.2.1", "react-native-svg": "12.1.1", "react-native-web": "~0.13.12", "react-number-format": "^4.6.4", From c2986ac1dc3755c4653364b627115f50b6400047 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 27 Jul 2021 10:41:40 +0800 Subject: [PATCH 12/48] quick save --- ...scrypt.native.ts => scrypt.native.temp.ts} | 0 app/api/wallet/provider/index.ts | 10 +- app/api/wallet/provider/mnemonic_encrypted.ts | 30 ++- .../OceanInterface/OceanInterface.tsx | 62 ++++- app/components/PinInput.tsx | 13 +- app/contexts/WalletManagementContext.tsx | 12 +- .../screens/Balances/BalancesScreen.tsx | 1 + .../screens/.WalletMnemonicCreate.tsx.swp | Bin 4096 -> 0 bytes .../screens/PinConfirmation.tsx | 6 +- app/store/ocean.test.ts | 6 +- package-lock.json | 216 +++++++++--------- package.json | 16 +- 12 files changed, 226 insertions(+), 146 deletions(-) rename app/api/{scrypt.native.ts => scrypt.native.temp.ts} (100%) delete mode 100644 app/screens/WalletNavigator/screens/.WalletMnemonicCreate.tsx.swp diff --git a/app/api/scrypt.native.ts b/app/api/scrypt.native.temp.ts similarity index 100% rename from app/api/scrypt.native.ts rename to app/api/scrypt.native.temp.ts diff --git a/app/api/wallet/provider/index.ts b/app/api/wallet/provider/index.ts index 30270e9a51..27cf538ff6 100644 --- a/app/api/wallet/provider/index.ts +++ b/app/api/wallet/provider/index.ts @@ -4,6 +4,7 @@ import { WhaleWalletAccount, WhaleWalletAccountProvider } from '@defichain/whale import { EnvironmentNetwork } from '../../../environment' import { getJellyfishNetwork } from '../network' import { WalletPersistenceData, WalletType } from '../persistence' +import { MnemonicEncrypted, PromptInterface } from './mnemonic_encrypted' import { MnemonicUnprotected } from './mnemonic_unprotected' /** @@ -11,10 +12,10 @@ import { MnemonicUnprotected } from './mnemonic_unprotected' */ export type WhaleWallet = JellyfishWallet -export function initWhaleWallet (data: WalletPersistenceData, network: EnvironmentNetwork, client: WhaleApiClient): WhaleWallet { +export function initWhaleWallet (data: WalletPersistenceData, network: EnvironmentNetwork, client: WhaleApiClient, promptInterface?: PromptInterface): WhaleWallet { const jellyfishNetwork = getJellyfishNetwork(network) - const walletProvider = resolveProvider(data, network) + const walletProvider = resolveProvider(data, network, promptInterface) const accountProvider = new WhaleWalletAccountProvider(client, jellyfishNetwork) return new JellyfishWallet(walletProvider, accountProvider) @@ -24,11 +25,14 @@ export function initWhaleWallet (data: WalletPersistenceData, network: Envi * @param {WalletPersistenceData} data to resolve wallet provider for init * @param {EnvironmentNetwork} network */ -function resolveProvider (data: WalletPersistenceData, network: EnvironmentNetwork): WalletHdNodeProvider { +function resolveProvider (data: WalletPersistenceData, network: EnvironmentNetwork, promptInterface?: PromptInterface): WalletHdNodeProvider { + console.log('wallet type', data.type) switch (data.type) { case WalletType.MNEMONIC_UNPROTECTED: return MnemonicUnprotected.initProvider(data, network) + case WalletType.MNEMONIC_ENCRYPTED: + return MnemonicEncrypted.initProvider(data, network, promptInterface) default: throw new Error(`wallet ${data.type as string} not available`) } diff --git a/app/api/wallet/provider/mnemonic_encrypted.ts b/app/api/wallet/provider/mnemonic_encrypted.ts index 8e159c2cad..45f2a7fbd8 100644 --- a/app/api/wallet/provider/mnemonic_encrypted.ts +++ b/app/api/wallet/provider/mnemonic_encrypted.ts @@ -1,29 +1,41 @@ -import { MnemonicHdNodeProvider, MnemonicProviderData } from '@defichain/jellyfish-wallet-mnemonic' +import { EncryptedHdNodeProvider, EncryptedProviderData, PromptPassphrase } from '@defichain/jellyfish-wallet-encrypted' import { EnvironmentNetwork } from '../../../environment' +import { scrypt } from '../../scrypt' import { getBip32Option } from '../network' import { WalletPersistenceData, WalletType } from '../persistence' -function initProvider (data: WalletPersistenceData, network: EnvironmentNetwork): MnemonicHdNodeProvider { - if (data.type !== WalletType.MNEMONIC_UNPROTECTED || data.version !== 'v1') { +export interface PromptInterface { + prompt: PromptPassphrase +} + +function initProvider ( + data: WalletPersistenceData, + network: EnvironmentNetwork, + promptInterface?: PromptInterface // must allow construction/new for every prompt +): EncryptedHdNodeProvider { + if (data.type !== WalletType.MNEMONIC_ENCRYPTED || data.version !== 'v1') { throw new Error('Unexpected WalletPersistenceData') } const options = getBip32Option(network) - return MnemonicHdNodeProvider.fromData(data.raw, options) + return EncryptedHdNodeProvider.init(data.raw, options, scrypt, async () => { + if (promptInterface === undefined) return '' // before OceanInterface bridged the UI + return await promptInterface.prompt() + }) } -function toData (mnemonic: string[], network: EnvironmentNetwork): WalletPersistenceData { +async function toData (mnemonic: string[], network: EnvironmentNetwork, passphrase: string): Promise> { const options = getBip32Option(network) - const data = MnemonicHdNodeProvider.wordsToData(mnemonic, options) + const data = await EncryptedHdNodeProvider.wordsToEncryptedData(mnemonic, options, scrypt, passphrase) return { version: 'v1', - type: WalletType.MNEMONIC_UNPROTECTED, + type: WalletType.MNEMONIC_ENCRYPTED, raw: data } } -export const MnemonicUnprotected = { +export const MnemonicEncrypted = { initProvider, toData, /** @@ -31,5 +43,5 @@ export const MnemonicUnprotected = { */ Abandon23Playground: toData([ 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'art' - ], EnvironmentNetwork.LocalPlayground) + ], EnvironmentNetwork.LocalPlayground, '123456') } diff --git a/app/components/OceanInterface/OceanInterface.tsx b/app/components/OceanInterface/OceanInterface.tsx index 8a81a07973..a08f4b265b 100644 --- a/app/components/OceanInterface/OceanInterface.tsx +++ b/app/components/OceanInterface/OceanInterface.tsx @@ -7,13 +7,16 @@ import { useDispatch, useSelector } from 'react-redux' import { Text } from '..' import { Logging } from '../../api/logging' import { useWallet } from '../../contexts/WalletContext' +import { useWalletManagementContext } from '../../contexts/WalletManagementContext' import { useWhaleApiClient } from '../../contexts/WhaleContext' import { RootState } from '../../store' import { firstTransactionSelector, ocean, OceanTransaction } from '../../store/ocean' import { tailwind } from '../../tailwind' import { translate } from '../../translations' +import { PinInput } from '../PinInput' const MAX_AUTO_RETRY = 1 +const PASSCODE_LENGTH = 6 async function gotoExplorer (txid: string): Promise { // TODO(thedoublejay) explorer URL @@ -44,6 +47,7 @@ async function broadcastTransaction (tx: CTransactionSegWit, client: WhaleApiCli export function OceanInterface (): JSX.Element | null { const dispatch = useDispatch() const client = useWhaleApiClient() + const walletManagement = useWalletManagementContext() const walletContext = useWallet() // store @@ -51,10 +55,15 @@ export function OceanInterface (): JSX.Element | null { const transaction = useSelector((state: RootState) => firstTransactionSelector(state.ocean)) const slideAnim = useRef(new Animated.Value(0)).current // state - const [tx, setTx] = useState(transaction) + const [tx, setTx] = useState() const [err, setError] = useState(e) const [txid, setTxid] = useState() + // passcode interface + const [isPrompting, setIsPrompting] = useState(false) + const [passcode, setPasscode] = useState('') + const passcodeResolverRef = useRef<(val: string) => void>() + const dismissDrawer = useCallback(() => { setTx(undefined) setError(undefined) @@ -65,10 +74,8 @@ export function OceanInterface (): JSX.Element | null { // last available job will remained in this UI state until get dismissed if (transaction !== undefined) { Animated.timing(slideAnim, { toValue: height, duration: 200, useNativeDriver: false }).start() - setTx({ - ...transaction, - broadcasted: false - }) + setTx(transaction) + transaction.sign(walletContext.get(0)) .then(async signedTx => { setTxid(signedTx.txId) @@ -84,12 +91,36 @@ export function OceanInterface (): JSX.Element | null { if (txid !== undefined) { errMsg = `${errMsg}. Txid: ${txid}` } + console.log(e) setError(new Error(errMsg)) }) .finally(() => dispatch(ocean.actions.popTransaction())) // remove the job as soon as completion } }, [transaction, walletContext]) + // UI provide interface to WalletContext to access pin request + useEffect(() => { + const passcodePromptConstructor = { + prompt: async (): Promise => { + setIsPrompting(true) + console.log('waiting') + const pass = await new Promise(resolve => { passcodeResolverRef.current = resolve }) + console.log('input: ') + setIsPrompting(false) + console.log('complete taking pin') + return pass + } + } + walletManagement.setPasscodePromptInterface(passcodePromptConstructor) + }, []) + + useEffect(() => { + if (!isPrompting && passcode.length === PASSCODE_LENGTH && passcodeResolverRef.current !== undefined) { + passcodeResolverRef.current(passcode) + passcodeResolverRef.current = undefined + } + }, [passcode, isPrompting]) + if (tx === undefined) { return null } @@ -104,13 +135,26 @@ export function OceanInterface (): JSX.Element | null { { err !== undefined ? - : + : ( + { + setIsPrompting(false) + setPasscode(pass) + // if (pass.length === PASSCODE_LENGTH && resolvePasscode !== undefined) resolvePasscode(pass) + // if (pass.length === PASSCODE_LENGTH && passcodeResolverRef.current !== undefined) passcodeResolverRef.current(pass) + }} + /> + ) } ) } -function TransactionDetail ({ broadcasted, txid, onClose }: { broadcasted: boolean, txid?: string, onClose: () => void }): JSX.Element { +function TransactionDetail ({ isPrompting, broadcasted, txid, onClose, onPasscodeInput }: { isPrompting: boolean, broadcasted: boolean, txid?: string, onClose: () => void, onPasscodeInput: (passcode: string) => void }): JSX.Element | null { let title = 'Signing...' if (txid !== undefined) title = 'Broadcasting...' if (broadcasted) title = 'Transaction Sent' @@ -125,6 +169,9 @@ function TransactionDetail ({ broadcasted, txid, onClose }: { broadcasted: boole style={tailwind('text-sm font-bold')} >{translate('screens/OceanInterface', title)} + { + isPrompting && + } { txid !== undefined && await gotoExplorer(txid)} /> } @@ -137,6 +184,7 @@ function TransactionDetail ({ broadcasted, txid, onClose }: { broadcasted: boole } function TransactionError ({ errMsg, onClose }: { errMsg: string | undefined, onClose: () => void }): JSX.Element { + console.log(errMsg) return ( <> diff --git a/app/components/PinInput.tsx b/app/components/PinInput.tsx index 5a17122afc..a42db0d2ec 100644 --- a/app/components/PinInput.tsx +++ b/app/components/PinInput.tsx @@ -1,5 +1,5 @@ import { MaterialIcons } from '@expo/vector-icons' -import React, { useState, useRef, useEffect } from 'react' +import React, { useState, useRef, useEffect, useLayoutEffect } from 'react' import { TextInput, TouchableOpacity } from 'react-native' import tailwind from 'tailwind-rn' @@ -18,6 +18,12 @@ export function PinInput ({ length, onChange }: PinInputOptions): JSX.Element { _textInput.current?.focus() }, [_textInput]) + useEffect(() => { + if (text.length === length) { + onChange(text) + } + }, [text]) + const digitBoxes = (): JSX.Element => { const arr = [] for (let i = 0; i < length; i++) { @@ -46,10 +52,7 @@ export function PinInput ({ length, onChange }: PinInputOptions): JSX.Element { secureTextEntry autoFocus maxLength={length} - onChangeText={txt => { - setText(txt) - onChange(txt) - }} + onChangeText={txt => { setText(txt) }} /> ) diff --git a/app/contexts/WalletManagementContext.tsx b/app/contexts/WalletManagementContext.tsx index d03c148c87..d49f0f512d 100644 --- a/app/contexts/WalletManagementContext.tsx +++ b/app/contexts/WalletManagementContext.tsx @@ -2,6 +2,7 @@ import React, { createContext, useContext, useEffect, useMemo, useState } from ' import { Logging } from '../api/logging' import { WalletPersistence, WalletPersistenceData } from '../api/wallet/persistence' import { initWhaleWallet, WhaleWallet } from '../api/wallet/provider' +import { PromptInterface } from '../api/wallet/provider/mnemonic_encrypted' import { useNetworkContext } from './NetworkContext' import { useWhaleApiClient } from './WhaleContext' @@ -9,6 +10,7 @@ interface WalletManagement { wallets: WhaleWallet[] setWallet: (data: WalletPersistenceData) => Promise clearWallets: () => Promise + setPasscodePromptInterface: (constructPrompt: PromptInterface) => void } const WalletManagementContext = createContext(undefined as any) @@ -21,6 +23,7 @@ export function WalletManagementProvider (props: React.PropsWithChildren): const { network } = useNetworkContext() const client = useWhaleApiClient() const [dataList, setDataList] = useState>>([]) + const [promptInterface, setPromptInterface] = useState() useEffect(() => { WalletPersistence.get().then(dataList => { @@ -29,8 +32,10 @@ export function WalletManagementProvider (props: React.PropsWithChildren): }, [network]) const wallets = useMemo(() => { - return dataList.map(data => initWhaleWallet(data, network, client)) - }, [dataList]) + console.log('use wallet memo, prompt constructor', promptInterface) + if (promptInterface !== undefined) console.log('prompt constructor set') + return dataList.map(data => initWhaleWallet(data, network, client, promptInterface)) + }, [dataList, promptInterface]) const management: WalletManagement = { wallets: wallets, @@ -41,6 +46,9 @@ export function WalletManagementProvider (props: React.PropsWithChildren): async clearWallets (): Promise { await WalletPersistence.set([]) setDataList(await WalletPersistence.get()) + }, + setPasscodePromptInterface (cb: PromptInterface): void { + setPromptInterface(cb) } } diff --git a/app/screens/AppNavigator/screens/Balances/BalancesScreen.tsx b/app/screens/AppNavigator/screens/Balances/BalancesScreen.tsx index fea388e32a..969a93ac06 100644 --- a/app/screens/AppNavigator/screens/Balances/BalancesScreen.tsx +++ b/app/screens/AppNavigator/screens/Balances/BalancesScreen.tsx @@ -28,6 +28,7 @@ export function BalancesScreen ({ navigation }: Props): JSX.Element { const dispatch = useDispatch() useEffect(() => { + console.log('height') dispatch(ocean.actions.setHeight(height)) }, [height]) diff --git a/app/screens/WalletNavigator/screens/.WalletMnemonicCreate.tsx.swp b/app/screens/WalletNavigator/screens/.WalletMnemonicCreate.tsx.swp deleted file mode 100644 index 460cc74269257e350052417e5e486c38918d7fe7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmYc?2=nw+u+TGP00IF929Ba%30F?DGGur&Fl3e`=7EF>0T}^V9y{U zoXYD!2I?m#W~b_>XO`%vq^4!+mnY`rq?YI>78K|gCl{rr<`wIQgN6JO%QDjwOY)1* z#C`KpbMx~ulbwrF6H8L{N{TCR+BYgO8UmvsfE)t648}%=1|Sh-B}D~cp-^PWQTAvE ejE2By2#kinXb6mkz-S1JhQMeDjE2B44*>w;JTIvL diff --git a/app/screens/WalletNavigator/screens/PinConfirmation.tsx b/app/screens/WalletNavigator/screens/PinConfirmation.tsx index b243562671..b71f4d921f 100644 --- a/app/screens/WalletNavigator/screens/PinConfirmation.tsx +++ b/app/screens/WalletNavigator/screens/PinConfirmation.tsx @@ -2,6 +2,7 @@ import { StackScreenProps } from '@react-navigation/stack' import React, { useState } from 'react' import { ScrollView } from 'react-native' import tailwind from 'tailwind-rn' +import { MnemonicEncrypted } from '../../../api/wallet/provider/mnemonic_encrypted' import { MnemonicUnprotected } from '../../../api/wallet/provider/mnemonic_unprotected' import { Text } from '../../../components' import { PinInput } from '../../../components/PinInput' @@ -28,7 +29,10 @@ export function PinConfirmation ({ route }: Props): JSX.Element { setInvalid(true) return } - setWallet(MnemonicUnprotected.toData(words, network)) + MnemonicEncrypted.toData(words, network, pin) + .then(async encrypted => { + await setWallet(encrypted) + }) .catch(e => console.log(e)) } diff --git a/app/store/ocean.test.ts b/app/store/ocean.test.ts index 1c1ffde0dc..4c8b08bf8a 100644 --- a/app/store/ocean.test.ts +++ b/app/store/ocean.test.ts @@ -26,18 +26,18 @@ describe('ocean reducer', () => { const v2 = '020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff050393700500ffffffff038260498a040000001976a9143db7aeb218455b697e94f6ff00c548e72221231d88ac7e67ce1d0000000017a914dd7730517e0e4969b4e43677ff5bee682e53420a870000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000' const buffer = SmartBuffer.fromBuffer(Buffer.from(v2, 'hex')) const sign = async () => new CTransactionSegWit(buffer) - const payload: Omit = { title: 'Sending', sign } + const payload: Omit = { title: 'Sending', sign } const addedTransaction = ocean.reducer(initialState, ocean.actions.queueTransaction(payload)); expect(addedTransaction).toStrictEqual({ transactions: [{ ...payload, - broadcasted: false + status: 'INITIAL' }], err: undefined, height: 49 }) const actual = ocean.reducer(addedTransaction, ocean.actions.queueTransaction(payload)); const pop = ocean.reducer(actual, ocean.actions.popTransaction()); expect(pop).toStrictEqual({ transactions: [{ ...payload, - broadcasted: false + status: 'INITIAL' }], err: undefined, height: 49 }) const removed = ocean.reducer(pop, ocean.actions.popTransaction()); expect(removed).toStrictEqual({ transactions: [], err: undefined, height: 49 }) diff --git a/package-lock.json b/package-lock.json index 5af46d007e..94f120cb3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,14 +7,14 @@ "name": "@defichain/wallet", "license": "MIT", "dependencies": { - "@defichain/jellyfish-address": ">=0.29.0", - "@defichain/jellyfish-api-core": ">=0.29.0", - "@defichain/jellyfish-network": ">=0.29.0", - "@defichain/jellyfish-transaction": ">=0.29.0", - "@defichain/jellyfish-transaction-builder": ">=0.29.0", - "@defichain/jellyfish-wallet": ">=0.29.0", - "@defichain/jellyfish-wallet-encrypted": ">=0.29.0", - "@defichain/jellyfish-wallet-mnemonic": ">=0.29.0", + "@defichain/jellyfish-address": ">=0.29.1", + "@defichain/jellyfish-api-core": ">=0.29.1", + "@defichain/jellyfish-network": ">=0.29.1", + "@defichain/jellyfish-transaction": ">=0.29.1", + "@defichain/jellyfish-transaction-builder": ">=0.29.1", + "@defichain/jellyfish-wallet": ">=0.29.1", + "@defichain/jellyfish-wallet-encrypted": ">=0.29.1", + "@defichain/jellyfish-wallet-mnemonic": ">=0.29.1", "@defichain/playground-api-client": ">=0.6.0", "@defichain/whale-api-client": ">=0.5.10", "@defichain/whale-api-wallet": ">=0.5.10", @@ -2017,28 +2017,28 @@ } }, "node_modules/@defichain/jellyfish-address": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-address/-/jellyfish-address-0.29.0.tgz", - "integrity": "sha512-miFMJsOsDMhDc7NklE6nmFwcGmAtv1E/l5Uw3SdEv0meU5u9fv3I41yZS7wFwk/kUgDvgF3LeZNfiDVx6f2v1Q==", + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-address/-/jellyfish-address-0.29.1.tgz", + "integrity": "sha512-XnhEHfciikRaFJqEE23FeLkriWAXK80Y/SueHHSOBv5LTTagNSJ8/rLnxppXIQaQQu97tsWLl72JSEku/SfY0w==", "dependencies": { - "@defichain/jellyfish-crypto": "^0.29.0", - "@defichain/jellyfish-network": "^0.29.0", - "@defichain/jellyfish-transaction": "^0.29.0", + "@defichain/jellyfish-crypto": "^0.29.1", + "@defichain/jellyfish-network": "^0.29.1", + "@defichain/jellyfish-transaction": "^0.29.1", "bs58": "^4.0.1" } }, "node_modules/@defichain/jellyfish-api-core": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-api-core/-/jellyfish-api-core-0.29.0.tgz", - "integrity": "sha512-gRmw55bQK0QwPHZ8Rfqv7GVdFJ6MOcjF/H2Xxtmqez+iFUA0EniGMHqloYvLjFABCNlhZvOJd8HU242IWStLsg==", + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-api-core/-/jellyfish-api-core-0.29.1.tgz", + "integrity": "sha512-jo5dn4gUmk3nYrS2DT+wbeRLqZ6IxaGLOyagOAqMv3Yqsqn6J6FF4OOGRADgikxjYf14AFh0wxIaZSNTAl0XUQ==", "dependencies": { - "@defichain/jellyfish-json": "^0.29.0" + "@defichain/jellyfish-json": "^0.29.1" } }, "node_modules/@defichain/jellyfish-crypto": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-crypto/-/jellyfish-crypto-0.29.0.tgz", - "integrity": "sha512-QGIN8WGisE8uIhQD1nCSic+QCIfU/s8pGMufiDEukceThovukcT+jhWvfjmGf8qnXSViuTf8SXR+EjT+R83R7A==", + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-crypto/-/jellyfish-crypto-0.29.1.tgz", + "integrity": "sha512-ixLUuYeDL2Fe/+k9p+tEEqG8ICKqtSUBjM/S0K8QIjjynfIcd+kg4LxQzIEBDpcnxcQD1JFLcfiDta6Q404f3g==", "dependencies": { "bech32": "^2.0.0", "bip66": "^1.1.5", @@ -2051,9 +2051,9 @@ } }, "node_modules/@defichain/jellyfish-json": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-json/-/jellyfish-json-0.29.0.tgz", - "integrity": "sha512-g9b0y0FXVNW4c9/RH8fSozVqU5+R1XR7huJi7P/ublnNJvZmI1c76pY+QIRh7dc8HXp8560hK+ZBVvL6QImO2g==", + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-json/-/jellyfish-json-0.29.1.tgz", + "integrity": "sha512-pSE5tdMK2peeYpPrFFr1oYhDkOsSOSyfJ342bL9DjeTXXHJ5ePxR1bfaIDS1u1RePNFsFA3OhH2+P8qpcVuUGQ==", "dependencies": { "@types/lossless-json": "^1.0.1", "lossless-json": "^1.0.4" @@ -2063,16 +2063,16 @@ } }, "node_modules/@defichain/jellyfish-network": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-network/-/jellyfish-network-0.29.0.tgz", - "integrity": "sha512-Ck189MzMPYrospxqOgWPGTvSdHeEuN7g0PE+Tk87aDPKethLWAcVyY9SNUXepHoGS5N48UDPaxvywoLlW+hQwA==" + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-network/-/jellyfish-network-0.29.1.tgz", + "integrity": "sha512-LJ1YH/a/r1tIFwDKL/oHAp3rj37JhaIETHyQ69ati+4DM4pUOneYhUr56ahMkxuRhx/X4oEPUrjemmvMHgkFLg==" }, "node_modules/@defichain/jellyfish-transaction": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-transaction/-/jellyfish-transaction-0.29.0.tgz", - "integrity": "sha512-d2cTzZparx9DUM6vtCiMKT0v3+xlXN5NcwBA8xp6+DrXFlscRGVaKBgj/c4+QCE89pb9EwpfKowwQt+6LmVq5w==", + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-transaction/-/jellyfish-transaction-0.29.1.tgz", + "integrity": "sha512-RVMaNWtIIZdnZOynz+LCUJ+UtqHI/W0ILFaf2/hGmhQG4e/J97bYhXeelNvaTwV4efU/TBYV0eVkkAVkyND7UA==", "dependencies": { - "@defichain/jellyfish-crypto": "^0.29.0", + "@defichain/jellyfish-crypto": "^0.29.1", "smart-buffer": "^4.1.0" }, "peerDependencies": { @@ -2080,25 +2080,25 @@ } }, "node_modules/@defichain/jellyfish-transaction-builder": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-transaction-builder/-/jellyfish-transaction-builder-0.29.0.tgz", - "integrity": "sha512-jm6I9DKBQofjmk0ASBUBnbjF1A7efHBQrB38OiNkOYTFk6PIbZHsW/8YsOg9JrtvoSYDYCKy0dys3x7g+la1mA==", + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-transaction-builder/-/jellyfish-transaction-builder-0.29.1.tgz", + "integrity": "sha512-gt/wJzPcUscxNxPwO+m70o9bkY0iKBPMR3gIhDIwDPru63iUNFMn0RH9MBIn/0DA8ZWIkoN4mLoOcDVFQ+GN/Q==", "dependencies": { - "@defichain/jellyfish-crypto": "^0.29.0", - "@defichain/jellyfish-transaction": "^0.29.0", - "@defichain/jellyfish-transaction-signature": "^0.29.0" + "@defichain/jellyfish-crypto": "^0.29.1", + "@defichain/jellyfish-transaction": "^0.29.1", + "@defichain/jellyfish-transaction-signature": "^0.29.1" }, "peerDependencies": { "bignumber.js": "^9.0.1" } }, "node_modules/@defichain/jellyfish-transaction-signature": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-transaction-signature/-/jellyfish-transaction-signature-0.29.0.tgz", - "integrity": "sha512-YVJVJRJXBpWv8Ajsl+VOP6STW1LLbaZwudvmemYYJBxRbqJQkxGT3xykDDJT6wnXGgO0aMDcPhx/S8zOhutViA==", + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-transaction-signature/-/jellyfish-transaction-signature-0.29.1.tgz", + "integrity": "sha512-nw3aVq1kE/5Ver5zZ5okVVcocwHPn1fPSQvrgN1Ml3w/DYG1/LRmvu0zMSU2GeV2gcbhUuAUfzAJb6wiilSi0g==", "dependencies": { - "@defichain/jellyfish-crypto": "^0.29.0", - "@defichain/jellyfish-transaction": "^0.29.0", + "@defichain/jellyfish-crypto": "^0.29.1", + "@defichain/jellyfish-transaction": "^0.29.1", "smart-buffer": "^4.1.0" }, "peerDependencies": { @@ -2106,35 +2106,35 @@ } }, "node_modules/@defichain/jellyfish-wallet": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet/-/jellyfish-wallet-0.29.0.tgz", - "integrity": "sha512-CcOFaqSJVOqSXjWwD4pfPsk8pTyifV+7eKz6+UstUVtj8cp3LzhPGdvGf/7TDdEMGp+HJwJEr/hTxbN13ukrTA==", + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet/-/jellyfish-wallet-0.29.1.tgz", + "integrity": "sha512-Vrg0KKBanm+AdML7thIhOASBv9F0JC66Dj+Anvai6P9FZ4UVgRzwqYPRQWpnfm6FIYPr9xkKhDQQG3ZD8ReB1w==", "dependencies": { - "@defichain/jellyfish-address": "^0.29.0", - "@defichain/jellyfish-crypto": "^0.29.0", - "@defichain/jellyfish-network": "^0.29.0", - "@defichain/jellyfish-transaction": "^0.29.0" + "@defichain/jellyfish-address": "^0.29.1", + "@defichain/jellyfish-crypto": "^0.29.1", + "@defichain/jellyfish-network": "^0.29.1", + "@defichain/jellyfish-transaction": "^0.29.1" }, "peerDependencies": { "bignumber.js": "^9.0.1" } }, "node_modules/@defichain/jellyfish-wallet-encrypted": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet-encrypted/-/jellyfish-wallet-encrypted-0.29.0.tgz", - "integrity": "sha512-Fo9ppgeP0IBdocqj0wnQPW/Q7X7PPsZXrORgNikvwdkTrSfN/yRUZioGKI+c3nziOR80OWdUkwGSdKYHu0/9Rw==", + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet-encrypted/-/jellyfish-wallet-encrypted-0.29.1.tgz", + "integrity": "sha512-qnM9OXMIyLU/lv6TfYIyqaeJxOJHM0nULtDhvroXyMTNDYsyv9tq6E3Aec0mriXPoxxlLXydRVeSIFtP7kX6tw==", "dependencies": { - "@defichain/jellyfish-wallet-mnemonic": "^0.29.0", + "@defichain/jellyfish-wallet-mnemonic": "^0.29.1", "scryptsy": "^2.1.0" } }, "node_modules/@defichain/jellyfish-wallet-mnemonic": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet-mnemonic/-/jellyfish-wallet-mnemonic-0.29.0.tgz", - "integrity": "sha512-zSbMynMCmdkKf3iwbHhpL0dSG/OG+jPfTKKAy3AIpD8bQ9Tf5x2oielYOYSGfcpXggMAgmTeFp57wt8aUVVSiQ==", + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet-mnemonic/-/jellyfish-wallet-mnemonic-0.29.1.tgz", + "integrity": "sha512-hZAZLq6fJaMotrvJJzSU2UX7Apx+kHtdLRi8xw9VvPGwiCET8yc2+xM/TEej/UvwjVqXbaB70qLDU+iXSQQl3Q==", "dependencies": { - "@defichain/jellyfish-transaction": "^0.29.0", - "@defichain/jellyfish-wallet": "^0.29.0", + "@defichain/jellyfish-transaction": "^0.29.1", + "@defichain/jellyfish-wallet": "^0.29.1", "bip32": "^2.0.6", "bip39": "^3.0.4" } @@ -42074,28 +42074,28 @@ } }, "@defichain/jellyfish-address": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-address/-/jellyfish-address-0.29.0.tgz", - "integrity": "sha512-miFMJsOsDMhDc7NklE6nmFwcGmAtv1E/l5Uw3SdEv0meU5u9fv3I41yZS7wFwk/kUgDvgF3LeZNfiDVx6f2v1Q==", + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-address/-/jellyfish-address-0.29.1.tgz", + "integrity": "sha512-XnhEHfciikRaFJqEE23FeLkriWAXK80Y/SueHHSOBv5LTTagNSJ8/rLnxppXIQaQQu97tsWLl72JSEku/SfY0w==", "requires": { - "@defichain/jellyfish-crypto": "^0.29.0", - "@defichain/jellyfish-network": "^0.29.0", - "@defichain/jellyfish-transaction": "^0.29.0", + "@defichain/jellyfish-crypto": "^0.29.1", + "@defichain/jellyfish-network": "^0.29.1", + "@defichain/jellyfish-transaction": "^0.29.1", "bs58": "^4.0.1" } }, "@defichain/jellyfish-api-core": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-api-core/-/jellyfish-api-core-0.29.0.tgz", - "integrity": "sha512-gRmw55bQK0QwPHZ8Rfqv7GVdFJ6MOcjF/H2Xxtmqez+iFUA0EniGMHqloYvLjFABCNlhZvOJd8HU242IWStLsg==", + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-api-core/-/jellyfish-api-core-0.29.1.tgz", + "integrity": "sha512-jo5dn4gUmk3nYrS2DT+wbeRLqZ6IxaGLOyagOAqMv3Yqsqn6J6FF4OOGRADgikxjYf14AFh0wxIaZSNTAl0XUQ==", "requires": { - "@defichain/jellyfish-json": "^0.29.0" + "@defichain/jellyfish-json": "^0.29.1" } }, "@defichain/jellyfish-crypto": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-crypto/-/jellyfish-crypto-0.29.0.tgz", - "integrity": "sha512-QGIN8WGisE8uIhQD1nCSic+QCIfU/s8pGMufiDEukceThovukcT+jhWvfjmGf8qnXSViuTf8SXR+EjT+R83R7A==", + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-crypto/-/jellyfish-crypto-0.29.1.tgz", + "integrity": "sha512-ixLUuYeDL2Fe/+k9p+tEEqG8ICKqtSUBjM/S0K8QIjjynfIcd+kg4LxQzIEBDpcnxcQD1JFLcfiDta6Q404f3g==", "requires": { "bech32": "^2.0.0", "bip66": "^1.1.5", @@ -42108,75 +42108,75 @@ } }, "@defichain/jellyfish-json": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-json/-/jellyfish-json-0.29.0.tgz", - "integrity": "sha512-g9b0y0FXVNW4c9/RH8fSozVqU5+R1XR7huJi7P/ublnNJvZmI1c76pY+QIRh7dc8HXp8560hK+ZBVvL6QImO2g==", + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-json/-/jellyfish-json-0.29.1.tgz", + "integrity": "sha512-pSE5tdMK2peeYpPrFFr1oYhDkOsSOSyfJ342bL9DjeTXXHJ5ePxR1bfaIDS1u1RePNFsFA3OhH2+P8qpcVuUGQ==", "requires": { "@types/lossless-json": "^1.0.1", "lossless-json": "^1.0.4" } }, "@defichain/jellyfish-network": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-network/-/jellyfish-network-0.29.0.tgz", - "integrity": "sha512-Ck189MzMPYrospxqOgWPGTvSdHeEuN7g0PE+Tk87aDPKethLWAcVyY9SNUXepHoGS5N48UDPaxvywoLlW+hQwA==" + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-network/-/jellyfish-network-0.29.1.tgz", + "integrity": "sha512-LJ1YH/a/r1tIFwDKL/oHAp3rj37JhaIETHyQ69ati+4DM4pUOneYhUr56ahMkxuRhx/X4oEPUrjemmvMHgkFLg==" }, "@defichain/jellyfish-transaction": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-transaction/-/jellyfish-transaction-0.29.0.tgz", - "integrity": "sha512-d2cTzZparx9DUM6vtCiMKT0v3+xlXN5NcwBA8xp6+DrXFlscRGVaKBgj/c4+QCE89pb9EwpfKowwQt+6LmVq5w==", + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-transaction/-/jellyfish-transaction-0.29.1.tgz", + "integrity": "sha512-RVMaNWtIIZdnZOynz+LCUJ+UtqHI/W0ILFaf2/hGmhQG4e/J97bYhXeelNvaTwV4efU/TBYV0eVkkAVkyND7UA==", "requires": { - "@defichain/jellyfish-crypto": "^0.29.0", + "@defichain/jellyfish-crypto": "^0.29.1", "smart-buffer": "^4.1.0" } }, "@defichain/jellyfish-transaction-builder": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-transaction-builder/-/jellyfish-transaction-builder-0.29.0.tgz", - "integrity": "sha512-jm6I9DKBQofjmk0ASBUBnbjF1A7efHBQrB38OiNkOYTFk6PIbZHsW/8YsOg9JrtvoSYDYCKy0dys3x7g+la1mA==", + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-transaction-builder/-/jellyfish-transaction-builder-0.29.1.tgz", + "integrity": "sha512-gt/wJzPcUscxNxPwO+m70o9bkY0iKBPMR3gIhDIwDPru63iUNFMn0RH9MBIn/0DA8ZWIkoN4mLoOcDVFQ+GN/Q==", "requires": { - "@defichain/jellyfish-crypto": "^0.29.0", - "@defichain/jellyfish-transaction": "^0.29.0", - "@defichain/jellyfish-transaction-signature": "^0.29.0" + "@defichain/jellyfish-crypto": "^0.29.1", + "@defichain/jellyfish-transaction": "^0.29.1", + "@defichain/jellyfish-transaction-signature": "^0.29.1" } }, "@defichain/jellyfish-transaction-signature": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-transaction-signature/-/jellyfish-transaction-signature-0.29.0.tgz", - "integrity": "sha512-YVJVJRJXBpWv8Ajsl+VOP6STW1LLbaZwudvmemYYJBxRbqJQkxGT3xykDDJT6wnXGgO0aMDcPhx/S8zOhutViA==", + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-transaction-signature/-/jellyfish-transaction-signature-0.29.1.tgz", + "integrity": "sha512-nw3aVq1kE/5Ver5zZ5okVVcocwHPn1fPSQvrgN1Ml3w/DYG1/LRmvu0zMSU2GeV2gcbhUuAUfzAJb6wiilSi0g==", "requires": { - "@defichain/jellyfish-crypto": "^0.29.0", - "@defichain/jellyfish-transaction": "^0.29.0", + "@defichain/jellyfish-crypto": "^0.29.1", + "@defichain/jellyfish-transaction": "^0.29.1", "smart-buffer": "^4.1.0" } }, "@defichain/jellyfish-wallet": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet/-/jellyfish-wallet-0.29.0.tgz", - "integrity": "sha512-CcOFaqSJVOqSXjWwD4pfPsk8pTyifV+7eKz6+UstUVtj8cp3LzhPGdvGf/7TDdEMGp+HJwJEr/hTxbN13ukrTA==", + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet/-/jellyfish-wallet-0.29.1.tgz", + "integrity": "sha512-Vrg0KKBanm+AdML7thIhOASBv9F0JC66Dj+Anvai6P9FZ4UVgRzwqYPRQWpnfm6FIYPr9xkKhDQQG3ZD8ReB1w==", "requires": { - "@defichain/jellyfish-address": "^0.29.0", - "@defichain/jellyfish-crypto": "^0.29.0", - "@defichain/jellyfish-network": "^0.29.0", - "@defichain/jellyfish-transaction": "^0.29.0" + "@defichain/jellyfish-address": "^0.29.1", + "@defichain/jellyfish-crypto": "^0.29.1", + "@defichain/jellyfish-network": "^0.29.1", + "@defichain/jellyfish-transaction": "^0.29.1" } }, "@defichain/jellyfish-wallet-encrypted": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet-encrypted/-/jellyfish-wallet-encrypted-0.29.0.tgz", - "integrity": "sha512-Fo9ppgeP0IBdocqj0wnQPW/Q7X7PPsZXrORgNikvwdkTrSfN/yRUZioGKI+c3nziOR80OWdUkwGSdKYHu0/9Rw==", + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet-encrypted/-/jellyfish-wallet-encrypted-0.29.1.tgz", + "integrity": "sha512-qnM9OXMIyLU/lv6TfYIyqaeJxOJHM0nULtDhvroXyMTNDYsyv9tq6E3Aec0mriXPoxxlLXydRVeSIFtP7kX6tw==", "requires": { - "@defichain/jellyfish-wallet-mnemonic": "^0.29.0", + "@defichain/jellyfish-wallet-mnemonic": "^0.29.1", "scryptsy": "^2.1.0" } }, "@defichain/jellyfish-wallet-mnemonic": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet-mnemonic/-/jellyfish-wallet-mnemonic-0.29.0.tgz", - "integrity": "sha512-zSbMynMCmdkKf3iwbHhpL0dSG/OG+jPfTKKAy3AIpD8bQ9Tf5x2oielYOYSGfcpXggMAgmTeFp57wt8aUVVSiQ==", + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet-mnemonic/-/jellyfish-wallet-mnemonic-0.29.1.tgz", + "integrity": "sha512-hZAZLq6fJaMotrvJJzSU2UX7Apx+kHtdLRi8xw9VvPGwiCET8yc2+xM/TEej/UvwjVqXbaB70qLDU+iXSQQl3Q==", "requires": { - "@defichain/jellyfish-transaction": "^0.29.0", - "@defichain/jellyfish-wallet": "^0.29.0", + "@defichain/jellyfish-transaction": "^0.29.1", + "@defichain/jellyfish-wallet": "^0.29.1", "bip32": "^2.0.6", "bip39": "^3.0.4" } diff --git a/package.json b/package.json index 21c2fce6d7..20103a8d68 100644 --- a/package.json +++ b/package.json @@ -17,14 +17,14 @@ "translation:missing": "ts-node app/translations/reporter/index.ts" }, "dependencies": { - "@defichain/jellyfish-address": ">=0.29.0", - "@defichain/jellyfish-api-core": ">=0.29.0", - "@defichain/jellyfish-network": ">=0.29.0", - "@defichain/jellyfish-transaction": ">=0.29.0", - "@defichain/jellyfish-transaction-builder": ">=0.29.0", - "@defichain/jellyfish-wallet": ">=0.29.0", - "@defichain/jellyfish-wallet-encrypted": ">=0.29.0", - "@defichain/jellyfish-wallet-mnemonic": ">=0.29.0", + "@defichain/jellyfish-address": ">=0.29.1", + "@defichain/jellyfish-api-core": ">=0.29.1", + "@defichain/jellyfish-network": ">=0.29.1", + "@defichain/jellyfish-transaction": ">=0.29.1", + "@defichain/jellyfish-transaction-builder": ">=0.29.1", + "@defichain/jellyfish-wallet": ">=0.29.1", + "@defichain/jellyfish-wallet-encrypted": ">=0.29.1", + "@defichain/jellyfish-wallet-mnemonic": ">=0.29.1", "@defichain/playground-api-client": ">=0.6.0", "@defichain/whale-api-client": ">=0.5.10", "@defichain/whale-api-wallet": ">=0.5.10", From 79d1b4a16d5ea5df7b794151a3df045f354863cd Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 27 Jul 2021 12:01:20 +0800 Subject: [PATCH 13/48] add custom max-w tailwind --- app/tailwind.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/app/tailwind.js b/app/tailwind.js index a81b0a96e1..e36eaaa9b7 100644 --- a/app/tailwind.js +++ b/app/tailwind.js @@ -24,9 +24,24 @@ const fonts = { } } +const maxWidth = { + 'max-w-1/4': { + maxWidth: '25%' + }, + 'max-w-1/2': { + maxWidth: '50%' + }, + 'max-w-3/4': { + maxWidth: '75%' + }, + 'max-w': { + maxWidth: '100%' + } +} + /** * @description Allows to use custom tailwind classes * @example `tailwind('text-primary') * */ -const { tailwind, getColor } = create({ ...styles, ...fonts }) +const { tailwind, getColor } = create({ ...styles, ...fonts, ...maxWidth }) export { tailwind, getColor } From 9001e48cce6aa2d65c3bbd08f7c2cb251497e8ef Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 27 Jul 2021 12:02:08 +0800 Subject: [PATCH 14/48] add delay for pin input callback, allow render before complete (if any) promise --- app/components/PinInput.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/PinInput.tsx b/app/components/PinInput.tsx index a42db0d2ec..30c655804a 100644 --- a/app/components/PinInput.tsx +++ b/app/components/PinInput.tsx @@ -1,5 +1,5 @@ import { MaterialIcons } from '@expo/vector-icons' -import React, { useState, useRef, useEffect, useLayoutEffect } from 'react' +import React, { useState, useRef, useEffect } from 'react' import { TextInput, TouchableOpacity } from 'react-native' import tailwind from 'tailwind-rn' @@ -20,7 +20,7 @@ export function PinInput ({ length, onChange }: PinInputOptions): JSX.Element { useEffect(() => { if (text.length === length) { - onChange(text) + setTimeout(() => onChange(text), 100) } }, [text]) From c723f9e416198735b517695f267e3ece8fbcb155 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 27 Jul 2021 12:02:24 +0800 Subject: [PATCH 15/48] delete accident swp --- .../screens/.WalletMnemonicCreate.tsx.swp | Bin 4096 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 app/screens/WalletNavigator/screens/.WalletMnemonicCreate.tsx.swp diff --git a/app/screens/WalletNavigator/screens/.WalletMnemonicCreate.tsx.swp b/app/screens/WalletNavigator/screens/.WalletMnemonicCreate.tsx.swp deleted file mode 100644 index 460cc74269257e350052417e5e486c38918d7fe7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmYc?2=nw+u+TGP00IF929Ba%30F?DGGur&Fl3e`=7EF>0T}^V9y{U zoXYD!2I?m#W~b_>XO`%vq^4!+mnY`rq?YI>78K|gCl{rr<`wIQgN6JO%QDjwOY)1* z#C`KpbMx~ulbwrF6H8L{N{TCR+BYgO8UmvsfE)t648}%=1|Sh-B}D~cp-^PWQTAvE ejE2By2#kinXb6mkz-S1JhQMeDjE2B44*>w;JTIvL From a870b72f76dd0ea8f7bd5412ed7ef048015e4fe9 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 27 Jul 2021 12:03:17 +0800 Subject: [PATCH 16/48] fix merge, migrate to new custom button --- app/screens/WalletNavigator/screens/PinCreationScreen.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/screens/WalletNavigator/screens/PinCreationScreen.tsx b/app/screens/WalletNavigator/screens/PinCreationScreen.tsx index 2f49b0a920..ac03026bf2 100644 --- a/app/screens/WalletNavigator/screens/PinCreationScreen.tsx +++ b/app/screens/WalletNavigator/screens/PinCreationScreen.tsx @@ -7,7 +7,7 @@ import tailwind from 'tailwind-rn' import { Text, View } from '../../../components' import { CreateWalletStepIndicator } from '../../../components/CreateWalletStepIndicator' import { PinInput } from '../../../components/PinInput' -import { PrimaryButton } from '../../../components/PrimaryButton' +import { Button } from '../../../components/Button' import { translate } from '../../../translations' import { WalletParamList } from '../WalletNavigator' @@ -43,7 +43,7 @@ export function PinCreation ({ route }: Props): JSX.Element { length={pinLength} onChange={val => setNewPin(val)} /> - { @@ -51,7 +51,7 @@ export function PinCreation ({ route }: Props): JSX.Element { }} > {translate('screens/PinCreation', 'CREATE PASSCODE')} - + ) } From 95f26eaac5319023ac2063532504cee305b93753 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 27 Jul 2021 12:04:37 +0800 Subject: [PATCH 17/48] done deferred prompt promise (UI thread resolve context init promise) --- .../OceanInterface/OceanInterface.tsx | 36 +++++++++---------- .../screens/PinConfirmation.tsx | 1 - 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/app/components/OceanInterface/OceanInterface.tsx b/app/components/OceanInterface/OceanInterface.tsx index a08f4b265b..b5b56133b6 100644 --- a/app/components/OceanInterface/OceanInterface.tsx +++ b/app/components/OceanInterface/OceanInterface.tsx @@ -61,15 +61,26 @@ export function OceanInterface (): JSX.Element | null { // passcode interface const [isPrompting, setIsPrompting] = useState(false) - const [passcode, setPasscode] = useState('') const passcodeResolverRef = useRef<(val: string) => void>() const dismissDrawer = useCallback(() => { setTx(undefined) setError(undefined) + setTxid(undefined) slideAnim.setValue(0) }, []) + const onPasscodeInput = useCallback((passcodeInput: string) => { + if (isPrompting && passcodeInput.length === PASSCODE_LENGTH && passcodeResolverRef.current !== undefined) { + setIsPrompting(false) + const resolver = passcodeResolverRef.current + setTimeout(() => { + resolver(passcodeInput) + passcodeResolverRef.current = undefined + }, 100) + } + }, [isPrompting]) + useEffect(() => { // last available job will remained in this UI state until get dismissed if (transaction !== undefined) { @@ -91,36 +102,25 @@ export function OceanInterface (): JSX.Element | null { if (txid !== undefined) { errMsg = `${errMsg}. Txid: ${txid}` } - console.log(e) setError(new Error(errMsg)) }) .finally(() => dispatch(ocean.actions.popTransaction())) // remove the job as soon as completion } }, [transaction, walletContext]) - // UI provide interface to WalletContext to access pin request + // UI provide interface to WalletContext to access pin request component useEffect(() => { const passcodePromptConstructor = { prompt: async (): Promise => { setIsPrompting(true) - console.log('waiting') const pass = await new Promise(resolve => { passcodeResolverRef.current = resolve }) - console.log('input: ') setIsPrompting(false) - console.log('complete taking pin') return pass } } walletManagement.setPasscodePromptInterface(passcodePromptConstructor) }, []) - useEffect(() => { - if (!isPrompting && passcode.length === PASSCODE_LENGTH && passcodeResolverRef.current !== undefined) { - passcodeResolverRef.current(passcode) - passcodeResolverRef.current = undefined - } - }, [passcode, isPrompting]) - if (tx === undefined) { return null } @@ -141,12 +141,7 @@ export function OceanInterface (): JSX.Element | null { broadcasted={tx.broadcasted} txid={txid} onClose={dismissDrawer} - onPasscodeInput={pass => { - setIsPrompting(false) - setPasscode(pass) - // if (pass.length === PASSCODE_LENGTH && resolvePasscode !== undefined) resolvePasscode(pass) - // if (pass.length === PASSCODE_LENGTH && passcodeResolverRef.current !== undefined) passcodeResolverRef.current(pass) - }} + onPasscodeInput={onPasscodeInput} /> ) } @@ -156,6 +151,7 @@ export function OceanInterface (): JSX.Element | null { function TransactionDetail ({ isPrompting, broadcasted, txid, onClose, onPasscodeInput }: { isPrompting: boolean, broadcasted: boolean, txid?: string, onClose: () => void, onPasscodeInput: (passcode: string) => void }): JSX.Element | null { let title = 'Signing...' + if (isPrompting) title = 'Authorization required' if (txid !== undefined) title = 'Broadcasting...' if (broadcasted) title = 'Transaction Sent' return ( @@ -208,7 +204,7 @@ function TransactionError ({ errMsg, onClose }: { errMsg: string | undefined, on function TransactionIDButton ({ txid, onPress }: { txid: string, onPress?: () => void }): JSX.Element { return ( Date: Tue, 27 Jul 2021 19:34:09 +0800 Subject: [PATCH 18/48] make standalone passcode prompt "page" rendered side by side with main AppNavigator --- .../OceanInterface/OceanInterface.tsx | 43 ++++++++------ app/components/PinInput.tsx | 2 + app/contexts/WalletManagementContext.tsx | 24 +++++++- app/screens/AppNavigator/AppNavigator.tsx | 4 +- .../AppNavigator/screens/UnlockWallet.tsx | 39 ++++++++++++ app/screens/RootNavigator.tsx | 59 ++++++++++++++++++- .../CreateWallet/VerifyMnemonicWallet.tsx | 3 - 7 files changed, 145 insertions(+), 29 deletions(-) create mode 100644 app/screens/AppNavigator/screens/UnlockWallet.tsx diff --git a/app/components/OceanInterface/OceanInterface.tsx b/app/components/OceanInterface/OceanInterface.tsx index b5b56133b6..d30f1877e1 100644 --- a/app/components/OceanInterface/OceanInterface.tsx +++ b/app/components/OceanInterface/OceanInterface.tsx @@ -1,5 +1,6 @@ import { CTransactionSegWit } from '@defichain/jellyfish-transaction/dist' import { WhaleApiClient } from '@defichain/whale-api-client' +import { WhaleWalletAccount } from '@defichain/whale-api-wallet' import { MaterialIcons } from '@expo/vector-icons' import React, { useCallback, useEffect, useRef, useState } from 'react' import { ActivityIndicator, Animated, Linking, TouchableOpacity, View } from 'react-native' @@ -17,6 +18,7 @@ import { PinInput } from '../PinInput' const MAX_AUTO_RETRY = 1 const PASSCODE_LENGTH = 6 +const MAX_SIGNING_RETRY = 3 async function gotoExplorer (txid: string): Promise { // TODO(thedoublejay) explorer URL @@ -28,6 +30,19 @@ async function gotoExplorer (txid: string): Promise { } } +async function signTransaction (tx: OceanTransaction, account: WhaleWalletAccount, incrementErrorCounter: () => void, retries: number = 0): Promise { + try { + return await tx.sign(account) + } catch (e) { + Logging.error(e) + if (retries < MAX_SIGNING_RETRY) { + incrementErrorCounter() + return await signTransaction(tx, account, incrementErrorCounter, retries + 1) + } + throw e + } +} + async function broadcastTransaction (tx: CTransactionSegWit, client: WhaleApiClient, retries: number = 0): Promise { try { return await client.transactions.send({ hex: tx.toHex() }) @@ -87,16 +102,19 @@ export function OceanInterface (): JSX.Element | null { Animated.timing(slideAnim, { toValue: height, duration: 200, useNativeDriver: false }).start() setTx(transaction) - transaction.sign(walletContext.get(0)) + signTransaction(transaction, walletContext.get(0), () => walletManagement.incrementPasscodeErrorCount()) .then(async signedTx => { setTxid(signedTx.txId) await broadcastTransaction(signedTx, client) }) - .then(() => setTx({ - ...transaction, - broadcasted: true, - title: translate('screens/OceanInterface', 'Transaction Sent') - })) + .then(() => { + walletManagement.resetErrorCount() + setTx({ + ...transaction, + broadcasted: true, + title: translate('screens/OceanInterface', 'Transaction Sent') + }) + }) .catch((e: Error) => { let errMsg = e.message if (txid !== undefined) { @@ -108,19 +126,6 @@ export function OceanInterface (): JSX.Element | null { } }, [transaction, walletContext]) - // UI provide interface to WalletContext to access pin request component - useEffect(() => { - const passcodePromptConstructor = { - prompt: async (): Promise => { - setIsPrompting(true) - const pass = await new Promise(resolve => { passcodeResolverRef.current = resolve }) - setIsPrompting(false) - return pass - } - } - walletManagement.setPasscodePromptInterface(passcodePromptConstructor) - }, []) - if (tx === undefined) { return null } diff --git a/app/components/PinInput.tsx b/app/components/PinInput.tsx index 30c655804a..8a16c7ef7a 100644 --- a/app/components/PinInput.tsx +++ b/app/components/PinInput.tsx @@ -20,6 +20,8 @@ export function PinInput ({ length, onChange }: PinInputOptions): JSX.Element { useEffect(() => { if (text.length === length) { + // allow UI thread complete render with updated textinput state + // before resolving long async task setTimeout(() => onChange(text), 100) } }, [text]) diff --git a/app/contexts/WalletManagementContext.tsx b/app/contexts/WalletManagementContext.tsx index 647f822b53..45c13704a8 100644 --- a/app/contexts/WalletManagementContext.tsx +++ b/app/contexts/WalletManagementContext.tsx @@ -1,4 +1,5 @@ import React, { createContext, useContext, useEffect, useMemo, useState } from 'react' + import { Logging } from '../api/logging' import { WalletPersistence, WalletPersistenceData } from '../api/wallet/persistence' import { initWhaleWallet, WhaleWallet } from '../api/wallet/provider' @@ -6,6 +7,8 @@ import { PromptInterface } from '../api/wallet/provider/mnemonic_encrypted' import { useNetworkContext } from './NetworkContext' import { useWhaleApiClient } from './WhaleContext' +const MAX_PASSCODE_ATTEMPTS = 3 + interface WalletManagement { wallets: WhaleWallet[] /** @@ -13,7 +16,12 @@ interface WalletManagement { */ setWallet: (data: WalletPersistenceData) => Promise clearWallets: () => Promise + + // logic to bridge promptPassphrase UI and jellyfish-txn-builder setPasscodePromptInterface: (constructPrompt: PromptInterface) => void + incrementPasscodeErrorCount: () => void + errorCount: number + resetErrorCount: () => void } const WalletManagementContext = createContext(undefined as any) @@ -33,6 +41,7 @@ export function WalletManagementProvider (props: React.PropsWithChildren): const client = useWhaleApiClient() const [dataList, setDataList] = useState>>([]) const [promptInterface, setPromptInterface] = useState() + const [errCount, setErrCount] = useState(0) useEffect(() => { WalletPersistence.get().then(dataList => { @@ -40,9 +49,13 @@ export function WalletManagementProvider (props: React.PropsWithChildren): }).catch(Logging.error) }, [network]) + useEffect(() => { + if (errCount > MAX_PASSCODE_ATTEMPTS) { + // TODO(@ivan-zynesis): wipe wallets from storage + } + }, [errCount]) + const wallets = useMemo(() => { - console.log('use wallet memo, prompt constructor', promptInterface) - if (promptInterface !== undefined) console.log('prompt constructor set') return dataList.map(data => initWhaleWallet(data, network, client, promptInterface)) }, [dataList, promptInterface]) @@ -58,6 +71,13 @@ export function WalletManagementProvider (props: React.PropsWithChildren): }, setPasscodePromptInterface (cb: PromptInterface): void { setPromptInterface(cb) + }, + incrementPasscodeErrorCount (): void { + setErrCount(errCount + 1) + }, + errorCount: errCount, + resetErrorCount: () => { + setErrCount(0) } } diff --git a/app/screens/AppNavigator/AppNavigator.tsx b/app/screens/AppNavigator/AppNavigator.tsx index 84f3d0f844..f5ad07aa60 100644 --- a/app/screens/AppNavigator/AppNavigator.tsx +++ b/app/screens/AppNavigator/AppNavigator.tsx @@ -6,9 +6,9 @@ import { DeFiChainTheme } from '../../constants/Theme' import { PlaygroundNavigator } from '../PlaygroundNavigator/PlaygroundNavigator' import { AppLinking, BottomTabNavigator } from './BottomTabNavigator' -const App = createStackNavigator() +const App = createStackNavigator() -export interface WalletParamList { +export interface AppParamList { App: undefined Playground: undefined NotFound: undefined diff --git a/app/screens/AppNavigator/screens/UnlockWallet.tsx b/app/screens/AppNavigator/screens/UnlockWallet.tsx new file mode 100644 index 0000000000..8e17e0a05c --- /dev/null +++ b/app/screens/AppNavigator/screens/UnlockWallet.tsx @@ -0,0 +1,39 @@ +import React from 'react' +import tailwind from 'tailwind-rn' +import { Text, View } from '../../../components' +import { PinInput } from '../../../components/PinInput' +import { translate } from '../../../translations' + +interface UnlockWalletProps { + pinLength: 4 | 6 + onPinInput: (pin: string) => void + errorCount: number +} + +/** + * This meant to be a full page UI (simple component) and NOT accessed via navigation + * this component render side by side with AppNavigator + */ +export function UnlockWallet (props: UnlockWalletProps): JSX.Element { + const { pinLength, onPinInput, errorCount = 0 } = props + + return ( + + {translate('screens/UnlockWallet', 'Enter passcode')} + {translate('screens/UnlockWallet', 'For transaction signing purpose')} + { + setTimeout(() => onPinInput(pin), 100) + }} + /> + { + (errorCount !== 0) ? ( + + {translate('screens/PinConfirmation', 'Wrong passcode. {{errorCount}} tries remaining', { errorCount })} + + ) : null + } + + ) +} diff --git a/app/screens/RootNavigator.tsx b/app/screens/RootNavigator.tsx index ea1dfbc02a..4d32800b72 100644 --- a/app/screens/RootNavigator.tsx +++ b/app/screens/RootNavigator.tsx @@ -1,22 +1,75 @@ -import React from 'react' +import React, { useEffect, useState, useRef } from 'react' +import tailwind from 'tailwind-rn' +import { View } from '../components' import { WalletProvider } from '../contexts/WalletContext' import { useWalletManagementContext } from '../contexts/WalletManagementContext' import { AppNavigator } from './AppNavigator/AppNavigator' +import { UnlockWallet } from './AppNavigator/screens/UnlockWallet' import { WalletNavigator } from './WalletNavigator/WalletNavigator' /** * Top Root Level Wallet State to control what screen to show */ export function RootNavigator (): JSX.Element { - const { wallets } = useWalletManagementContext() + const { wallets, setPasscodePromptInterface, errorCount } = useWalletManagementContext() + + const [isPrompting, setIsPrompting] = useState(false) + const promptInterface = useRef<(pin: string) => void>() + + useEffect(() => { + setPasscodePromptInterface({ + prompt: async () => { + return await new Promise(resolve => { + promptInterface.current = resolve + setIsPrompting(true) + }) + } + }) + }, []) if (wallets.length === 0) { return } + const appContainerStyle: { height?: number } = {} + if (isPrompting) { + // TO BE IMPROVED + // hackish method to hide WITHOUT losing state, state must retained + appContainerStyle.height = 0 + } + return ( - + { + if (promptInterface.current !== undefined) { + promptInterface.current(pin) + setIsPrompting(false) + } + }} + /> + + + ) } + +function WalletContextPasscodePrompt ({ isPrompting, onPinInput, errorCount }: { isPrompting: boolean, onPinInput: (pin: string) => void, errorCount: number }): JSX.Element | null { + /** + * UI literally stuck on this page until either + * 1. successfully resolve promise with valid pin (successful decryption) + * 2. error count hit threshold, wallet wiped, will auto redirect to onboarding again + */ + if (!isPrompting && errorCount === 0) return null + + return ( + + ) +} diff --git a/app/screens/WalletNavigator/screens/CreateWallet/VerifyMnemonicWallet.tsx b/app/screens/WalletNavigator/screens/CreateWallet/VerifyMnemonicWallet.tsx index a817966b8e..904f3fc7fe 100644 --- a/app/screens/WalletNavigator/screens/CreateWallet/VerifyMnemonicWallet.tsx +++ b/app/screens/WalletNavigator/screens/CreateWallet/VerifyMnemonicWallet.tsx @@ -16,12 +16,9 @@ export function VerifyMnemonicWallet ({ route }: Props): JSX.Element { const enteredWords: string[] = [] const [valid, setValid] = useState(true) - // const { network } = useNetworkContext() - // const { setWallet } = useWalletManagementContext() async function onVerify (): Promise { if (actualWords.join(' ') === enteredWords.join(' ')) { - // await setWallet(MnemonicUnprotected.toData(enteredWords, network)) navigation.navigate('PinCreation', { words: actualWords, pinLength: 6 From ba8c29e983cc8fc6eb967e80a3333f55bcab7237 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 27 Jul 2021 19:46:36 +0800 Subject: [PATCH 19/48] remove pininput in oceaninterface --- .../OceanInterface/OceanInterface.tsx | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/app/components/OceanInterface/OceanInterface.tsx b/app/components/OceanInterface/OceanInterface.tsx index d30f1877e1..5119235160 100644 --- a/app/components/OceanInterface/OceanInterface.tsx +++ b/app/components/OceanInterface/OceanInterface.tsx @@ -14,10 +14,8 @@ import { RootState } from '../../store' import { firstTransactionSelector, ocean, OceanTransaction } from '../../store/ocean' import { tailwind } from '../../tailwind' import { translate } from '../../translations' -import { PinInput } from '../PinInput' const MAX_AUTO_RETRY = 1 -const PASSCODE_LENGTH = 6 const MAX_SIGNING_RETRY = 3 async function gotoExplorer (txid: string): Promise { @@ -74,10 +72,6 @@ export function OceanInterface (): JSX.Element | null { const [err, setError] = useState(e) const [txid, setTxid] = useState() - // passcode interface - const [isPrompting, setIsPrompting] = useState(false) - const passcodeResolverRef = useRef<(val: string) => void>() - const dismissDrawer = useCallback(() => { setTx(undefined) setError(undefined) @@ -85,17 +79,6 @@ export function OceanInterface (): JSX.Element | null { slideAnim.setValue(0) }, []) - const onPasscodeInput = useCallback((passcodeInput: string) => { - if (isPrompting && passcodeInput.length === PASSCODE_LENGTH && passcodeResolverRef.current !== undefined) { - setIsPrompting(false) - const resolver = passcodeResolverRef.current - setTimeout(() => { - resolver(passcodeInput) - passcodeResolverRef.current = undefined - }, 100) - } - }, [isPrompting]) - useEffect(() => { // last available job will remained in this UI state until get dismissed if (transaction !== undefined) { @@ -142,11 +125,9 @@ export function OceanInterface (): JSX.Element | null { ? : ( ) } @@ -154,9 +135,8 @@ export function OceanInterface (): JSX.Element | null { ) } -function TransactionDetail ({ isPrompting, broadcasted, txid, onClose, onPasscodeInput }: { isPrompting: boolean, broadcasted: boolean, txid?: string, onClose: () => void, onPasscodeInput: (passcode: string) => void }): JSX.Element | null { +function TransactionDetail ({ broadcasted, txid, onClose }: { broadcasted: boolean, txid?: string, onClose: () => void }): JSX.Element | null { let title = 'Signing...' - if (isPrompting) title = 'Authorization required' if (txid !== undefined) title = 'Broadcasting...' if (broadcasted) title = 'Transaction Sent' return ( @@ -170,9 +150,6 @@ function TransactionDetail ({ isPrompting, broadcasted, txid, onClose, onPasscod style={tailwind('text-sm font-bold')} >{translate('screens/OceanInterface', title)} - { - isPrompting && - } { txid !== undefined && await gotoExplorer(txid)} /> } From c428fe237df6699f77094db1033d35b55efbcd80 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 28 Jul 2021 16:33:08 +0800 Subject: [PATCH 20/48] quick save --- app/api/wallet/passcode_attempt_counter.ts | 23 ++++ .../OceanInterface/OceanInterface.tsx | 24 +--- app/components/PinInput.tsx | 9 +- app/contexts/WalletManagementContext.tsx | 32 ++--- .../screens/Balances/ConvertScreen.tsx | 7 +- .../screens/Balances/screens/SendScreen.tsx | 7 +- .../screens/Dex/DexRemoveLiquidity.tsx | 7 +- .../AppNavigator/screens/UnlockWallet.tsx | 53 +++++--- app/screens/RootNavigator.tsx | 125 ++++++++++++++++-- app/store/index.ts | 4 +- app/store/ocean.test.ts | 4 +- app/store/ocean.ts | 3 +- app/store/transaction.ts | 33 +++++ app/translations/index.ts | 12 +- 14 files changed, 243 insertions(+), 100 deletions(-) create mode 100644 app/api/wallet/passcode_attempt_counter.ts create mode 100644 app/store/transaction.ts diff --git a/app/api/wallet/passcode_attempt_counter.ts b/app/api/wallet/passcode_attempt_counter.ts new file mode 100644 index 0000000000..fee737b89c --- /dev/null +++ b/app/api/wallet/passcode_attempt_counter.ts @@ -0,0 +1,23 @@ +import { StorageAPI } from '../storage' + +const KEY = 'PASSCODE_ERROR.count' + +async function get (): Promise { + const str = await StorageAPI.getItem(KEY) + return str === undefined ? 0 : Number(str) +} + +/** + * @param wallets to set, override previous set wallet + */ +async function set (count: number): Promise { + await StorageAPI.setItem(KEY, `${count}`) +} + +/** + * Multi Wallet Persistence Layer + */ +export const PasscodeAttemptCounter = { + set, + get +} diff --git a/app/components/OceanInterface/OceanInterface.tsx b/app/components/OceanInterface/OceanInterface.tsx index 5119235160..a5751ebec4 100644 --- a/app/components/OceanInterface/OceanInterface.tsx +++ b/app/components/OceanInterface/OceanInterface.tsx @@ -1,6 +1,5 @@ import { CTransactionSegWit } from '@defichain/jellyfish-transaction/dist' import { WhaleApiClient } from '@defichain/whale-api-client' -import { WhaleWalletAccount } from '@defichain/whale-api-wallet' import { MaterialIcons } from '@expo/vector-icons' import React, { useCallback, useEffect, useRef, useState } from 'react' import { ActivityIndicator, Animated, Linking, TouchableOpacity, View } from 'react-native' @@ -8,7 +7,6 @@ import { useDispatch, useSelector } from 'react-redux' import { Text } from '..' import { Logging } from '../../api/logging' import { useWallet } from '../../contexts/WalletContext' -import { useWalletManagementContext } from '../../contexts/WalletManagementContext' import { useWhaleApiClient } from '../../contexts/WhaleContext' import { RootState } from '../../store' import { firstTransactionSelector, ocean, OceanTransaction } from '../../store/ocean' @@ -16,7 +14,6 @@ import { tailwind } from '../../tailwind' import { translate } from '../../translations' const MAX_AUTO_RETRY = 1 -const MAX_SIGNING_RETRY = 3 async function gotoExplorer (txid: string): Promise { // TODO(thedoublejay) explorer URL @@ -28,19 +25,6 @@ async function gotoExplorer (txid: string): Promise { } } -async function signTransaction (tx: OceanTransaction, account: WhaleWalletAccount, incrementErrorCounter: () => void, retries: number = 0): Promise { - try { - return await tx.sign(account) - } catch (e) { - Logging.error(e) - if (retries < MAX_SIGNING_RETRY) { - incrementErrorCounter() - return await signTransaction(tx, account, incrementErrorCounter, retries + 1) - } - throw e - } -} - async function broadcastTransaction (tx: CTransactionSegWit, client: WhaleApiClient, retries: number = 0): Promise { try { return await client.transactions.send({ hex: tx.toHex() }) @@ -60,7 +44,6 @@ async function broadcastTransaction (tx: CTransactionSegWit, client: WhaleApiCli export function OceanInterface (): JSX.Element | null { const dispatch = useDispatch() const client = useWhaleApiClient() - const walletManagement = useWalletManagementContext() const walletContext = useWallet() // store @@ -85,13 +68,8 @@ export function OceanInterface (): JSX.Element | null { Animated.timing(slideAnim, { toValue: height, duration: 200, useNativeDriver: false }).start() setTx(transaction) - signTransaction(transaction, walletContext.get(0), () => walletManagement.incrementPasscodeErrorCount()) - .then(async signedTx => { - setTxid(signedTx.txId) - await broadcastTransaction(signedTx, client) - }) + broadcastTransaction(transaction.tx, client) .then(() => { - walletManagement.resetErrorCount() setTx({ ...transaction, broadcasted: true, diff --git a/app/components/PinInput.tsx b/app/components/PinInput.tsx index 8a16c7ef7a..7c237603b2 100644 --- a/app/components/PinInput.tsx +++ b/app/components/PinInput.tsx @@ -8,10 +8,11 @@ import { View } from '.' interface PinInputOptions { length: 4 | 6 // should be easy to support 4-8 numeric, fix it to 4 or 6 first onChange: (text: string) => void + value?: string } -export function PinInput ({ length, onChange }: PinInputOptions): JSX.Element { - const [text, setText] = useState('') +export function PinInput ({ length, onChange, value }: PinInputOptions): JSX.Element { + const [text, setText] = useState(value ?? '') const _textInput = useRef(null) useEffect(() => { @@ -22,7 +23,9 @@ export function PinInput ({ length, onChange }: PinInputOptions): JSX.Element { if (text.length === length) { // allow UI thread complete render with updated textinput state // before resolving long async task - setTimeout(() => onChange(text), 100) + setTimeout(() => { + onChange(text) + }, 100) } }, [text]) diff --git a/app/contexts/WalletManagementContext.tsx b/app/contexts/WalletManagementContext.tsx index 45c13704a8..d00e5869b6 100644 --- a/app/contexts/WalletManagementContext.tsx +++ b/app/contexts/WalletManagementContext.tsx @@ -1,14 +1,12 @@ import React, { createContext, useContext, useEffect, useMemo, useState } from 'react' - import { Logging } from '../api/logging' +import { PasscodeAttemptCounter } from '../api/wallet/passcode_attempt_counter' import { WalletPersistence, WalletPersistenceData } from '../api/wallet/persistence' import { initWhaleWallet, WhaleWallet } from '../api/wallet/provider' import { PromptInterface } from '../api/wallet/provider/mnemonic_encrypted' import { useNetworkContext } from './NetworkContext' import { useWhaleApiClient } from './WhaleContext' -const MAX_PASSCODE_ATTEMPTS = 3 - interface WalletManagement { wallets: WhaleWallet[] /** @@ -19,9 +17,9 @@ interface WalletManagement { // logic to bridge promptPassphrase UI and jellyfish-txn-builder setPasscodePromptInterface: (constructPrompt: PromptInterface) => void - incrementPasscodeErrorCount: () => void - errorCount: number - resetErrorCount: () => void + incrementPasscodeErrorCount: () => Promise + errorCount: () => Promise + resetErrorCount: () => Promise } const WalletManagementContext = createContext(undefined as any) @@ -36,12 +34,14 @@ export function useWalletManagementContext (): WalletManagement { return useContext(WalletManagementContext) } +export const MAX_PASSCODE_ATTEMPT = 3 + export function WalletManagementProvider (props: React.PropsWithChildren): JSX.Element | null { const { network } = useNetworkContext() const client = useWhaleApiClient() const [dataList, setDataList] = useState>>([]) + const [promptInterface, setPromptInterface] = useState() - const [errCount, setErrCount] = useState(0) useEffect(() => { WalletPersistence.get().then(dataList => { @@ -49,12 +49,6 @@ export function WalletManagementProvider (props: React.PropsWithChildren): }).catch(Logging.error) }, [network]) - useEffect(() => { - if (errCount > MAX_PASSCODE_ATTEMPTS) { - // TODO(@ivan-zynesis): wipe wallets from storage - } - }, [errCount]) - const wallets = useMemo(() => { return dataList.map(data => initWhaleWallet(data, network, client, promptInterface)) }, [dataList, promptInterface]) @@ -72,12 +66,14 @@ export function WalletManagementProvider (props: React.PropsWithChildren): setPasscodePromptInterface (cb: PromptInterface): void { setPromptInterface(cb) }, - incrementPasscodeErrorCount (): void { - setErrCount(errCount + 1) + async incrementPasscodeErrorCount (): Promise { + const failed = await PasscodeAttemptCounter.get() + if (failed + 1 > MAX_PASSCODE_ATTEMPT) return await this.clearWallets() + return await PasscodeAttemptCounter.set(failed + 1) }, - errorCount: errCount, - resetErrorCount: () => { - setErrCount(0) + errorCount: PasscodeAttemptCounter.get, + async resetErrorCount (): Promise { + return await PasscodeAttemptCounter.set(0) } } diff --git a/app/screens/AppNavigator/screens/Balances/ConvertScreen.tsx b/app/screens/AppNavigator/screens/Balances/ConvertScreen.tsx index f047046d39..a8f5ea3518 100644 --- a/app/screens/AppNavigator/screens/Balances/ConvertScreen.tsx +++ b/app/screens/AppNavigator/screens/Balances/ConvertScreen.tsx @@ -16,7 +16,7 @@ import { Button } from '../../../../components/Button' import { getTokenIcon } from '../../../../components/icons/tokens/_index' import { useTokensAPI } from '../../../../hooks/wallet/TokensAPI' import { RootState } from '../../../../store' -import { hasTxQueued, ocean } from '../../../../store/ocean' +import { hasTxQueued, transactionQueue } from '../../../../store/transaction' import { tailwind } from '../../../../tailwind' import { translate } from '../../../../translations' import LoadingScreen from '../../../LoadingNavigator/LoadingScreen' @@ -33,7 +33,7 @@ export function ConvertScreen (props: Props): JSX.Element { const dispatch = useDispatch() // global state const tokens = useTokensAPI() - const hasPendingJob = useSelector((state: RootState) => hasTxQueued(state.ocean)) + const hasPendingJob = useSelector((state: RootState) => hasTxQueued(state.transactionQueue)) const [mode, setMode] = useState(props.route.params.mode) const [sourceToken, setSourceToken] = useState() @@ -62,7 +62,6 @@ export function ConvertScreen (props: Props): JSX.Element { dispatch ).catch(e => { Logging.error(e) - dispatch(ocean.actions.setError(e)) }) } @@ -264,7 +263,7 @@ async function constructSignedConversionAndSend (mode: ConversionMode, amount: B return new CTransactionSegWit(signed) } - dispatch(ocean.actions.queueTransaction({ + dispatch(transactionQueue.actions.push({ sign: signer, title: `${translate('screens/ConvertScreen', 'Converting DFI')}` })) diff --git a/app/screens/AppNavigator/screens/Balances/screens/SendScreen.tsx b/app/screens/AppNavigator/screens/Balances/screens/SendScreen.tsx index 1d6b0eb616..cdedb049e9 100644 --- a/app/screens/AppNavigator/screens/Balances/screens/SendScreen.tsx +++ b/app/screens/AppNavigator/screens/Balances/screens/SendScreen.tsx @@ -20,7 +20,7 @@ import { SectionTitle } from '../../../../../components/SectionTitle' import { useNetworkContext } from '../../../../../contexts/NetworkContext' import { useWhaleApiClient } from '../../../../../contexts/WhaleContext' import { RootState } from '../../../../../store' -import { hasTxQueued, ocean } from '../../../../../store/ocean' +import { transactionQueue, hasTxQueued } from '../../../../../store/transaction' import { WalletToken } from '../../../../../store/wallet' import { tailwind } from '../../../../../tailwind' import { translate } from '../../../../../translations' @@ -58,13 +58,12 @@ async function send ({ return new CTransactionSegWit(signed) } - dispatch(ocean.actions.queueTransaction({ + dispatch(transactionQueue.actions.push({ sign: signer, title: `${translate('screens/SendScreen', 'Sending')} ${token.symbol}` })) } catch (e) { Logging.error(e) - dispatch(ocean.actions.setError(e)) } } @@ -78,7 +77,7 @@ export function SendScreen ({ route, navigation }: Props): JSX.Element { const dispatch = useDispatch() const [fee, setFee] = useState(new BigNumber(0.0001)) const [isSubmitting, setIsSubmitting] = useState(false) - const hasPendingJob = useSelector((state: RootState) => hasTxQueued(state.ocean)) + const hasPendingJob = useSelector((state: RootState) => hasTxQueued(state.transactionQueue)) useEffect(() => { client.transactions.estimateFee().then((f) => setFee(new BigNumber(f))).catch((e) => Logging.error(e)) diff --git a/app/screens/AppNavigator/screens/Dex/DexRemoveLiquidity.tsx b/app/screens/AppNavigator/screens/Dex/DexRemoveLiquidity.tsx index 9b416dd0ac..95460d0f71 100644 --- a/app/screens/AppNavigator/screens/Dex/DexRemoveLiquidity.tsx +++ b/app/screens/AppNavigator/screens/Dex/DexRemoveLiquidity.tsx @@ -17,7 +17,7 @@ import { Button } from '../../../../components/Button' import { getTokenIcon } from '../../../../components/icons/tokens/_index' import { useTokensAPI } from '../../../../hooks/wallet/TokensAPI' import { RootState } from '../../../../store' -import { hasTxQueued, ocean } from '../../../../store/ocean' +import { hasTxQueued, transactionQueue } from '../../../../store/transaction' import { tailwind } from '../../../../tailwind' import { translate } from '../../../../translations' import { DexParamList } from './DexNavigator' @@ -25,7 +25,7 @@ import { DexParamList } from './DexNavigator' type Props = StackScreenProps export function RemoveLiquidityScreen (props: Props): JSX.Element { - const hasPendingJob = useSelector((state: RootState) => hasTxQueued(state.ocean)) + const hasPendingJob = useSelector((state: RootState) => hasTxQueued(state.transactionQueue)) // this component state const [tokenAAmount, setTokenAAmount] = useState(new BigNumber(0)) const [tokenBAmount, setTokenBAmount] = useState(new BigNumber(0)) @@ -64,7 +64,6 @@ export function RemoveLiquidityScreen (props: Props): JSX.Element { dispatch ).catch(e => { Logging.error(e) - dispatch(ocean.actions.setError(e)) }) }, [amount]) @@ -202,7 +201,7 @@ async function constructSignedRemoveLiqAndSend (tokenId: number, amount: BigNumb return new CTransactionSegWit(dfTx) } - dispatch(ocean.actions.queueTransaction({ + dispatch(transactionQueue.actions.push({ sign: signer, title: `${translate('screens/RemoveLiquidity', 'Removing Liquidity')}` })) diff --git a/app/screens/AppNavigator/screens/UnlockWallet.tsx b/app/screens/AppNavigator/screens/UnlockWallet.tsx index 8e17e0a05c..a7a994ea60 100644 --- a/app/screens/AppNavigator/screens/UnlockWallet.tsx +++ b/app/screens/AppNavigator/screens/UnlockWallet.tsx @@ -1,5 +1,6 @@ -import React from 'react' -import tailwind from 'tailwind-rn' +import React, { useState } from 'react' +import { TouchableOpacity } from 'react-native' +import { tailwind } from '../../../tailwind' import { Text, View } from '../../../components' import { PinInput } from '../../../components/PinInput' import { translate } from '../../../translations' @@ -7,7 +8,8 @@ import { translate } from '../../../translations' interface UnlockWalletProps { pinLength: 4 | 6 onPinInput: (pin: string) => void - errorCount: number + onCancel: () => void + attemptsRemaining?: number } /** @@ -15,25 +17,36 @@ interface UnlockWalletProps { * this component render side by side with AppNavigator */ export function UnlockWallet (props: UnlockWalletProps): JSX.Element { - const { pinLength, onPinInput, errorCount = 0 } = props + const { pinLength, onPinInput, attemptsRemaining, onCancel } = props + const [passcode, setPasscode] = useState('') return ( - - {translate('screens/UnlockWallet', 'Enter passcode')} - {translate('screens/UnlockWallet', 'For transaction signing purpose')} - { - setTimeout(() => onPinInput(pin), 100) - }} - /> - { - (errorCount !== 0) ? ( - - {translate('screens/PinConfirmation', 'Wrong passcode. {{errorCount}} tries remaining', { errorCount })} - - ) : null - } + + + {translate('components/UnlockWallet', 'CANCEL')} + + + + {translate('screens/UnlockWallet', 'Enter passcode')} + {translate('screens/UnlockWallet', 'For transaction signing purpose')} + { + setPasscode(pin) + setTimeout(() => onPinInput(pin), 100) + }} + /> + { + (attemptsRemaining !== undefined) ? ( + + {translate('screens/PinConfirmation', 'Wrong passcode. %{attemptsRemaining} tries remaining', { attemptsRemaining })} + + ) : null + } + ) } diff --git a/app/screens/RootNavigator.tsx b/app/screens/RootNavigator.tsx index 4d32800b72..d8845b9d68 100644 --- a/app/screens/RootNavigator.tsx +++ b/app/screens/RootNavigator.tsx @@ -1,32 +1,92 @@ -import React, { useEffect, useState, useRef } from 'react' +import { CTransactionSegWit } from '@defichain/jellyfish-transaction/dist' +import { WhaleWalletAccount } from '@defichain/whale-api-wallet' +import React, { useEffect, useState, useRef, useCallback } from 'react' +import { useDispatch, useSelector } from 'react-redux' import tailwind from 'tailwind-rn' +import { Logging } from '../api' import { View } from '../components' import { WalletProvider } from '../contexts/WalletContext' -import { useWalletManagementContext } from '../contexts/WalletManagementContext' +import { useWalletManagementContext, MAX_PASSCODE_ATTEMPT } from '../contexts/WalletManagementContext' +import { RootState } from '../store' +import { ocean } from '../store/ocean' +import { DfTxSigner, first, transactionQueue } from '../store/transaction' import { AppNavigator } from './AppNavigator/AppNavigator' import { UnlockWallet } from './AppNavigator/screens/UnlockWallet' import { WalletNavigator } from './WalletNavigator/WalletNavigator' +const PASSCODE_LENGTH = 6 + /** * Top Root Level Wallet State to control what screen to show */ export function RootNavigator (): JSX.Element { - const { wallets, setPasscodePromptInterface, errorCount } = useWalletManagementContext() + const walletManagement = useWalletManagementContext() + const { wallets, setPasscodePromptInterface } = useWalletManagementContext() + + // store + const dispatch = useDispatch() + const transaction = useSelector((state: RootState) => first(state.transactionQueue)) + // state + const [errCount, setErrCount] = useState(0) const [isPrompting, setIsPrompting] = useState(false) - const promptInterface = useRef<(pin: string) => void>() + // proxied resolve/reject + const promptResolve = useRef<(pin: string) => void>() + const promptReject = useRef<(e: Error) => void>() + + const incrementError = useCallback(async () => { + setErrCount(errCount + 1) + await walletManagement.incrementPasscodeErrorCount() + }, [errCount]) + const resetError = useCallback(async () => { + setErrCount(0) + await walletManagement.resetErrorCount() + }, [errCount]) + + useEffect(() => { + // last available job will remained in this UI state until get dismissed + if (transaction !== undefined) { + let result: CTransactionSegWit | null // 3 types of result + signTransaction(transaction, wallets[0].get(0), incrementError) + .then(async signedTx => { result = signedTx }) // positive + .catch(e => { + if (e.message !== 'USER_CANCELED') result = null // negative + // else result = undefined // neutral + }) + .then(async () => { + if (result === undefined) { // cancel + setIsPrompting(false) // dismiss prompt UI + dispatch(transactionQueue.actions.pop()) // remove job + } else if (result === null) { // consecutive error + await walletManagement.clearWallets() + } else { + setIsPrompting(false) // dismiss prompt UI + dispatch(transactionQueue.actions.pop()) // remove job + dispatch(ocean.actions.queueTransaction({ tx: result })) // push signed result for broadcasting + await resetError() + } + }).catch(e => Logging.error(e)) + } + }, [transaction, wallets]) + + // setup interface for encryptedProvider::promptPassphrase call useEffect(() => { setPasscodePromptInterface({ prompt: async () => { - return await new Promise(resolve => { - promptInterface.current = resolve + return await new Promise((resolve, reject) => { + promptResolve.current = resolve + promptReject.current = reject setIsPrompting(true) }) } }) }, []) + useEffect(() => { + walletManagement.errorCount().then(count => setErrCount(count)).catch(e => Logging.error(e)) + }, []) + if (wallets.length === 0) { return } @@ -42,11 +102,21 @@ export function RootNavigator (): JSX.Element { { - if (promptInterface.current !== undefined) { - promptInterface.current(pin) - setIsPrompting(false) + if (promptResolve.current !== undefined) { + const resolve = promptResolve.current + resolve(pin) + promptResolve.current = undefined + promptReject.current = undefined + } + }} + onCancel={() => { + if (promptReject.current !== undefined) { + const reject = promptReject.current + reject(new Error('USER_CANCELED')) + promptResolve.current = undefined + promptReject.current = undefined } }} /> @@ -57,19 +127,46 @@ export function RootNavigator (): JSX.Element { ) } -function WalletContextPasscodePrompt ({ isPrompting, onPinInput, errorCount }: { isPrompting: boolean, onPinInput: (pin: string) => void, errorCount: number }): JSX.Element | null { +function WalletContextPasscodePrompt ({ isPrompting, onPinInput, errorCount, onCancel }: { + isPrompting: boolean + onPinInput: (pin: string) => void + errorCount: number + onCancel: () => void +}): JSX.Element | null { /** * UI literally stuck on this page until either * 1. successfully resolve promise with valid pin (successful decryption) * 2. error count hit threshold, wallet wiped, will auto redirect to onboarding again */ - if (!isPrompting && errorCount === 0) return null + if (!isPrompting) return null + /** + * prompting , err = show prompt + * not prompting, err = show prompt + * prompting, no err = show prompt + * not prompting, no error = show null + */ + const attemptsRemaining = errorCount === 0 ? undefined : MAX_PASSCODE_ATTEMPT - errorCount return ( ) } + +async function signTransaction (tx: DfTxSigner, account: WhaleWalletAccount, incrementError: () => Promise, retries: number = 0): Promise { + try { + return await tx.sign(account) + } catch (e) { + if (e.message === 'USER_CANCELED') throw e + + if (retries < MAX_PASSCODE_ATTEMPT) { + await incrementError() + return await signTransaction(tx, account, incrementError, retries + 1) + } + throw e + } +} diff --git a/app/store/index.ts b/app/store/index.ts index 1e7e90431c..e7c9338154 100644 --- a/app/store/index.ts +++ b/app/store/index.ts @@ -2,6 +2,7 @@ import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit' import { block } from './block' import { ocean } from './ocean' import { wallet } from './wallet' +import { transactionQueue } from './transaction' /** * RootState for DeFi Wallet App @@ -16,7 +17,8 @@ export const store = configureStore({ reducer: { block: block.reducer, wallet: wallet.reducer, - ocean: ocean.reducer + ocean: ocean.reducer, + transactionQueue: transactionQueue.reducer }, middleware: [ ...getDefaultMiddleware({ serializableCheck: false }) diff --git a/app/store/ocean.test.ts b/app/store/ocean.test.ts index 252bbd6db7..57948388e8 100644 --- a/app/store/ocean.test.ts +++ b/app/store/ocean.test.ts @@ -25,8 +25,8 @@ describe('ocean reducer', () => { it('should handle queueTransaction and popTransaction', () => { const v2 = '020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff050393700500ffffffff038260498a040000001976a9143db7aeb218455b697e94f6ff00c548e72221231d88ac7e67ce1d0000000017a914dd7730517e0e4969b4e43677ff5bee682e53420a870000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000' const buffer = SmartBuffer.fromBuffer(Buffer.from(v2, 'hex')) - const sign = async () => new CTransactionSegWit(buffer) - const payload: Omit = { title: 'Sending', sign } + const signed = new CTransactionSegWit(buffer) + const payload: Omit = { title: 'Sending', tx: signed } const addedTransaction = ocean.reducer(initialState, ocean.actions.queueTransaction(payload)); expect(addedTransaction).toStrictEqual({ transactions: [{ ...payload, diff --git a/app/store/ocean.ts b/app/store/ocean.ts index f5af75f51d..a2c9719a07 100644 --- a/app/store/ocean.ts +++ b/app/store/ocean.ts @@ -1,10 +1,9 @@ import { CTransactionSegWit } from '@defichain/jellyfish-transaction' -import { WhaleWalletAccount } from '@defichain/whale-api-wallet' import { createSelector, createSlice, PayloadAction } from '@reduxjs/toolkit' export interface OceanTransaction { broadcasted: boolean - sign: (account: WhaleWalletAccount) => Promise + tx: CTransactionSegWit title?: string } diff --git a/app/store/transaction.ts b/app/store/transaction.ts new file mode 100644 index 0000000000..fb80a6aea6 --- /dev/null +++ b/app/store/transaction.ts @@ -0,0 +1,33 @@ +import { CTransactionSegWit } from '@defichain/jellyfish-transaction' +import { WhaleWalletAccount } from '@defichain/whale-api-wallet' +import { createSelector, createSlice, PayloadAction } from '@reduxjs/toolkit' + +export interface DfTxSigner { + sign: (account: WhaleWalletAccount) => Promise + title?: string +} + +export interface TransactionQueue { + transactions: DfTxSigner[] +} + +const initialState: TransactionQueue = { + transactions: [] +} + +export const transactionQueue = createSlice({ + name: 'tx_queue', + initialState, + reducers: { + push: (state, action: PayloadAction) => { + state.transactions = [...state.transactions, action.payload] + }, + pop: (state) => { + state.transactions.shift() + state.transactions = [...state.transactions] + } + } +}) + +export const first = createSelector((state: TransactionQueue) => state.transactions, (transactions) => transactions[0]) +export const hasTxQueued = createSelector((state: TransactionQueue) => state.transactions, (transactions) => transactions.length > 0) diff --git a/app/translations/index.ts b/app/translations/index.ts index d504c2b2ae..6469f2c84f 100644 --- a/app/translations/index.ts +++ b/app/translations/index.ts @@ -52,10 +52,12 @@ export function translate (path: string, text: string, options?: TranslateOption if (!init) { initI18n() } - const translation = i18n.translate(`${path}.${text}`, options) - if (translation !== null && translation !== undefined && translation !== '') { - return translation - } else { - return text + let translation = i18n.translate(`${path}.${text}`, options) ?? text + if (options !== undefined) { + // TODO(@ivan-zynesis): fix with i18n method + Object.keys(options).forEach(k => { + translation = translation.replaceAll(`%{${k}}`, options[k]) + }) } + return translation } From 6ca5f00e77c0b7b482b3759f16f1463128150d12 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 28 Jul 2021 19:31:41 +0800 Subject: [PATCH 21/48] completed encrypted wallet implementation (refactored) - ui implementation (page, pin input) - tx queue (in store), split from broadcast queue - retry mech + state management - clear wallet after consecutive failure (persistent failure counter, reset upon valid pin) --- app/components/PinInput.tsx | 32 ++-- app/contexts/WalletManagementContext.tsx | 5 +- .../AppNavigator/screens/UnlockWallet.tsx | 34 ++-- app/screens/EncryptedWallet.tsx | 144 ++++++++++++++++ app/screens/RootNavigator.tsx | 160 +----------------- 5 files changed, 194 insertions(+), 181 deletions(-) create mode 100644 app/screens/EncryptedWallet.tsx diff --git a/app/components/PinInput.tsx b/app/components/PinInput.tsx index 7c237603b2..f3e765cb80 100644 --- a/app/components/PinInput.tsx +++ b/app/components/PinInput.tsx @@ -1,33 +1,40 @@ import { MaterialIcons } from '@expo/vector-icons' -import React, { useState, useRef, useEffect } from 'react' - +import React, { useState, useRef, useEffect, useCallback } from 'react' import { TextInput, TouchableOpacity } from 'react-native' import tailwind from 'tailwind-rn' import { View } from '.' +import { Text } from './Text' interface PinInputOptions { length: 4 | 6 // should be easy to support 4-8 numeric, fix it to 4 or 6 first onChange: (text: string) => void - value?: string } -export function PinInput ({ length, onChange, value }: PinInputOptions): JSX.Element { - const [text, setText] = useState(value ?? '') +export function PinInput ({ length, onChange }: PinInputOptions): JSX.Element { + const [text, setText] = useState('') const _textInput = useRef(null) - useEffect(() => { + const focus = useCallback(() => { _textInput.current?.focus() - }, [_textInput]) + }, [_textInput, _textInput?.current]) + + // CAUTION: as digit box is meant to display same length as an invisible textinput value + // should be used side by side with setText('') + const clear = useCallback(() => { + focus() + _textInput.current?.clear() + }, [_textInput, _textInput?.current, focus]) useEffect(() => { if (text.length === length) { - // allow UI thread complete render with updated textinput state - // before resolving long async task + // allow UI thread to complete painting before attempt heavy async task in callback setTimeout(() => { + clear() + setText('') onChange(text) }, 100) } - }, [text]) + }, [text, clear]) const digitBoxes = (): JSX.Element => { const arr = [] @@ -35,6 +42,9 @@ export function PinInput ({ length, onChange, value }: PinInputOptions): JSX.Ele let child: JSX.Element | null = null if (text.length > i) { child = + if (text.length - 1 === i) { + child = {text[i]} + } } arr.push( @@ -48,7 +58,7 @@ export function PinInput ({ length, onChange, value }: PinInputOptions): JSX.Ele } return ( - _textInput.current?.focus()}> + focus()}> {digitBoxes()} { _textInput.current = ref }} diff --git a/app/contexts/WalletManagementContext.tsx b/app/contexts/WalletManagementContext.tsx index d00e5869b6..103964509d 100644 --- a/app/contexts/WalletManagementContext.tsx +++ b/app/contexts/WalletManagementContext.tsx @@ -7,6 +7,9 @@ import { PromptInterface } from '../api/wallet/provider/mnemonic_encrypted' import { useNetworkContext } from './NetworkContext' import { useWhaleApiClient } from './WhaleContext' +export const MAX_PASSCODE_ATTEMPT = 3 +export const PASSCODE_LENGTH = 6 + interface WalletManagement { wallets: WhaleWallet[] /** @@ -34,8 +37,6 @@ export function useWalletManagementContext (): WalletManagement { return useContext(WalletManagementContext) } -export const MAX_PASSCODE_ATTEMPT = 3 - export function WalletManagementProvider (props: React.PropsWithChildren): JSX.Element | null { const { network } = useNetworkContext() const client = useWhaleApiClient() diff --git a/app/screens/AppNavigator/screens/UnlockWallet.tsx b/app/screens/AppNavigator/screens/UnlockWallet.tsx index a7a994ea60..9ee404e1b9 100644 --- a/app/screens/AppNavigator/screens/UnlockWallet.tsx +++ b/app/screens/AppNavigator/screens/UnlockWallet.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import React from 'react' import { TouchableOpacity } from 'react-native' import { tailwind } from '../../../tailwind' import { Text, View } from '../../../components' @@ -6,6 +6,7 @@ import { PinInput } from '../../../components/PinInput' import { translate } from '../../../translations' interface UnlockWalletProps { + isPrompting: boolean pinLength: 4 | 6 onPinInput: (pin: string) => void onCancel: () => void @@ -13,12 +14,11 @@ interface UnlockWalletProps { } /** - * This meant to be a full page UI (simple component) and NOT accessed via navigation - * this component render side by side with AppNavigator + * FULL page sized (only) stateless UI component + * not navigate-able */ -export function UnlockWallet (props: UnlockWalletProps): JSX.Element { - const { pinLength, onPinInput, attemptsRemaining, onCancel } = props - const [passcode, setPasscode] = useState('') +export function UnlockWalletInterface (props: UnlockWalletProps): JSX.Element { + const { pinLength, onPinInput, attemptsRemaining, onCancel, isPrompting } = props return ( @@ -31,14 +31,8 @@ export function UnlockWallet (props: UnlockWalletProps): JSX.Element { {translate('screens/UnlockWallet', 'Enter passcode')} {translate('screens/UnlockWallet', 'For transaction signing purpose')} - { - setPasscode(pin) - setTimeout(() => onPinInput(pin), 100) - }} - /> + {/* TODO: switch authorization method here when biometric supported */} + { (attemptsRemaining !== undefined) ? ( @@ -50,3 +44,15 @@ export function UnlockWallet (props: UnlockWalletProps): JSX.Element { ) } + +function PassphraseInput ({ isPrompting, pinLength, onPinInput }: { + isPrompting: boolean + pinLength: 4 | 6 + onPinInput: (pin: string) => void +}): JSX.Element | null { + if (!isPrompting) return // minimize visible flicker + + return ( + + ) +} diff --git a/app/screens/EncryptedWallet.tsx b/app/screens/EncryptedWallet.tsx new file mode 100644 index 0000000000..e71b3e870f --- /dev/null +++ b/app/screens/EncryptedWallet.tsx @@ -0,0 +1,144 @@ +import { CTransactionSegWit } from '@defichain/jellyfish-transaction/dist' +import { WhaleWalletAccount } from '@defichain/whale-api-wallet' +import React, { useEffect, useState, useRef, useCallback } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import tailwind from 'tailwind-rn' +import { Logging } from '../api' +import { View } from '../components' +import { useWalletManagementContext, MAX_PASSCODE_ATTEMPT, PASSCODE_LENGTH } from '../contexts/WalletManagementContext' +import { RootState } from '../store' +import { ocean } from '../store/ocean' +import { DfTxSigner, first, transactionQueue } from '../store/transaction' +import { UnlockWalletInterface } from './AppNavigator/screens/UnlockWallet' + +/** + * Side by side with AppNavigator at root level + */ +export function EncryptedWallet (): JSX.Element { + const walletManagement = useWalletManagementContext() + const { wallets, setPasscodePromptInterface } = useWalletManagementContext() + + // store + const dispatch = useDispatch() + const transaction = useSelector((state: RootState) => first(state.transactionQueue)) + + // state + const [attemptsRemaining, setAttemptsRemaining] = useState(MAX_PASSCODE_ATTEMPT) + // setting this to default true (require pin callback setup), cause wallet require unlock upon this component reconstruct + const [awaitingPromise, setAwaitingPromise] = useState(false) // waiting whole passphrase promise to be resolve + const [awaitingPin, setAwaitingPin] = useState(false) // waiting pin input + + // proxied resolve/reject + const promptResolve = useRef<(pin: string) => void>() + const promptReject = useRef<(e: Error) => void>() + + // update persistent + resolve state + const onRetry = useCallback(async (attempts: number) => { + setAttemptsRemaining(MAX_PASSCODE_ATTEMPT - attempts) + setAwaitingPin(true) // ensure next retry can update state of pin input + await walletManagement.incrementPasscodeErrorCount() + }, [attemptsRemaining, awaitingPin]) + const onSuccess = useCallback(async () => { + setAttemptsRemaining(MAX_PASSCODE_ATTEMPT) + await walletManagement.resetErrorCount() + }, [attemptsRemaining]) + + // consume pending to sign TransactionQueue from store + useEffect(() => { + // last available job will remained in this UI state until get dismissed + if (transaction !== undefined) { + let result: CTransactionSegWit | null // 3 types of result + signTransaction(transaction, wallets[0].get(0), onRetry) + .then(async signedTx => { result = signedTx }) // positive + .catch(e => { + if (e.message !== 'USER_CANCELED') result = null // negative + // else result = undefined // neutral + }) + .then(async () => { + if (result === undefined) { + // case: cancel + setAwaitingPromise(false) // dismiss prompt UI + dispatch(transactionQueue.actions.pop()) // remove job + } else if (result === null) { + // case: consecutive error + await walletManagement.clearWallets() + } else { + // case: success + setAwaitingPromise(false) // dismiss prompt UI + dispatch(transactionQueue.actions.pop()) // remove job + dispatch(ocean.actions.queueTransaction({ tx: result })) // push signed result for broadcasting + await onSuccess() + } + }).catch(e => Logging.error(e)) + } + }, [transaction, wallets]) + + // setup interface for encryptedProvider::promptPassphrase call + useEffect(() => { + setPasscodePromptInterface({ + prompt: async () => { + return await new Promise((resolve, reject) => { + promptResolve.current = resolve + promptReject.current = reject + setAwaitingPromise(true) + setAwaitingPin(true) + }) + } + }) + + // load accumulated attempts failure from persistent storage ONCE + walletManagement.errorCount() + .then(count => setAttemptsRemaining(MAX_PASSCODE_ATTEMPT - count)) + .catch(e => Logging.error(e)) + }, []) + + const viewHeight: { height?: number } = {} + if (!awaitingPromise && !awaitingPin) { + // TO BE IMPROVED + // hackish method to hide prompt UI WITHOUT losing state, state MUST be RETAINED (proxied promise resolver etc) + // across multiple retries (to prevent flickers) + viewHeight.height = 0 + } + + return ( + + = MAX_PASSCODE_ATTEMPT ? undefined : attemptsRemaining} + onPinInput={pin => { + if (promptResolve.current !== undefined) { + const resolve = promptResolve.current + resolve(pin) + promptResolve.current = undefined + promptReject.current = undefined + } + setAwaitingPin(false) + }} + onCancel={() => { + if (promptReject.current !== undefined) { + const reject = promptReject.current + reject(new Error('USER_CANCELED')) + promptResolve.current = undefined + promptReject.current = undefined + } + setAwaitingPin(false) + }} + /> + + ) +} + +async function signTransaction (tx: DfTxSigner, account: WhaleWalletAccount, onAutoRetry: (attempts: number) => Promise, retries: number = 0): Promise { + try { + return await tx.sign(account) + } catch (e) { + if (e.message === 'USER_CANCELED') throw e + + if (retries < MAX_PASSCODE_ATTEMPT) { + await onAutoRetry(retries + 1) + return await signTransaction(tx, account, onAutoRetry, retries + 1) + } + throw e + } +} diff --git a/app/screens/RootNavigator.tsx b/app/screens/RootNavigator.tsx index d8845b9d68..5dfb2fd2ed 100644 --- a/app/screens/RootNavigator.tsx +++ b/app/screens/RootNavigator.tsx @@ -1,172 +1,24 @@ -import { CTransactionSegWit } from '@defichain/jellyfish-transaction/dist' -import { WhaleWalletAccount } from '@defichain/whale-api-wallet' -import React, { useEffect, useState, useRef, useCallback } from 'react' -import { useDispatch, useSelector } from 'react-redux' -import tailwind from 'tailwind-rn' -import { Logging } from '../api' -import { View } from '../components' +import React from 'react' import { WalletProvider } from '../contexts/WalletContext' -import { useWalletManagementContext, MAX_PASSCODE_ATTEMPT } from '../contexts/WalletManagementContext' -import { RootState } from '../store' -import { ocean } from '../store/ocean' -import { DfTxSigner, first, transactionQueue } from '../store/transaction' +import { useWalletManagementContext } from '../contexts/WalletManagementContext' import { AppNavigator } from './AppNavigator/AppNavigator' -import { UnlockWallet } from './AppNavigator/screens/UnlockWallet' import { WalletNavigator } from './WalletNavigator/WalletNavigator' - -const PASSCODE_LENGTH = 6 +import { EncryptedWallet } from './EncryptedWallet' /** * Top Root Level Wallet State to control what screen to show */ export function RootNavigator (): JSX.Element { - const walletManagement = useWalletManagementContext() - const { wallets, setPasscodePromptInterface } = useWalletManagementContext() - - // store - const dispatch = useDispatch() - const transaction = useSelector((state: RootState) => first(state.transactionQueue)) - - // state - const [errCount, setErrCount] = useState(0) - const [isPrompting, setIsPrompting] = useState(false) - - // proxied resolve/reject - const promptResolve = useRef<(pin: string) => void>() - const promptReject = useRef<(e: Error) => void>() - - const incrementError = useCallback(async () => { - setErrCount(errCount + 1) - await walletManagement.incrementPasscodeErrorCount() - }, [errCount]) - const resetError = useCallback(async () => { - setErrCount(0) - await walletManagement.resetErrorCount() - }, [errCount]) - - useEffect(() => { - // last available job will remained in this UI state until get dismissed - if (transaction !== undefined) { - let result: CTransactionSegWit | null // 3 types of result - signTransaction(transaction, wallets[0].get(0), incrementError) - .then(async signedTx => { result = signedTx }) // positive - .catch(e => { - if (e.message !== 'USER_CANCELED') result = null // negative - // else result = undefined // neutral - }) - .then(async () => { - if (result === undefined) { // cancel - setIsPrompting(false) // dismiss prompt UI - dispatch(transactionQueue.actions.pop()) // remove job - } else if (result === null) { // consecutive error - await walletManagement.clearWallets() - } else { - setIsPrompting(false) // dismiss prompt UI - dispatch(transactionQueue.actions.pop()) // remove job - dispatch(ocean.actions.queueTransaction({ tx: result })) // push signed result for broadcasting - await resetError() - } - }).catch(e => Logging.error(e)) - } - }, [transaction, wallets]) - - // setup interface for encryptedProvider::promptPassphrase call - useEffect(() => { - setPasscodePromptInterface({ - prompt: async () => { - return await new Promise((resolve, reject) => { - promptResolve.current = resolve - promptReject.current = reject - setIsPrompting(true) - }) - } - }) - }, []) - - useEffect(() => { - walletManagement.errorCount().then(count => setErrCount(count)).catch(e => Logging.error(e)) - }, []) + const { wallets } = useWalletManagementContext() if (wallets.length === 0) { return } - const appContainerStyle: { height?: number } = {} - if (isPrompting) { - // TO BE IMPROVED - // hackish method to hide WITHOUT losing state, state must retained - appContainerStyle.height = 0 - } - return ( - { - if (promptResolve.current !== undefined) { - const resolve = promptResolve.current - resolve(pin) - promptResolve.current = undefined - promptReject.current = undefined - } - }} - onCancel={() => { - if (promptReject.current !== undefined) { - const reject = promptReject.current - reject(new Error('USER_CANCELED')) - promptResolve.current = undefined - promptReject.current = undefined - } - }} - /> - - - + + ) } - -function WalletContextPasscodePrompt ({ isPrompting, onPinInput, errorCount, onCancel }: { - isPrompting: boolean - onPinInput: (pin: string) => void - errorCount: number - onCancel: () => void -}): JSX.Element | null { - /** - * UI literally stuck on this page until either - * 1. successfully resolve promise with valid pin (successful decryption) - * 2. error count hit threshold, wallet wiped, will auto redirect to onboarding again - */ - if (!isPrompting) return null - - /** - * prompting , err = show prompt - * not prompting, err = show prompt - * prompting, no err = show prompt - * not prompting, no error = show null - */ - const attemptsRemaining = errorCount === 0 ? undefined : MAX_PASSCODE_ATTEMPT - errorCount - return ( - - ) -} - -async function signTransaction (tx: DfTxSigner, account: WhaleWalletAccount, incrementError: () => Promise, retries: number = 0): Promise { - try { - return await tx.sign(account) - } catch (e) { - if (e.message === 'USER_CANCELED') throw e - - if (retries < MAX_PASSCODE_ATTEMPT) { - await incrementError() - return await signTransaction(tx, account, incrementError, retries + 1) - } - throw e - } -} From 7b9c7c4b5cc2b9a2d338eacd6cc84f074b1a015a Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 28 Jul 2021 20:12:41 +0800 Subject: [PATCH 22/48] syntax and comment fixes --- app/api/wallet/passcode_attempt_counter.ts | 5 +---- .../OceanInterface/OceanInterface.test.tsx | 2 +- app/components/OceanInterface/OceanInterface.tsx | 16 ++++------------ .../screens/Balances/BalancesScreen.tsx | 1 - app/screens/EncryptedWallet.tsx | 6 ++---- app/store/ocean.test.ts | 4 ++-- 6 files changed, 10 insertions(+), 24 deletions(-) diff --git a/app/api/wallet/passcode_attempt_counter.ts b/app/api/wallet/passcode_attempt_counter.ts index fee737b89c..50d824644b 100644 --- a/app/api/wallet/passcode_attempt_counter.ts +++ b/app/api/wallet/passcode_attempt_counter.ts @@ -7,15 +7,12 @@ async function get (): Promise { return str === undefined ? 0 : Number(str) } -/** - * @param wallets to set, override previous set wallet - */ async function set (count: number): Promise { await StorageAPI.setItem(KEY, `${count}`) } /** - * Multi Wallet Persistence Layer + * Failed passcode input counter persistence layer */ export const PasscodeAttemptCounter = { set, diff --git a/app/components/OceanInterface/OceanInterface.test.tsx b/app/components/OceanInterface/OceanInterface.test.tsx index cd7b112302..a5c940338b 100644 --- a/app/components/OceanInterface/OceanInterface.test.tsx +++ b/app/components/OceanInterface/OceanInterface.test.tsx @@ -46,7 +46,7 @@ describe('oceanInterface', () => { height: 49, transactions: [{ broadcasted: false, - sign: async () => signed + tx: signed }] } }; diff --git a/app/components/OceanInterface/OceanInterface.tsx b/app/components/OceanInterface/OceanInterface.tsx index a5751ebec4..73e167778a 100644 --- a/app/components/OceanInterface/OceanInterface.tsx +++ b/app/components/OceanInterface/OceanInterface.tsx @@ -53,12 +53,10 @@ export function OceanInterface (): JSX.Element | null { // state const [tx, setTx] = useState() const [err, setError] = useState(e) - const [txid, setTxid] = useState() const dismissDrawer = useCallback(() => { setTx(undefined) setError(undefined) - setTxid(undefined) slideAnim.setValue(0) }, []) @@ -77,10 +75,7 @@ export function OceanInterface (): JSX.Element | null { }) }) .catch((e: Error) => { - let errMsg = e.message - if (txid !== undefined) { - errMsg = `${errMsg}. Txid: ${txid}` - } + const errMsg = `${e.message}. Txid: ${transaction.tx.txId}` setError(new Error(errMsg)) }) .finally(() => dispatch(ocean.actions.popTransaction())) // remove the job as soon as completion @@ -104,7 +99,7 @@ export function OceanInterface (): JSX.Element | null { : ( ) @@ -113,10 +108,8 @@ export function OceanInterface (): JSX.Element | null { ) } -function TransactionDetail ({ broadcasted, txid, onClose }: { broadcasted: boolean, txid?: string, onClose: () => void }): JSX.Element | null { - let title = 'Signing...' - if (txid !== undefined) title = 'Broadcasting...' - if (broadcasted) title = 'Transaction Sent' +function TransactionDetail ({ broadcasted, txid, onClose }: { broadcasted: boolean, txid: string, onClose: () => void }): JSX.Element | null { + const title = broadcasted ? 'Transaction Sent' : 'Broadcasting...' return ( <> { @@ -140,7 +133,6 @@ function TransactionDetail ({ broadcasted, txid, onClose }: { broadcasted: boole } function TransactionError ({ errMsg, onClose }: { errMsg: string | undefined, onClose: () => void }): JSX.Element { - console.log(errMsg) return ( <> diff --git a/app/screens/AppNavigator/screens/Balances/BalancesScreen.tsx b/app/screens/AppNavigator/screens/Balances/BalancesScreen.tsx index 969a93ac06..fea388e32a 100644 --- a/app/screens/AppNavigator/screens/Balances/BalancesScreen.tsx +++ b/app/screens/AppNavigator/screens/Balances/BalancesScreen.tsx @@ -28,7 +28,6 @@ export function BalancesScreen ({ navigation }: Props): JSX.Element { const dispatch = useDispatch() useEffect(() => { - console.log('height') dispatch(ocean.actions.setHeight(height)) }, [height]) diff --git a/app/screens/EncryptedWallet.tsx b/app/screens/EncryptedWallet.tsx index e71b3e870f..9592298fa3 100644 --- a/app/screens/EncryptedWallet.tsx +++ b/app/screens/EncryptedWallet.tsx @@ -108,8 +108,7 @@ export function EncryptedWallet (): JSX.Element { attemptsRemaining={attemptsRemaining >= MAX_PASSCODE_ATTEMPT ? undefined : attemptsRemaining} onPinInput={pin => { if (promptResolve.current !== undefined) { - const resolve = promptResolve.current - resolve(pin) + promptResolve.current(pin) promptResolve.current = undefined promptReject.current = undefined } @@ -117,8 +116,7 @@ export function EncryptedWallet (): JSX.Element { }} onCancel={() => { if (promptReject.current !== undefined) { - const reject = promptReject.current - reject(new Error('USER_CANCELED')) + promptReject.current(new Error('USER_CANCELED')) promptResolve.current = undefined promptReject.current = undefined } diff --git a/app/store/ocean.test.ts b/app/store/ocean.test.ts index 57948388e8..12ed865f15 100644 --- a/app/store/ocean.test.ts +++ b/app/store/ocean.test.ts @@ -30,14 +30,14 @@ describe('ocean reducer', () => { const addedTransaction = ocean.reducer(initialState, ocean.actions.queueTransaction(payload)); expect(addedTransaction).toStrictEqual({ transactions: [{ ...payload, - status: 'INITIAL' + broadcasted: false }], err: undefined, height: 49 }) const actual = ocean.reducer(addedTransaction, ocean.actions.queueTransaction(payload)); const pop = ocean.reducer(actual, ocean.actions.popTransaction()); expect(pop).toStrictEqual({ transactions: [{ ...payload, - status: 'INITIAL' + broadcasted: false }], err: undefined, height: 49 }) const removed = ocean.reducer(pop, ocean.actions.popTransaction()); expect(removed).toStrictEqual({ transactions: [], err: undefined, height: 49 }) From b575ed02b3a37bced4b61c661be8a21e2d049db8 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 29 Jul 2021 10:37:56 +0800 Subject: [PATCH 23/48] rename scrypt native module file, to build correctly --- .../{scrypt.native.temp.ts => scrypt.native.ts} | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) rename app/api/{scrypt.native.temp.ts => scrypt.native.ts} (54%) diff --git a/app/api/scrypt.native.temp.ts b/app/api/scrypt.native.ts similarity index 54% rename from app/api/scrypt.native.temp.ts rename to app/api/scrypt.native.ts index f4bce17166..1bf3fe9742 100644 --- a/app/api/scrypt.native.temp.ts +++ b/app/api/scrypt.native.ts @@ -8,10 +8,8 @@ const DEFAULT_SCRYPT_PARAMS: ScryptParams = { } class NativeScryptModule implements ScryptProvider { - passphraseToKey (nfcUtf8: string, salt: Buffer, desiredKeyLen: number): Buffer { - let result: Buffer | null = null - let err: Error | null = null - rnScrypt( + async passphraseToKey (nfcUtf8: string, salt: Buffer, desiredKeyLen: number): Promise { + return await rnScrypt( Buffer.from(nfcUtf8, 'ascii'), salt, DEFAULT_SCRYPT_PARAMS.N, @@ -20,16 +18,6 @@ class NativeScryptModule implements ScryptProvider { desiredKeyLen, 'buffer' ) - .then(buffer => { result = buffer }) - .catch((e: Error) => { err = e }) - - const start = Date.now() - while (true) { - if (result !== null) { - return result - } - if (err !== null || (Date.now() > start + 10000)) throw new Error() - } } } From 7651b74b8b42c7d6b369e93a433d6d54f814a7aa Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 29 Jul 2021 11:59:00 +0800 Subject: [PATCH 24/48] rename --- app/api/wallet/provider/index.ts | 1 - ...kWallet.tsx => AuthorizationInterface.tsx} | 26 +++++++++++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) rename app/screens/AppNavigator/screens/{UnlockWallet.tsx => AuthorizationInterface.tsx} (70%) diff --git a/app/api/wallet/provider/index.ts b/app/api/wallet/provider/index.ts index 27cf538ff6..0a133b918c 100644 --- a/app/api/wallet/provider/index.ts +++ b/app/api/wallet/provider/index.ts @@ -26,7 +26,6 @@ export function initWhaleWallet (data: WalletPersistenceData, network: Envi * @param {EnvironmentNetwork} network */ function resolveProvider (data: WalletPersistenceData, network: EnvironmentNetwork, promptInterface?: PromptInterface): WalletHdNodeProvider { - console.log('wallet type', data.type) switch (data.type) { case WalletType.MNEMONIC_UNPROTECTED: return MnemonicUnprotected.initProvider(data, network) diff --git a/app/screens/AppNavigator/screens/UnlockWallet.tsx b/app/screens/AppNavigator/screens/AuthorizationInterface.tsx similarity index 70% rename from app/screens/AppNavigator/screens/UnlockWallet.tsx rename to app/screens/AppNavigator/screens/AuthorizationInterface.tsx index 9ee404e1b9..c3ab6d10fb 100644 --- a/app/screens/AppNavigator/screens/UnlockWallet.tsx +++ b/app/screens/AppNavigator/screens/AuthorizationInterface.tsx @@ -1,24 +1,27 @@ import React from 'react' -import { TouchableOpacity } from 'react-native' +import { ActivityIndicator, TouchableOpacity } from 'react-native' import { tailwind } from '../../../tailwind' import { Text, View } from '../../../components' import { PinInput } from '../../../components/PinInput' import { translate } from '../../../translations' interface UnlockWalletProps { - isPrompting: boolean pinLength: 4 | 6 onPinInput: (pin: string) => void onCancel: () => void attemptsRemaining?: number + + // very customized props + isPrompting: boolean // used as a switch, enable/disable the pin component, thus controlling the lifecycle + spinnerMessage?: string // indicate main promise (sign + broadcast) still ongoing } /** * FULL page sized (only) stateless UI component * not navigate-able */ -export function UnlockWalletInterface (props: UnlockWalletProps): JSX.Element { - const { pinLength, onPinInput, attemptsRemaining, onCancel, isPrompting } = props +export function AuthorizationInterface (props: UnlockWalletProps): JSX.Element { + const { pinLength, onPinInput, attemptsRemaining, onCancel, isPrompting, spinnerMessage } = props return ( @@ -33,6 +36,7 @@ export function UnlockWalletInterface (props: UnlockWalletProps): JSX.Element { {translate('screens/UnlockWallet', 'For transaction signing purpose')} {/* TODO: switch authorization method here when biometric supported */} + { (attemptsRemaining !== undefined) ? ( @@ -50,9 +54,21 @@ function PassphraseInput ({ isPrompting, pinLength, onPinInput }: { pinLength: 4 | 6 onPinInput: (pin: string) => void }): JSX.Element | null { - if (!isPrompting) return // minimize visible flicker + if (!isPrompting) { + return null + } return ( ) } + +function Loading ({ message }: { message?: string }): JSX.Element | null { + if (message === undefined) return null + return ( + + + {message} + + ) +} From 627bf8bb0ca8cf7fea735e031b3484a777f7acdd Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 29 Jul 2021 11:59:55 +0800 Subject: [PATCH 25/48] added loading after collect pin, before signing complete --- .../OceanInterface/OceanInterface.tsx | 2 +- app/components/PinInput.tsx | 12 ++++++- app/screens/EncryptedWallet.tsx | 36 +++++++++++++------ 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/app/components/OceanInterface/OceanInterface.tsx b/app/components/OceanInterface/OceanInterface.tsx index 73e167778a..c226efde7a 100644 --- a/app/components/OceanInterface/OceanInterface.tsx +++ b/app/components/OceanInterface/OceanInterface.tsx @@ -99,7 +99,7 @@ export function OceanInterface (): JSX.Element | null { : ( ) diff --git a/app/components/PinInput.tsx b/app/components/PinInput.tsx index f3e765cb80..07b47f654a 100644 --- a/app/components/PinInput.tsx +++ b/app/components/PinInput.tsx @@ -8,9 +8,18 @@ import { Text } from './Text' interface PinInputOptions { length: 4 | 6 // should be easy to support 4-8 numeric, fix it to 4 or 6 first onChange: (text: string) => void + disabled?: boolean } -export function PinInput ({ length, onChange }: PinInputOptions): JSX.Element { +/** + * JSX component to collect 4 or 6 digit pin + * + * @param {PinInputOptions} props + * @param {4|6} props.length pin length + * @param {(text: string) => void} props.onChange on triggered when user input reach `length`, clear input after fired + * @param {boolean} disabled + */ +export function PinInput ({ length, onChange, disabled }: PinInputOptions): JSX.Element { const [text, setText] = useState('') const _textInput = useRef(null) @@ -61,6 +70,7 @@ export function PinInput ({ length, onChange }: PinInputOptions): JSX.Element { focus()}> {digitBoxes()} { _textInput.current = ref }} style={tailwind('opacity-0 h-0')} keyboardType='numeric' diff --git a/app/screens/EncryptedWallet.tsx b/app/screens/EncryptedWallet.tsx index 9592298fa3..84bf80ab9c 100644 --- a/app/screens/EncryptedWallet.tsx +++ b/app/screens/EncryptedWallet.tsx @@ -9,7 +9,8 @@ import { useWalletManagementContext, MAX_PASSCODE_ATTEMPT, PASSCODE_LENGTH } fro import { RootState } from '../store' import { ocean } from '../store/ocean' import { DfTxSigner, first, transactionQueue } from '../store/transaction' -import { UnlockWalletInterface } from './AppNavigator/screens/UnlockWallet' +import { translate } from '../translations' +import { AuthorizationInterface } from './AppNavigator/screens/AuthorizationInterface' /** * Side by side with AppNavigator at root level @@ -24,6 +25,7 @@ export function EncryptedWallet (): JSX.Element { // state const [attemptsRemaining, setAttemptsRemaining] = useState(MAX_PASSCODE_ATTEMPT) + const [spinnerMessage, setSpinnerMessage] = useState() // setting this to default true (require pin callback setup), cause wallet require unlock upon this component reconstruct const [awaitingPromise, setAwaitingPromise] = useState(false) // waiting whole passphrase promise to be resolve const [awaitingPin, setAwaitingPin] = useState(false) // waiting pin input @@ -43,6 +45,11 @@ export function EncryptedWallet (): JSX.Element { await walletManagement.resetErrorCount() }, [attemptsRemaining]) + useEffect(() => { + if (!awaitingPromise || awaitingPin) setSpinnerMessage(undefined) + else setSpinnerMessage(translate('components/EncryptedWallet', 'Unlocking wallet...')) + }, [awaitingPromise, awaitingPin]) + // consume pending to sign TransactionQueue from store useEffect(() => { // last available job will remained in this UI state until get dismissed @@ -102,25 +109,32 @@ export function EncryptedWallet (): JSX.Element { return ( - = MAX_PASSCODE_ATTEMPT ? undefined : attemptsRemaining} onPinInput={pin => { + setAwaitingPin(false) if (promptResolve.current !== undefined) { - promptResolve.current(pin) - promptResolve.current = undefined - promptReject.current = undefined + const resolve = promptResolve.current + setTimeout(() => { + resolve(pin) + promptResolve.current = undefined + promptReject.current = undefined + }, 50) } - setAwaitingPin(false) }} onCancel={() => { + setAwaitingPin(false) if (promptReject.current !== undefined) { - promptReject.current(new Error('USER_CANCELED')) - promptResolve.current = undefined - promptReject.current = undefined + const reject = promptReject.current + setTimeout(() => { + reject(new Error('USER_CANCELED')) + promptResolve.current = undefined + promptReject.current = undefined + }, 50) } - setAwaitingPin(false) }} /> From b6be12b8d6cac6af8034fb532501b8df42d9c809 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 29 Jul 2021 12:56:19 +0800 Subject: [PATCH 26/48] fix test snapshot --- .../OceanInterface.test.tsx.snap | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/app/components/OceanInterface/__snapshots__/OceanInterface.test.tsx.snap b/app/components/OceanInterface/__snapshots__/OceanInterface.test.tsx.snap index 0730314dfa..a77729b899 100644 --- a/app/components/OceanInterface/__snapshots__/OceanInterface.test.tsx.snap +++ b/app/components/OceanInterface/__snapshots__/OceanInterface.test.tsx.snap @@ -67,8 +67,58 @@ exports[`oceanInterface should match snapshot 1`] = ` ] } > - Signing... + Broadcasting... + + + da5660376145f7a8c43d5c0628e9d03a3671027e3a82f878fb23dbe4c6ea2cf2 + + + `; From 98456b0f3a1d436726f1ff1109319d52207613b2 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 29 Jul 2021 12:56:48 +0800 Subject: [PATCH 27/48] try manual select module (by platform.os) for bundling --- app/api/scrypt.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/api/scrypt.ts b/app/api/scrypt.ts index 55870c5612..8985585463 100644 --- a/app/api/scrypt.ts +++ b/app/api/scrypt.ts @@ -1,3 +1,7 @@ import { SimpleScryptsy, Scrypt } from '@defichain/jellyfish-wallet-encrypted' +import { Platform } from 'react-native' +import { scrypt as nativeScrypt } from './scrypt.native' -export const scrypt = new Scrypt(new SimpleScryptsy()) +export const scrypt = Platform.OS === 'web' + ? new Scrypt(new SimpleScryptsy()) + : nativeScrypt From 2165182a1a147a14a95fd584748e45ad7d8bb235 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 29 Jul 2021 13:36:29 +0800 Subject: [PATCH 28/48] require at runtime --- app/api/scrypt.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/api/scrypt.ts b/app/api/scrypt.ts index 8985585463..24d7c1b148 100644 --- a/app/api/scrypt.ts +++ b/app/api/scrypt.ts @@ -1,7 +1,8 @@ -import { SimpleScryptsy, Scrypt } from '@defichain/jellyfish-wallet-encrypted' +import { Scrypt } from '@defichain/jellyfish-wallet-encrypted' import { Platform } from 'react-native' import { scrypt as nativeScrypt } from './scrypt.native' export const scrypt = Platform.OS === 'web' - ? new Scrypt(new SimpleScryptsy()) + // eslint-disable-next-line + ? new Scrypt(new (require('@defichain/jellyfish-wallet-encrypted')).SimpleScryptsy()) : nativeScrypt From 391d04bcccf51b9bd3aac6dcc8e547f93d960aa0 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 29 Jul 2021 13:49:57 +0800 Subject: [PATCH 29/48] temp disable web scryptsy --- app/api/scrypt.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/api/scrypt.ts b/app/api/scrypt.ts index 24d7c1b148..93a8bd3967 100644 --- a/app/api/scrypt.ts +++ b/app/api/scrypt.ts @@ -1,8 +1,4 @@ -import { Scrypt } from '@defichain/jellyfish-wallet-encrypted' -import { Platform } from 'react-native' +// import { Scrypt } from '@defichain/jellyfish-wallet-encrypted' import { scrypt as nativeScrypt } from './scrypt.native' -export const scrypt = Platform.OS === 'web' - // eslint-disable-next-line - ? new Scrypt(new (require('@defichain/jellyfish-wallet-encrypted')).SimpleScryptsy()) - : nativeScrypt +export const scrypt = nativeScrypt // new Scrypt(new SimpleScryptsy()) From ec5da2e9404556df20e5cf51f0ab712b6f2daff9 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 29 Jul 2021 14:40:27 +0800 Subject: [PATCH 30/48] implement scryptsy for web locally, dep removed from jellyfish --- app/api/scrypt.ts | 45 ++++++++++++++++++++++++++++++++++++++++++--- package-lock.json | 20 ++++++++++++++++++++ package.json | 2 ++ 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/app/api/scrypt.ts b/app/api/scrypt.ts index 93a8bd3967..dfc8cdf307 100644 --- a/app/api/scrypt.ts +++ b/app/api/scrypt.ts @@ -1,4 +1,43 @@ -// import { Scrypt } from '@defichain/jellyfish-wallet-encrypted' -import { scrypt as nativeScrypt } from './scrypt.native' +import { Scrypt, ScryptProvider } from '@defichain/jellyfish-wallet-encrypted' +import scryptsy from 'scryptsy' -export const scrypt = nativeScrypt // new Scrypt(new SimpleScryptsy()) +export interface ScryptParams { + N: number + r: number + p: number +} + +const DEFAULT_SCRYPT_PARAMS: ScryptParams = { + N: 16384, + r: 8, + p: 8 +} + +// web implementation, required crypto module under the hood +export class SimpleScryptsy implements ScryptProvider { + constructor (private readonly params: ScryptParams = DEFAULT_SCRYPT_PARAMS) {} + + /** + * Derive a specific length buffer via Scrypt implementation + * Recommended (by bip38) to serve as an private key encryption key + * + * @param {string} passphrase utf8 string + * @param {Buffer} salt + * @param {number} keyLength desired output buffer length + * @returns {Buffer} + */ + async passphraseToKey (passphrase: string, salt: Buffer, keyLength: number): Promise { + const secret = Buffer.from(passphrase.normalize('NFC'), 'utf8') + + return scryptsy( + secret, + salt, + this.params.N, + this.params.r, + this.params.p, + keyLength + ) + } +} + +export const scrypt = new Scrypt(new SimpleScryptsy()) diff --git a/package-lock.json b/package-lock.json index c939c372fc..fb3b13a738 100644 --- a/package-lock.json +++ b/package-lock.json @@ -63,6 +63,7 @@ "react-native-web": "~0.13.12", "react-number-format": "^4.6.4", "react-redux": "^7.2.4", + "scryptsy": "^2.1.0", "serve": "^12.0.0", "smart-buffer": "^4.1.0", "stream-browserify": "^3.0.0", @@ -84,6 +85,7 @@ "@types/react-native-loading-spinner-overlay": "^0.5.3", "@types/react-redux": "^7.1.18", "@types/react-test-renderer": "^17.0.1", + "@types/scryptsy": "^2.0.0", "babel-plugin-istanbul": "^6.0.0", "cypress": "^8.0.0", "find-in-files": "^0.5.0", @@ -8054,6 +8056,15 @@ "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz", "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==" }, + "node_modules/@types/scryptsy": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/scryptsy/-/scryptsy-2.0.0.tgz", + "integrity": "sha512-iDmneBKWSsmsR3SlGisOVnpCz7sB5Mqhv4I/pLg1DYq2zttWT65r+1gOVZtoxXiTsSf/JO8NhgyCKhx7cvGbaA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/sinonjs__fake-timers": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.3.tgz", @@ -47015,6 +47026,15 @@ "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz", "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==" }, + "@types/scryptsy": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/scryptsy/-/scryptsy-2.0.0.tgz", + "integrity": "sha512-iDmneBKWSsmsR3SlGisOVnpCz7sB5Mqhv4I/pLg1DYq2zttWT65r+1gOVZtoxXiTsSf/JO8NhgyCKhx7cvGbaA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/sinonjs__fake-timers": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.3.tgz", diff --git a/package.json b/package.json index dbeb352514..02781b21b2 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "react-native-web": "~0.13.12", "react-number-format": "^4.6.4", "react-redux": "^7.2.4", + "scryptsy": "^2.1.0", "serve": "^12.0.0", "smart-buffer": "^4.1.0", "stream-browserify": "^3.0.0", @@ -94,6 +95,7 @@ "@types/react-native-loading-spinner-overlay": "^0.5.3", "@types/react-redux": "^7.1.18", "@types/react-test-renderer": "^17.0.1", + "@types/scryptsy": "^2.0.0", "babel-plugin-istanbul": "^6.0.0", "cypress": "^8.0.0", "find-in-files": "^0.5.0", From 227f59c613ff372618de7015254d24d09bad6dcc Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 29 Jul 2021 15:26:29 +0800 Subject: [PATCH 31/48] move pin creation screens under wallet/ --- .../screens/PinConfirmation.tsx | 55 ------------------ .../screens/PinCreationScreen.tsx | 57 ------------------- 2 files changed, 112 deletions(-) delete mode 100644 app/screens/WalletNavigator/screens/PinConfirmation.tsx delete mode 100644 app/screens/WalletNavigator/screens/PinCreationScreen.tsx diff --git a/app/screens/WalletNavigator/screens/PinConfirmation.tsx b/app/screens/WalletNavigator/screens/PinConfirmation.tsx deleted file mode 100644 index 5a1ba36aa3..0000000000 --- a/app/screens/WalletNavigator/screens/PinConfirmation.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { StackScreenProps } from '@react-navigation/stack' -import React, { useState } from 'react' -import { ScrollView } from 'react-native' -import tailwind from 'tailwind-rn' -import { MnemonicEncrypted } from '../../../api/wallet/provider/mnemonic_encrypted' -import { Text } from '../../../components' -import { PinInput } from '../../../components/PinInput' -import { useNetworkContext } from '../../../contexts/NetworkContext' -import { useWalletManagementContext } from '../../../contexts/WalletManagementContext' -import { translate } from '../../../translations' -import { WalletParamList } from '../WalletNavigator' - -type Props = StackScreenProps - -export function PinConfirmation ({ route }: Props): JSX.Element { - const { network } = useNetworkContext() - const { pin, words } = route.params - const { setWallet } = useWalletManagementContext() - const [invalid, setInvalid] = useState(false) - - if (![4, 6].includes(pin.length)) throw new Error('Unexpected pin length') - - // inherit from MnemonicVerify screen - // TODO(@ivan-zynesis): encrypt seed - function verifyPin (input: string): void { - if (input.length !== pin.length) return - if (input !== pin) { - setInvalid(true) - return - } - MnemonicEncrypted.toData(words, network, pin) - .then(async encrypted => { - await setWallet(encrypted) - }) - .catch(e => console.log(e)) - } - - return ( - - {translate('screens/PinConfirmation', 'Verify your passcode')} - {translate('screens/PinConfirmation', 'Enter your passcode again to verify')} - - { - (invalid) ? ( - - {translate('screens/PinConfirmation', 'Wrong passcode entered')} - - ) : null - } - - ) -} diff --git a/app/screens/WalletNavigator/screens/PinCreationScreen.tsx b/app/screens/WalletNavigator/screens/PinCreationScreen.tsx deleted file mode 100644 index ac03026bf2..0000000000 --- a/app/screens/WalletNavigator/screens/PinCreationScreen.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { MaterialIcons } from '@expo/vector-icons' -import { NavigationProp, useNavigation } from '@react-navigation/native' -import { StackScreenProps } from '@react-navigation/stack' -import React, { useState } from 'react' -import { ScrollView } from 'react-native' -import tailwind from 'tailwind-rn' -import { Text, View } from '../../../components' -import { CreateWalletStepIndicator } from '../../../components/CreateWalletStepIndicator' -import { PinInput } from '../../../components/PinInput' -import { Button } from '../../../components/Button' -import { translate } from '../../../translations' -import { WalletParamList } from '../WalletNavigator' - -type Props = StackScreenProps - -export function PinCreation ({ route }: Props): JSX.Element { - const navigation = useNavigation>() - const { pinLength, words } = route.params - const [newPin, setNewPin] = useState('') - - return ( - - - - {translate('screens/PinCreation', 'Secure your wallet')} - - - {translate('screens/PinCreation', 'Well done! You answered correctly. Now let\'s make your wallet safe by creating a passcode. Don\'t share your passcode to anyone.')} - - - - {translate('screens/PinCreation', 'Create a passcode for your wallet')} - - setNewPin(val)} - /> - - - ) -} From 5b71533e808d0be750b2a38e4e07bff98366a21a Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 29 Jul 2021 15:26:51 +0800 Subject: [PATCH 32/48] fix pin input not auto focused (race condition) --- app/components/PinInput.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/components/PinInput.tsx b/app/components/PinInput.tsx index 07b47f654a..3ebad12693 100644 --- a/app/components/PinInput.tsx +++ b/app/components/PinInput.tsx @@ -45,6 +45,8 @@ export function PinInput ({ length, onChange, disabled }: PinInputOptions): JSX. } }, [text, clear]) + useEffect(() => focus(), []) + const digitBoxes = (): JSX.Element => { const arr = [] for (let i = 0; i < length; i++) { From f4a470b23155bf1de7c32e589a22b33998fbecc0 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 29 Jul 2021 15:27:26 +0800 Subject: [PATCH 33/48] bump dep jellyfish-wallet-encrypted 0.31 --- package-lock.json | 218 +++++++++++++++++++++++----------------------- package.json | 16 ++-- 2 files changed, 116 insertions(+), 118 deletions(-) diff --git a/package-lock.json b/package-lock.json index fb3b13a738..0b794ddf47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,14 +7,14 @@ "name": "@defichain/wallet", "license": "MIT", "dependencies": { - "@defichain/jellyfish-address": ">=0.30.0", - "@defichain/jellyfish-api-core": ">=0.30.0", - "@defichain/jellyfish-network": ">=0.30.0", - "@defichain/jellyfish-transaction": ">=0.30.0", - "@defichain/jellyfish-transaction-builder": ">=0.30.0", - "@defichain/jellyfish-wallet": ">=0.30.0", - "@defichain/jellyfish-wallet-encrypted": ">=0.30.0", - "@defichain/jellyfish-wallet-mnemonic": ">=0.30.0", + "@defichain/jellyfish-address": ">=0.31.0", + "@defichain/jellyfish-api-core": ">=0.31.0", + "@defichain/jellyfish-network": ">=0.31.0", + "@defichain/jellyfish-transaction": ">=0.31.0", + "@defichain/jellyfish-transaction-builder": ">=0.31.0", + "@defichain/jellyfish-wallet": ">=0.31.0", + "@defichain/jellyfish-wallet-encrypted": ">=0.31.0", + "@defichain/jellyfish-wallet-mnemonic": ">=0.31.0", "@defichain/playground-api-client": ">=0.6.0", "@defichain/whale-api-client": ">=0.5.11", "@defichain/whale-api-wallet": ">=0.5.11", @@ -2022,28 +2022,28 @@ } }, "node_modules/@defichain/jellyfish-address": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-address/-/jellyfish-address-0.30.0.tgz", - "integrity": "sha512-KSHdcEfEEt5GgnQt6TZZ4cfGb0siOV5HQcgJ3diJxYSXqLgsBgaFVpjcvgxixYq6R89dK/fIWYpEyCWgPGMqaQ==", + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-address/-/jellyfish-address-0.31.0.tgz", + "integrity": "sha512-7P2hnG1Wmv4wRi026hDhNt6zqFhMtgj064h9wqUFRjQNH3z3kkakG2Me+Oa8HqCZfhFHvs99Z6wtxkxj59p0NA==", "dependencies": { - "@defichain/jellyfish-crypto": "^0.30.0", - "@defichain/jellyfish-network": "^0.30.0", - "@defichain/jellyfish-transaction": "^0.30.0", + "@defichain/jellyfish-crypto": "^0.31.0", + "@defichain/jellyfish-network": "^0.31.0", + "@defichain/jellyfish-transaction": "^0.31.0", "bs58": "^4.0.1" } }, "node_modules/@defichain/jellyfish-api-core": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-api-core/-/jellyfish-api-core-0.30.0.tgz", - "integrity": "sha512-I94+4u7UUm7B+MSHc1mH+gdMN460HCD0yCIe8RGPbAjxKk5iigC4mqskPpfjTt8dfcZWQ+0fpOIXaLjdupzySw==", + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-api-core/-/jellyfish-api-core-0.31.0.tgz", + "integrity": "sha512-nlF5XnZNFPInJeFpUGHmhudgRTVtMwhtP7TsEvXDZwOy8heKG7er0FGeimeBVX+ifFzf00+nsMMqtJMip7BT5w==", "dependencies": { - "@defichain/jellyfish-json": "^0.30.0" + "@defichain/jellyfish-json": "^0.31.0" } }, "node_modules/@defichain/jellyfish-crypto": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-crypto/-/jellyfish-crypto-0.30.0.tgz", - "integrity": "sha512-ttYPCa0VU7hOTNHOVJJrDXWmzvOd5JvKwQg5riuyCr6hhIQZUpBFr2szr9LlKnHGoe6n2g5fmRbk3PlswtXh0A==", + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-crypto/-/jellyfish-crypto-0.31.0.tgz", + "integrity": "sha512-Mg5nAx9rcYRPRoQ6dScqrg6E1rHLoton8JNi8MzBl1dS6C0/o/WRtX473hFlat3/iPa+aJNrkAPr0y1ZCeTKvg==", "dependencies": { "bech32": "^2.0.0", "bip66": "^1.1.5", @@ -2056,9 +2056,9 @@ } }, "node_modules/@defichain/jellyfish-json": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-json/-/jellyfish-json-0.30.0.tgz", - "integrity": "sha512-TvTclMasqisgCW6Clin/BUFROkeEJebRyzTU7vEGj06ivlRWWXXdzygtJrJVwy0x6naNj7cXelL8TGqnc7qIkw==", + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-json/-/jellyfish-json-0.31.0.tgz", + "integrity": "sha512-RuyvZwyrN7WL0HxI8wxJ7uqpq5DUKRA94cMDWyEteAJIqHaq4y+tlx/qu6Px61dF/pTRsVJGRewnRMDYX/Pu4w==", "dependencies": { "@types/lossless-json": "^1.0.1", "lossless-json": "^1.0.5" @@ -2068,16 +2068,16 @@ } }, "node_modules/@defichain/jellyfish-network": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-network/-/jellyfish-network-0.30.0.tgz", - "integrity": "sha512-5zDUFaNU8cLxVKE2kRJItFND32XBdYCVeqd+FhRc23y1nFvImXxGntmiAyamxZgc6LyBJ3STaG91/iNWh0RvRg==" + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-network/-/jellyfish-network-0.31.0.tgz", + "integrity": "sha512-0bPKjZXQqNEONPHSm76a2ZTdwfsJm+HQsiwPqR8cqnX2Yt+Xy2mZe1oHGpQCWlWOpECrkSzlNSa+Z6/ZLdYOXg==" }, "node_modules/@defichain/jellyfish-transaction": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-transaction/-/jellyfish-transaction-0.30.0.tgz", - "integrity": "sha512-G82937URU1TqjXwKyW5mp9dfc4Km6GFbSBaj1QNvrBX9n5Oi24T6QZhjBEoqyMHMQvUHbZgLBNQlCkdXz0LPJw==", + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-transaction/-/jellyfish-transaction-0.31.0.tgz", + "integrity": "sha512-V/4rkSA4uAiMO6IDEpygtA90ognvCdMqwsVFv2PbA+jIRHUvFL8h+U1EPibtu+jg6H6uBdGqzp2V6XKuq3cu+w==", "dependencies": { - "@defichain/jellyfish-crypto": "^0.30.0", + "@defichain/jellyfish-crypto": "^0.31.0", "smart-buffer": "^4.1.0" }, "peerDependencies": { @@ -2085,25 +2085,25 @@ } }, "node_modules/@defichain/jellyfish-transaction-builder": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-transaction-builder/-/jellyfish-transaction-builder-0.30.0.tgz", - "integrity": "sha512-tGpWEmTOp44+JQeq+/W9MOQKpyobQ6vNXS9pxsCebC/C0lBpGu/d6UABmeFArGSDf4iqgkOH1Z6K6Oao8yyhMA==", + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-transaction-builder/-/jellyfish-transaction-builder-0.31.0.tgz", + "integrity": "sha512-d7aJqeiCkaSfvo1oVHQC60UM4+AXtHMWdqGn5j7Pi4HEdADG1h0eVjn55E0MERaS+Cu0pC8wiYMSAu1zRbwu6g==", "dependencies": { - "@defichain/jellyfish-crypto": "^0.30.0", - "@defichain/jellyfish-transaction": "^0.30.0", - "@defichain/jellyfish-transaction-signature": "^0.30.0" + "@defichain/jellyfish-crypto": "^0.31.0", + "@defichain/jellyfish-transaction": "^0.31.0", + "@defichain/jellyfish-transaction-signature": "^0.31.0" }, "peerDependencies": { "bignumber.js": "^9.0.1" } }, "node_modules/@defichain/jellyfish-transaction-signature": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-transaction-signature/-/jellyfish-transaction-signature-0.30.0.tgz", - "integrity": "sha512-AY68RVKjstwaZbPnAEYBN+ZoATwRlAMo6hr/Z6EBnoBlu8Kfw/7UNgsCFBhzHhc2Ch/xQtrECf7GEI/QurIGlw==", + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-transaction-signature/-/jellyfish-transaction-signature-0.31.0.tgz", + "integrity": "sha512-GR3IHP62QSvAyKq51vN0FkuGesIfDknVRlvRGdG0wiJ4SqC8EN6mYRngWhWFTlsbcZvFzb+TvZzknOk7Jl4u/w==", "dependencies": { - "@defichain/jellyfish-crypto": "^0.30.0", - "@defichain/jellyfish-transaction": "^0.30.0", + "@defichain/jellyfish-crypto": "^0.31.0", + "@defichain/jellyfish-transaction": "^0.31.0", "smart-buffer": "^4.1.0" }, "peerDependencies": { @@ -2111,35 +2111,34 @@ } }, "node_modules/@defichain/jellyfish-wallet": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet/-/jellyfish-wallet-0.30.0.tgz", - "integrity": "sha512-gAtM7uDXqJx+ZAIR4lerX5CaAcWKPqmj0aTWnewVA7EaCa7WkpP/Z4D7XqzlZA2juQOCipbXndvnxEjV66sD2A==", + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet/-/jellyfish-wallet-0.31.0.tgz", + "integrity": "sha512-5i7OjAB8rtG2vExgmMUQNFaM7n3maZUDF+uHJdwsYae4gz1qOLLaqsbY0GXzl/qduq6lbtK+IPVqd7GXNCd2mQ==", "dependencies": { - "@defichain/jellyfish-address": "^0.30.0", - "@defichain/jellyfish-crypto": "^0.30.0", - "@defichain/jellyfish-network": "^0.30.0", - "@defichain/jellyfish-transaction": "^0.30.0" + "@defichain/jellyfish-address": "^0.31.0", + "@defichain/jellyfish-crypto": "^0.31.0", + "@defichain/jellyfish-network": "^0.31.0", + "@defichain/jellyfish-transaction": "^0.31.0" }, "peerDependencies": { "bignumber.js": "^9.0.1" } }, "node_modules/@defichain/jellyfish-wallet-encrypted": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet-encrypted/-/jellyfish-wallet-encrypted-0.30.0.tgz", - "integrity": "sha512-8LFEeDUAD9wueoFNcS/tCipjJBAqb9t7x6uNaehLA6SynBCHHM3rNsqoNR6arClCQ9VWvwSFzQkFF2tUcAcAtg==", + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet-encrypted/-/jellyfish-wallet-encrypted-0.31.0.tgz", + "integrity": "sha512-K6REp0KsZw7yMG+Tige2fH568DbsiGGH4VEdFc1+5KYLsmki1gXSYRzym7r++owGQAOkd0M7u4uGa+NG2iYgfw==", "dependencies": { - "@defichain/jellyfish-wallet-mnemonic": "^0.30.0", - "scryptsy": "^2.1.0" + "@defichain/jellyfish-wallet-mnemonic": "^0.31.0" } }, "node_modules/@defichain/jellyfish-wallet-mnemonic": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet-mnemonic/-/jellyfish-wallet-mnemonic-0.30.0.tgz", - "integrity": "sha512-likkL1sQqF5Yl5FPab7qEzEqmRn1TAVq4uOqF7yjknX7+l0wt3nzmREFq5x856FoJ8Am9t2PVZMFhJYJoOLPtQ==", + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet-mnemonic/-/jellyfish-wallet-mnemonic-0.31.0.tgz", + "integrity": "sha512-X+KAur2cnyv93gOT1YVRthlVA3xLUgPnmx/tnoQ2EyrnT2JgRuXW2MWfwxQjKIalNpkPGxb2e3ojAgloOj/hww==", "dependencies": { - "@defichain/jellyfish-transaction": "^0.30.0", - "@defichain/jellyfish-wallet": "^0.30.0", + "@defichain/jellyfish-transaction": "^0.31.0", + "@defichain/jellyfish-wallet": "^0.31.0", "bip32": "^2.0.6", "bip39": "^3.0.4" } @@ -42113,28 +42112,28 @@ } }, "@defichain/jellyfish-address": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-address/-/jellyfish-address-0.30.0.tgz", - "integrity": "sha512-KSHdcEfEEt5GgnQt6TZZ4cfGb0siOV5HQcgJ3diJxYSXqLgsBgaFVpjcvgxixYq6R89dK/fIWYpEyCWgPGMqaQ==", + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-address/-/jellyfish-address-0.31.0.tgz", + "integrity": "sha512-7P2hnG1Wmv4wRi026hDhNt6zqFhMtgj064h9wqUFRjQNH3z3kkakG2Me+Oa8HqCZfhFHvs99Z6wtxkxj59p0NA==", "requires": { - "@defichain/jellyfish-crypto": "^0.30.0", - "@defichain/jellyfish-network": "^0.30.0", - "@defichain/jellyfish-transaction": "^0.30.0", + "@defichain/jellyfish-crypto": "^0.31.0", + "@defichain/jellyfish-network": "^0.31.0", + "@defichain/jellyfish-transaction": "^0.31.0", "bs58": "^4.0.1" } }, "@defichain/jellyfish-api-core": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-api-core/-/jellyfish-api-core-0.30.0.tgz", - "integrity": "sha512-I94+4u7UUm7B+MSHc1mH+gdMN460HCD0yCIe8RGPbAjxKk5iigC4mqskPpfjTt8dfcZWQ+0fpOIXaLjdupzySw==", + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-api-core/-/jellyfish-api-core-0.31.0.tgz", + "integrity": "sha512-nlF5XnZNFPInJeFpUGHmhudgRTVtMwhtP7TsEvXDZwOy8heKG7er0FGeimeBVX+ifFzf00+nsMMqtJMip7BT5w==", "requires": { - "@defichain/jellyfish-json": "^0.30.0" + "@defichain/jellyfish-json": "^0.31.0" } }, "@defichain/jellyfish-crypto": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-crypto/-/jellyfish-crypto-0.30.0.tgz", - "integrity": "sha512-ttYPCa0VU7hOTNHOVJJrDXWmzvOd5JvKwQg5riuyCr6hhIQZUpBFr2szr9LlKnHGoe6n2g5fmRbk3PlswtXh0A==", + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-crypto/-/jellyfish-crypto-0.31.0.tgz", + "integrity": "sha512-Mg5nAx9rcYRPRoQ6dScqrg6E1rHLoton8JNi8MzBl1dS6C0/o/WRtX473hFlat3/iPa+aJNrkAPr0y1ZCeTKvg==", "requires": { "bech32": "^2.0.0", "bip66": "^1.1.5", @@ -42147,75 +42146,74 @@ } }, "@defichain/jellyfish-json": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-json/-/jellyfish-json-0.30.0.tgz", - "integrity": "sha512-TvTclMasqisgCW6Clin/BUFROkeEJebRyzTU7vEGj06ivlRWWXXdzygtJrJVwy0x6naNj7cXelL8TGqnc7qIkw==", + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-json/-/jellyfish-json-0.31.0.tgz", + "integrity": "sha512-RuyvZwyrN7WL0HxI8wxJ7uqpq5DUKRA94cMDWyEteAJIqHaq4y+tlx/qu6Px61dF/pTRsVJGRewnRMDYX/Pu4w==", "requires": { "@types/lossless-json": "^1.0.1", "lossless-json": "^1.0.5" } }, "@defichain/jellyfish-network": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-network/-/jellyfish-network-0.30.0.tgz", - "integrity": "sha512-5zDUFaNU8cLxVKE2kRJItFND32XBdYCVeqd+FhRc23y1nFvImXxGntmiAyamxZgc6LyBJ3STaG91/iNWh0RvRg==" + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-network/-/jellyfish-network-0.31.0.tgz", + "integrity": "sha512-0bPKjZXQqNEONPHSm76a2ZTdwfsJm+HQsiwPqR8cqnX2Yt+Xy2mZe1oHGpQCWlWOpECrkSzlNSa+Z6/ZLdYOXg==" }, "@defichain/jellyfish-transaction": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-transaction/-/jellyfish-transaction-0.30.0.tgz", - "integrity": "sha512-G82937URU1TqjXwKyW5mp9dfc4Km6GFbSBaj1QNvrBX9n5Oi24T6QZhjBEoqyMHMQvUHbZgLBNQlCkdXz0LPJw==", + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-transaction/-/jellyfish-transaction-0.31.0.tgz", + "integrity": "sha512-V/4rkSA4uAiMO6IDEpygtA90ognvCdMqwsVFv2PbA+jIRHUvFL8h+U1EPibtu+jg6H6uBdGqzp2V6XKuq3cu+w==", "requires": { - "@defichain/jellyfish-crypto": "^0.30.0", + "@defichain/jellyfish-crypto": "^0.31.0", "smart-buffer": "^4.1.0" } }, "@defichain/jellyfish-transaction-builder": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-transaction-builder/-/jellyfish-transaction-builder-0.30.0.tgz", - "integrity": "sha512-tGpWEmTOp44+JQeq+/W9MOQKpyobQ6vNXS9pxsCebC/C0lBpGu/d6UABmeFArGSDf4iqgkOH1Z6K6Oao8yyhMA==", + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-transaction-builder/-/jellyfish-transaction-builder-0.31.0.tgz", + "integrity": "sha512-d7aJqeiCkaSfvo1oVHQC60UM4+AXtHMWdqGn5j7Pi4HEdADG1h0eVjn55E0MERaS+Cu0pC8wiYMSAu1zRbwu6g==", "requires": { - "@defichain/jellyfish-crypto": "^0.30.0", - "@defichain/jellyfish-transaction": "^0.30.0", - "@defichain/jellyfish-transaction-signature": "^0.30.0" + "@defichain/jellyfish-crypto": "^0.31.0", + "@defichain/jellyfish-transaction": "^0.31.0", + "@defichain/jellyfish-transaction-signature": "^0.31.0" } }, "@defichain/jellyfish-transaction-signature": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-transaction-signature/-/jellyfish-transaction-signature-0.30.0.tgz", - "integrity": "sha512-AY68RVKjstwaZbPnAEYBN+ZoATwRlAMo6hr/Z6EBnoBlu8Kfw/7UNgsCFBhzHhc2Ch/xQtrECf7GEI/QurIGlw==", + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-transaction-signature/-/jellyfish-transaction-signature-0.31.0.tgz", + "integrity": "sha512-GR3IHP62QSvAyKq51vN0FkuGesIfDknVRlvRGdG0wiJ4SqC8EN6mYRngWhWFTlsbcZvFzb+TvZzknOk7Jl4u/w==", "requires": { - "@defichain/jellyfish-crypto": "^0.30.0", - "@defichain/jellyfish-transaction": "^0.30.0", + "@defichain/jellyfish-crypto": "^0.31.0", + "@defichain/jellyfish-transaction": "^0.31.0", "smart-buffer": "^4.1.0" } }, "@defichain/jellyfish-wallet": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet/-/jellyfish-wallet-0.30.0.tgz", - "integrity": "sha512-gAtM7uDXqJx+ZAIR4lerX5CaAcWKPqmj0aTWnewVA7EaCa7WkpP/Z4D7XqzlZA2juQOCipbXndvnxEjV66sD2A==", + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet/-/jellyfish-wallet-0.31.0.tgz", + "integrity": "sha512-5i7OjAB8rtG2vExgmMUQNFaM7n3maZUDF+uHJdwsYae4gz1qOLLaqsbY0GXzl/qduq6lbtK+IPVqd7GXNCd2mQ==", "requires": { - "@defichain/jellyfish-address": "^0.30.0", - "@defichain/jellyfish-crypto": "^0.30.0", - "@defichain/jellyfish-network": "^0.30.0", - "@defichain/jellyfish-transaction": "^0.30.0" + "@defichain/jellyfish-address": "^0.31.0", + "@defichain/jellyfish-crypto": "^0.31.0", + "@defichain/jellyfish-network": "^0.31.0", + "@defichain/jellyfish-transaction": "^0.31.0" } }, "@defichain/jellyfish-wallet-encrypted": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet-encrypted/-/jellyfish-wallet-encrypted-0.30.0.tgz", - "integrity": "sha512-8LFEeDUAD9wueoFNcS/tCipjJBAqb9t7x6uNaehLA6SynBCHHM3rNsqoNR6arClCQ9VWvwSFzQkFF2tUcAcAtg==", + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet-encrypted/-/jellyfish-wallet-encrypted-0.31.0.tgz", + "integrity": "sha512-K6REp0KsZw7yMG+Tige2fH568DbsiGGH4VEdFc1+5KYLsmki1gXSYRzym7r++owGQAOkd0M7u4uGa+NG2iYgfw==", "requires": { - "@defichain/jellyfish-wallet-mnemonic": "^0.30.0", - "scryptsy": "^2.1.0" + "@defichain/jellyfish-wallet-mnemonic": "^0.31.0" } }, "@defichain/jellyfish-wallet-mnemonic": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet-mnemonic/-/jellyfish-wallet-mnemonic-0.30.0.tgz", - "integrity": "sha512-likkL1sQqF5Yl5FPab7qEzEqmRn1TAVq4uOqF7yjknX7+l0wt3nzmREFq5x856FoJ8Am9t2PVZMFhJYJoOLPtQ==", + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet-mnemonic/-/jellyfish-wallet-mnemonic-0.31.0.tgz", + "integrity": "sha512-X+KAur2cnyv93gOT1YVRthlVA3xLUgPnmx/tnoQ2EyrnT2JgRuXW2MWfwxQjKIalNpkPGxb2e3ojAgloOj/hww==", "requires": { - "@defichain/jellyfish-transaction": "^0.30.0", - "@defichain/jellyfish-wallet": "^0.30.0", + "@defichain/jellyfish-transaction": "^0.31.0", + "@defichain/jellyfish-wallet": "^0.31.0", "bip32": "^2.0.6", "bip39": "^3.0.4" } diff --git a/package.json b/package.json index 02781b21b2..5a8b76ba40 100644 --- a/package.json +++ b/package.json @@ -17,14 +17,14 @@ "translation:missing": "ts-node app/translations/reporter/index.ts" }, "dependencies": { - "@defichain/jellyfish-address": ">=0.30.0", - "@defichain/jellyfish-api-core": ">=0.30.0", - "@defichain/jellyfish-network": ">=0.30.0", - "@defichain/jellyfish-transaction": ">=0.30.0", - "@defichain/jellyfish-transaction-builder": ">=0.30.0", - "@defichain/jellyfish-wallet": ">=0.30.0", - "@defichain/jellyfish-wallet-encrypted": ">=0.30.0", - "@defichain/jellyfish-wallet-mnemonic": ">=0.30.0", + "@defichain/jellyfish-address": ">=0.31.0", + "@defichain/jellyfish-api-core": ">=0.31.0", + "@defichain/jellyfish-network": ">=0.31.0", + "@defichain/jellyfish-transaction": ">=0.31.0", + "@defichain/jellyfish-transaction-builder": ">=0.31.0", + "@defichain/jellyfish-wallet": ">=0.31.0", + "@defichain/jellyfish-wallet-encrypted": ">=0.31.0", + "@defichain/jellyfish-wallet-mnemonic": ">=0.31.0", "@defichain/playground-api-client": ">=0.6.0", "@defichain/whale-api-client": ">=0.5.11", "@defichain/whale-api-wallet": ">=0.5.11", From da8e2961b3bb43bee4be848c10208f6b760a315e Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 29 Jul 2021 15:28:07 +0800 Subject: [PATCH 34/48] fix outdated redux action usage, move files --- .../screens/Dex/DexConfirmAddLiquidity.tsx | 3 +- .../screens/Dex/PoolSwap/PoolSwapScreen.tsx | 3 +- .../WalletNavigator/WalletNavigator.tsx | 4 +- .../screens/CreateWallet/PinConfirmation.tsx | 68 +++++++++++++++++++ .../CreateWallet/PinCreationScreen.tsx | 53 +++++++++++++++ 5 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 app/screens/WalletNavigator/screens/CreateWallet/PinConfirmation.tsx create mode 100644 app/screens/WalletNavigator/screens/CreateWallet/PinCreationScreen.tsx diff --git a/app/screens/AppNavigator/screens/Dex/DexConfirmAddLiquidity.tsx b/app/screens/AppNavigator/screens/Dex/DexConfirmAddLiquidity.tsx index c7470fd536..d12782b6d6 100644 --- a/app/screens/AppNavigator/screens/Dex/DexConfirmAddLiquidity.tsx +++ b/app/screens/AppNavigator/screens/Dex/DexConfirmAddLiquidity.tsx @@ -14,6 +14,7 @@ import { Text, View } from '../../../../components' import { Button } from '../../../../components/Button' import { RootState } from '../../../../store' import { hasTxQueued, ocean } from '../../../../store/ocean' +import { transactionQueue } from '../../../../store/transaction' import { tailwind } from '../../../../tailwind' import { translate } from '../../../../translations' import { DexParamList } from './DexNavigator' @@ -179,7 +180,7 @@ async function constructSignedAddLiqAndSend ( return new CTransactionSegWit(dfTx) } - dispatch(ocean.actions.queueTransaction({ + dispatch(transactionQueue.actions.push({ sign: signer, title: `${translate('screens/ConfirmLiquidity', 'Adding Liquidity')}` })) diff --git a/app/screens/AppNavigator/screens/Dex/PoolSwap/PoolSwapScreen.tsx b/app/screens/AppNavigator/screens/Dex/PoolSwap/PoolSwapScreen.tsx index 8efbf34462..15850785d3 100644 --- a/app/screens/AppNavigator/screens/Dex/PoolSwap/PoolSwapScreen.tsx +++ b/app/screens/AppNavigator/screens/Dex/PoolSwap/PoolSwapScreen.tsx @@ -18,6 +18,7 @@ import { useWallet } from '../../../../../contexts/WalletContext' import { useTokensAPI } from '../../../../../hooks/wallet/TokensAPI' import { RootState } from '../../../../../store' import { hasTxQueued, ocean } from '../../../../../store/ocean' +import { transactionQueue } from '../../../../../store/transaction' import { tailwind } from '../../../../../tailwind' import { translate } from '../../../../../translations' import LoadingScreen from '../../../../LoadingNavigator/LoadingScreen' @@ -328,7 +329,7 @@ async function constructSignedSwapAndSend ( return new CTransactionSegWit(dfTx) } - dispatch(ocean.actions.queueTransaction({ + dispatch(transactionQueue.actions.push({ sign: signer, title: `${translate('screens/PoolSwapScreen', 'Swapping Token')}` })) diff --git a/app/screens/WalletNavigator/WalletNavigator.tsx b/app/screens/WalletNavigator/WalletNavigator.tsx index 0bdf50b2a3..8d2c27a9ce 100644 --- a/app/screens/WalletNavigator/WalletNavigator.tsx +++ b/app/screens/WalletNavigator/WalletNavigator.tsx @@ -11,8 +11,8 @@ import { GuidelinesRecoveryWords } from './screens/CreateWallet/GuidelinesRecove import { VerifyMnemonicWallet } from './screens/CreateWallet/VerifyMnemonicWallet' import { Onboarding } from './screens/Onboarding' import { RestoreMnemonicWallet } from './screens/RestoreWallet/RestoreMnemonicWallet' -import { PinCreation } from './screens/PinCreationScreen' -import { PinConfirmation } from './screens/PinConfirmation' +import { PinCreation } from './screens/CreateWallet/PinCreationScreen' +import { PinConfirmation } from './screens/CreateWallet/PinConfirmation' export interface WalletParamList { WalletOnboardingScreen: undefined diff --git a/app/screens/WalletNavigator/screens/CreateWallet/PinConfirmation.tsx b/app/screens/WalletNavigator/screens/CreateWallet/PinConfirmation.tsx new file mode 100644 index 0000000000..ed2e988c1b --- /dev/null +++ b/app/screens/WalletNavigator/screens/CreateWallet/PinConfirmation.tsx @@ -0,0 +1,68 @@ +import { StackScreenProps } from '@react-navigation/stack' +import React, { useState } from 'react' +import { ActivityIndicator, ScrollView } from 'react-native' +import tailwind from 'tailwind-rn' +import { MnemonicEncrypted } from '../../../../api/wallet/provider/mnemonic_encrypted' +import { Text, View } from '../../../../components' +import { PinInput } from '../../../../components/PinInput' +import { useNetworkContext } from '../../../../contexts/NetworkContext' +import { useWalletManagementContext } from '../../../../contexts/WalletManagementContext' +import { translate } from '../../../../translations' +import { WalletParamList } from '../../WalletNavigator' + +type Props = StackScreenProps + +export function PinConfirmation ({ route }: Props): JSX.Element { + const { network } = useNetworkContext() + const { pin, words } = route.params + const { setWallet } = useWalletManagementContext() + + const [invalid, setInvalid] = useState(false) + const [spinnerMessage, setSpinnerMessage] = useState() + + if (![4, 6].includes(pin.length)) throw new Error('Unexpected pin length') + + function verifyPin (input: string): void { + if (input.length !== pin.length) return + if (input !== pin) { + setInvalid(true) + return + } + + setSpinnerMessage(translate('screens/PinConfirmation', 'Encrypting wallet...')) + const copy = { words, network, pin } + setTimeout(() => { + MnemonicEncrypted.toData(copy.words, copy.network, copy.pin) + .then(async encrypted => { + await setWallet(encrypted) + }) + .catch(e => console.log(e)) + }, 50) // allow UI render the spinner before async task + } + + return ( + + {translate('screens/PinConfirmation', 'Verify your passcode')} + {translate('screens/PinConfirmation', 'Enter your passcode again to verify')} + + { + (spinnerMessage !== undefined) ? ( + + + {spinnerMessage} + + ) : null + } + { + (invalid) ? ( + + {translate('screens/PinConfirmation', 'Wrong passcode entered')} + + ) : null + } + + ) +} diff --git a/app/screens/WalletNavigator/screens/CreateWallet/PinCreationScreen.tsx b/app/screens/WalletNavigator/screens/CreateWallet/PinCreationScreen.tsx new file mode 100644 index 0000000000..7271686f45 --- /dev/null +++ b/app/screens/WalletNavigator/screens/CreateWallet/PinCreationScreen.tsx @@ -0,0 +1,53 @@ +import { MaterialIcons } from '@expo/vector-icons' +import { NavigationProp, useNavigation } from '@react-navigation/native' +import { StackScreenProps } from '@react-navigation/stack' +import React, { useState } from 'react' +import { ScrollView } from 'react-native' +import tailwind from 'tailwind-rn' +import { Text, View } from '../../../../components' +import { CreateWalletStepIndicator } from '../../../../components/CreateWalletStepIndicator' +import { PinInput } from '../../../../components/PinInput' +import { Button } from '../../../../components/Button' +import { translate } from '../../../../translations' +import { WalletParamList } from '../../WalletNavigator' + +type Props = StackScreenProps + +export function PinCreation ({ route }: Props): JSX.Element { + const navigation = useNavigation>() + const { pinLength, words } = route.params + const [newPin, setNewPin] = useState('') + + return ( + + + + {translate('screens/PinCreation', 'Well done! You answered correctly. Now let\'s make your wallet safe by creating a passcode. Don\'t share your passcode to anyone.')} + + + + {translate('screens/PinCreation', 'Create a passcode for your wallet')} + + setNewPin(val)} + /> +