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
package/src/identity/saga.ts
CHANGED
|
@@ -1,94 +1,16 @@
|
|
|
1
|
-
import { showErrorInline } from 'src/alert/actions'
|
|
2
|
-
import { SendEvents } from 'src/analytics/Events'
|
|
3
|
-
import AppAnalytics from 'src/analytics/AppAnalytics'
|
|
4
|
-
import { ErrorMessages } from 'src/app/ErrorMessages'
|
|
5
|
-
import {
|
|
6
|
-
Actions,
|
|
7
|
-
ValidateRecipientAddressAction,
|
|
8
|
-
validateRecipientAddressSuccess,
|
|
9
|
-
} from 'src/identity/actions'
|
|
10
1
|
import {
|
|
11
2
|
doImportContactsWrapper,
|
|
12
3
|
fetchAddressVerificationSaga,
|
|
13
4
|
fetchAddressesAndValidateSaga,
|
|
14
5
|
saveContacts,
|
|
15
6
|
} from 'src/identity/contactMapping'
|
|
16
|
-
import {
|
|
17
|
-
import { validateAndReturnMatch } from 'src/identity/secureSend'
|
|
18
|
-
import { e164NumberToAddressSelector } from 'src/identity/selectors'
|
|
19
|
-
import { recipientHasNumber } from 'src/recipients/recipient'
|
|
7
|
+
import { Actions } from 'src/identity/actions'
|
|
20
8
|
import Logger from 'src/utils/Logger'
|
|
21
|
-
import { ensureError } from 'src/utils/ensureError'
|
|
22
9
|
import { safely } from 'src/utils/safely'
|
|
23
|
-
import {
|
|
24
|
-
import { cancelled, put, select, spawn, takeEvery, takeLatest, takeLeading } from 'typed-redux-saga'
|
|
10
|
+
import { cancelled, spawn, takeEvery, takeLatest, takeLeading } from 'typed-redux-saga'
|
|
25
11
|
|
|
26
12
|
const TAG = 'identity/saga'
|
|
27
13
|
|
|
28
|
-
export function* validateRecipientAddressSaga({
|
|
29
|
-
userInputOfFullAddressOrLastFourDigits,
|
|
30
|
-
addressValidationType,
|
|
31
|
-
recipient,
|
|
32
|
-
requesterAddress,
|
|
33
|
-
}: ValidateRecipientAddressAction) {
|
|
34
|
-
Logger.debug(TAG, 'Starting Recipient Address Validation')
|
|
35
|
-
try {
|
|
36
|
-
if (!recipientHasNumber(recipient)) {
|
|
37
|
-
throw Error(`Invalid recipient type for Secure Send, does not have e164Number`)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const userAddress = yield* select(currentAccountSelector)
|
|
41
|
-
if (!userAddress) {
|
|
42
|
-
// This should never happen
|
|
43
|
-
throw Error(`No userAddress set`)
|
|
44
|
-
}
|
|
45
|
-
const e164NumberToAddress = yield* select(e164NumberToAddressSelector)
|
|
46
|
-
const { e164PhoneNumber } = recipient
|
|
47
|
-
const possibleRecievingAddresses = e164NumberToAddress[e164PhoneNumber]
|
|
48
|
-
|
|
49
|
-
// Should never happen - Secure Send is initiated to deal with
|
|
50
|
-
// there being several possible addresses
|
|
51
|
-
if (!possibleRecievingAddresses) {
|
|
52
|
-
throw Error('There are no possible recipient addresses to validate against')
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// E164NumberToAddress in redux store only holds verified addresses
|
|
56
|
-
// Need to add the requester address to the option set in the event
|
|
57
|
-
// a request is coming from an unverified account
|
|
58
|
-
if (requesterAddress && !possibleRecievingAddresses.includes(requesterAddress)) {
|
|
59
|
-
possibleRecievingAddresses.push(requesterAddress)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const validatedAddress = validateAndReturnMatch(
|
|
63
|
-
userInputOfFullAddressOrLastFourDigits,
|
|
64
|
-
possibleRecievingAddresses,
|
|
65
|
-
userAddress,
|
|
66
|
-
addressValidationType
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
AppAnalytics.track(SendEvents.send_secure_complete, {
|
|
70
|
-
confirmByScan: false,
|
|
71
|
-
partialAddressValidation: addressValidationType === AddressValidationType.PARTIAL,
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
yield* put(validateRecipientAddressSuccess(e164PhoneNumber, validatedAddress))
|
|
75
|
-
} catch (err) {
|
|
76
|
-
const error = ensureError(err)
|
|
77
|
-
AppAnalytics.track(SendEvents.send_secure_incorrect, {
|
|
78
|
-
confirmByScan: false,
|
|
79
|
-
partialAddressValidation: addressValidationType === AddressValidationType.PARTIAL,
|
|
80
|
-
error: error.message,
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
Logger.error(TAG, 'validateRecipientAddressSaga/Address validation error: ', error)
|
|
84
|
-
if (Object.values(ErrorMessages).includes(error.message as ErrorMessages)) {
|
|
85
|
-
yield* put(showErrorInline(error.message as ErrorMessages))
|
|
86
|
-
} else {
|
|
87
|
-
yield* put(showErrorInline(ErrorMessages.ADDRESS_VALIDATION_ERROR))
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
14
|
function* watchContactMapping() {
|
|
93
15
|
yield* takeLeading(Actions.IMPORT_CONTACTS, safely(doImportContactsWrapper))
|
|
94
16
|
yield* takeLatest(
|
|
@@ -97,10 +19,6 @@ function* watchContactMapping() {
|
|
|
97
19
|
)
|
|
98
20
|
}
|
|
99
21
|
|
|
100
|
-
export function* watchValidateRecipientAddress() {
|
|
101
|
-
yield* takeLatest(Actions.VALIDATE_RECIPIENT_ADDRESS, safely(validateRecipientAddressSaga))
|
|
102
|
-
}
|
|
103
|
-
|
|
104
22
|
function* watchFetchAddressVerification() {
|
|
105
23
|
yield* takeEvery(Actions.FETCH_ADDRESS_VERIFICATION_STATUS, safely(fetchAddressVerificationSaga))
|
|
106
24
|
}
|
|
@@ -109,7 +27,6 @@ export function* identitySaga() {
|
|
|
109
27
|
Logger.debug(TAG, 'Initializing identity sagas')
|
|
110
28
|
try {
|
|
111
29
|
yield* spawn(watchContactMapping)
|
|
112
|
-
yield* spawn(watchValidateRecipientAddress)
|
|
113
30
|
yield* spawn(watchFetchAddressVerification)
|
|
114
31
|
yield* spawn(saveContacts) // save contacts on app start
|
|
115
32
|
} catch (error) {
|
|
@@ -5,8 +5,6 @@ export const addressToVerificationStatusSelector = (state: RootState) =>
|
|
|
5
5
|
state.identity.addressToVerificationStatus
|
|
6
6
|
export const addressToE164NumberSelector = (state: RootState) => state.identity.addressToE164Number
|
|
7
7
|
export const addressToVerifiedBySelector = (state: RootState) => state.identity.addressToVerifiedBy
|
|
8
|
-
export const secureSendPhoneNumberMappingSelector = (state: RootState) =>
|
|
9
|
-
state.identity.secureSendPhoneNumberMapping
|
|
10
8
|
export const importContactsProgressSelector = (state: RootState) =>
|
|
11
9
|
state.identity.importContactsProgress
|
|
12
10
|
export const addressToDisplayNameSelector = (state: RootState) =>
|
package/src/images/Images.ts
CHANGED
|
@@ -16,7 +16,9 @@ export const fiatExchange = require('src/images/assets/fiat-exchange.png')
|
|
|
16
16
|
export const getVerified = require('src/images/assets/get-verified.png')
|
|
17
17
|
export const inviteModal = require('src/images/assets/invite-modal.png')
|
|
18
18
|
export const learnCelo = require('src/images/assets/learn-celo.png')
|
|
19
|
+
export const miniPay = require('src/images/assets/minipay.png')
|
|
19
20
|
export const pointsCardBackground = require('src/images/assets/points-card-background.png')
|
|
20
21
|
export const pointsIllustration = require('src/images/assets/points-illustration.png')
|
|
22
|
+
export const valora = require('src/images/assets/valora.png')
|
|
21
23
|
export const walletSafe = require('src/images/assets/wallet-safe.png')
|
|
22
24
|
export const earnCardBackground = require('src/images/assets/earn-card-background.png')
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/index.d.ts
CHANGED
|
@@ -102,16 +102,11 @@ import PointsIntro from 'src/points/PointsIntro'
|
|
|
102
102
|
import { NavigatorScreen } from 'src/public/navigate'
|
|
103
103
|
import { RootState } from 'src/redux/reducers'
|
|
104
104
|
import { store } from 'src/redux/store'
|
|
105
|
+
import SelectRecipientAddress from 'src/send/SelectRecipientAddress'
|
|
105
106
|
import SendConfirmation, { sendConfirmationScreenNavOptions } from 'src/send/SendConfirmation'
|
|
106
107
|
import SendEnterAmount from 'src/send/SendEnterAmount'
|
|
107
108
|
import SendInvite from 'src/send/SendInvite'
|
|
108
109
|
import SendSelectRecipient from 'src/send/SendSelectRecipient'
|
|
109
|
-
import ValidateRecipientAccount, {
|
|
110
|
-
validateRecipientAccountScreenNavOptions,
|
|
111
|
-
} from 'src/send/ValidateRecipientAccount'
|
|
112
|
-
import ValidateRecipientIntro, {
|
|
113
|
-
validateRecipientIntroScreenNavOptions,
|
|
114
|
-
} from 'src/send/ValidateRecipientIntro'
|
|
115
110
|
import { getFeatureGate } from 'src/statsig'
|
|
116
111
|
import { StatsigFeatureGates } from 'src/statsig/types'
|
|
117
112
|
import styles from 'src/styles/styles'
|
|
@@ -243,14 +238,9 @@ const sendScreens = (Navigator: typeof Stack) => (
|
|
|
243
238
|
options={sendConfirmationScreenNavOptions as NativeStackNavigationOptions}
|
|
244
239
|
/>
|
|
245
240
|
<Navigator.Screen
|
|
246
|
-
name={Screens.
|
|
247
|
-
component={
|
|
248
|
-
options={
|
|
249
|
-
/>
|
|
250
|
-
<Navigator.Screen
|
|
251
|
-
name={Screens.ValidateRecipientAccount}
|
|
252
|
-
component={ValidateRecipientAccount}
|
|
253
|
-
options={validateRecipientAccountScreenNavOptions}
|
|
241
|
+
name={Screens.SelectRecipientAddress}
|
|
242
|
+
component={SelectRecipientAddress}
|
|
243
|
+
options={SelectRecipientAddress.navigationOptions as NativeStackNavigationOptions}
|
|
254
244
|
/>
|
|
255
245
|
<Navigator.Screen
|
|
256
246
|
name={Screens.SendEnterAmount}
|
|
@@ -76,6 +76,7 @@ export enum Screens {
|
|
|
76
76
|
SelectLocalCurrency = 'SelectLocalCurrency',
|
|
77
77
|
SelectProvider = 'SelectProvider',
|
|
78
78
|
SendInvite = 'SendInvite',
|
|
79
|
+
SelectRecipientAddress = 'SelectRecipientAddress',
|
|
79
80
|
SendSelectRecipient = 'SendSelectRecipient',
|
|
80
81
|
SendConfirmation = 'SendConfirmation',
|
|
81
82
|
SendEnterAmount = 'SendEnterAmount',
|
|
@@ -95,8 +96,6 @@ export enum Screens {
|
|
|
95
96
|
TokenImport = 'TokenImport',
|
|
96
97
|
TransactionDetailsScreen = 'TransactionDetailsScreen',
|
|
97
98
|
UpgradeScreen = 'UpgradeScreen',
|
|
98
|
-
ValidateRecipientAccount = 'ValidateRecipientAccount',
|
|
99
|
-
ValidateRecipientIntro = 'ValidateRecipientIntro',
|
|
100
99
|
VerificationCodeInputScreen = 'VerificationCodeInputScreen',
|
|
101
100
|
VerificationStartScreen = 'VerificationStartScreen',
|
|
102
101
|
WalletConnectRequest = 'WalletConnectRequest',
|
package/src/navigator/types.tsx
CHANGED
|
@@ -9,7 +9,7 @@ import { KeylessBackupFlow, KeylessBackupOrigin } from 'src/keylessBackup/types'
|
|
|
9
9
|
import { Screens } from 'src/navigator/Screens'
|
|
10
10
|
import { Nft } from 'src/nfts/types'
|
|
11
11
|
import { EarnPosition } from 'src/positions/types'
|
|
12
|
-
import { Recipient } from 'src/recipients/recipient'
|
|
12
|
+
import { MobileRecipient, Recipient } from 'src/recipients/recipient'
|
|
13
13
|
import { QrCode, TransactionDataInput } from 'src/send/types'
|
|
14
14
|
import type { SwapTransaction } from 'src/swap/types'
|
|
15
15
|
import type { SerializedTokenBalance } from 'src/tokens/slice'
|
|
@@ -45,10 +45,9 @@ type SendEnterAmountParams = {
|
|
|
45
45
|
isMiniPayRecipient?: boolean
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
interface
|
|
49
|
-
|
|
48
|
+
interface SelectRecipientAddressParams {
|
|
49
|
+
recipient: MobileRecipient
|
|
50
50
|
origin: SendOrigin
|
|
51
|
-
recipient: Recipient
|
|
52
51
|
forceTokenId?: boolean
|
|
53
52
|
defaultTokenIdOverride?: string
|
|
54
53
|
}
|
|
@@ -262,6 +261,7 @@ export type StackParamList = {
|
|
|
262
261
|
}
|
|
263
262
|
}
|
|
264
263
|
[Screens.SendInvite]: { recipient: Recipient; shareUrl: string }
|
|
264
|
+
[Screens.SelectRecipientAddress]: SelectRecipientAddressParams
|
|
265
265
|
[Screens.SendSelectRecipient]:
|
|
266
266
|
| {
|
|
267
267
|
forceTokenId?: boolean
|
|
@@ -301,8 +301,6 @@ export type StackParamList = {
|
|
|
301
301
|
transaction: TokenTransaction
|
|
302
302
|
}
|
|
303
303
|
[Screens.UpgradeScreen]: undefined
|
|
304
|
-
[Screens.ValidateRecipientIntro]: ValidateRecipientParams
|
|
305
|
-
[Screens.ValidateRecipientAccount]: ValidateRecipientParams
|
|
306
304
|
[Screens.VerificationStartScreen]:
|
|
307
305
|
| {
|
|
308
306
|
hasOnboarded?: boolean
|
|
@@ -3,45 +3,32 @@ import * as React from 'react'
|
|
|
3
3
|
import 'react-native'
|
|
4
4
|
import { View } from 'react-native'
|
|
5
5
|
import { expectSaga } from 'redux-saga-test-plan'
|
|
6
|
-
import {
|
|
6
|
+
import { select } from 'redux-saga-test-plan/matchers'
|
|
7
7
|
import { showError } from 'src/alert/actions'
|
|
8
8
|
import AppAnalytics from 'src/analytics/AppAnalytics'
|
|
9
9
|
import { QrScreenEvents } from 'src/analytics/Events'
|
|
10
10
|
import { HooksEnablePreviewOrigin, SendOrigin } from 'src/analytics/types'
|
|
11
11
|
import { ErrorMessages } from 'src/app/ErrorMessages'
|
|
12
12
|
import { DEEP_LINK_URL_SCHEME } from 'src/config'
|
|
13
|
-
import {
|
|
14
|
-
e164NumberToAddressSelector,
|
|
15
|
-
secureSendPhoneNumberMappingSelector,
|
|
16
|
-
} from 'src/identity/selectors'
|
|
13
|
+
import { e164NumberToAddressSelector } from 'src/identity/selectors'
|
|
17
14
|
import { navigate } from 'src/navigator/NavigationService'
|
|
18
15
|
import { Screens } from 'src/navigator/Screens'
|
|
19
16
|
import { handleEnableHooksPreviewDeepLink } from 'src/positions/saga'
|
|
20
17
|
import { allowHooksPreviewSelector } from 'src/positions/selectors'
|
|
21
18
|
import { urlFromUriData } from 'src/qrcode/schema'
|
|
22
|
-
import {
|
|
23
|
-
QRCodeTypes,
|
|
24
|
-
handleQRCodeDefault,
|
|
25
|
-
handleQRCodeSecureSend,
|
|
26
|
-
handleSecureSend,
|
|
27
|
-
useQRContent,
|
|
28
|
-
} from 'src/qrcode/utils'
|
|
19
|
+
import { QRCodeTypes, handleQRCodeDefault, useQRContent } from 'src/qrcode/utils'
|
|
29
20
|
import { RecipientType } from 'src/recipients/recipient'
|
|
30
21
|
import { recipientInfoSelector } from 'src/recipients/reducer'
|
|
31
|
-
import { handleQRCodeDetected
|
|
22
|
+
import { handleQRCodeDetected } from 'src/send/actions'
|
|
32
23
|
import { QrCode } from 'src/send/types'
|
|
33
24
|
import { createMockStore } from 'test/utils'
|
|
34
25
|
import {
|
|
35
26
|
mockAccount,
|
|
36
|
-
mockAccount2,
|
|
37
27
|
mockAccount3,
|
|
38
28
|
mockE164Number,
|
|
39
29
|
mockE164Number3,
|
|
40
|
-
mockE164NumberToAddress,
|
|
41
|
-
mockEthTokenId,
|
|
42
30
|
mockName,
|
|
43
31
|
mockQrCodeData,
|
|
44
|
-
mockRecipient,
|
|
45
32
|
mockRecipientInfo,
|
|
46
33
|
} from 'test/values'
|
|
47
34
|
|
|
@@ -212,82 +199,3 @@ describe('handleQRCodeDefault', () => {
|
|
|
212
199
|
expect(AppAnalytics.track).toHaveBeenCalledWith(QrScreenEvents.qr_scanned, qrCode)
|
|
213
200
|
})
|
|
214
201
|
})
|
|
215
|
-
|
|
216
|
-
describe('handleQRCodeSecureSend', () => {
|
|
217
|
-
it('handles a valid address and navigates to send enter amount when there is no transaction data', async () => {
|
|
218
|
-
const data: QrCode = { type: QRCodeTypes.QR_CODE, data: mockAccount }
|
|
219
|
-
await expectSaga(
|
|
220
|
-
handleQRCodeSecureSend,
|
|
221
|
-
handleQRCodeDetectedSecureSend(data, mockRecipient, mockAccount2, false, mockEthTokenId)
|
|
222
|
-
)
|
|
223
|
-
.provide([
|
|
224
|
-
[select(e164NumberToAddressSelector), mockE164NumberToAddress],
|
|
225
|
-
[
|
|
226
|
-
select(secureSendPhoneNumberMappingSelector),
|
|
227
|
-
{
|
|
228
|
-
[mockRecipient.e164PhoneNumber]: {
|
|
229
|
-
address: mockAccount,
|
|
230
|
-
addressValidationType: undefined,
|
|
231
|
-
},
|
|
232
|
-
},
|
|
233
|
-
],
|
|
234
|
-
[
|
|
235
|
-
call(
|
|
236
|
-
handleSecureSend,
|
|
237
|
-
mockAccount.toLowerCase(),
|
|
238
|
-
mockE164NumberToAddress,
|
|
239
|
-
mockRecipient,
|
|
240
|
-
mockAccount2
|
|
241
|
-
),
|
|
242
|
-
true,
|
|
243
|
-
],
|
|
244
|
-
])
|
|
245
|
-
.run()
|
|
246
|
-
expect(navigate).toHaveBeenCalledWith(Screens.SendEnterAmount, {
|
|
247
|
-
origin: SendOrigin.AppSendFlow,
|
|
248
|
-
recipient: {
|
|
249
|
-
...mockRecipient,
|
|
250
|
-
address: mockAccount,
|
|
251
|
-
},
|
|
252
|
-
isFromScan: true,
|
|
253
|
-
forceTokenId: false,
|
|
254
|
-
defaultTokenIdOverride: mockEthTokenId,
|
|
255
|
-
})
|
|
256
|
-
expect(AppAnalytics.track).toHaveBeenCalledWith(QrScreenEvents.qr_scanned, data)
|
|
257
|
-
})
|
|
258
|
-
it('handles an invalid address', async () => {
|
|
259
|
-
const data: QrCode = { type: QRCodeTypes.QR_CODE, data: 'invalid-address' }
|
|
260
|
-
await expectSaga(
|
|
261
|
-
handleQRCodeSecureSend,
|
|
262
|
-
handleQRCodeDetectedSecureSend(data, mockRecipient, mockAccount2)
|
|
263
|
-
)
|
|
264
|
-
.provide([[select(e164NumberToAddressSelector), mockE164NumberToAddress]])
|
|
265
|
-
.put(showError(ErrorMessages.QR_FAILED_INVALID_ADDRESS))
|
|
266
|
-
.run()
|
|
267
|
-
expect(navigate).not.toHaveBeenCalled()
|
|
268
|
-
expect(AppAnalytics.track).toHaveBeenCalledWith(QrScreenEvents.qr_scanned, data)
|
|
269
|
-
})
|
|
270
|
-
it('handles failed address lookup', async () => {
|
|
271
|
-
const data: QrCode = { type: QRCodeTypes.QR_CODE, data: mockAccount }
|
|
272
|
-
await expectSaga(
|
|
273
|
-
handleQRCodeSecureSend,
|
|
274
|
-
handleQRCodeDetectedSecureSend(data, mockRecipient, mockAccount2)
|
|
275
|
-
)
|
|
276
|
-
.provide([
|
|
277
|
-
[select(e164NumberToAddressSelector), mockE164NumberToAddress],
|
|
278
|
-
[
|
|
279
|
-
call(
|
|
280
|
-
handleSecureSend,
|
|
281
|
-
mockAccount.toLowerCase(),
|
|
282
|
-
mockE164NumberToAddress,
|
|
283
|
-
mockRecipient,
|
|
284
|
-
mockAccount2
|
|
285
|
-
),
|
|
286
|
-
false,
|
|
287
|
-
],
|
|
288
|
-
])
|
|
289
|
-
.run()
|
|
290
|
-
expect(navigate).not.toHaveBeenCalled()
|
|
291
|
-
expect(AppAnalytics.track).toHaveBeenCalledWith(QrScreenEvents.qr_scanned, data)
|
|
292
|
-
})
|
|
293
|
-
})
|
package/src/qrcode/utils.ts
CHANGED
|
@@ -1,40 +1,18 @@
|
|
|
1
1
|
import * as RNFS from '@valora/react-native-fs'
|
|
2
2
|
import { useMemo } from 'react'
|
|
3
3
|
import Share from 'react-native-share'
|
|
4
|
-
import { showError
|
|
4
|
+
import { showError } from 'src/alert/actions'
|
|
5
5
|
import AppAnalytics from 'src/analytics/AppAnalytics'
|
|
6
|
-
import { QrScreenEvents
|
|
7
|
-
import {
|
|
8
|
-
HooksEnablePreviewOrigin,
|
|
9
|
-
SendOrigin,
|
|
10
|
-
WalletConnectPairingOrigin,
|
|
11
|
-
} from 'src/analytics/types'
|
|
6
|
+
import { QrScreenEvents } from 'src/analytics/Events'
|
|
7
|
+
import { HooksEnablePreviewOrigin, WalletConnectPairingOrigin } from 'src/analytics/types'
|
|
12
8
|
import { ErrorMessages } from 'src/app/ErrorMessages'
|
|
13
9
|
import { DEEP_LINK_URL_SCHEME } from 'src/config'
|
|
14
|
-
import { validateRecipientAddressSuccess } from 'src/identity/actions'
|
|
15
|
-
import { E164NumberToAddressType } from 'src/identity/reducer'
|
|
16
|
-
import { getSecureSendAddress } from 'src/identity/secureSend'
|
|
17
|
-
import {
|
|
18
|
-
e164NumberToAddressSelector,
|
|
19
|
-
secureSendPhoneNumberMappingSelector,
|
|
20
|
-
} from 'src/identity/selectors'
|
|
21
|
-
import { navigate } from 'src/navigator/NavigationService'
|
|
22
|
-
import { Screens } from 'src/navigator/Screens'
|
|
23
10
|
import { handleEnableHooksPreviewDeepLink } from 'src/positions/saga'
|
|
24
11
|
import { allowHooksPreviewSelector } from 'src/positions/selectors'
|
|
25
12
|
import { UriData, uriDataFromUrl } from 'src/qrcode/schema'
|
|
26
|
-
import {
|
|
27
|
-
Recipient,
|
|
28
|
-
RecipientInfo,
|
|
29
|
-
getRecipientFromAddress,
|
|
30
|
-
recipientHasNumber,
|
|
31
|
-
} from 'src/recipients/recipient'
|
|
13
|
+
import { RecipientInfo, getRecipientFromAddress } from 'src/recipients/recipient'
|
|
32
14
|
import { recipientInfoSelector } from 'src/recipients/reducer'
|
|
33
|
-
import {
|
|
34
|
-
HandleQRCodeDetectedAction,
|
|
35
|
-
HandleQRCodeDetectedSecureSendAction,
|
|
36
|
-
SVG,
|
|
37
|
-
} from 'src/send/actions'
|
|
15
|
+
import { HandleQRCodeDetectedAction, SVG } from 'src/send/actions'
|
|
38
16
|
import { QrCode } from 'src/send/types'
|
|
39
17
|
import { handleSendPaymentData } from 'src/send/utils'
|
|
40
18
|
import Logger from 'src/utils/Logger'
|
|
@@ -85,48 +63,6 @@ export async function shareSVGImage(svg: SVG) {
|
|
|
85
63
|
})
|
|
86
64
|
}
|
|
87
65
|
|
|
88
|
-
export function* handleSecureSend(
|
|
89
|
-
address: string,
|
|
90
|
-
e164NumberToAddress: E164NumberToAddressType,
|
|
91
|
-
recipient: Recipient,
|
|
92
|
-
requesterAddress?: string
|
|
93
|
-
) {
|
|
94
|
-
if (!recipientHasNumber(recipient)) {
|
|
95
|
-
throw Error('Invalid recipient type for Secure Send, has no mobile number')
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const userScannedAddress = address.toLowerCase()
|
|
99
|
-
const { e164PhoneNumber } = recipient
|
|
100
|
-
const possibleReceivingAddresses = e164NumberToAddress[e164PhoneNumber]
|
|
101
|
-
// This should never happen. Secure Send is triggered when there are
|
|
102
|
-
// multiple addresses for a given phone number
|
|
103
|
-
if (!possibleReceivingAddresses) {
|
|
104
|
-
throw Error("No addresses associated with recipient's phone number")
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Need to add the requester address to the option set in the event
|
|
108
|
-
// a request is coming from an unverified account
|
|
109
|
-
if (requesterAddress && !possibleReceivingAddresses.includes(requesterAddress)) {
|
|
110
|
-
possibleReceivingAddresses.push(requesterAddress)
|
|
111
|
-
}
|
|
112
|
-
const possibleReceivingAddressesFormatted = possibleReceivingAddresses.map((addr) =>
|
|
113
|
-
addr.toLowerCase()
|
|
114
|
-
)
|
|
115
|
-
if (!possibleReceivingAddressesFormatted.includes(userScannedAddress)) {
|
|
116
|
-
const error = ErrorMessages.QR_FAILED_INVALID_RECIPIENT
|
|
117
|
-
AppAnalytics.track(SendEvents.send_secure_incorrect, {
|
|
118
|
-
confirmByScan: true,
|
|
119
|
-
error,
|
|
120
|
-
})
|
|
121
|
-
yield* put(showMessage(error))
|
|
122
|
-
return false
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
AppAnalytics.track(SendEvents.send_secure_complete, { confirmByScan: true })
|
|
126
|
-
yield* put(validateRecipientAddressSuccess(e164PhoneNumber, userScannedAddress))
|
|
127
|
-
return true
|
|
128
|
-
}
|
|
129
|
-
|
|
130
66
|
function* extractQRAddressData(qrCode: QrCode) {
|
|
131
67
|
// strip network prefix if present
|
|
132
68
|
const qrAddress = qrCode.data.split(':').at(-1) || qrCode.data
|
|
@@ -183,48 +119,3 @@ export function* handleQRCodeDefault({
|
|
|
183
119
|
defaultTokenIdOverride,
|
|
184
120
|
})
|
|
185
121
|
}
|
|
186
|
-
|
|
187
|
-
export function* handleQRCodeSecureSend({
|
|
188
|
-
qrCode,
|
|
189
|
-
requesterAddress,
|
|
190
|
-
recipient,
|
|
191
|
-
forceTokenId,
|
|
192
|
-
defaultTokenIdOverride,
|
|
193
|
-
}: HandleQRCodeDetectedSecureSendAction) {
|
|
194
|
-
const e164NumberToAddress = yield* select(e164NumberToAddressSelector)
|
|
195
|
-
AppAnalytics.track(QrScreenEvents.qr_scanned, qrCode)
|
|
196
|
-
|
|
197
|
-
const qrData = yield* call(extractQRAddressData, qrCode)
|
|
198
|
-
if (!qrData) {
|
|
199
|
-
return
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const success: boolean = yield* call(
|
|
203
|
-
handleSecureSend,
|
|
204
|
-
qrData.address,
|
|
205
|
-
e164NumberToAddress,
|
|
206
|
-
recipient,
|
|
207
|
-
requesterAddress
|
|
208
|
-
)
|
|
209
|
-
if (!success) {
|
|
210
|
-
return
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const secureSendPhoneNumberMapping = yield* select(secureSendPhoneNumberMappingSelector)
|
|
214
|
-
const address = getSecureSendAddress(recipient, secureSendPhoneNumberMapping)
|
|
215
|
-
if (!address) {
|
|
216
|
-
// should never happen b/c if handleSecureSend succeeds then address should be there
|
|
217
|
-
Logger.error(TAG, `No secure send address found for recipient ${recipient}`)
|
|
218
|
-
return
|
|
219
|
-
}
|
|
220
|
-
navigate(Screens.SendEnterAmount, {
|
|
221
|
-
origin: SendOrigin.AppSendFlow,
|
|
222
|
-
recipient: {
|
|
223
|
-
...recipient,
|
|
224
|
-
address,
|
|
225
|
-
},
|
|
226
|
-
isFromScan: true,
|
|
227
|
-
forceTokenId,
|
|
228
|
-
defaultTokenIdOverride,
|
|
229
|
-
})
|
|
230
|
-
}
|
|
@@ -68,6 +68,7 @@ import {
|
|
|
68
68
|
v251Schema,
|
|
69
69
|
v253Schema,
|
|
70
70
|
v254Schema,
|
|
71
|
+
v255Schema,
|
|
71
72
|
v28Schema,
|
|
72
73
|
v2Schema,
|
|
73
74
|
v35Schema,
|
|
@@ -1946,4 +1947,16 @@ describe('Redux persist migrations', () => {
|
|
|
1946
1947
|
const migratedSchema = migrations[255](oldSchema)
|
|
1947
1948
|
expect(migratedSchema.identity.addressToVerifiedBy).toStrictEqual({})
|
|
1948
1949
|
})
|
|
1950
|
+
|
|
1951
|
+
it('works from 255 to 256', () => {
|
|
1952
|
+
const oldSchema = {
|
|
1953
|
+
...v255Schema,
|
|
1954
|
+
identity: {
|
|
1955
|
+
...v255Schema.identity,
|
|
1956
|
+
secureSendPhoneNumberMapping: { '+14155550000': {} },
|
|
1957
|
+
},
|
|
1958
|
+
}
|
|
1959
|
+
const migratedSchema = migrations[256](oldSchema)
|
|
1960
|
+
expect(migratedSchema.identity.secureSendPhoneNumberMapping).toBeUndefined()
|
|
1961
|
+
})
|
|
1949
1962
|
})
|
package/src/redux/migrations.ts
CHANGED
package/src/redux/store.test.ts
CHANGED
|
@@ -143,7 +143,7 @@ describe('store state', () => {
|
|
|
143
143
|
{
|
|
144
144
|
"_persist": {
|
|
145
145
|
"rehydrated": true,
|
|
146
|
-
"version":
|
|
146
|
+
"version": 256,
|
|
147
147
|
},
|
|
148
148
|
"account": {
|
|
149
149
|
"acceptedTerms": false,
|
|
@@ -254,7 +254,6 @@ describe('store state', () => {
|
|
|
254
254
|
"total": 0,
|
|
255
255
|
},
|
|
256
256
|
"lastSavedContactsHash": null,
|
|
257
|
-
"secureSendPhoneNumberMapping": {},
|
|
258
257
|
"shouldRefreshStoredPasswordHash": true,
|
|
259
258
|
},
|
|
260
259
|
"imports": {
|
package/src/redux/store.ts
CHANGED
|
@@ -30,7 +30,7 @@ const persistConfig: PersistConfig<ReducersRootState> = {
|
|
|
30
30
|
key: 'root',
|
|
31
31
|
// default is -1, increment as we make migrations
|
|
32
32
|
// See https://github.com/valora-xyz/wallet/tree/main/WALLET.md#redux-state-migration
|
|
33
|
-
version:
|
|
33
|
+
version: 256,
|
|
34
34
|
keyPrefix: `reduxStore-`, // the redux-persist default is `persist:` which doesn't work with some file systems.
|
|
35
35
|
storage: FSStorage(),
|
|
36
36
|
blacklist: ['networkInfo', 'alert', 'imports', 'keylessBackup', transactionFeedV2Api.reducerPath],
|