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

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.
@@ -2174,6 +2174,7 @@
2174
2174
  "recipient": "Recipient",
2175
2175
  "explanation": "<0>{{name}}</0> has more than one wallet linked to the phone number. Choose which one to send to."
2176
2176
  },
2177
+ "unverifiedAddress": "Unverified",
2177
2178
  "sendSelectRecipient": {
2178
2179
  "searchText": "Search by name, phone, wallet...",
2179
2180
  "searchInputLabel": "To",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wallet-stack",
3
- "version": "1.0.0-alpha.136",
3
+ "version": "1.0.0-alpha.137",
4
4
  "author": "Valora Inc",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -172,6 +172,32 @@ describe('ReviewSummaryItemContact', () => {
172
172
  expect(subtitle).toHaveTextContent('MiniPay', { exact: false })
173
173
  })
174
174
 
175
+ it('shows the unverified warning in the subtitle for phone recipients with a known-unverified address', () => {
176
+ const address = '0x0123456789012345678901234567890123456789'
177
+ const recipient = {
178
+ name: 'John Doe',
179
+ e164PhoneNumber: '+222222222',
180
+ address,
181
+ } as Recipient
182
+ const tree = renderContact(recipient, {
183
+ identity: { addressToVerifiedBy: { [address]: null } },
184
+ })
185
+
186
+ const subtitle = tree.getByTestId('ContactItem/SecondaryValue')
187
+ expect(subtitle).toHaveTextContent('0x0123...6789', { exact: false })
188
+ expect(subtitle).toHaveTextContent('unverifiedAddress', { exact: false })
189
+ })
190
+
191
+ it('omits the unverified warning for address-only recipients (the address is already the primary value)', () => {
192
+ const address = '0x0123456789012345678901234567890123456789'
193
+ const recipient = { address } as Recipient
194
+ const tree = renderContact(recipient, {
195
+ identity: { addressToVerifiedBy: { [address]: null } },
196
+ })
197
+
198
+ expect(tree.queryByTestId('ContactItem/SecondaryValue')).toBeNull()
199
+ })
200
+
175
201
  it('logs an error if no name/phone/address exist', () => {
176
202
  const recipient = {} as Recipient
177
203
  const tree = renderContact(recipient)
@@ -11,6 +11,7 @@ import CustomHeader from 'src/components/header/CustomHeader'
11
11
  import SkeletonPlaceholder from 'src/components/SkeletonPlaceholder'
12
12
  import { formatValueToDisplay } from 'src/components/TokenDisplay'
13
13
  import Touchable from 'src/components/Touchable'
14
+ import AttentionIcon from 'src/icons/Attention'
14
15
  import InfoIcon from 'src/icons/InfoIcon'
15
16
  import WalletIcon from 'src/icons/navigator/Wallet'
16
17
  import PhoneIcon from 'src/icons/Phone'
@@ -126,17 +127,31 @@ export function ReviewSummaryItem(props: {
126
127
 
127
128
  function renderAddressAndVerifier(
128
129
  shortAddress: string | undefined,
129
- verifierName: string | undefined
130
+ verifierName: string | null | undefined
130
131
  ): ReactNode {
131
- if (!shortAddress && !verifierName) return undefined
132
+ if (!shortAddress && verifierName === undefined) return undefined
133
+ const isUnverified = verifierName === null
132
134
  return (
133
135
  <>
134
- {!!shortAddress && <Text style={styles.reviewSummaryItemSecondaryValue}>{shortAddress}</Text>}
135
- {!!verifierName && (
136
+ {!!shortAddress && (
137
+ <Text style={[styles.reviewSummaryItemSecondaryValue, isUnverified && styles.warningText]}>
138
+ {shortAddress}
139
+ </Text>
140
+ )}
141
+ {isUnverified ? (
136
142
  <>
137
- <VerifiedBadge color={colors.contentSecondary} />
138
- <Text style={styles.reviewSummaryItemSecondaryValue}>{verifierName}</Text>
143
+ <AttentionIcon size={14} color={colors.warningPrimary} />
144
+ <Text style={[styles.reviewSummaryItemSecondaryValue, styles.warningText]}>
145
+ <Trans i18nKey="unverifiedAddress" />
146
+ </Text>
139
147
  </>
148
+ ) : (
149
+ !!verifierName && (
150
+ <>
151
+ <VerifiedBadge color={colors.contentSecondary} />
152
+ <Text style={styles.reviewSummaryItemSecondaryValue}>{verifierName}</Text>
153
+ </>
154
+ )
140
155
  )}
141
156
  </>
142
157
  )
@@ -155,6 +170,7 @@ export function ReviewSummaryItemContact({
155
170
  const phone = recipient.displayNumber || recipient.e164PhoneNumber
156
171
  // For recipients with a phone mapping, surface the resolved on-chain address (and verifier,
157
172
  // if known) as a subtitle so the user can verify the actual destination they are signing.
173
+ // When the address is known to be unverified, swap the verified badge for a warning.
158
174
  const shortAddress = recipient.address ? formatShortenedAddress(recipient.address) : undefined
159
175
  const phoneSubtitle = renderAddressAndVerifier(shortAddress, verifierName)
160
176
 
@@ -169,7 +185,9 @@ export function ReviewSummaryItemContact({
169
185
  if (recipient.address) {
170
186
  return {
171
187
  title: recipient.address,
172
- subtitle: renderAddressAndVerifier(undefined, verifierName),
188
+ // For plain wallet recipients, suppress the unverified warning
189
+ // by collapsing `null` to `undefined`.
190
+ subtitle: renderAddressAndVerifier(undefined, verifierName ?? undefined),
173
191
  icon: WalletIcon,
174
192
  }
175
193
  }
@@ -513,6 +531,9 @@ const styles = StyleSheet.create({
513
531
  ...typeScale.bodySmall,
514
532
  color: colors.contentSecondary,
515
533
  },
534
+ warningText: {
535
+ color: colors.warningPrimary,
536
+ },
516
537
  reviewSummaryItemSecondaryValueWrapper: {
517
538
  flexDirection: 'row',
518
539
  gap: Spacing.Smallest8,
@@ -13,8 +13,10 @@ export function isKnownVerifier(verifier: string | null | undefined): verifier i
13
13
  return !!verifier && Object.hasOwn(VERIFIERS, verifier)
14
14
  }
15
15
 
16
- export function useVerifierName(address: string | undefined): string | undefined {
16
+ export function useVerifierName(address: string | undefined): string | null | undefined {
17
17
  const addressToVerifiedBy = useSelector(addressToVerifiedBySelector)
18
- const verifier = address ? addressToVerifiedBy[address.toLowerCase()] : undefined
19
- return isKnownVerifier(verifier) ? VERIFIERS[verifier].name : undefined
18
+ if (!address) return undefined
19
+ const verifier = addressToVerifiedBy[address.toLowerCase()]
20
+ if (isKnownVerifier(verifier)) return VERIFIERS[verifier].name
21
+ return verifier === null ? null : undefined
20
22
  }
@@ -23,6 +23,7 @@ import {
23
23
  mockCusdTokenBalance,
24
24
  mockCusdTokenId,
25
25
  mockPoofTokenId,
26
+ mockRecipient,
26
27
  mockTokenBalances,
27
28
  mockTokenTransactionData,
28
29
  } from 'test/values'
@@ -174,7 +175,27 @@ describe('SendConfirmation', () => {
174
175
  it('shows the unknown address warning when the recipient address is not a known app user', () => {
175
176
  const { getByTestId } = renderScreen(mockSendConfirmationProps, {
176
177
  identity: {
177
- addressToVerifiedBy: { [mockAccount]: null },
178
+ addressToVerifiedBy: { [mockAccount.toLowerCase()]: null },
179
+ },
180
+ })
181
+
182
+ expect(getByTestId('UnknownAddressInfo')).toBeTruthy()
183
+ })
184
+
185
+ it('shows the unknown address warning for phone recipients whose resolved address is known-unverified', () => {
186
+ const phoneRecipientProps = getMockStackScreenProps(Screens.SendConfirmation, {
187
+ ...mockBaseScreenProps,
188
+ transactionData: {
189
+ ...mockTokenTransactionData,
190
+ recipient: { ...mockRecipient, address: mockAccount },
191
+ },
192
+ prepareTransactionsResult: getSerializablePreparedTransactionsPossible(
193
+ mockPrepareTransactionsResultPossible
194
+ ),
195
+ })
196
+ const { getByTestId } = renderScreen(phoneRecipientProps, {
197
+ identity: {
198
+ addressToVerifiedBy: { [mockAccount.toLowerCase()]: null },
178
199
  },
179
200
  })
180
201
 
@@ -184,7 +205,7 @@ describe('SendConfirmation', () => {
184
205
  it('does not show the unknown address warning when the recipient address is verified', () => {
185
206
  const { queryByTestId } = renderScreen(mockSendConfirmationProps, {
186
207
  identity: {
187
- addressToVerifiedBy: { [mockAccount]: 'valora' },
208
+ addressToVerifiedBy: { [mockAccount.toLowerCase()]: 'valora' },
188
209
  },
189
210
  })
190
211
 
@@ -30,7 +30,6 @@ import { getLocalCurrencyCode, getLocalCurrencySymbol } from 'src/localCurrency/
30
30
  import { noHeader } from 'src/navigator/Headers'
31
31
  import { Screens } from 'src/navigator/Screens'
32
32
  import { StackParamList } from 'src/navigator/types'
33
- import { RecipientType } from 'src/recipients/recipient'
34
33
  import { useDispatch, useSelector } from 'src/redux/hooks'
35
34
  import { sendPayment } from 'src/send/actions'
36
35
  import { isSendingSelector } from 'src/send/selectors'
@@ -93,9 +92,7 @@ export default function SendConfirmation({ route: { params } }: Props) {
93
92
  const walletAddress = useSelector(walletAddressSelector)
94
93
  const addressToVerifiedBy = useSelector(addressToVerifiedBySelector)
95
94
  const showUnknownAddressInfo =
96
- recipient.recipientType === RecipientType.Address &&
97
- !!recipient.address &&
98
- addressToVerifiedBy[recipient.address] === null
95
+ !!recipient.address && addressToVerifiedBy[recipient.address.toLowerCase()] === null
99
96
 
100
97
  const feeCurrencies = useSelector((state) => feeCurrenciesSelector(state, tokenInfo!.networkId))
101
98
  const {