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.
@@ -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>(() => defaultToken ?? tokens[0])
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) => feeCurrenciesSelector(state, token.networkId))
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.tokenId,
162
- currentTokenAddress: token.address,
163
- currentNetworkId: token.networkId,
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
- {prepareTransactionsResult?.type !== 'not-enough-balance-for-gas' && !!networkFee && (
240
- <View style={styles.feeContainer}>
241
- <ReviewDetailsItem
242
- approx
243
- testID="SendEnterAmount/NetworkFee"
244
- type="token-amount"
245
- label={t('networkFee')}
246
- tokenAmount={networkFee.amount}
247
- localAmount={networkFee.localAmount}
248
- tokenInfo={networkFee.token}
249
- localCurrencySymbol={localCurrencySymbol}
250
- onInfoPress={() => feeInfoBottomSheetRef.current?.snapToIndex(0)}
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
- <FeeInfoBottomSheet forwardedRef={feeInfoBottomSheetRef} networkFee={networkFee} />
254
- </View>
255
- )}
272
+ <FeeInfoBottomSheet forwardedRef={feeInfoBottomSheetRef} networkFee={networkFee} />
273
+ </View>
274
+ )}
256
275
  </View>
257
276
 
258
- {showLowerAmountError && (
259
- <InLineNotification
260
- variant={NotificationVariant.Warning}
261
- title={t('sendEnterAmountScreen.insufficientBalanceWarning.title', {
262
- tokenSymbol: token.symbol,
263
- })}
264
- description={t('sendEnterAmountScreen.insufficientBalanceWarning.description', {
265
- tokenSymbol: token.symbol,
266
- })}
267
- style={styles.warning}
268
- testID="SendEnterAmount/NotEnoughBalanceWarning"
269
- />
270
- )}
271
- {showMaxAmountWarning && (
272
- <InLineNotification
273
- variant={NotificationVariant.Warning}
274
- title={t('sendEnterAmountScreen.maxAmountWarning.title')}
275
- description={t('sendEnterAmountScreen.maxAmountWarning.description', {
276
- feeTokenSymbol: prepareTransactionsResult.feeCurrency.symbol,
277
- })}
278
- style={styles.warning}
279
- testID="SendEnterAmount/MaxAmountWarning"
280
- />
281
- )}
282
- {showNotEnoughBalanceForGasWarning && (
283
- <InLineNotification
284
- variant={NotificationVariant.Warning}
285
- title={t('sendEnterAmountScreen.notEnoughBalanceForGasWarning.title', {
286
- feeTokenSymbol: prepareTransactionsResult.feeCurrencies[0].symbol,
287
- })}
288
- description={t('sendEnterAmountScreen.notEnoughBalanceForGasWarning.description', {
289
- feeTokenSymbol: prepareTransactionsResult.feeCurrencies[0].symbol,
290
- })}
291
- style={styles.warning}
292
- testID="SendEnterAmount/NotEnoughForGasWarning"
293
- />
294
- )}
295
- {prepareTransactionError && (
296
- <InLineNotification
297
- variant={NotificationVariant.Error}
298
- title={t('sendEnterAmountScreen.prepareTransactionError.title')}
299
- description={t('sendEnterAmountScreen.prepareTransactionError.description')}
300
- style={styles.warning}
301
- testID="SendEnterAmount/PrepareTransactionError"
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
- {children}
326
+ {children}
306
327
 
307
- <EnterAmountOptions
308
- onPressAmount={onSelectPercentageAmount}
309
- selectedAmount={selectedPercentage}
310
- testID="SendEnterAmount/AmountOptions"
311
- />
328
+ <EnterAmountOptions
329
+ onPressAmount={onSelectPercentageAmount}
330
+ selectedAmount={selectedPercentage}
331
+ testID="SendEnterAmount/AmountOptions"
332
+ />
312
333
 
313
- <ProceedComponent
314
- tokenAmount={processedAmounts.token.bignum}
315
- localAmount={processedAmounts.local.bignum}
316
- token={token}
317
- amountEnteredIn={amountType}
318
- onPressProceed={onPressProceed}
319
- disabled={disabled}
320
- showLoading={prepareTransactionsLoading}
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
- <Text style={styles.verifier}>{VERIFIERS[verifier].name}</Text>
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
- addressToVerificationStatus: { [mockAccount]: false },
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
- addressToVerificationStatus: { [mockAccount]: true },
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: { addressToVerificationStatus: {} },
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 { addressToVerificationStatusSelector } from 'src/identity/selectors'
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 addressToVerificationStatus = useSelector(addressToVerificationStatusSelector)
94
+ const addressToVerifiedBy = useSelector(addressToVerifiedBySelector)
95
95
  const showUnknownAddressInfo =
96
96
  recipient.recipientType === RecipientType.Address &&
97
97
  !!recipient.address &&
98
- addressToVerificationStatus[recipient.address] === false
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, getRecipientVerificationStatus } from 'src/recipients/recipient'
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
- jest
207
- .mocked(getRecipientVerificationStatus)
208
- .mockReturnValue(RecipientVerificationStatus.VERIFIED)
209
-
210
- const store = createMockStore(storeWithPhoneVerified)
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(defaultStore)
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
- jest
271
- .mocked(getRecipientVerificationStatus)
272
- .mockReturnValue(RecipientVerificationStatus.VERIFIED)
273
-
274
- const store = createMockStore(storeWithPhoneVerified)
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
- jest
303
- .mocked(getRecipientVerificationStatus)
304
- .mockReturnValue(RecipientVerificationStatus.UNVERIFIED)
305
-
306
- const store = createMockStore(storeWithPhoneVerified)
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(storeWithPhoneVerified)
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
- jest
377
- .mocked(getRecipientVerificationStatus)
378
- .mockReturnValue(RecipientVerificationStatus.UNVERIFIED)
379
-
380
- const store = createMockStore(storeWithPhoneVerified)
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 addressToVerificationStatus = useSelector(addressToVerificationStatusSelector)
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 (addressToVerificationStatus[selectedRecipient.address]) {
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
- // addressToVerificationStatus is updated after a successful address lookup
47
+ // addressToVerifiedBy is updated after a successful address lookup
53
48
  setRecipientVerificationStatus(
54
- getRecipientVerificationStatus(recipient, e164NumberToAddress, addressToVerificationStatus)
49
+ getRecipientVerificationStatus(recipient, e164NumberToAddress, addressToVerifiedBy)
55
50
  )
56
51
  }
57
- }, [e164NumberToAddress, addressToVerificationStatus, recipient, recipientVerificationStatus])
52
+ }, [e164NumberToAddress, addressToVerifiedBy, recipient, recipientVerificationStatus])
58
53
 
59
54
  return {
60
55
  recipient,