wallet-stack 1.0.0-alpha.137 → 1.0.0-alpha.139

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wallet-stack",
3
- "version": "1.0.0-alpha.137",
3
+ "version": "1.0.0-alpha.139",
4
4
  "author": "Valora Inc",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
package/src/app/saga.ts CHANGED
@@ -260,9 +260,8 @@ function* watchDeepLinks() {
260
260
 
261
261
  export function* handleOpenUrl(action: OpenUrlAction) {
262
262
  const { url, openExternal, isSecureOrigin } = action
263
- const walletConnectEnabled: boolean = yield* call(isWalletConnectEnabled, url)
264
263
  Logger.debug(TAG, 'Handling url', url)
265
- if (isDeepLink(url) || (walletConnectEnabled && isWalletConnectDeepLink(url))) {
264
+ if (isDeepLink(url) || (isWalletConnectDeepLink(url) && (yield* call(isWalletConnectEnabled)))) {
266
265
  // Handle celo links directly, this avoids showing the "Open with App" sheet on Android
267
266
  yield* call(handleDeepLink, openDeepLink(url, isSecureOrigin))
268
267
  } else if (/^https?:\/\//i.test(url) === true && !openExternal) {
package/src/dapps/saga.ts CHANGED
@@ -43,8 +43,10 @@ export function* handleOpenDapp(action: PayloadAction<DappSelectedAction>) {
43
43
  ).inAppWebviewEnabled
44
44
 
45
45
  if (dappsWebViewEnabled) {
46
- const walletConnectEnabled: boolean = yield* call(isWalletConnectEnabled, dappUrl)
47
- if (isDeepLink(dappUrl) || (walletConnectEnabled && isWalletConnectDeepLink(dappUrl))) {
46
+ if (
47
+ isDeepLink(dappUrl) ||
48
+ (isWalletConnectDeepLink(dappUrl) && (yield* call(isWalletConnectEnabled)))
49
+ ) {
48
50
  yield* call(handleDeepLink, openDeepLink(dappUrl, true))
49
51
  } else {
50
52
  navigate(Screens.WebViewScreen, { uri: dappUrl })
@@ -42,7 +42,9 @@ type SendEnterAmountParams = {
42
42
  origin: SendOrigin
43
43
  forceTokenId?: boolean
44
44
  defaultTokenIdOverride?: string
45
- isMiniPayRecipient?: boolean
45
+ // Set to true when the caller has just performed a fresh phone-number lookup
46
+ // (e.g. recipient picker) so the enter-amount screen can skip re-fetching mappings.
47
+ skipRecipientLookup?: boolean
46
48
  }
47
49
 
48
50
  interface SelectRecipientAddressParams {
@@ -88,11 +88,9 @@ export function* handleQRCodeDefault({
88
88
  }: HandleQRCodeDetectedAction) {
89
89
  AppAnalytics.track(QrScreenEvents.qr_scanned, qrCode)
90
90
 
91
- const walletConnectEnabled: boolean = yield* call(isWalletConnectEnabled, qrCode.data)
92
-
93
91
  // TODO there's some duplication with deep links handing
94
92
  // would be nice to refactor this
95
- if (qrCode.data.startsWith('wc:') && walletConnectEnabled) {
93
+ if (qrCode.data.startsWith('wc:') && (yield* call(isWalletConnectEnabled))) {
96
94
  yield* fork(handleLoadingWithTimeout, WalletConnectPairingOrigin.Scan)
97
95
  yield* call(initialiseWalletConnect, qrCode.data, WalletConnectPairingOrigin.Scan)
98
96
  return
@@ -30,6 +30,7 @@ import { useSelector } from 'src/redux/hooks'
30
30
  import EnterAmountOptions from 'src/send/EnterAmountOptions'
31
31
  import { AmountEnteredIn } from 'src/send/types'
32
32
  import { typeScale } from 'src/styles/fonts'
33
+ import Colors from 'src/styles/colors'
33
34
  import { Spacing } from 'src/styles/styles'
34
35
  import { feeCurrenciesSelector } from 'src/tokens/selectors'
35
36
  import { TokenBalance } from 'src/tokens/slice'
@@ -69,6 +70,7 @@ interface Props {
69
70
  ProceedComponent: ComponentType<ProceedComponentProps>
70
71
  disableBalanceCheck?: boolean
71
72
  filterChips?: FilterChip<TokenBalance>[]
73
+ recipientSlot?: React.ReactNode
72
74
  }
73
75
 
74
76
  export const SendProceed = ({
@@ -111,6 +113,7 @@ export default function EnterAmount({
111
113
  ProceedComponent,
112
114
  disableBalanceCheck = false,
113
115
  filterChips,
116
+ recipientSlot,
114
117
  }: Props) {
115
118
  const { t } = useTranslation()
116
119
  const insets = useSafeAreaInsets()
@@ -253,6 +256,13 @@ export default function EnterAmount({
253
256
  onOpenTokenPicker={tokenSelectionDisabled ? undefined : onOpenTokenPicker}
254
257
  />
255
258
 
259
+ {!!recipientSlot && (
260
+ <>
261
+ <View style={styles.connectorLine} />
262
+ {recipientSlot}
263
+ </>
264
+ )}
265
+
256
266
  {token &&
257
267
  prepareTransactionsResult?.type !== 'not-enough-balance-for-gas' &&
258
268
  !!networkFee && (
@@ -384,4 +394,11 @@ const styles = StyleSheet.create({
384
394
  paddingHorizontal: Spacing.Regular16,
385
395
  borderRadius: 16,
386
396
  },
397
+ connectorLine: {
398
+ width: 2,
399
+ height: 12,
400
+ backgroundColor: Colors.borderPrimary,
401
+ alignSelf: 'center',
402
+ marginVertical: Spacing.Tiny4,
403
+ },
387
404
  })
@@ -102,7 +102,7 @@ describe('SelectRecipientAddress', () => {
102
102
  expect(queryByTestId(`SelectRecipientAddress/Row/${mockAccount3.toLowerCase()}`)).toBeNull()
103
103
  })
104
104
 
105
- it('navigates to SendEnterAmount on Valora row tap with isMiniPayRecipient=false', () => {
105
+ it('navigates to SendEnterAmount on Valora row tap', () => {
106
106
  const { getByTestId } = renderScreen({})
107
107
  fireEvent.press(getByTestId(`SelectRecipientAddress/Row/${mockAccount2.toLowerCase()}`))
108
108
 
@@ -115,7 +115,7 @@ describe('SelectRecipientAddress', () => {
115
115
  address: mockAccount2.toLowerCase(),
116
116
  },
117
117
  origin: SendOrigin.AppSendFlow,
118
- isMiniPayRecipient: false,
118
+ skipRecipientLookup: true,
119
119
  })
120
120
  expect(AppAnalytics.track).toHaveBeenCalledWith(
121
121
  SendEvents.send_select_recipient_address_select,
@@ -123,7 +123,7 @@ describe('SelectRecipientAddress', () => {
123
123
  )
124
124
  })
125
125
 
126
- it('navigates to SendEnterAmount on MiniPay row tap with isMiniPayRecipient=true', () => {
126
+ it('navigates to SendEnterAmount on MiniPay row tap', () => {
127
127
  const { getByTestId } = renderScreen({})
128
128
  fireEvent.press(getByTestId(`SelectRecipientAddress/Row/${mockAccount3.toLowerCase()}`))
129
129
 
@@ -136,7 +136,7 @@ describe('SelectRecipientAddress', () => {
136
136
  address: mockAccount3.toLowerCase(),
137
137
  },
138
138
  origin: SendOrigin.AppSendFlow,
139
- isMiniPayRecipient: true,
139
+ skipRecipientLookup: true,
140
140
  })
141
141
  expect(AppAnalytics.track).toHaveBeenCalledWith(
142
142
  SendEvents.send_select_recipient_address_select,
@@ -1,35 +1,27 @@
1
1
  import { NativeStackScreenProps } from '@react-navigation/native-stack'
2
2
  import React, { useEffect } from 'react'
3
3
  import { Trans, useTranslation } from 'react-i18next'
4
- import { Image, ScrollView, StyleSheet, Text, View } from 'react-native'
4
+ import { ScrollView, StyleSheet, Text } from 'react-native'
5
5
  import { SafeAreaView } from 'react-native-safe-area-context'
6
- import { formatShortenedAddress } from 'src/account/utils'
7
6
  import AppAnalytics from 'src/analytics/AppAnalytics'
8
7
  import { SendEvents } from 'src/analytics/Events'
9
8
  import BackButton from 'src/components/BackButton'
10
- import Touchable from 'src/components/Touchable'
11
9
  import CustomHeader from 'src/components/header/CustomHeader'
12
- import VerifiedBadge from 'src/icons/VerifiedBadge'
13
10
  import { addressToVerifiedBySelector, e164NumberToAddressSelector } from 'src/identity/selectors'
14
11
  import { noHeader } from 'src/navigator/Headers'
15
12
  import { navigate } from 'src/navigator/NavigationService'
16
13
  import { Screens } from 'src/navigator/Screens'
17
14
  import { StackParamList } from 'src/navigator/types'
18
15
  import { getDisplayName } from 'src/recipients/recipient'
19
- import { VERIFIERS, Verifier, isKnownVerifier } from 'src/recipients/verifier'
16
+ import { Verifier, isKnownVerifier } from 'src/recipients/verifier'
20
17
  import { useSelector } from 'src/redux/hooks'
18
+ import SelectRecipientAddressList from 'src/send/SelectRecipientAddressList'
21
19
  import Colors from 'src/styles/colors'
22
20
  import { typeScale } from 'src/styles/fonts'
23
21
  import { Spacing } from 'src/styles/styles'
24
22
 
25
23
  type Props = NativeStackScreenProps<StackParamList, Screens.SelectRecipientAddress>
26
24
 
27
- const ICON_SIZE = 40
28
-
29
- function VerifierIcon({ verifier }: { verifier: Verifier }) {
30
- return <Image source={VERIFIERS[verifier].icon} style={styles.icon} resizeMode="contain" />
31
- }
32
-
33
25
  function SelectRecipientAddress({ route }: Props) {
34
26
  const { t } = useTranslation()
35
27
  const { recipient, origin, forceTokenId, defaultTokenIdOverride } = route.params
@@ -63,7 +55,7 @@ function SelectRecipientAddress({ route }: Props) {
63
55
  address,
64
56
  },
65
57
  origin,
66
- isMiniPayRecipient: verifier === 'minipay',
58
+ skipRecipientLookup: true,
67
59
  })
68
60
  }
69
61
 
@@ -83,24 +75,7 @@ function SelectRecipientAddress({ route }: Props) {
83
75
  <Text style={styles.explanationName} />
84
76
  </Trans>
85
77
  </Text>
86
- {verifiedEntries.map(({ address, verifier }) => (
87
- <Touchable
88
- key={address}
89
- onPress={() => onSelectAddress(address, verifier)}
90
- testID={`SelectRecipientAddress/Row/${address}`}
91
- >
92
- <View style={styles.row}>
93
- <VerifierIcon verifier={verifier} />
94
- <View style={styles.rowContent}>
95
- <Text style={styles.address}>{formatShortenedAddress(address)}</Text>
96
- <View style={styles.verifier}>
97
- <VerifiedBadge color={Colors.contentSecondary} />
98
- <Text style={styles.verifierName}>{VERIFIERS[verifier].name}</Text>
99
- </View>
100
- </View>
101
- </View>
102
- </Touchable>
103
- ))}
78
+ <SelectRecipientAddressList entries={verifiedEntries} onSelectAddress={onSelectAddress} />
104
79
  </ScrollView>
105
80
  </SafeAreaView>
106
81
  )
@@ -131,32 +106,6 @@ const styles = StyleSheet.create({
131
106
  explanationName: {
132
107
  fontWeight: 'bold',
133
108
  },
134
- icon: {
135
- width: ICON_SIZE,
136
- height: ICON_SIZE,
137
- },
138
- row: {
139
- flexDirection: 'row',
140
- alignItems: 'center',
141
- paddingHorizontal: Spacing.Regular16,
142
- paddingVertical: Spacing.Regular16,
143
- },
144
- rowContent: {
145
- flex: 1,
146
- marginLeft: Spacing.Small12,
147
- },
148
- address: {
149
- ...typeScale.labelMedium,
150
- },
151
- verifier: {
152
- flexDirection: 'row',
153
- alignItems: 'center',
154
- gap: Spacing.Tiny4,
155
- },
156
- verifierName: {
157
- ...typeScale.bodySmall,
158
- color: Colors.contentSecondary,
159
- },
160
109
  })
161
110
 
162
111
  SelectRecipientAddress.navigationOptions = noHeader
@@ -0,0 +1,68 @@
1
+ import { fireEvent, render } from '@testing-library/react-native'
2
+ import * as React from 'react'
3
+ import SelectRecipientAddressList from 'src/send/SelectRecipientAddressList'
4
+
5
+ const mockAddress = '0x0000000000000000000000000000000000000001'
6
+ const mockAddress2 = '0x0000000000000000000000000000000000000002'
7
+
8
+ describe('SelectRecipientAddressList', () => {
9
+ it('renders one row per entry with the verifier name and a shortened address', () => {
10
+ const { getByTestId } = render(
11
+ <SelectRecipientAddressList
12
+ entries={[
13
+ { address: mockAddress, verifier: 'valora' },
14
+ { address: mockAddress2, verifier: 'minipay' },
15
+ ]}
16
+ onSelectAddress={jest.fn()}
17
+ />
18
+ )
19
+
20
+ const valoraRow = getByTestId(`SelectRecipientAddress/Row/${mockAddress}`)
21
+ expect(valoraRow).toHaveTextContent('Valora', { exact: false })
22
+ expect(valoraRow).toHaveTextContent('0x0000...0001', { exact: false })
23
+
24
+ const minipayRow = getByTestId(`SelectRecipientAddress/Row/${mockAddress2}`)
25
+ expect(minipayRow).toHaveTextContent('MiniPay', { exact: false })
26
+ expect(minipayRow).toHaveTextContent('0x0000...0002', { exact: false })
27
+ })
28
+
29
+ it('renders an unverified row with a warning label when verifier is null', () => {
30
+ const { getByTestId } = render(
31
+ <SelectRecipientAddressList
32
+ entries={[{ address: mockAddress, verifier: null }]}
33
+ onSelectAddress={jest.fn()}
34
+ />
35
+ )
36
+
37
+ const row = getByTestId(`SelectRecipientAddress/Row/${mockAddress}`)
38
+ expect(row).toHaveTextContent('unverifiedAddress', { exact: false })
39
+ expect(row).toHaveTextContent('0x0000...0001', { exact: false })
40
+ })
41
+
42
+ it('invokes onSelectAddress with null verifier when an unverified row is tapped', () => {
43
+ const onSelectAddress = jest.fn()
44
+ const { getByTestId } = render(
45
+ <SelectRecipientAddressList
46
+ entries={[{ address: mockAddress, verifier: null }]}
47
+ onSelectAddress={onSelectAddress}
48
+ />
49
+ )
50
+
51
+ fireEvent.press(getByTestId(`SelectRecipientAddress/Row/${mockAddress}`))
52
+ expect(onSelectAddress).toHaveBeenCalledWith(mockAddress, null)
53
+ })
54
+
55
+ it('invokes onSelectAddress with the row address and verifier when tapped', () => {
56
+ const onSelectAddress = jest.fn()
57
+ const { getByTestId } = render(
58
+ <SelectRecipientAddressList
59
+ entries={[{ address: mockAddress, verifier: 'minipay' }]}
60
+ onSelectAddress={onSelectAddress}
61
+ />
62
+ )
63
+
64
+ fireEvent.press(getByTestId(`SelectRecipientAddress/Row/${mockAddress}`))
65
+
66
+ expect(onSelectAddress).toHaveBeenCalledWith(mockAddress, 'minipay')
67
+ })
68
+ })
@@ -0,0 +1,117 @@
1
+ import React from 'react'
2
+ import { useTranslation } from 'react-i18next'
3
+ import { Image, StyleSheet, Text, View } from 'react-native'
4
+ import { formatShortenedAddress } from 'src/account/utils'
5
+ import Touchable from 'src/components/Touchable'
6
+ import AttentionIcon from 'src/icons/Attention'
7
+ import WalletIcon from 'src/icons/navigator/Wallet'
8
+ import VerifiedBadge from 'src/icons/VerifiedBadge'
9
+ import { VERIFIERS, Verifier } from 'src/recipients/verifier'
10
+ import Colors from 'src/styles/colors'
11
+ import { typeScale } from 'src/styles/fonts'
12
+ import { Spacing } from 'src/styles/styles'
13
+
14
+ const ICON_SIZE = 40
15
+
16
+ function VerifierIcon({ verifier }: { verifier: Verifier | null }) {
17
+ if (!verifier) {
18
+ return (
19
+ <View style={styles.unverifiedIcon}>
20
+ <WalletIcon color={Colors.contentPrimary} size={24} />
21
+ </View>
22
+ )
23
+ }
24
+ return <Image source={VERIFIERS[verifier].icon} style={styles.icon} resizeMode="contain" />
25
+ }
26
+
27
+ export interface Entry {
28
+ address: string
29
+ verifier: Verifier | null
30
+ }
31
+
32
+ interface Props {
33
+ entries: Entry[]
34
+ onSelectAddress(address: string, verifier: Verifier | null): void
35
+ // Compact rows for the in-sheet variant — the standalone screen keeps the original padding.
36
+ compact?: boolean
37
+ }
38
+
39
+ export default function SelectRecipientAddressList({ entries, onSelectAddress, compact }: Props) {
40
+ const { t } = useTranslation()
41
+ return (
42
+ <>
43
+ {entries.map(({ address, verifier }) => (
44
+ <Touchable
45
+ key={address}
46
+ onPress={() => onSelectAddress(address, verifier)}
47
+ testID={`SelectRecipientAddress/Row/${address}`}
48
+ >
49
+ <View style={[styles.row, compact && styles.rowCompact]}>
50
+ <VerifierIcon verifier={verifier} />
51
+ <View style={styles.rowContent}>
52
+ <Text style={styles.address}>{formatShortenedAddress(address)}</Text>
53
+ {verifier ? (
54
+ <View style={styles.verifier}>
55
+ <VerifiedBadge color={Colors.contentSecondary} />
56
+ <Text style={styles.verifierName}>{VERIFIERS[verifier].name}</Text>
57
+ </View>
58
+ ) : (
59
+ <View style={styles.verifier}>
60
+ <AttentionIcon size={14} color={Colors.warningPrimary} />
61
+ <Text style={styles.unverifiedText}>{t('unverifiedAddress')}</Text>
62
+ </View>
63
+ )}
64
+ </View>
65
+ </View>
66
+ </Touchable>
67
+ ))}
68
+ </>
69
+ )
70
+ }
71
+
72
+ const styles = StyleSheet.create({
73
+ icon: {
74
+ width: ICON_SIZE,
75
+ height: ICON_SIZE,
76
+ },
77
+ unverifiedIcon: {
78
+ width: ICON_SIZE,
79
+ height: ICON_SIZE,
80
+ borderRadius: ICON_SIZE / 2,
81
+ backgroundColor: Colors.backgroundSecondary,
82
+ borderWidth: 1,
83
+ borderColor: Colors.borderPrimary,
84
+ alignItems: 'center',
85
+ justifyContent: 'center',
86
+ },
87
+ row: {
88
+ flexDirection: 'row',
89
+ alignItems: 'center',
90
+ paddingHorizontal: Spacing.Regular16,
91
+ paddingVertical: Spacing.Regular16,
92
+ },
93
+ rowCompact: {
94
+ paddingHorizontal: 0,
95
+ paddingVertical: Spacing.Smallest8,
96
+ },
97
+ rowContent: {
98
+ flex: 1,
99
+ marginLeft: Spacing.Small12,
100
+ },
101
+ address: {
102
+ ...typeScale.labelMedium,
103
+ },
104
+ verifier: {
105
+ flexDirection: 'row',
106
+ alignItems: 'center',
107
+ gap: Spacing.Tiny4,
108
+ },
109
+ verifierName: {
110
+ ...typeScale.bodySmall,
111
+ color: Colors.contentSecondary,
112
+ },
113
+ unverifiedText: {
114
+ ...typeScale.bodySmall,
115
+ color: Colors.warningPrimary,
116
+ },
117
+ })
@@ -0,0 +1,96 @@
1
+ import { fireEvent, render } from '@testing-library/react-native'
2
+ import * as React from 'react'
3
+ import { Provider } from 'react-redux'
4
+ import { Recipient, RecipientType } from 'src/recipients/recipient'
5
+ import SelectedRecipientCard from 'src/send/SelectedRecipientCard'
6
+ import { createMockStore } from 'test/utils'
7
+ import { mockAccount, mockAccount2, mockE164Number, mockName } from 'test/values'
8
+
9
+ jest.mock('src/components/BottomSheet', () => {
10
+ const React = require('react')
11
+ const { View } = require('react-native')
12
+ // Render the sheet's children inline so the verified rows are queryable in tests.
13
+ const BottomSheet = React.forwardRef(({ children }: any, _ref: any) =>
14
+ React.createElement(View, { testID: 'BottomSheet' }, children)
15
+ )
16
+ return { __esModule: true, default: BottomSheet }
17
+ })
18
+
19
+ const phoneRecipient = {
20
+ name: mockName,
21
+ e164PhoneNumber: mockE164Number,
22
+ recipientType: RecipientType.PhoneNumber,
23
+ address: mockAccount,
24
+ } as Recipient & { address: string }
25
+
26
+ function renderCard(
27
+ props: Partial<React.ComponentProps<typeof SelectedRecipientCard>> = {},
28
+ storeOverrides: Record<string, unknown> = {}
29
+ ) {
30
+ const onSelectAddress = jest.fn()
31
+ const utils = render(
32
+ <Provider store={createMockStore(storeOverrides)}>
33
+ <SelectedRecipientCard
34
+ recipient={phoneRecipient}
35
+ status="verified"
36
+ verifiedAddresses={[]}
37
+ originalAddress={phoneRecipient.address}
38
+ onSelectAddress={onSelectAddress}
39
+ {...props}
40
+ />
41
+ </Provider>
42
+ )
43
+ return { ...utils, onSelectAddress }
44
+ }
45
+
46
+ describe('SelectedRecipientCard', () => {
47
+ it('shows the recipient name as the title and a spinner while the lookup is loading', () => {
48
+ const { getByTestId } = renderCard({ status: 'loading' })
49
+ expect(getByTestId('SelectedRecipientCard')).toHaveTextContent(mockName, { exact: false })
50
+ expect(getByTestId('SelectedRecipientCard/Spinner')).toBeTruthy()
51
+ })
52
+
53
+ it('shows an unverified warning subtitle when the resolved address is known-unverified', () => {
54
+ const { getByTestId } = renderCard({ status: 'unverified' })
55
+ expect(getByTestId('SelectedRecipientCard/Unverified')).toHaveTextContent('unverifiedAddress', {
56
+ exact: false,
57
+ })
58
+ })
59
+
60
+ it('is not tappable when there is only one address option (the current one)', () => {
61
+ const { getByTestId } = renderCard({
62
+ status: 'verified',
63
+ verifiedAddresses: [{ address: mockAccount.toLowerCase(), verifier: 'valora' }],
64
+ })
65
+ const touchable = getByTestId('SelectedRecipientCard/Touchable')
66
+ expect(touchable).toBeDisabled()
67
+ })
68
+
69
+ it('opens the sheet and invokes onSelectAddress when tapping a row', () => {
70
+ const { getByTestId, onSelectAddress } = renderCard({
71
+ status: 'verified',
72
+ verifiedAddresses: [
73
+ { address: mockAccount.toLowerCase(), verifier: 'valora' },
74
+ { address: mockAccount2.toLowerCase(), verifier: 'minipay' },
75
+ ],
76
+ })
77
+
78
+ fireEvent.press(getByTestId(`SelectRecipientAddress/Row/${mockAccount2.toLowerCase()}`))
79
+ expect(onSelectAddress).toHaveBeenCalledWith(mockAccount2.toLowerCase())
80
+ })
81
+
82
+ it('keeps the original (unverified) address selectable in the sheet alongside verified options', () => {
83
+ const { getByTestId, onSelectAddress } = renderCard({
84
+ status: 'verified',
85
+ // Verified set does not include the original address — common after the user picked a
86
+ // verified option from a recipient that came in via recents with a stale mapping.
87
+ verifiedAddresses: [{ address: mockAccount2.toLowerCase(), verifier: 'valora' }],
88
+ originalAddress: mockAccount,
89
+ recipient: { ...phoneRecipient, address: mockAccount2.toLowerCase() },
90
+ })
91
+
92
+ // The original (unverified) address still renders as a selectable row.
93
+ fireEvent.press(getByTestId(`SelectRecipientAddress/Row/${mockAccount}`))
94
+ expect(onSelectAddress).toHaveBeenCalledWith(mockAccount)
95
+ })
96
+ })