wallet-stack 1.0.0-alpha.131 → 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.
Files changed (59) hide show
  1. package/locales/base/translation.json +11 -0
  2. package/package.json +2 -3
  3. package/src/analytics/Events.tsx +3 -10
  4. package/src/analytics/Properties.tsx +9 -25
  5. package/src/analytics/docs.ts +11 -8
  6. package/src/app/ErrorMessages.ts +1 -7
  7. package/src/identity/actions.ts +1 -97
  8. package/src/identity/contactMapping.test.ts +3 -28
  9. package/src/identity/contactMapping.ts +2 -88
  10. package/src/identity/reducer.ts +0 -77
  11. package/src/identity/saga.ts +2 -85
  12. package/src/identity/selectors.ts +0 -2
  13. package/src/images/Images.ts +3 -0
  14. package/src/images/assets/invite-modal.png +0 -0
  15. package/src/images/assets/invite-modal@1.5x.png +0 -0
  16. package/src/images/assets/invite-modal@2x.png +0 -0
  17. package/src/images/assets/invite-modal@3x.png +0 -0
  18. package/src/images/assets/invite-modal@4x.png +0 -0
  19. package/src/images/assets/minipay.png +0 -0
  20. package/src/images/assets/minipay@1.5x.png +0 -0
  21. package/src/images/assets/minipay@2x.png +0 -0
  22. package/src/images/assets/minipay@3x.png +0 -0
  23. package/src/images/assets/minipay@4x.png +0 -0
  24. package/src/images/assets/valora.png +0 -0
  25. package/src/images/assets/valora@1.5x.png +0 -0
  26. package/src/images/assets/valora@2x.png +0 -0
  27. package/src/images/assets/valora@3x.png +0 -0
  28. package/src/images/assets/valora@4x.png +0 -0
  29. package/src/index.d.ts +0 -1
  30. package/src/navigator/Navigator.tsx +10 -14
  31. package/src/navigator/Screens.tsx +2 -2
  32. package/src/navigator/types.tsx +5 -6
  33. package/src/qrcode/utils.test.tsx +4 -96
  34. package/src/qrcode/utils.ts +5 -114
  35. package/src/redux/migrations.test.ts +13 -0
  36. package/src/redux/migrations.ts +4 -0
  37. package/src/redux/store.test.ts +1 -2
  38. package/src/redux/store.ts +1 -1
  39. package/src/send/SelectRecipientAddress.test.tsx +146 -0
  40. package/src/send/SelectRecipientAddress.tsx +166 -0
  41. package/src/send/SendConfirmation.test.tsx +28 -0
  42. package/src/send/SendConfirmation.tsx +18 -1
  43. package/src/send/SendInvite.test.tsx +107 -0
  44. package/src/send/SendInvite.tsx +99 -0
  45. package/src/send/SendSelectRecipient.test.tsx +44 -223
  46. package/src/send/SendSelectRecipient.tsx +41 -149
  47. package/src/send/actions.ts +0 -26
  48. package/src/send/saga.ts +1 -6
  49. package/src/components/AccountNumberCard.tsx +0 -23
  50. package/src/components/ErrorMessageInline.tsx +0 -78
  51. package/src/components/SingleDigitInput.tsx +0 -53
  52. package/src/icons/HamburgerCard.tsx +0 -55
  53. package/src/identity/saga.test.ts +0 -103
  54. package/src/identity/secureSend.ts +0 -171
  55. package/src/send/ValidateRecipientAccount.test.tsx +0 -182
  56. package/src/send/ValidateRecipientAccount.tsx +0 -392
  57. package/src/send/ValidateRecipientIntro.test.tsx +0 -61
  58. package/src/send/ValidateRecipientIntro.tsx +0 -136
  59. package/src/send/__snapshots__/ValidateRecipientAccount.test.tsx.snap +0 -777
@@ -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
- })