wallet-stack 1.0.0-alpha.133 → 1.0.0-alpha.135
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/package.json +1 -1
- package/src/analytics/Properties.tsx +1 -1
- package/src/components/ReviewTransaction.test.tsx +49 -8
- package/src/components/ReviewTransaction.tsx +58 -12
- package/src/icons/VerifiedBadge.tsx +27 -0
- package/src/identity/actions.ts +0 -17
- package/src/identity/contactMapping.test.ts +78 -19
- package/src/identity/contactMapping.ts +50 -20
- package/src/identity/reducer.ts +7 -21
- package/src/identity/selectors.ts +0 -2
- package/src/recipients/RecipientItemV2.test.tsx +11 -72
- package/src/recipients/RecipientItemV2.tsx +1 -34
- package/src/recipients/recipient.test.ts +6 -5
- package/src/recipients/recipient.ts +7 -12
- package/src/recipients/verifier.ts +20 -0
- package/src/redux/migrations.test.ts +25 -0
- package/src/redux/migrations.ts +21 -0
- package/src/redux/store.test.ts +1 -2
- package/src/redux/store.ts +1 -1
- package/src/send/EnterAmount.tsx +106 -83
- package/src/send/SelectRecipientAddress.tsx +11 -13
- package/src/send/SendConfirmation.test.tsx +3 -3
- package/src/send/SendConfirmation.tsx +3 -3
- package/src/send/SendEnterAmount.test.tsx +27 -0
- package/src/send/SendSelectRecipient.test.tsx +37 -51
- package/src/send/useFetchRecipientVerificationStatus.ts +6 -11
- package/src/transactions/UserSection.tsx +37 -11
- package/src/transactions/feed/TransactionDetailsScreen.test.tsx +6 -6
- package/src/utils/phoneNumbers.ts +0 -11
package/src/send/EnterAmount.tsx
CHANGED
|
@@ -10,7 +10,7 @@ import BackButton from 'src/components/BackButton'
|
|
|
10
10
|
import { BottomSheetModalRefType } from 'src/components/BottomSheet'
|
|
11
11
|
import Button, { BtnSizes } from 'src/components/Button'
|
|
12
12
|
import FeeInfoBottomSheet from 'src/components/FeeInfoBottomSheet'
|
|
13
|
-
import { FilterChip } from 'src/components/FilterChipsCarousel'
|
|
13
|
+
import { FilterChip, isNetworkChip } from 'src/components/FilterChipsCarousel'
|
|
14
14
|
import InLineNotification, { NotificationVariant } from 'src/components/InLineNotification'
|
|
15
15
|
import KeyboardAwareScrollView from 'src/components/KeyboardAwareScrollView'
|
|
16
16
|
import { ReviewDetailsItem } from 'src/components/ReviewTransaction'
|
|
@@ -34,6 +34,7 @@ import { Spacing } from 'src/styles/styles'
|
|
|
34
34
|
import { feeCurrenciesSelector } from 'src/tokens/selectors'
|
|
35
35
|
import { TokenBalance } from 'src/tokens/slice'
|
|
36
36
|
import { PreparedTransactionsResult } from 'src/viem/prepareTransactions'
|
|
37
|
+
import networkConfig from 'src/web3/networkConfig'
|
|
37
38
|
|
|
38
39
|
export interface ProceedArgs {
|
|
39
40
|
tokenAmount: BigNumber
|
|
@@ -113,9 +114,20 @@ export default function EnterAmount({
|
|
|
113
114
|
}: Props) {
|
|
114
115
|
const { t } = useTranslation()
|
|
115
116
|
const insets = useSafeAreaInsets()
|
|
116
|
-
const [token, setToken] = useState<TokenBalance>(() =>
|
|
117
|
+
const [token, setToken] = useState<TokenBalance | undefined>(() => {
|
|
118
|
+
if (defaultToken) return defaultToken
|
|
119
|
+
const activeFilters = filterChips?.filter((chip) => chip.isSelected) ?? []
|
|
120
|
+
const selectableTokens = tokens.filter((t) =>
|
|
121
|
+
activeFilters.every((filter) =>
|
|
122
|
+
isNetworkChip(filter) ? filter.filterFn(t, filter.selectedNetworkIds) : filter.filterFn(t)
|
|
123
|
+
)
|
|
124
|
+
)
|
|
125
|
+
return selectableTokens[0]
|
|
126
|
+
})
|
|
117
127
|
const [selectedPercentage, setSelectedPercentage] = useState<number | null>(null)
|
|
118
|
-
const feeCurrencies = useSelector((state) =>
|
|
128
|
+
const feeCurrencies = useSelector((state) =>
|
|
129
|
+
feeCurrenciesSelector(state, token?.networkId ?? networkConfig.defaultNetworkId)
|
|
130
|
+
)
|
|
119
131
|
const networkFee = useNetworkFee(prepareTransactionsResult)
|
|
120
132
|
const localCurrencySymbol = useSelector(getLocalCurrencySymbol) ?? LocalCurrencySymbol.USD
|
|
121
133
|
|
|
@@ -142,6 +154,8 @@ export default function EnterAmount({
|
|
|
142
154
|
useEffect(() => {
|
|
143
155
|
onClearPreparedTransactions()
|
|
144
156
|
|
|
157
|
+
if (!token) return
|
|
158
|
+
|
|
145
159
|
const canRefresh =
|
|
146
160
|
processedAmounts.token.bignum &&
|
|
147
161
|
processedAmounts.token.bignum.gt(0) &&
|
|
@@ -158,9 +172,9 @@ export default function EnterAmount({
|
|
|
158
172
|
const onOpenTokenPicker = () => {
|
|
159
173
|
tokenBottomSheetRef.current?.snapToIndex(0)
|
|
160
174
|
AppAnalytics.track(SendEvents.token_dropdown_opened, {
|
|
161
|
-
currentTokenId: token
|
|
162
|
-
currentTokenAddress: token
|
|
163
|
-
currentNetworkId: token
|
|
175
|
+
currentTokenId: token?.tokenId ?? '',
|
|
176
|
+
currentTokenAddress: token?.address ?? null,
|
|
177
|
+
currentNetworkId: token?.networkId ?? null,
|
|
164
178
|
})
|
|
165
179
|
}
|
|
166
180
|
|
|
@@ -174,6 +188,7 @@ export default function EnterAmount({
|
|
|
174
188
|
}
|
|
175
189
|
|
|
176
190
|
const onSelectPercentageAmount = (percentage: number) => {
|
|
191
|
+
if (!token) return
|
|
177
192
|
handleSelectPercentageAmount(percentage)
|
|
178
193
|
setSelectedPercentage(percentage)
|
|
179
194
|
|
|
@@ -187,6 +202,7 @@ export default function EnterAmount({
|
|
|
187
202
|
}
|
|
188
203
|
|
|
189
204
|
const showLowerAmountError =
|
|
205
|
+
token &&
|
|
190
206
|
processedAmounts.token.bignum &&
|
|
191
207
|
!processedAmounts.token.bignum.lte(token.balance) &&
|
|
192
208
|
!disableBalanceCheck
|
|
@@ -205,6 +221,7 @@ export default function EnterAmount({
|
|
|
205
221
|
prepareTransactionsResult.transactions.length > 0
|
|
206
222
|
|
|
207
223
|
const disabled =
|
|
224
|
+
!token ||
|
|
208
225
|
disableProceed ||
|
|
209
226
|
(disableBalanceCheck ? !!processedAmounts.token.bignum?.isZero() : !transactionIsPossible)
|
|
210
227
|
|
|
@@ -236,89 +253,95 @@ export default function EnterAmount({
|
|
|
236
253
|
onOpenTokenPicker={tokenSelectionDisabled ? undefined : onOpenTokenPicker}
|
|
237
254
|
/>
|
|
238
255
|
|
|
239
|
-
{
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
256
|
+
{token &&
|
|
257
|
+
prepareTransactionsResult?.type !== 'not-enough-balance-for-gas' &&
|
|
258
|
+
!!networkFee && (
|
|
259
|
+
<View style={styles.feeContainer}>
|
|
260
|
+
<ReviewDetailsItem
|
|
261
|
+
approx
|
|
262
|
+
testID="SendEnterAmount/NetworkFee"
|
|
263
|
+
type="token-amount"
|
|
264
|
+
label={t('networkFee')}
|
|
265
|
+
tokenAmount={networkFee.amount}
|
|
266
|
+
localAmount={networkFee.localAmount}
|
|
267
|
+
tokenInfo={networkFee.token}
|
|
268
|
+
localCurrencySymbol={localCurrencySymbol}
|
|
269
|
+
onInfoPress={() => feeInfoBottomSheetRef.current?.snapToIndex(0)}
|
|
270
|
+
/>
|
|
252
271
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
272
|
+
<FeeInfoBottomSheet forwardedRef={feeInfoBottomSheetRef} networkFee={networkFee} />
|
|
273
|
+
</View>
|
|
274
|
+
)}
|
|
256
275
|
</View>
|
|
257
276
|
|
|
258
|
-
{
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
277
|
+
{token && (
|
|
278
|
+
<>
|
|
279
|
+
{showLowerAmountError && (
|
|
280
|
+
<InLineNotification
|
|
281
|
+
variant={NotificationVariant.Warning}
|
|
282
|
+
title={t('sendEnterAmountScreen.insufficientBalanceWarning.title', {
|
|
283
|
+
tokenSymbol: token.symbol,
|
|
284
|
+
})}
|
|
285
|
+
description={t('sendEnterAmountScreen.insufficientBalanceWarning.description', {
|
|
286
|
+
tokenSymbol: token.symbol,
|
|
287
|
+
})}
|
|
288
|
+
style={styles.warning}
|
|
289
|
+
testID="SendEnterAmount/NotEnoughBalanceWarning"
|
|
290
|
+
/>
|
|
291
|
+
)}
|
|
292
|
+
{showMaxAmountWarning && (
|
|
293
|
+
<InLineNotification
|
|
294
|
+
variant={NotificationVariant.Warning}
|
|
295
|
+
title={t('sendEnterAmountScreen.maxAmountWarning.title')}
|
|
296
|
+
description={t('sendEnterAmountScreen.maxAmountWarning.description', {
|
|
297
|
+
feeTokenSymbol: prepareTransactionsResult.feeCurrency.symbol,
|
|
298
|
+
})}
|
|
299
|
+
style={styles.warning}
|
|
300
|
+
testID="SendEnterAmount/MaxAmountWarning"
|
|
301
|
+
/>
|
|
302
|
+
)}
|
|
303
|
+
{showNotEnoughBalanceForGasWarning && (
|
|
304
|
+
<InLineNotification
|
|
305
|
+
variant={NotificationVariant.Warning}
|
|
306
|
+
title={t('sendEnterAmountScreen.notEnoughBalanceForGasWarning.title', {
|
|
307
|
+
feeTokenSymbol: prepareTransactionsResult.feeCurrencies[0].symbol,
|
|
308
|
+
})}
|
|
309
|
+
description={t('sendEnterAmountScreen.notEnoughBalanceForGasWarning.description', {
|
|
310
|
+
feeTokenSymbol: prepareTransactionsResult.feeCurrencies[0].symbol,
|
|
311
|
+
})}
|
|
312
|
+
style={styles.warning}
|
|
313
|
+
testID="SendEnterAmount/NotEnoughForGasWarning"
|
|
314
|
+
/>
|
|
315
|
+
)}
|
|
316
|
+
{prepareTransactionError && (
|
|
317
|
+
<InLineNotification
|
|
318
|
+
variant={NotificationVariant.Error}
|
|
319
|
+
title={t('sendEnterAmountScreen.prepareTransactionError.title')}
|
|
320
|
+
description={t('sendEnterAmountScreen.prepareTransactionError.description')}
|
|
321
|
+
style={styles.warning}
|
|
322
|
+
testID="SendEnterAmount/PrepareTransactionError"
|
|
323
|
+
/>
|
|
324
|
+
)}
|
|
304
325
|
|
|
305
|
-
|
|
326
|
+
{children}
|
|
306
327
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
328
|
+
<EnterAmountOptions
|
|
329
|
+
onPressAmount={onSelectPercentageAmount}
|
|
330
|
+
selectedAmount={selectedPercentage}
|
|
331
|
+
testID="SendEnterAmount/AmountOptions"
|
|
332
|
+
/>
|
|
312
333
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
334
|
+
<ProceedComponent
|
|
335
|
+
tokenAmount={processedAmounts.token.bignum}
|
|
336
|
+
localAmount={processedAmounts.local.bignum}
|
|
337
|
+
token={token}
|
|
338
|
+
amountEnteredIn={amountType}
|
|
339
|
+
onPressProceed={onPressProceed}
|
|
340
|
+
disabled={disabled}
|
|
341
|
+
showLoading={prepareTransactionsLoading}
|
|
342
|
+
/>
|
|
343
|
+
</>
|
|
344
|
+
)}
|
|
322
345
|
</KeyboardAwareScrollView>
|
|
323
346
|
<TokenBottomSheet
|
|
324
347
|
forwardedRef={tokenBottomSheetRef}
|
|
@@ -9,13 +9,14 @@ import { SendEvents } from 'src/analytics/Events'
|
|
|
9
9
|
import BackButton from 'src/components/BackButton'
|
|
10
10
|
import Touchable from 'src/components/Touchable'
|
|
11
11
|
import CustomHeader from 'src/components/header/CustomHeader'
|
|
12
|
+
import VerifiedBadge from 'src/icons/VerifiedBadge'
|
|
12
13
|
import { addressToVerifiedBySelector, e164NumberToAddressSelector } from 'src/identity/selectors'
|
|
13
|
-
import { miniPay, valora } from 'src/images/Images'
|
|
14
14
|
import { noHeader } from 'src/navigator/Headers'
|
|
15
15
|
import { navigate } from 'src/navigator/NavigationService'
|
|
16
16
|
import { Screens } from 'src/navigator/Screens'
|
|
17
17
|
import { StackParamList } from 'src/navigator/types'
|
|
18
18
|
import { getDisplayName } from 'src/recipients/recipient'
|
|
19
|
+
import { VERIFIERS, Verifier, isKnownVerifier } from 'src/recipients/verifier'
|
|
19
20
|
import { useSelector } from 'src/redux/hooks'
|
|
20
21
|
import Colors from 'src/styles/colors'
|
|
21
22
|
import { typeScale } from 'src/styles/fonts'
|
|
@@ -23,19 +24,8 @@ import { Spacing } from 'src/styles/styles'
|
|
|
23
24
|
|
|
24
25
|
type Props = NativeStackScreenProps<StackParamList, Screens.SelectRecipientAddress>
|
|
25
26
|
|
|
26
|
-
const VERIFIERS = {
|
|
27
|
-
valora: { name: 'Valora', icon: valora },
|
|
28
|
-
minipay: { name: 'MiniPay', icon: miniPay },
|
|
29
|
-
} as const
|
|
30
|
-
|
|
31
|
-
export type Verifier = keyof typeof VERIFIERS
|
|
32
|
-
|
|
33
27
|
const ICON_SIZE = 40
|
|
34
28
|
|
|
35
|
-
function isKnownVerifier(verifier: string | undefined): verifier is Verifier {
|
|
36
|
-
return !!verifier && Object.hasOwn(VERIFIERS, verifier)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
29
|
function VerifierIcon({ verifier }: { verifier: Verifier }) {
|
|
40
30
|
return <Image source={VERIFIERS[verifier].icon} style={styles.icon} resizeMode="contain" />
|
|
41
31
|
}
|
|
@@ -103,7 +93,10 @@ function SelectRecipientAddress({ route }: Props) {
|
|
|
103
93
|
<VerifierIcon verifier={verifier} />
|
|
104
94
|
<View style={styles.rowContent}>
|
|
105
95
|
<Text style={styles.address}>{formatShortenedAddress(address)}</Text>
|
|
106
|
-
<
|
|
96
|
+
<View style={styles.verifier}>
|
|
97
|
+
<VerifiedBadge color={Colors.contentSecondary} />
|
|
98
|
+
<Text style={styles.verifierName}>{VERIFIERS[verifier].name}</Text>
|
|
99
|
+
</View>
|
|
107
100
|
</View>
|
|
108
101
|
</View>
|
|
109
102
|
</Touchable>
|
|
@@ -156,6 +149,11 @@ const styles = StyleSheet.create({
|
|
|
156
149
|
...typeScale.labelMedium,
|
|
157
150
|
},
|
|
158
151
|
verifier: {
|
|
152
|
+
flexDirection: 'row',
|
|
153
|
+
alignItems: 'center',
|
|
154
|
+
gap: Spacing.Tiny4,
|
|
155
|
+
},
|
|
156
|
+
verifierName: {
|
|
159
157
|
...typeScale.bodySmall,
|
|
160
158
|
color: Colors.contentSecondary,
|
|
161
159
|
},
|
|
@@ -174,7 +174,7 @@ describe('SendConfirmation', () => {
|
|
|
174
174
|
it('shows the unknown address warning when the recipient address is not a known app user', () => {
|
|
175
175
|
const { getByTestId } = renderScreen(mockSendConfirmationProps, {
|
|
176
176
|
identity: {
|
|
177
|
-
|
|
177
|
+
addressToVerifiedBy: { [mockAccount]: null },
|
|
178
178
|
},
|
|
179
179
|
})
|
|
180
180
|
|
|
@@ -184,7 +184,7 @@ describe('SendConfirmation', () => {
|
|
|
184
184
|
it('does not show the unknown address warning when the recipient address is verified', () => {
|
|
185
185
|
const { queryByTestId } = renderScreen(mockSendConfirmationProps, {
|
|
186
186
|
identity: {
|
|
187
|
-
|
|
187
|
+
addressToVerifiedBy: { [mockAccount]: 'valora' },
|
|
188
188
|
},
|
|
189
189
|
})
|
|
190
190
|
|
|
@@ -193,7 +193,7 @@ describe('SendConfirmation', () => {
|
|
|
193
193
|
|
|
194
194
|
it('does not show the unknown address warning when the address has not been looked up yet', () => {
|
|
195
195
|
const { queryByTestId } = renderScreen(mockSendConfirmationProps, {
|
|
196
|
-
identity: {
|
|
196
|
+
identity: { addressToVerifiedBy: {} },
|
|
197
197
|
})
|
|
198
198
|
|
|
199
199
|
expect(queryByTestId('UnknownAddressInfo')).toBeNull()
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
} from 'src/components/ReviewTransaction'
|
|
25
25
|
import { formatValueToDisplay } from 'src/components/TokenDisplay'
|
|
26
26
|
import TokenIcon from 'src/components/TokenIcon'
|
|
27
|
-
import {
|
|
27
|
+
import { addressToVerifiedBySelector } from 'src/identity/selectors'
|
|
28
28
|
import { LocalCurrencySymbol } from 'src/localCurrency/consts'
|
|
29
29
|
import { getLocalCurrencyCode, getLocalCurrencySymbol } from 'src/localCurrency/selectors'
|
|
30
30
|
import { noHeader } from 'src/navigator/Headers'
|
|
@@ -91,11 +91,11 @@ export default function SendConfirmation({ route: { params } }: Props) {
|
|
|
91
91
|
const localAmount = useTokenToLocalAmount(tokenAmount, tokenId)
|
|
92
92
|
const usdAmount = useAmountAsUsd(tokenAmount, tokenId)
|
|
93
93
|
const walletAddress = useSelector(walletAddressSelector)
|
|
94
|
-
const
|
|
94
|
+
const addressToVerifiedBy = useSelector(addressToVerifiedBySelector)
|
|
95
95
|
const showUnknownAddressInfo =
|
|
96
96
|
recipient.recipientType === RecipientType.Address &&
|
|
97
97
|
!!recipient.address &&
|
|
98
|
-
|
|
98
|
+
addressToVerifiedBy[recipient.address] === null
|
|
99
99
|
|
|
100
100
|
const feeCurrencies = useSelector((state) => feeCurrenciesSelector(state, tokenInfo!.networkId))
|
|
101
101
|
const {
|
|
@@ -308,6 +308,33 @@ describe('SendEnterAmount', () => {
|
|
|
308
308
|
expect(queryByText('sendEnterAmountScreen.miniPayFilterChip')).toBeFalsy()
|
|
309
309
|
})
|
|
310
310
|
|
|
311
|
+
it('should not select a default token when isMiniPayRecipient is true and user has no MiniPay tokens', () => {
|
|
312
|
+
jest.mocked(getDynamicConfigParams).mockReturnValue({
|
|
313
|
+
miniPayTokenIds: ['celo-alfajores:0xNOT_HELD_BY_USER'],
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
const { getByText, queryByTestId, getByTestId } = render(
|
|
317
|
+
<Provider store={store}>
|
|
318
|
+
<MockedNavigator
|
|
319
|
+
component={SendEnterAmount}
|
|
320
|
+
params={{ ...params, isMiniPayRecipient: true }}
|
|
321
|
+
/>
|
|
322
|
+
</Provider>
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
// "Select token" placeholder is rendered in place of the selected token
|
|
326
|
+
expect(getByText('tokenEnterAmount.selectToken')).toBeTruthy()
|
|
327
|
+
// Amount input, percentage options, and Review button are not rendered
|
|
328
|
+
expect(queryByTestId('SendEnterAmount/TokenAmountInput')).toBeNull()
|
|
329
|
+
expect(queryByTestId('SendEnterAmount/AmountOptions')).toBeNull()
|
|
330
|
+
expect(queryByTestId('SendEnterAmount/ReviewButton')).toBeNull()
|
|
331
|
+
// MiniPay filter chip is still present (inside the token picker)
|
|
332
|
+
expect(getByText('sendEnterAmountScreen.miniPayFilterChip')).toBeTruthy()
|
|
333
|
+
// Tapping the token row opens the picker
|
|
334
|
+
fireEvent.press(getByTestId('SendEnterAmount/TokenSelect'))
|
|
335
|
+
expect(getByTestId('TokenBottomSheet')).toBeTruthy()
|
|
336
|
+
})
|
|
337
|
+
|
|
311
338
|
it('should include isMiniPayRecipient in send_amount_continue analytics', async () => {
|
|
312
339
|
jest.mocked(usePrepareSendTransactions).mockReturnValue({
|
|
313
340
|
prepareTransactionsResult: mockPrepareTransactionsResultPossible,
|
|
@@ -7,10 +7,9 @@ import { SendEvents } from 'src/analytics/Events'
|
|
|
7
7
|
import { SendOrigin } from 'src/analytics/types'
|
|
8
8
|
import { getAppConfig } from 'src/appConfig'
|
|
9
9
|
import { fetchAddressVerification, fetchAddressesAndValidate } from 'src/identity/actions'
|
|
10
|
-
import { RecipientVerificationStatus } from 'src/identity/types'
|
|
11
10
|
import { navigate } from 'src/navigator/NavigationService'
|
|
12
11
|
import { Screens } from 'src/navigator/Screens'
|
|
13
|
-
import { RecipientType
|
|
12
|
+
import { RecipientType } from 'src/recipients/recipient'
|
|
14
13
|
import SendSelectRecipient from 'src/send/SendSelectRecipient'
|
|
15
14
|
import { getDynamicConfigParams } from 'src/statsig'
|
|
16
15
|
import { StatsigDynamicConfigs } from 'src/statsig/types'
|
|
@@ -31,10 +30,6 @@ import {
|
|
|
31
30
|
jest.mock('@react-native-clipboard/clipboard')
|
|
32
31
|
jest.mock('src/utils/IosVersionUtils')
|
|
33
32
|
jest.mock('src/recipients/resolve-id')
|
|
34
|
-
jest.mock('src/recipients/recipient', () => ({
|
|
35
|
-
...(jest.requireActual('src/recipients/recipient') as any),
|
|
36
|
-
getRecipientVerificationStatus: jest.fn(),
|
|
37
|
-
}))
|
|
38
33
|
|
|
39
34
|
jest.mock('react-native-device-info', () => ({ getFontScaleSync: () => 1 }))
|
|
40
35
|
jest.mock('src/statsig')
|
|
@@ -203,11 +198,13 @@ describe('SendSelectRecipient', () => {
|
|
|
203
198
|
expect(getByTestId('SelectRecipient/NoResults')).toBeTruthy()
|
|
204
199
|
})
|
|
205
200
|
it('navigates to send amount when a verified phone recipient is tapped in search results', async () => {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
201
|
+
const store = createMockStore({
|
|
202
|
+
...storeWithPhoneVerified,
|
|
203
|
+
identity: {
|
|
204
|
+
e164NumberToAddress: { [mockE164Number2Invite]: [mockAccount3] },
|
|
205
|
+
addressToVerifiedBy: { [mockAccount3]: 'valora' },
|
|
206
|
+
},
|
|
207
|
+
})
|
|
211
208
|
|
|
212
209
|
const { getByTestId } = render(
|
|
213
210
|
<Provider store={store}>
|
|
@@ -237,7 +234,12 @@ describe('SendSelectRecipient', () => {
|
|
|
237
234
|
})
|
|
238
235
|
})
|
|
239
236
|
it('navigates to send amount when an address is tapped and the user phone number is not verified', async () => {
|
|
240
|
-
const store = createMockStore(
|
|
237
|
+
const store = createMockStore({
|
|
238
|
+
...defaultStore,
|
|
239
|
+
identity: {
|
|
240
|
+
addressToVerifiedBy: { [mockAddressRecipient.address.toLowerCase()]: 'valora' },
|
|
241
|
+
},
|
|
242
|
+
})
|
|
241
243
|
|
|
242
244
|
const { getByTestId } = render(
|
|
243
245
|
<Provider store={store}>
|
|
@@ -267,11 +269,12 @@ describe('SendSelectRecipient', () => {
|
|
|
267
269
|
})
|
|
268
270
|
|
|
269
271
|
it('dispatches address verification when an address is tapped and the user phone number is verified', async () => {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
272
|
+
const store = createMockStore({
|
|
273
|
+
...storeWithPhoneVerified,
|
|
274
|
+
identity: {
|
|
275
|
+
addressToVerifiedBy: { [mockAccount2.toLowerCase()]: 'valora' },
|
|
276
|
+
},
|
|
277
|
+
})
|
|
275
278
|
|
|
276
279
|
const { getByTestId } = render(
|
|
277
280
|
<Provider store={store}>
|
|
@@ -299,11 +302,12 @@ describe('SendSelectRecipient', () => {
|
|
|
299
302
|
expect(store.getActions()).toEqual([fetchAddressVerification(mockAccount2.toLowerCase())])
|
|
300
303
|
})
|
|
301
304
|
it('does not navigate when an unverified phone recipient is tapped and no share URL is configured', async () => {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
305
|
+
const store = createMockStore({
|
|
306
|
+
...storeWithPhoneVerified,
|
|
307
|
+
identity: {
|
|
308
|
+
e164NumberToAddress: { [mockE164Number2Invite]: null },
|
|
309
|
+
},
|
|
310
|
+
})
|
|
307
311
|
|
|
308
312
|
const { getByTestId } = render(
|
|
309
313
|
<Provider store={store}>
|
|
@@ -340,11 +344,13 @@ describe('SendSelectRecipient', () => {
|
|
|
340
344
|
inviteFriends: { shareUrl },
|
|
341
345
|
},
|
|
342
346
|
})
|
|
343
|
-
jest
|
|
344
|
-
.mocked(getRecipientVerificationStatus)
|
|
345
|
-
.mockReturnValue(RecipientVerificationStatus.UNVERIFIED)
|
|
346
347
|
|
|
347
|
-
const store = createMockStore(
|
|
348
|
+
const store = createMockStore({
|
|
349
|
+
...storeWithPhoneVerified,
|
|
350
|
+
identity: {
|
|
351
|
+
e164NumberToAddress: { [mockE164Number2Invite]: null },
|
|
352
|
+
},
|
|
353
|
+
})
|
|
348
354
|
|
|
349
355
|
const { getByTestId } = render(
|
|
350
356
|
<Provider store={store}>
|
|
@@ -373,11 +379,11 @@ describe('SendSelectRecipient', () => {
|
|
|
373
379
|
})
|
|
374
380
|
|
|
375
381
|
it('navigates and dispatches address verification when an unknown address is tapped and the user phone number is verified', async () => {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
382
|
+
// addressToVerifiedBy entry is `null` → checked and not verified
|
|
383
|
+
const store = createMockStore({
|
|
384
|
+
...storeWithPhoneVerified,
|
|
385
|
+
identity: { addressToVerifiedBy: { [mockAccount2.toLowerCase()]: null } },
|
|
386
|
+
})
|
|
381
387
|
|
|
382
388
|
const { getByTestId } = render(
|
|
383
389
|
<Provider store={store}>
|
|
@@ -468,10 +474,6 @@ describe('SendSelectRecipient', () => {
|
|
|
468
474
|
})
|
|
469
475
|
|
|
470
476
|
it('navigates to send amount when a verified phone recipient with a single address is tapped', async () => {
|
|
471
|
-
jest
|
|
472
|
-
.mocked(getRecipientVerificationStatus)
|
|
473
|
-
.mockReturnValue(RecipientVerificationStatus.VERIFIED)
|
|
474
|
-
|
|
475
477
|
const store = createMockStore({
|
|
476
478
|
...storeWithPhoneVerified,
|
|
477
479
|
identity: {
|
|
@@ -511,10 +513,6 @@ describe('SendSelectRecipient', () => {
|
|
|
511
513
|
})
|
|
512
514
|
})
|
|
513
515
|
it('navigates with isMiniPayRecipient when address is verified by minipay', async () => {
|
|
514
|
-
jest
|
|
515
|
-
.mocked(getRecipientVerificationStatus)
|
|
516
|
-
.mockReturnValue(RecipientVerificationStatus.VERIFIED)
|
|
517
|
-
|
|
518
516
|
const store = createMockStore({
|
|
519
517
|
...storeWithPhoneVerified,
|
|
520
518
|
identity: {
|
|
@@ -552,10 +550,6 @@ describe('SendSelectRecipient', () => {
|
|
|
552
550
|
})
|
|
553
551
|
})
|
|
554
552
|
it('navigates to address picker when phone number recipient has multiple verified addresses', async () => {
|
|
555
|
-
jest
|
|
556
|
-
.mocked(getRecipientVerificationStatus)
|
|
557
|
-
.mockReturnValue(RecipientVerificationStatus.VERIFIED)
|
|
558
|
-
|
|
559
553
|
const store = createMockStore({
|
|
560
554
|
...storeWithPhoneVerified,
|
|
561
555
|
identity: {
|
|
@@ -598,10 +592,6 @@ describe('SendSelectRecipient', () => {
|
|
|
598
592
|
})
|
|
599
593
|
})
|
|
600
594
|
it('navigates to send enter amount when phone number has multiple raw addresses but only one with a verifier', async () => {
|
|
601
|
-
jest
|
|
602
|
-
.mocked(getRecipientVerificationStatus)
|
|
603
|
-
.mockReturnValue(RecipientVerificationStatus.VERIFIED)
|
|
604
|
-
|
|
605
595
|
const store = createMockStore({
|
|
606
596
|
...storeWithPhoneVerified,
|
|
607
597
|
identity: {
|
|
@@ -652,10 +642,6 @@ describe('SendSelectRecipient', () => {
|
|
|
652
642
|
it.each([{ searchAddress: mockAccount2 }, { searchAddress: mockAccount3 }])(
|
|
653
643
|
'navigates to send enter amount with correct address if an address is entered which also maps to a phone number with multiple addresses',
|
|
654
644
|
async ({ searchAddress }) => {
|
|
655
|
-
jest
|
|
656
|
-
.mocked(getRecipientVerificationStatus)
|
|
657
|
-
.mockReturnValue(RecipientVerificationStatus.VERIFIED)
|
|
658
|
-
|
|
659
645
|
const store = createMockStore({
|
|
660
646
|
...storeWithPhoneVerified,
|
|
661
647
|
identity: {
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react'
|
|
2
2
|
import { phoneNumberVerifiedSelector } from 'src/app/selectors'
|
|
3
3
|
import { fetchAddressVerification, fetchAddressesAndValidate } from 'src/identity/actions'
|
|
4
|
-
import {
|
|
5
|
-
addressToVerificationStatusSelector,
|
|
6
|
-
e164NumberToAddressSelector,
|
|
7
|
-
} from 'src/identity/selectors'
|
|
4
|
+
import { addressToVerifiedBySelector, e164NumberToAddressSelector } from 'src/identity/selectors'
|
|
8
5
|
import { RecipientVerificationStatus } from 'src/identity/types'
|
|
9
6
|
import { Recipient, RecipientType, getRecipientVerificationStatus } from 'src/recipients/recipient'
|
|
10
7
|
import { useDispatch, useSelector } from 'src/redux/hooks'
|
|
@@ -16,7 +13,7 @@ const useFetchRecipientVerificationStatus = () => {
|
|
|
16
13
|
)
|
|
17
14
|
|
|
18
15
|
const e164NumberToAddress = useSelector(e164NumberToAddressSelector)
|
|
19
|
-
const
|
|
16
|
+
const addressToVerifiedBy = useSelector(addressToVerifiedBySelector)
|
|
20
17
|
const phoneNumberVerified = useSelector(phoneNumberVerifiedSelector)
|
|
21
18
|
const dispatch = useDispatch()
|
|
22
19
|
|
|
@@ -36,9 +33,7 @@ const useFetchRecipientVerificationStatus = () => {
|
|
|
36
33
|
) {
|
|
37
34
|
dispatch(fetchAddressesAndValidate(selectedRecipient.e164PhoneNumber))
|
|
38
35
|
} else if (selectedRecipient?.address) {
|
|
39
|
-
if (
|
|
40
|
-
setRecipientVerificationStatus(RecipientVerificationStatus.VERIFIED)
|
|
41
|
-
} else if (phoneNumberVerified) {
|
|
36
|
+
if (phoneNumberVerified) {
|
|
42
37
|
dispatch(fetchAddressVerification(selectedRecipient.address))
|
|
43
38
|
} else {
|
|
44
39
|
setRecipientVerificationStatus(RecipientVerificationStatus.UNVERIFIED)
|
|
@@ -49,12 +44,12 @@ const useFetchRecipientVerificationStatus = () => {
|
|
|
49
44
|
useEffect(() => {
|
|
50
45
|
if (recipient && recipientVerificationStatus === RecipientVerificationStatus.UNKNOWN) {
|
|
51
46
|
// e164NumberToAddress is updated after a successful phone number lookup,
|
|
52
|
-
//
|
|
47
|
+
// addressToVerifiedBy is updated after a successful address lookup
|
|
53
48
|
setRecipientVerificationStatus(
|
|
54
|
-
getRecipientVerificationStatus(recipient, e164NumberToAddress,
|
|
49
|
+
getRecipientVerificationStatus(recipient, e164NumberToAddress, addressToVerifiedBy)
|
|
55
50
|
)
|
|
56
51
|
}
|
|
57
|
-
}, [e164NumberToAddress,
|
|
52
|
+
}, [e164NumberToAddress, addressToVerifiedBy, recipient, recipientVerificationStatus])
|
|
58
53
|
|
|
59
54
|
return {
|
|
60
55
|
recipient,
|