wallet-stack 1.0.0-alpha.133 → 1.0.0-alpha.134

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.
@@ -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,
@@ -1,14 +1,17 @@
1
1
  import React, { useState } from 'react'
2
2
  import { useTranslation } from 'react-i18next'
3
3
  import { LayoutAnimation, StyleSheet, Text, View } from 'react-native'
4
+ import { formatShortenedAddress } from 'src/account/utils'
4
5
  import AccountNumber from 'src/components/AccountNumber'
5
6
  import Expandable from 'src/components/Expandable'
6
7
  import Touchable from 'src/components/Touchable'
8
+ import VerifiedBadge from 'src/icons/VerifiedBadge'
7
9
  import { Screens } from 'src/navigator/Screens'
8
10
  import { getDisplayName, Recipient, recipientHasNumber } from 'src/recipients/recipient'
11
+ import { useVerifierName } from 'src/recipients/verifier'
9
12
  import colors from 'src/styles/colors'
10
13
  import { typeScale } from 'src/styles/fonts'
11
- import { getDisplayNumberInternational } from 'src/utils/phoneNumbers'
14
+ import { Spacing } from 'src/styles/styles'
12
15
 
13
16
  interface Props {
14
17
  type: 'sent' | 'received' | 'withdrawn'
@@ -36,11 +39,30 @@ export default function UserSection({
36
39
  }
37
40
 
38
41
  const displayName = getDisplayName(recipient, t)
39
- const displayNumber = recipientHasNumber(recipient)
40
- ? getDisplayNumberInternational(recipient.e164PhoneNumber)
41
- : undefined
42
42
  const address = recipient.address || ''
43
43
 
44
+ const verifierName = useVerifierName(recipient.address)
45
+
46
+ // Show short address in the secondary row when the primary (displayName) isn't already
47
+ // a formatted address — i.e., when the recipient has a name or phone number so the
48
+ // address would add information rather than duplicating the primary.
49
+ const hasIdentity = !!recipient.name || recipientHasNumber(recipient)
50
+ const shortAddress =
51
+ hasIdentity && recipient.address ? formatShortenedAddress(recipient.address) : undefined
52
+
53
+ const secondaryContent = verifierName ? (
54
+ <View style={styles.secondaryContent}>
55
+ {!!shortAddress && <Text style={styles.secondaryText}>{shortAddress}</Text>}
56
+ <VerifiedBadge
57
+ color={colors.contentSecondary}
58
+ testID={testID ? `${testID}/VerifierBadge` : undefined}
59
+ />
60
+ <Text style={styles.secondaryText}>{verifierName}</Text>
61
+ </View>
62
+ ) : shortAddress ? (
63
+ <Text style={styles.secondaryText}>{shortAddress}</Text>
64
+ ) : null
65
+
44
66
  const sectionLabel = {
45
67
  received: t('receivedFrom'),
46
68
  sent: t('sentTo'),
@@ -54,16 +76,14 @@ export default function UserSection({
54
76
  <Text style={styles.sectionLabel}>{sectionLabel}</Text>
55
77
  <Touchable onPress={toggleExpanded} disabled={!expandable}>
56
78
  <>
57
- <Expandable isExpandable={expandable && !displayNumber} isExpanded={expanded}>
79
+ <Expandable isExpandable={expandable && !secondaryContent} isExpanded={expanded}>
58
80
  <Text style={styles.username} testID={`${testID}/name`}>
59
81
  {displayName}
60
82
  </Text>
61
83
  </Expandable>
62
- {!!displayNumber && (
63
- <Expandable isExpandable={expandable && !!displayNumber} isExpanded={expanded}>
64
- <Text style={styles.phoneNumber} testID={`${testID}/number`}>
65
- {displayNumber}
66
- </Text>
84
+ {!!secondaryContent && (
85
+ <Expandable isExpandable={expandable} isExpanded={expanded}>
86
+ <View testID={`${testID}/address`}>{secondaryContent}</View>
67
87
  </Expandable>
68
88
  )}
69
89
  </>
@@ -104,10 +124,16 @@ const styles = StyleSheet.create({
104
124
  username: {
105
125
  ...typeScale.bodyMedium,
106
126
  },
107
- phoneNumber: {
127
+ secondaryText: {
108
128
  ...typeScale.bodySmall,
109
129
  color: colors.contentSecondary,
110
130
  },
131
+ secondaryContent: {
132
+ flexDirection: 'row',
133
+ alignItems: 'center',
134
+ gap: Spacing.Tiny4,
135
+ flexShrink: 1,
136
+ },
111
137
  avatarContainer: {
112
138
  flex: 1,
113
139
  justifyContent: 'center',
@@ -47,7 +47,6 @@ import {
47
47
  mockClaimRewardTransaction,
48
48
  mockCusdAddress,
49
49
  mockCusdTokenId,
50
- mockDisplayNumber2,
51
50
  mockE164Number2,
52
51
  mockEarnClaimRewardTransaction,
53
52
  mockEarnDepositTransaction,
@@ -326,8 +325,8 @@ describe('TransactionDetailsScreen', () => {
326
325
  const nameComponent = getByTestId('TransferSent/name')
327
326
  expect(getElementText(nameComponent)).toEqual(mockName)
328
327
 
329
- const numberComponent = getByTestId('TransferSent/number')
330
- expect(getElementText(numberComponent)).toEqual(mockDisplayNumber2)
328
+ const addressComponent = getByTestId('TransferSent/address')
329
+ expect(getElementText(addressComponent)).toEqual('0x8C3b...ca4E')
331
330
 
332
331
  expect(getByTestId('TransactionDetails/FeeRowItem')).toHaveTextContent('0.01 CELO', {
333
332
  exact: false,
@@ -359,8 +358,8 @@ describe('TransactionDetailsScreen', () => {
359
358
  const nameComponent = getByTestId('TransferReceived/name')
360
359
  expect(getElementText(nameComponent)).toEqual(mockName)
361
360
 
362
- const numberComponent = getByTestId('TransferReceived/number')
363
- expect(getElementText(numberComponent)).toEqual(mockDisplayNumber2)
361
+ const addressComponent = getByTestId('TransferReceived/address')
362
+ expect(getElementText(addressComponent)).toEqual('0x8C3b...ca4E')
364
363
 
365
364
  const totalComponent = getByTestId('TotalLineItem/Total')
366
365
  expect(getElementText(totalComponent)).toEqual('€4.00')
@@ -381,7 +380,8 @@ describe('TransactionDetailsScreen', () => {
381
380
  const nameComponent = getByTestId('RewardReceived/name')
382
381
  expect(getElementText(nameComponent)).toEqual('feedItemRewardReceivedTitle')
383
382
 
384
- expect(queryByTestId('RewardReceived/number')).toBeNull()
383
+ // Rewards senders don't have a known verifier, so the verifier composite isn't shown.
384
+ expect(queryByTestId('RewardReceived/VerifierBadge')).toBeNull()
385
385
 
386
386
  const totalComponent = getByTestId('TotalLineItem/Total')
387
387
  expect(getElementText(totalComponent)).toEqual('€4.00')
@@ -69,17 +69,6 @@ export function getDisplayPhoneNumber(phoneNumber: string, defaultCountryCode: s
69
69
  }
70
70
  }
71
71
 
72
- export function getDisplayNumberInternational(e164PhoneNumber: string) {
73
- const countryCode = getCountryCode(e164PhoneNumber)
74
- const phoneDetails = parsePhoneNumber(e164PhoneNumber, (countryCode || '').toString())
75
- if (phoneDetails) {
76
- return phoneDetails.displayNumberInternational
77
- } else {
78
- // Fallback to input instead of showing nothing for invalid numbers
79
- return e164PhoneNumber
80
- }
81
- }
82
-
83
72
  export function isE164NumberStrict(phoneNumber: string) {
84
73
  try {
85
74
  const parsedPhoneNumber = phoneUtil.parse(phoneNumber)