wallet-stack 1.0.0-alpha.132 → 1.0.0-alpha.133
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/locales/base/translation.json +5 -0
- package/package.json +1 -2
- package/src/analytics/Events.tsx +3 -10
- package/src/analytics/Properties.tsx +9 -25
- package/src/analytics/docs.ts +11 -8
- package/src/app/ErrorMessages.ts +0 -7
- package/src/identity/actions.ts +1 -97
- package/src/identity/contactMapping.test.ts +3 -28
- package/src/identity/contactMapping.ts +2 -88
- package/src/identity/reducer.ts +0 -77
- package/src/identity/saga.ts +2 -85
- package/src/identity/selectors.ts +0 -2
- package/src/images/Images.ts +2 -0
- package/src/images/assets/minipay.png +0 -0
- package/src/images/assets/minipay@1.5x.png +0 -0
- package/src/images/assets/minipay@2x.png +0 -0
- package/src/images/assets/minipay@3x.png +0 -0
- package/src/images/assets/minipay@4x.png +0 -0
- package/src/images/assets/valora.png +0 -0
- package/src/images/assets/valora@1.5x.png +0 -0
- package/src/images/assets/valora@2x.png +0 -0
- package/src/images/assets/valora@3x.png +0 -0
- package/src/images/assets/valora@4x.png +0 -0
- package/src/index.d.ts +0 -1
- package/src/navigator/Navigator.tsx +4 -14
- package/src/navigator/Screens.tsx +1 -2
- package/src/navigator/types.tsx +4 -6
- package/src/qrcode/utils.test.tsx +4 -96
- package/src/qrcode/utils.ts +5 -114
- package/src/redux/migrations.test.ts +13 -0
- package/src/redux/migrations.ts +4 -0
- package/src/redux/store.test.ts +1 -2
- package/src/redux/store.ts +1 -1
- package/src/send/SelectRecipientAddress.test.tsx +146 -0
- package/src/send/SelectRecipientAddress.tsx +166 -0
- package/src/send/SendSelectRecipient.test.tsx +16 -87
- package/src/send/SendSelectRecipient.tsx +12 -27
- package/src/send/actions.ts +0 -26
- package/src/send/saga.ts +1 -6
- package/src/components/AccountNumberCard.tsx +0 -23
- package/src/components/ErrorMessageInline.tsx +0 -78
- package/src/components/SingleDigitInput.tsx +0 -53
- package/src/icons/HamburgerCard.tsx +0 -55
- package/src/identity/saga.test.ts +0 -103
- package/src/identity/secureSend.ts +0 -171
- package/src/send/ValidateRecipientAccount.test.tsx +0 -182
- package/src/send/ValidateRecipientAccount.tsx +0 -392
- package/src/send/ValidateRecipientIntro.test.tsx +0 -61
- package/src/send/ValidateRecipientIntro.tsx +0 -136
- package/src/send/__snapshots__/ValidateRecipientAccount.test.tsx.snap +0 -777
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { WithTranslation } from 'react-i18next'
|
|
3
|
-
import { StyleSheet, Text, View } from 'react-native'
|
|
4
|
-
import { connect } from 'react-redux'
|
|
5
|
-
import { hideAlert } from 'src/alert/actions'
|
|
6
|
-
import { ErrorDisplayType } from 'src/alert/reducer'
|
|
7
|
-
import { ErrorMessages } from 'src/app/ErrorMessages'
|
|
8
|
-
import { withTranslation } from 'src/i18n'
|
|
9
|
-
import { RootState } from 'src/redux/reducers'
|
|
10
|
-
import colors from 'src/styles/colors'
|
|
11
|
-
import { typeScale } from 'src/styles/fonts'
|
|
12
|
-
|
|
13
|
-
const DISMISS_DEFAULT = 5
|
|
14
|
-
|
|
15
|
-
interface StateProps {
|
|
16
|
-
displayMethod: ErrorDisplayType | null
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
interface OwnProps {
|
|
20
|
-
error?: ErrorMessages | null
|
|
21
|
-
dismissAfter?: number
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
interface DispatchProps {
|
|
25
|
-
hideAlert: typeof hideAlert
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const mapStateToProps = (state: RootState): StateProps => {
|
|
29
|
-
const displayMethod = state.alert ? state.alert.displayMethod : null
|
|
30
|
-
return {
|
|
31
|
-
displayMethod,
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const mapDispatchToProps = {
|
|
36
|
-
hideAlert,
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
type Props = DispatchProps & OwnProps & WithTranslation & StateProps
|
|
40
|
-
|
|
41
|
-
function ErrorMessageInline(props: Props) {
|
|
42
|
-
const { error, displayMethod, dismissAfter, t } = props
|
|
43
|
-
|
|
44
|
-
// Want to initiate/cleanup a timer for each new error
|
|
45
|
-
React.useEffect(() => {
|
|
46
|
-
const timer = window.setTimeout(props.hideAlert, (dismissAfter || DISMISS_DEFAULT) * 1000)
|
|
47
|
-
return () => window.clearTimeout(timer)
|
|
48
|
-
}, [error])
|
|
49
|
-
|
|
50
|
-
// Keep the space empty when there isn't an inline error
|
|
51
|
-
// displayMethod lives in redux store and we want to be able to use this component
|
|
52
|
-
// without populating it so much check if other types are already displayed
|
|
53
|
-
// rather than if INLINE is
|
|
54
|
-
if (!error || displayMethod === ErrorDisplayType.BANNER) {
|
|
55
|
-
return <View style={dismissAfter !== null && styles.errorContainer} />
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return (
|
|
59
|
-
<View style={dismissAfter !== null && styles.errorContainer}>
|
|
60
|
-
<Text style={styles.errorMessage}>{t(error)} </Text>
|
|
61
|
-
</View>
|
|
62
|
-
)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const styles = StyleSheet.create({
|
|
66
|
-
errorContainer: {
|
|
67
|
-
height: 64,
|
|
68
|
-
},
|
|
69
|
-
errorMessage: {
|
|
70
|
-
...typeScale.bodySmall,
|
|
71
|
-
color: colors.errorPrimary,
|
|
72
|
-
},
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
export default connect<StateProps, DispatchProps, OwnProps, RootState>(
|
|
76
|
-
(state: RootState) => mapStateToProps,
|
|
77
|
-
mapDispatchToProps
|
|
78
|
-
)(withTranslation<Props>()(ErrorMessageInline))
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import * as React from 'react'
|
|
2
|
-
import { StyleSheet } from 'react-native'
|
|
3
|
-
import TextInput from 'src/components/TextInput'
|
|
4
|
-
import colors from 'src/styles/colors'
|
|
5
|
-
|
|
6
|
-
export interface SingleDigitInputProps {
|
|
7
|
-
inputValue: string
|
|
8
|
-
inputPlaceholder: string
|
|
9
|
-
onInputChange: (value: string) => void
|
|
10
|
-
testID?: string
|
|
11
|
-
forwardedRef: any
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
type Props = SingleDigitInputProps
|
|
15
|
-
|
|
16
|
-
// Multiline enabled as to handle unexpected cursor behavior
|
|
17
|
-
// https://github.com/facebook/react-native/issues/28794#issuecomment-877769852
|
|
18
|
-
|
|
19
|
-
export function SingleDigitInput({
|
|
20
|
-
inputValue,
|
|
21
|
-
inputPlaceholder,
|
|
22
|
-
onInputChange,
|
|
23
|
-
testID,
|
|
24
|
-
forwardedRef,
|
|
25
|
-
}: Props) {
|
|
26
|
-
return (
|
|
27
|
-
<TextInput
|
|
28
|
-
multiline={true}
|
|
29
|
-
textAlign={'center'}
|
|
30
|
-
value={inputValue}
|
|
31
|
-
placeholder={inputPlaceholder}
|
|
32
|
-
onChangeText={onInputChange}
|
|
33
|
-
maxLength={1}
|
|
34
|
-
showClearButton={false}
|
|
35
|
-
style={styles.codeInput}
|
|
36
|
-
testID={testID}
|
|
37
|
-
ref={forwardedRef}
|
|
38
|
-
/>
|
|
39
|
-
)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const styles = StyleSheet.create({
|
|
43
|
-
codeInput: {
|
|
44
|
-
borderColor: colors.borderPrimary,
|
|
45
|
-
borderRadius: 3,
|
|
46
|
-
borderWidth: 1,
|
|
47
|
-
flex: 0,
|
|
48
|
-
backgroundColor: colors.backgroundPrimary,
|
|
49
|
-
height: 50,
|
|
50
|
-
width: 50,
|
|
51
|
-
marginVertical: 5,
|
|
52
|
-
},
|
|
53
|
-
})
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import * as React from 'react'
|
|
2
|
-
import { StyleSheet, View } from 'react-native'
|
|
3
|
-
import { Line, Svg } from 'react-native-svg'
|
|
4
|
-
import { default as Colors, default as colors } from 'src/styles/colors'
|
|
5
|
-
import globalStyles from 'src/styles/styles'
|
|
6
|
-
|
|
7
|
-
function HamburgerCard() {
|
|
8
|
-
return (
|
|
9
|
-
<View style={styles.container}>
|
|
10
|
-
<Svg testID="Hamburger" width="32" height="32" viewBox="0 0 32 32" fill="none">
|
|
11
|
-
<Line
|
|
12
|
-
x1="7.25"
|
|
13
|
-
y1="9.75"
|
|
14
|
-
x2="24.75"
|
|
15
|
-
y2="9.75"
|
|
16
|
-
stroke={Colors.contentPrimary}
|
|
17
|
-
strokeWidth="2.5"
|
|
18
|
-
strokeLinecap="round"
|
|
19
|
-
/>
|
|
20
|
-
<Line
|
|
21
|
-
x1="7.25"
|
|
22
|
-
y1="15.75"
|
|
23
|
-
x2="24.75"
|
|
24
|
-
y2="15.75"
|
|
25
|
-
stroke={Colors.contentPrimary}
|
|
26
|
-
strokeWidth="2.5"
|
|
27
|
-
strokeLinecap="round"
|
|
28
|
-
/>
|
|
29
|
-
<Line
|
|
30
|
-
x1="7.25"
|
|
31
|
-
y1="21.75"
|
|
32
|
-
x2="24.75"
|
|
33
|
-
y2="21.75"
|
|
34
|
-
stroke={Colors.contentPrimary}
|
|
35
|
-
strokeWidth="2.5"
|
|
36
|
-
strokeLinecap="round"
|
|
37
|
-
/>
|
|
38
|
-
</Svg>
|
|
39
|
-
</View>
|
|
40
|
-
)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const styles = StyleSheet.create({
|
|
44
|
-
container: {
|
|
45
|
-
backgroundColor: colors.backgroundPrimary,
|
|
46
|
-
...globalStyles.softShadowLight,
|
|
47
|
-
alignItems: 'center',
|
|
48
|
-
justifyContent: 'center',
|
|
49
|
-
borderRadius: 4,
|
|
50
|
-
height: 32,
|
|
51
|
-
width: 32,
|
|
52
|
-
},
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
export default React.memo(HamburgerCard)
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { expectSaga } from 'redux-saga-test-plan'
|
|
2
|
-
import { select } from 'redux-saga/effects'
|
|
3
|
-
import { showErrorInline } from 'src/alert/actions'
|
|
4
|
-
import { ErrorMessages } from 'src/app/ErrorMessages'
|
|
5
|
-
import {
|
|
6
|
-
Actions,
|
|
7
|
-
ValidateRecipientAddressAction,
|
|
8
|
-
validateRecipientAddressSuccess,
|
|
9
|
-
} from 'src/identity/actions'
|
|
10
|
-
import { AddressValidationType } from 'src/identity/reducer'
|
|
11
|
-
import { watchValidateRecipientAddress } from 'src/identity/saga'
|
|
12
|
-
import { e164NumberToAddressSelector } from 'src/identity/selectors'
|
|
13
|
-
import { currentAccountSelector } from 'src/web3/selectors'
|
|
14
|
-
import {
|
|
15
|
-
mockAccount,
|
|
16
|
-
mockAccount2,
|
|
17
|
-
mockAccountInvite,
|
|
18
|
-
mockE164NumberInvite,
|
|
19
|
-
mockE164NumberToAddress,
|
|
20
|
-
mockInvitableRecipient2,
|
|
21
|
-
} from 'test/values'
|
|
22
|
-
|
|
23
|
-
describe(watchValidateRecipientAddress, () => {
|
|
24
|
-
beforeAll(() => {
|
|
25
|
-
jest.useRealTimers()
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
afterEach(() => {
|
|
29
|
-
jest.clearAllMocks()
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it('full validation fails if the inputted address does not belong to the recipient', async () => {
|
|
33
|
-
const validateAction: ValidateRecipientAddressAction = {
|
|
34
|
-
type: Actions.VALIDATE_RECIPIENT_ADDRESS,
|
|
35
|
-
userInputOfFullAddressOrLastFourDigits: mockAccount2,
|
|
36
|
-
addressValidationType: AddressValidationType.FULL,
|
|
37
|
-
recipient: mockInvitableRecipient2,
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
await expectSaga(watchValidateRecipientAddress)
|
|
41
|
-
.provide([
|
|
42
|
-
[select(currentAccountSelector), mockAccount],
|
|
43
|
-
[select(e164NumberToAddressSelector), mockE164NumberToAddress],
|
|
44
|
-
])
|
|
45
|
-
.dispatch(validateAction)
|
|
46
|
-
.put(showErrorInline(ErrorMessages.ADDRESS_VALIDATION_NO_MATCH))
|
|
47
|
-
.run()
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
it('full validation succeeds if the inputted address belongs to the recipient', async () => {
|
|
51
|
-
const validateAction: ValidateRecipientAddressAction = {
|
|
52
|
-
type: Actions.VALIDATE_RECIPIENT_ADDRESS,
|
|
53
|
-
userInputOfFullAddressOrLastFourDigits: mockAccountInvite,
|
|
54
|
-
addressValidationType: AddressValidationType.FULL,
|
|
55
|
-
recipient: mockInvitableRecipient2,
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
await expectSaga(watchValidateRecipientAddress)
|
|
59
|
-
.provide([
|
|
60
|
-
[select(currentAccountSelector), mockAccount],
|
|
61
|
-
[select(e164NumberToAddressSelector), mockE164NumberToAddress],
|
|
62
|
-
])
|
|
63
|
-
.dispatch(validateAction)
|
|
64
|
-
.put(validateRecipientAddressSuccess(mockE164NumberInvite, mockAccountInvite.toLowerCase()))
|
|
65
|
-
.run()
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
it('partial validation fails if the inputted address does not belong to the recipient', async () => {
|
|
69
|
-
const validateAction: ValidateRecipientAddressAction = {
|
|
70
|
-
type: Actions.VALIDATE_RECIPIENT_ADDRESS,
|
|
71
|
-
userInputOfFullAddressOrLastFourDigits: mockAccount2.slice(-4),
|
|
72
|
-
addressValidationType: AddressValidationType.PARTIAL,
|
|
73
|
-
recipient: mockInvitableRecipient2,
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
await expectSaga(watchValidateRecipientAddress)
|
|
77
|
-
.provide([
|
|
78
|
-
[select(currentAccountSelector), mockAccount],
|
|
79
|
-
[select(e164NumberToAddressSelector), mockE164NumberToAddress],
|
|
80
|
-
])
|
|
81
|
-
.dispatch(validateAction)
|
|
82
|
-
.put(showErrorInline(ErrorMessages.ADDRESS_VALIDATION_NO_MATCH))
|
|
83
|
-
.run()
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
it('partial validation succeeds if the inputted address belongs to the recipient', async () => {
|
|
87
|
-
const validateAction: ValidateRecipientAddressAction = {
|
|
88
|
-
type: Actions.VALIDATE_RECIPIENT_ADDRESS,
|
|
89
|
-
userInputOfFullAddressOrLastFourDigits: mockAccountInvite.slice(-4),
|
|
90
|
-
addressValidationType: AddressValidationType.PARTIAL,
|
|
91
|
-
recipient: mockInvitableRecipient2,
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
await expectSaga(watchValidateRecipientAddress)
|
|
95
|
-
.provide([
|
|
96
|
-
[select(currentAccountSelector), mockAccount],
|
|
97
|
-
[select(e164NumberToAddressSelector), mockE164NumberToAddress],
|
|
98
|
-
])
|
|
99
|
-
.dispatch(validateAction)
|
|
100
|
-
.put(validateRecipientAddressSuccess(mockE164NumberInvite, mockAccountInvite.toLowerCase()))
|
|
101
|
-
.run()
|
|
102
|
-
})
|
|
103
|
-
})
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
import { ErrorMessages } from 'src/app/ErrorMessages'
|
|
2
|
-
import { AddressValidationType, SecureSendPhoneNumberMapping } from 'src/identity/reducer'
|
|
3
|
-
import { Recipient, recipientHasNumber } from 'src/recipients/recipient'
|
|
4
|
-
import Logger from 'src/utils/Logger'
|
|
5
|
-
|
|
6
|
-
const TAG = 'identity/secureSend'
|
|
7
|
-
|
|
8
|
-
// Given addresses can be added and deleted we can't rely on changes in length to signal address changes
|
|
9
|
-
// Not sure if address order in array is consistent so not assuming it
|
|
10
|
-
function newAddressesAdded(oldAddresses: string[], newAddresses: string[]) {
|
|
11
|
-
const oldAddressesSorted = oldAddresses.sort()
|
|
12
|
-
const newAddressesSorted = newAddresses.sort()
|
|
13
|
-
|
|
14
|
-
for (let i = 0; i < newAddressesSorted.length; i += 1) {
|
|
15
|
-
if (oldAddressesSorted[i] !== newAddressesSorted[i]) {
|
|
16
|
-
return true
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return false
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function last4DigitsAreUnique(addressArr: string[]) {
|
|
24
|
-
const last4DigitArr = addressArr.map((address) => address.slice(-4).toLowerCase())
|
|
25
|
-
const last4DigitSet = new Set()
|
|
26
|
-
last4DigitArr.forEach((endDigits) => last4DigitSet.add(endDigits))
|
|
27
|
-
return last4DigitArr.length === last4DigitSet.size
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Check if there are multiple addresses but somehow a preferred address hasn't been set yet
|
|
31
|
-
// Should never happen in production but makes the change backwards compatible for testing env
|
|
32
|
-
function accidentallyBypassedValidation(
|
|
33
|
-
newAddresses: string[] | null,
|
|
34
|
-
e164Number: string,
|
|
35
|
-
secureSendPhoneNumberMapping: SecureSendPhoneNumberMapping
|
|
36
|
-
) {
|
|
37
|
-
const validatedAddress =
|
|
38
|
-
secureSendPhoneNumberMapping[e164Number] && secureSendPhoneNumberMapping[e164Number].address
|
|
39
|
-
return newAddresses && newAddresses.length > 1 && !validatedAddress
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function checkIfValidationRequired(
|
|
43
|
-
oldAddresses: string[],
|
|
44
|
-
possibleAddresses: string[],
|
|
45
|
-
userAddress: string,
|
|
46
|
-
secureSendPhoneNumberMapping: SecureSendPhoneNumberMapping,
|
|
47
|
-
e164Number: string
|
|
48
|
-
) {
|
|
49
|
-
// No validation needed if there is only 1 possible address
|
|
50
|
-
if (possibleAddresses.length < 2) {
|
|
51
|
-
return AddressValidationType.NONE
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (
|
|
55
|
-
newAddressesAdded(oldAddresses, possibleAddresses) ||
|
|
56
|
-
accidentallyBypassedValidation(possibleAddresses, e164Number, secureSendPhoneNumberMapping)
|
|
57
|
-
) {
|
|
58
|
-
Logger.debug(TAG, 'Address needs to be validated by user')
|
|
59
|
-
// Adding user's address so they don't mistakenly verify with last 4 digits of their own address
|
|
60
|
-
if (last4DigitsAreUnique([userAddress, ...possibleAddresses])) {
|
|
61
|
-
return AddressValidationType.PARTIAL
|
|
62
|
-
}
|
|
63
|
-
return AddressValidationType.FULL
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return AddressValidationType.NONE
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function hasSpecialChars(address: string, addressValidationType: AddressValidationType) {
|
|
70
|
-
const regex = new RegExp('[^0-9A-Za-z]', 'g')
|
|
71
|
-
const cleanedAddress = address.replace(regex, '')
|
|
72
|
-
|
|
73
|
-
if (cleanedAddress !== address) {
|
|
74
|
-
const errorMessage =
|
|
75
|
-
addressValidationType === AddressValidationType.FULL
|
|
76
|
-
? ErrorMessages.ADDRESS_VALIDATION_FULL_POORLY_FORMATTED
|
|
77
|
-
: ErrorMessages.ADDRESS_VALIDATION_PARTIAL_POORLY_FORMATTED
|
|
78
|
-
throw Error(errorMessage)
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function validateFullAddressAndReturnMatch(
|
|
83
|
-
userInputtedAddress: string,
|
|
84
|
-
possibleRecievingAddresses: string[],
|
|
85
|
-
userAddress: string
|
|
86
|
-
) {
|
|
87
|
-
if (userInputtedAddress.length !== 42 || userInputtedAddress.slice(0, 2) !== '0x') {
|
|
88
|
-
throw Error(ErrorMessages.ADDRESS_VALIDATION_FULL_POORLY_FORMATTED)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (userInputtedAddress === userAddress) {
|
|
92
|
-
throw Error(ErrorMessages.ADDRESS_VALIDATION_FULL_OWN_ADDRESS)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (!possibleRecievingAddresses.includes(userInputtedAddress)) {
|
|
96
|
-
throw Error(ErrorMessages.ADDRESS_VALIDATION_NO_MATCH)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return userInputtedAddress
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function validatePartialAddressAndReturnMatch(
|
|
103
|
-
lastFourDigitsOfUserInputtedAddress: string,
|
|
104
|
-
possibleRecievingAddresses: string[],
|
|
105
|
-
userAddress: string
|
|
106
|
-
) {
|
|
107
|
-
if (lastFourDigitsOfUserInputtedAddress.length !== 4) {
|
|
108
|
-
throw Error(ErrorMessages.ADDRESS_VALIDATION_PARTIAL_POORLY_FORMATTED)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (lastFourDigitsOfUserInputtedAddress === userAddress.slice(-4)) {
|
|
112
|
-
throw Error(ErrorMessages.ADDRESS_VALIDATION_PARTIAL_OWN_ADDRESS)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const targetAddress = possibleRecievingAddresses.find(
|
|
116
|
-
(address) => address.slice(-4) === lastFourDigitsOfUserInputtedAddress
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
if (!targetAddress) {
|
|
120
|
-
throw Error(ErrorMessages.ADDRESS_VALIDATION_NO_MATCH)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return targetAddress
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export function validateAndReturnMatch(
|
|
127
|
-
userInputOfFullAddressOrLastFourDigits: string,
|
|
128
|
-
possibleRecievingAddresses: string[],
|
|
129
|
-
userAddress: string,
|
|
130
|
-
addressValidationType: AddressValidationType
|
|
131
|
-
) {
|
|
132
|
-
const userInput = userInputOfFullAddressOrLastFourDigits.toLowerCase()
|
|
133
|
-
const possibleAddresses = possibleRecievingAddresses.map((address) => address.toLowerCase())
|
|
134
|
-
const userOwnAddress = userAddress.toLowerCase()
|
|
135
|
-
|
|
136
|
-
hasSpecialChars(userInputOfFullAddressOrLastFourDigits, addressValidationType)
|
|
137
|
-
|
|
138
|
-
if (addressValidationType === AddressValidationType.FULL) {
|
|
139
|
-
return validateFullAddressAndReturnMatch(userInput, possibleAddresses, userOwnAddress)
|
|
140
|
-
}
|
|
141
|
-
return validatePartialAddressAndReturnMatch(userInput, possibleAddresses, userOwnAddress)
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export function getAddressValidationType(
|
|
145
|
-
recipient: Recipient,
|
|
146
|
-
secureSendPhoneNumberMapping: SecureSendPhoneNumberMapping
|
|
147
|
-
) {
|
|
148
|
-
const e164PhoneNumber = recipient.e164PhoneNumber
|
|
149
|
-
|
|
150
|
-
if (
|
|
151
|
-
!e164PhoneNumber ||
|
|
152
|
-
!secureSendPhoneNumberMapping[e164PhoneNumber] ||
|
|
153
|
-
!secureSendPhoneNumberMapping[e164PhoneNumber].addressValidationType
|
|
154
|
-
) {
|
|
155
|
-
return AddressValidationType.NONE
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return secureSendPhoneNumberMapping[e164PhoneNumber].addressValidationType
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export function getSecureSendAddress(
|
|
162
|
-
recipient: Recipient,
|
|
163
|
-
secureSendPhoneNumberMapping: SecureSendPhoneNumberMapping
|
|
164
|
-
) {
|
|
165
|
-
const e164PhoneNumber = recipientHasNumber(recipient) ? recipient.e164PhoneNumber : undefined
|
|
166
|
-
if (!e164PhoneNumber || !secureSendPhoneNumberMapping[e164PhoneNumber]) {
|
|
167
|
-
return undefined
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return secureSendPhoneNumberMapping[e164PhoneNumber].address
|
|
171
|
-
}
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
import { fireEvent, render } from '@testing-library/react-native'
|
|
2
|
-
import * as React from 'react'
|
|
3
|
-
import { Provider } from 'react-redux'
|
|
4
|
-
import { SendOrigin } from 'src/analytics/types'
|
|
5
|
-
import { AddressValidationType } from 'src/identity/reducer'
|
|
6
|
-
import { navigate } from 'src/navigator/NavigationService'
|
|
7
|
-
import { Screens } from 'src/navigator/Screens'
|
|
8
|
-
import ValidateRecipientAccount from 'src/send/ValidateRecipientAccount'
|
|
9
|
-
import { createMockStore, getMockStackScreenProps } from 'test/utils'
|
|
10
|
-
import {
|
|
11
|
-
mockAccountInvite,
|
|
12
|
-
mockCusdAddress,
|
|
13
|
-
mockE164NumberInvite,
|
|
14
|
-
mockInvitableRecipient2,
|
|
15
|
-
} from 'test/values'
|
|
16
|
-
|
|
17
|
-
describe('ValidateRecipientAccount', () => {
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
jest.clearAllMocks()
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
it('renders correctly when full validation required', () => {
|
|
23
|
-
const store = createMockStore({
|
|
24
|
-
identity: {
|
|
25
|
-
secureSendPhoneNumberMapping: {
|
|
26
|
-
[mockE164NumberInvite]: {
|
|
27
|
-
addressValidationType: AddressValidationType.FULL,
|
|
28
|
-
validationSuccessful: false,
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
})
|
|
33
|
-
const tree = render(
|
|
34
|
-
<Provider store={store}>
|
|
35
|
-
<ValidateRecipientAccount
|
|
36
|
-
{...getMockStackScreenProps(Screens.ValidateRecipientAccount, {
|
|
37
|
-
recipient: mockInvitableRecipient2,
|
|
38
|
-
origin: SendOrigin.AppSendFlow,
|
|
39
|
-
})}
|
|
40
|
-
/>
|
|
41
|
-
</Provider>
|
|
42
|
-
)
|
|
43
|
-
expect(tree).toMatchSnapshot()
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
it('renders correctly when partial validation required', () => {
|
|
47
|
-
const store = createMockStore({
|
|
48
|
-
identity: {
|
|
49
|
-
secureSendPhoneNumberMapping: {
|
|
50
|
-
[mockE164NumberInvite]: {
|
|
51
|
-
addressValidationType: AddressValidationType.PARTIAL,
|
|
52
|
-
validationSuccessful: false,
|
|
53
|
-
},
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
})
|
|
57
|
-
const tree = render(
|
|
58
|
-
<Provider store={store}>
|
|
59
|
-
<ValidateRecipientAccount
|
|
60
|
-
{...getMockStackScreenProps(Screens.ValidateRecipientAccount, {
|
|
61
|
-
recipient: mockInvitableRecipient2,
|
|
62
|
-
origin: SendOrigin.AppSendFlow,
|
|
63
|
-
})}
|
|
64
|
-
/>
|
|
65
|
-
</Provider>
|
|
66
|
-
)
|
|
67
|
-
expect(tree).toMatchSnapshot()
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it('typing correct last four of account enables submit button', () => {
|
|
71
|
-
const store = createMockStore({
|
|
72
|
-
identity: {
|
|
73
|
-
secureSendPhoneNumberMapping: {
|
|
74
|
-
[mockE164NumberInvite]: {
|
|
75
|
-
addressValidationType: AddressValidationType.PARTIAL,
|
|
76
|
-
validationSuccessful: false,
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
})
|
|
81
|
-
const tree = render(
|
|
82
|
-
<Provider store={store}>
|
|
83
|
-
<ValidateRecipientAccount
|
|
84
|
-
{...getMockStackScreenProps(Screens.ValidateRecipientAccount, {
|
|
85
|
-
recipient: mockInvitableRecipient2,
|
|
86
|
-
origin: SendOrigin.AppSendFlow,
|
|
87
|
-
})}
|
|
88
|
-
/>
|
|
89
|
-
</Provider>
|
|
90
|
-
)
|
|
91
|
-
expect(tree.getByTestId('ConfirmAccountButton')).toBeDisabled()
|
|
92
|
-
fireEvent.changeText(tree.getByTestId('SingleDigitInput/digit0'), mockCusdAddress[38])
|
|
93
|
-
fireEvent.changeText(tree.getByTestId('SingleDigitInput/digit1'), mockCusdAddress[39])
|
|
94
|
-
fireEvent.changeText(tree.getByTestId('SingleDigitInput/digit2'), mockCusdAddress[40])
|
|
95
|
-
fireEvent.changeText(tree.getByTestId('SingleDigitInput/digit3'), mockCusdAddress[41])
|
|
96
|
-
expect(tree.getByTestId('ConfirmAccountButton')).not.toBeDisabled()
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
it('enables submit button for full validation', () => {
|
|
100
|
-
const store = createMockStore({
|
|
101
|
-
identity: {
|
|
102
|
-
secureSendPhoneNumberMapping: {
|
|
103
|
-
[mockE164NumberInvite]: {
|
|
104
|
-
addressValidationType: AddressValidationType.FULL,
|
|
105
|
-
validationSuccessful: false,
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
})
|
|
110
|
-
const tree = render(
|
|
111
|
-
<Provider store={store}>
|
|
112
|
-
<ValidateRecipientAccount
|
|
113
|
-
{...getMockStackScreenProps(Screens.ValidateRecipientAccount, {
|
|
114
|
-
recipient: mockInvitableRecipient2,
|
|
115
|
-
origin: SendOrigin.AppSendFlow,
|
|
116
|
-
})}
|
|
117
|
-
/>
|
|
118
|
-
</Provider>
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
expect(tree.getByTestId('ConfirmAccountButton')).toBeDisabled()
|
|
122
|
-
fireEvent.changeText(
|
|
123
|
-
tree.getByTestId('ValidateRecipientAccount/TextInput'),
|
|
124
|
-
mockCusdAddress[38]
|
|
125
|
-
)
|
|
126
|
-
expect(tree.getByTestId('ConfirmAccountButton')).not.toBeDisabled()
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
it('navigates to send enter amount with validated address when validation is successful', () => {
|
|
130
|
-
const store = createMockStore({
|
|
131
|
-
identity: {
|
|
132
|
-
secureSendPhoneNumberMapping: {
|
|
133
|
-
[mockE164NumberInvite]: {
|
|
134
|
-
addressValidationType: AddressValidationType.NONE,
|
|
135
|
-
validationSuccessful: false,
|
|
136
|
-
},
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
const props = getMockStackScreenProps(Screens.ValidateRecipientAccount, {
|
|
142
|
-
recipient: mockInvitableRecipient2,
|
|
143
|
-
origin: SendOrigin.AppSendFlow,
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
const tree = render(
|
|
147
|
-
<Provider store={store}>
|
|
148
|
-
<ValidateRecipientAccount {...props} />
|
|
149
|
-
</Provider>
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
const updatedStore = createMockStore({
|
|
153
|
-
identity: {
|
|
154
|
-
secureSendPhoneNumberMapping: {
|
|
155
|
-
[mockE164NumberInvite]: {
|
|
156
|
-
addressValidationType: AddressValidationType.NONE,
|
|
157
|
-
address: mockAccountInvite,
|
|
158
|
-
validationSuccessful: true,
|
|
159
|
-
},
|
|
160
|
-
},
|
|
161
|
-
},
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
tree.rerender(
|
|
165
|
-
<Provider store={updatedStore}>
|
|
166
|
-
<ValidateRecipientAccount {...props} />
|
|
167
|
-
</Provider>
|
|
168
|
-
)
|
|
169
|
-
|
|
170
|
-
expect(navigate).toHaveBeenCalledWith(Screens.SendEnterAmount, {
|
|
171
|
-
origin: SendOrigin.AppSendFlow,
|
|
172
|
-
isFromScan: false,
|
|
173
|
-
defaultTokenIdOverride: undefined,
|
|
174
|
-
forceTokenId: undefined,
|
|
175
|
-
recipient: {
|
|
176
|
-
...mockInvitableRecipient2,
|
|
177
|
-
address: mockAccountInvite,
|
|
178
|
-
},
|
|
179
|
-
isMiniPayRecipient: false,
|
|
180
|
-
})
|
|
181
|
-
})
|
|
182
|
-
})
|