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.
- 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/SelectRecipientAddress.tsx +11 -13
- package/src/send/SendConfirmation.test.tsx +3 -3
- package/src/send/SendConfirmation.tsx +3 -3
- 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/identity/reducer.ts
CHANGED
|
@@ -31,12 +31,11 @@ export interface ImportContactProgress {
|
|
|
31
31
|
total: number
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
export interface AddressToVerificationStatus {
|
|
35
|
-
[address: string]: boolean | undefined
|
|
36
|
-
}
|
|
37
|
-
|
|
38
34
|
export interface AddressToVerifiedByType {
|
|
39
|
-
|
|
35
|
+
// undefined = never checked / unknown
|
|
36
|
+
// null = checked, no known verifier
|
|
37
|
+
// string = checked, verified by that verifier (e.g. "valora", "minipay")
|
|
38
|
+
[address: string]: string | null | undefined
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
interface State {
|
|
@@ -48,8 +47,6 @@ interface State {
|
|
|
48
47
|
// Has the user already been asked for contacts permission
|
|
49
48
|
askedContactsPermission: boolean
|
|
50
49
|
importContactsProgress: ImportContactProgress
|
|
51
|
-
// Mapping of address to verification status; undefined entries represent a loading state
|
|
52
|
-
addressToVerificationStatus: AddressToVerificationStatus
|
|
53
50
|
// Mapping of address to the entity that verified it (e.g. "valora", "minipay")
|
|
54
51
|
addressToVerifiedBy: AddressToVerifiedByType
|
|
55
52
|
lastSavedContactsHash: string | null
|
|
@@ -66,7 +63,6 @@ const initialState: State = {
|
|
|
66
63
|
current: 0,
|
|
67
64
|
total: 0,
|
|
68
65
|
},
|
|
69
|
-
addressToVerificationStatus: {},
|
|
70
66
|
addressToVerifiedBy: {},
|
|
71
67
|
lastSavedContactsHash: null,
|
|
72
68
|
shouldRefreshStoredPasswordHash: false,
|
|
@@ -144,21 +140,11 @@ export const reducer = (
|
|
|
144
140
|
e164NumberToAddress: state.e164NumberToAddress,
|
|
145
141
|
}
|
|
146
142
|
case Actions.FETCH_ADDRESS_VERIFICATION_STATUS:
|
|
147
|
-
// If the current status is false or does not exist, we set it to undefined
|
|
148
|
-
// to mark it as being in a loading state.
|
|
149
|
-
return {
|
|
150
|
-
...state,
|
|
151
|
-
addressToVerificationStatus: {
|
|
152
|
-
...state.addressToVerificationStatus,
|
|
153
|
-
[action.address]: state.addressToVerificationStatus[action.address] || undefined,
|
|
154
|
-
},
|
|
155
|
-
}
|
|
156
|
-
case Actions.ADDRESS_VERIFICATION_STATUS_RECEIVED:
|
|
157
143
|
return {
|
|
158
144
|
...state,
|
|
159
|
-
|
|
160
|
-
...state.
|
|
161
|
-
[action.address]:
|
|
145
|
+
addressToVerifiedBy: {
|
|
146
|
+
...state.addressToVerifiedBy,
|
|
147
|
+
[action.address]: undefined,
|
|
162
148
|
},
|
|
163
149
|
}
|
|
164
150
|
case Actions.CONTACTS_SAVED:
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { RootState } from 'src/redux/reducers'
|
|
2
2
|
|
|
3
3
|
export const e164NumberToAddressSelector = (state: RootState) => state.identity.e164NumberToAddress
|
|
4
|
-
export const addressToVerificationStatusSelector = (state: RootState) =>
|
|
5
|
-
state.identity.addressToVerificationStatus
|
|
6
4
|
export const addressToE164NumberSelector = (state: RootState) => state.identity.addressToE164Number
|
|
7
5
|
export const addressToVerifiedBySelector = (state: RootState) => state.identity.addressToVerifiedBy
|
|
8
6
|
export const importContactsProgressSelector = (state: RootState) =>
|
|
@@ -4,23 +4,12 @@ import 'react-native'
|
|
|
4
4
|
import { Provider } from 'react-redux'
|
|
5
5
|
import RecipientItem from 'src/recipients/RecipientItemV2'
|
|
6
6
|
import { createMockStore } from 'test/utils'
|
|
7
|
-
import {
|
|
8
|
-
mockAddressRecipient,
|
|
9
|
-
mockInvitableRecipient,
|
|
10
|
-
mockPhoneRecipient,
|
|
11
|
-
mockRecipient,
|
|
12
|
-
} from 'test/values'
|
|
7
|
+
import { mockAddressRecipient, mockInvitableRecipient, mockPhoneRecipient } from 'test/values'
|
|
13
8
|
|
|
14
9
|
describe('RecipientItemV2', () => {
|
|
15
|
-
it('renders
|
|
16
|
-
const {
|
|
17
|
-
<Provider
|
|
18
|
-
store={createMockStore({
|
|
19
|
-
identity: {
|
|
20
|
-
e164NumberToAddress: {},
|
|
21
|
-
},
|
|
22
|
-
})}
|
|
23
|
-
>
|
|
10
|
+
it('renders contact name and phone number', () => {
|
|
11
|
+
const { getByText } = render(
|
|
12
|
+
<Provider store={createMockStore()}>
|
|
24
13
|
<RecipientItem
|
|
25
14
|
recipient={mockInvitableRecipient}
|
|
26
15
|
onSelectRecipient={jest.fn()}
|
|
@@ -30,35 +19,23 @@ describe('RecipientItemV2', () => {
|
|
|
30
19
|
)
|
|
31
20
|
expect(getByText(mockInvitableRecipient.name)).toBeTruthy()
|
|
32
21
|
expect(getByText(mockInvitableRecipient.displayNumber)).toBeTruthy()
|
|
33
|
-
expect(queryByTestId('RecipientItem/AppIcon')).toBeFalsy()
|
|
34
|
-
expect(queryByTestId('RecipientItem/ActivityIndicator')).toBeFalsy()
|
|
35
22
|
})
|
|
36
23
|
|
|
37
|
-
it('renders
|
|
38
|
-
const {
|
|
39
|
-
<Provider
|
|
40
|
-
store={createMockStore({
|
|
41
|
-
identity: {
|
|
42
|
-
e164NumberToAddress: { [mockInvitableRecipient.e164PhoneNumber]: null },
|
|
43
|
-
},
|
|
44
|
-
})}
|
|
45
|
-
>
|
|
24
|
+
it('renders spinner while loading', () => {
|
|
25
|
+
const { getByTestId } = render(
|
|
26
|
+
<Provider store={createMockStore()}>
|
|
46
27
|
<RecipientItem
|
|
47
28
|
recipient={mockInvitableRecipient}
|
|
48
29
|
onSelectRecipient={jest.fn()}
|
|
49
|
-
loading={
|
|
30
|
+
loading={true}
|
|
50
31
|
/>
|
|
51
32
|
</Provider>
|
|
52
33
|
)
|
|
53
|
-
expect(
|
|
54
|
-
expect(getByText(mockInvitableRecipient.displayNumber)).toBeTruthy()
|
|
55
|
-
expect(queryByTestId('RecipientItem/AppIcon')).toBeFalsy()
|
|
56
|
-
expect(queryByTestId('RecipientItem/ActivityIndicator')).toBeFalsy()
|
|
34
|
+
expect(getByTestId('RecipientItem/ActivityIndicator')).toBeTruthy()
|
|
57
35
|
})
|
|
58
36
|
|
|
59
|
-
it('
|
|
60
|
-
const { queryByTestId
|
|
61
|
-
// default store includes a cached mapping
|
|
37
|
+
it('hides spinner when not loading', () => {
|
|
38
|
+
const { queryByTestId } = render(
|
|
62
39
|
<Provider store={createMockStore()}>
|
|
63
40
|
<RecipientItem
|
|
64
41
|
recipient={mockInvitableRecipient}
|
|
@@ -67,47 +44,9 @@ describe('RecipientItemV2', () => {
|
|
|
67
44
|
/>
|
|
68
45
|
</Provider>
|
|
69
46
|
)
|
|
70
|
-
expect(getByText(mockInvitableRecipient.name)).toBeTruthy()
|
|
71
|
-
expect(getByText(mockInvitableRecipient.displayNumber)).toBeTruthy()
|
|
72
|
-
expect(getByTestId('RecipientItem/AppIcon')).toBeTruthy()
|
|
73
|
-
expect(queryByTestId('RecipientItem/ActivityIndicator')).toBeFalsy()
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
it('renders correctly with app icon if address recipient is an app user', () => {
|
|
77
|
-
const { queryByTestId, getByText, getByTestId } = render(
|
|
78
|
-
// default store includes a cached mapping
|
|
79
|
-
<Provider
|
|
80
|
-
store={createMockStore({
|
|
81
|
-
identity: {
|
|
82
|
-
addressToVerificationStatus: {
|
|
83
|
-
[mockRecipient.address]: true,
|
|
84
|
-
},
|
|
85
|
-
},
|
|
86
|
-
})}
|
|
87
|
-
>
|
|
88
|
-
<RecipientItem recipient={mockRecipient} onSelectRecipient={jest.fn()} loading={false} />
|
|
89
|
-
</Provider>
|
|
90
|
-
)
|
|
91
|
-
expect(getByText(mockRecipient.name)).toBeTruthy()
|
|
92
|
-
expect(getByTestId('RecipientItem/AppIcon')).toBeTruthy()
|
|
93
47
|
expect(queryByTestId('RecipientItem/ActivityIndicator')).toBeFalsy()
|
|
94
48
|
})
|
|
95
49
|
|
|
96
|
-
it('renders correctly if loading is set to true', () => {
|
|
97
|
-
const { getByText, getByTestId } = render(
|
|
98
|
-
<Provider store={createMockStore()}>
|
|
99
|
-
<RecipientItem
|
|
100
|
-
recipient={mockInvitableRecipient}
|
|
101
|
-
onSelectRecipient={jest.fn()}
|
|
102
|
-
loading={true}
|
|
103
|
-
/>
|
|
104
|
-
</Provider>
|
|
105
|
-
)
|
|
106
|
-
expect(getByText(mockInvitableRecipient.name)).toBeTruthy()
|
|
107
|
-
expect(getByText(mockInvitableRecipient.displayNumber)).toBeTruthy()
|
|
108
|
-
expect(getByTestId('RecipientItem/ActivityIndicator')).toBeTruthy()
|
|
109
|
-
})
|
|
110
|
-
|
|
111
50
|
it('tapping item invokes onSelectRecipient', () => {
|
|
112
51
|
const mockSelectRecipient = jest.fn()
|
|
113
52
|
const { getByTestId } = render(
|
|
@@ -1,23 +1,16 @@
|
|
|
1
|
-
import React, { memo
|
|
1
|
+
import React, { memo } from 'react'
|
|
2
2
|
import { useTranslation } from 'react-i18next'
|
|
3
3
|
import { ActivityIndicator, Keyboard, StyleSheet, Text, View } from 'react-native'
|
|
4
4
|
import ContactCircle from 'src/components/ContactCircle'
|
|
5
5
|
import Touchable from 'src/components/Touchable'
|
|
6
6
|
import PhoneIcon from 'src/icons/Phone'
|
|
7
7
|
import WalletIcon from 'src/icons/navigator/Wallet'
|
|
8
|
-
import {
|
|
9
|
-
addressToVerificationStatusSelector,
|
|
10
|
-
e164NumberToAddressSelector,
|
|
11
|
-
} from 'src/identity/selectors'
|
|
12
|
-
import Checkmark from 'src/icons/Checkmark'
|
|
13
8
|
import {
|
|
14
9
|
Recipient,
|
|
15
|
-
RecipientType,
|
|
16
10
|
getDisplayDetail,
|
|
17
11
|
getDisplayName,
|
|
18
12
|
recipientHasNumber,
|
|
19
13
|
} from 'src/recipients/recipient'
|
|
20
|
-
import { useSelector } from 'src/redux/hooks'
|
|
21
14
|
import Colors from 'src/styles/colors'
|
|
22
15
|
import { typeScale } from 'src/styles/fonts'
|
|
23
16
|
import { Spacing } from 'src/styles/styles'
|
|
@@ -29,8 +22,6 @@ interface Props {
|
|
|
29
22
|
selected?: boolean
|
|
30
23
|
}
|
|
31
24
|
|
|
32
|
-
const ICON_SIZE = 10
|
|
33
|
-
|
|
34
25
|
function RecipientItem({ recipient, onSelectRecipient, loading, selected }: Props) {
|
|
35
26
|
const { t } = useTranslation()
|
|
36
27
|
|
|
@@ -39,17 +30,6 @@ function RecipientItem({ recipient, onSelectRecipient, loading, selected }: Prop
|
|
|
39
30
|
onSelectRecipient(recipient)
|
|
40
31
|
}
|
|
41
32
|
|
|
42
|
-
const e164NumberToAddress = useSelector(e164NumberToAddressSelector)
|
|
43
|
-
const addressToVerificationStatus = useSelector(addressToVerificationStatusSelector)
|
|
44
|
-
|
|
45
|
-
// TODO(ACT-980): avoid icon flash when a known contact is clicked
|
|
46
|
-
const showAppIcon = useMemo(() => {
|
|
47
|
-
if (recipient.recipientType === RecipientType.PhoneNumber) {
|
|
48
|
-
return recipient.e164PhoneNumber && !!e164NumberToAddress[recipient.e164PhoneNumber]
|
|
49
|
-
}
|
|
50
|
-
return recipient.address && addressToVerificationStatus[recipient.address]
|
|
51
|
-
}, [e164NumberToAddress, recipient])
|
|
52
|
-
|
|
53
33
|
return (
|
|
54
34
|
<Touchable onPress={onPress} testID="RecipientItem">
|
|
55
35
|
<View style={[styles.row, selected && styles.rowSelected]}>
|
|
@@ -62,11 +42,6 @@ function RecipientItem({ recipient, onSelectRecipient, loading, selected }: Prop
|
|
|
62
42
|
borderColor={Colors.borderPrimary}
|
|
63
43
|
DefaultIcon={() => renderDefaultIcon(recipient)} // no need to honor color props here since the color we need match the defaults
|
|
64
44
|
/>
|
|
65
|
-
{!!showAppIcon && (
|
|
66
|
-
<View style={styles.appIcon} testID="RecipientItem/AppIcon">
|
|
67
|
-
<Checkmark color={Colors.contentTertiary} height={ICON_SIZE} width={ICON_SIZE} />
|
|
68
|
-
</View>
|
|
69
|
-
)}
|
|
70
45
|
</View>
|
|
71
46
|
<View style={styles.contentContainer}>
|
|
72
47
|
<Text numberOfLines={1} ellipsizeMode={'tail'} style={styles.name}>
|
|
@@ -121,14 +96,6 @@ const styles = StyleSheet.create({
|
|
|
121
96
|
justifyContent: 'center',
|
|
122
97
|
alignItems: 'center',
|
|
123
98
|
},
|
|
124
|
-
appIcon: {
|
|
125
|
-
position: 'absolute',
|
|
126
|
-
top: 22,
|
|
127
|
-
left: 22,
|
|
128
|
-
backgroundColor: Colors.accent,
|
|
129
|
-
borderRadius: 100,
|
|
130
|
-
padding: 2,
|
|
131
|
-
},
|
|
132
99
|
})
|
|
133
100
|
|
|
134
101
|
export default memo(RecipientItem)
|
|
@@ -76,15 +76,16 @@ describe('getRecipientVerificationStatus', () => {
|
|
|
76
76
|
{ recipient: mockRecipient, type: 'with phone number' },
|
|
77
77
|
])('address recipient $type', ({ recipient }) => {
|
|
78
78
|
it('returns appropriate status', () => {
|
|
79
|
-
|
|
79
|
+
const key = recipient.address.toLowerCase()
|
|
80
|
+
expect(getRecipientVerificationStatus(recipient, {}, { [key]: 'valora' })).toEqual(
|
|
80
81
|
RecipientVerificationStatus.VERIFIED
|
|
81
82
|
)
|
|
82
|
-
expect(getRecipientVerificationStatus(recipient, {}, { [
|
|
83
|
+
expect(getRecipientVerificationStatus(recipient, {}, { [key]: null })).toEqual(
|
|
83
84
|
RecipientVerificationStatus.UNVERIFIED
|
|
84
85
|
)
|
|
85
|
-
expect(
|
|
86
|
-
|
|
87
|
-
)
|
|
86
|
+
expect(getRecipientVerificationStatus(recipient, {}, { [key]: undefined })).toEqual(
|
|
87
|
+
RecipientVerificationStatus.UNKNOWN
|
|
88
|
+
)
|
|
88
89
|
expect(getRecipientVerificationStatus(recipient, {}, {})).toEqual(
|
|
89
90
|
RecipientVerificationStatus.UNKNOWN
|
|
90
91
|
)
|
|
@@ -5,7 +5,7 @@ import { formatShortenedAddress } from 'src/account/utils'
|
|
|
5
5
|
import {
|
|
6
6
|
AddressToDisplayNameType,
|
|
7
7
|
AddressToE164NumberType,
|
|
8
|
-
|
|
8
|
+
AddressToVerifiedByType,
|
|
9
9
|
E164NumberToAddressType,
|
|
10
10
|
} from 'src/identity/reducer'
|
|
11
11
|
import { RecipientVerificationStatus } from 'src/identity/types'
|
|
@@ -168,7 +168,7 @@ export function getRecipientFromAddress(
|
|
|
168
168
|
export function getRecipientVerificationStatus(
|
|
169
169
|
recipient: Recipient,
|
|
170
170
|
e164NumberToAddress: E164NumberToAddressType,
|
|
171
|
-
|
|
171
|
+
addressToVerifiedBy: AddressToVerifiedByType
|
|
172
172
|
): RecipientVerificationStatus {
|
|
173
173
|
// phone recipients should always have a number, the extra check is to ensure typing
|
|
174
174
|
if (recipient.recipientType === RecipientType.PhoneNumber && recipientHasNumber(recipient)) {
|
|
@@ -183,18 +183,13 @@ export function getRecipientVerificationStatus(
|
|
|
183
183
|
|
|
184
184
|
return RecipientVerificationStatus.VERIFIED
|
|
185
185
|
}
|
|
186
|
-
if (recipientHasAddress(recipient)
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
case false:
|
|
191
|
-
return RecipientVerificationStatus.UNVERIFIED
|
|
192
|
-
case undefined:
|
|
193
|
-
return RecipientVerificationStatus.UNKNOWN
|
|
194
|
-
}
|
|
195
|
-
} else {
|
|
186
|
+
if (recipientHasAddress(recipient)) {
|
|
187
|
+
const entry = addressToVerifiedBy[recipient.address.toLowerCase()]
|
|
188
|
+
if (entry) return RecipientVerificationStatus.VERIFIED
|
|
189
|
+
if (entry === null) return RecipientVerificationStatus.UNVERIFIED
|
|
196
190
|
return RecipientVerificationStatus.UNKNOWN
|
|
197
191
|
}
|
|
192
|
+
return RecipientVerificationStatus.UNKNOWN
|
|
198
193
|
}
|
|
199
194
|
|
|
200
195
|
type PreparedRecipient = Recipient & {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { addressToVerifiedBySelector } from 'src/identity/selectors'
|
|
2
|
+
import { miniPay, valora } from 'src/images/Images'
|
|
3
|
+
import { useSelector } from 'src/redux/hooks'
|
|
4
|
+
|
|
5
|
+
export const VERIFIERS = {
|
|
6
|
+
valora: { name: 'Valora', icon: valora },
|
|
7
|
+
minipay: { name: 'MiniPay', icon: miniPay },
|
|
8
|
+
} as const
|
|
9
|
+
|
|
10
|
+
export type Verifier = keyof typeof VERIFIERS
|
|
11
|
+
|
|
12
|
+
export function isKnownVerifier(verifier: string | null | undefined): verifier is Verifier {
|
|
13
|
+
return !!verifier && Object.hasOwn(VERIFIERS, verifier)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function useVerifierName(address: string | undefined): string | undefined {
|
|
17
|
+
const addressToVerifiedBy = useSelector(addressToVerifiedBySelector)
|
|
18
|
+
const verifier = address ? addressToVerifiedBy[address.toLowerCase()] : undefined
|
|
19
|
+
return isKnownVerifier(verifier) ? VERIFIERS[verifier].name : undefined
|
|
20
|
+
}
|
|
@@ -69,6 +69,7 @@ import {
|
|
|
69
69
|
v253Schema,
|
|
70
70
|
v254Schema,
|
|
71
71
|
v255Schema,
|
|
72
|
+
v256Schema,
|
|
72
73
|
v28Schema,
|
|
73
74
|
v2Schema,
|
|
74
75
|
v35Schema,
|
|
@@ -1959,4 +1960,28 @@ describe('Redux persist migrations', () => {
|
|
|
1959
1960
|
const migratedSchema = migrations[256](oldSchema)
|
|
1960
1961
|
expect(migratedSchema.identity.secureSendPhoneNumberMapping).toBeUndefined()
|
|
1961
1962
|
})
|
|
1963
|
+
|
|
1964
|
+
it('works from 256 to 257', () => {
|
|
1965
|
+
const oldSchema = {
|
|
1966
|
+
...v256Schema,
|
|
1967
|
+
identity: {
|
|
1968
|
+
...v256Schema.identity,
|
|
1969
|
+
addressToVerificationStatus: {
|
|
1970
|
+
'0xAAA': true,
|
|
1971
|
+
'0xbbb': false,
|
|
1972
|
+
'0xccc': undefined,
|
|
1973
|
+
},
|
|
1974
|
+
addressToVerifiedBy: {
|
|
1975
|
+
'0xddd': 'minipay',
|
|
1976
|
+
},
|
|
1977
|
+
},
|
|
1978
|
+
}
|
|
1979
|
+
const migratedSchema = migrations[257](oldSchema)
|
|
1980
|
+
expect(migratedSchema.identity.addressToVerificationStatus).toBeUndefined()
|
|
1981
|
+
expect(migratedSchema.identity.addressToVerifiedBy).toStrictEqual({
|
|
1982
|
+
'0xddd': 'minipay',
|
|
1983
|
+
'0xaaa': 'valora', // `true` carried over (and lowercased)
|
|
1984
|
+
// `false` and `undefined` entries are dropped — `false` was ambiguous in the old schema
|
|
1985
|
+
})
|
|
1986
|
+
})
|
|
1962
1987
|
})
|
package/src/redux/migrations.ts
CHANGED
|
@@ -2081,4 +2081,25 @@ export const migrations = {
|
|
|
2081
2081
|
...state,
|
|
2082
2082
|
identity: _.omit(state.identity, 'secureSendPhoneNumberMapping'),
|
|
2083
2083
|
}),
|
|
2084
|
+
257: (state: any) => {
|
|
2085
|
+
// Carry over previously confirmed verifications (all Valora)
|
|
2086
|
+
const oldMap: Record<string, boolean | undefined> =
|
|
2087
|
+
state.identity?.addressToVerificationStatus ?? {}
|
|
2088
|
+
const carriedOver: Record<string, string> = {}
|
|
2089
|
+
for (const [address, verified] of Object.entries(oldMap)) {
|
|
2090
|
+
if (verified === true) {
|
|
2091
|
+
carriedOver[address.toLowerCase()] = 'valora'
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
return {
|
|
2095
|
+
...state,
|
|
2096
|
+
identity: {
|
|
2097
|
+
..._.omit(state.identity, 'addressToVerificationStatus'),
|
|
2098
|
+
addressToVerifiedBy: {
|
|
2099
|
+
...state.identity.addressToVerifiedBy,
|
|
2100
|
+
...carriedOver,
|
|
2101
|
+
},
|
|
2102
|
+
},
|
|
2103
|
+
}
|
|
2104
|
+
},
|
|
2084
2105
|
}
|
package/src/redux/store.test.ts
CHANGED
|
@@ -143,7 +143,7 @@ describe('store state', () => {
|
|
|
143
143
|
{
|
|
144
144
|
"_persist": {
|
|
145
145
|
"rehydrated": true,
|
|
146
|
-
"version":
|
|
146
|
+
"version": 257,
|
|
147
147
|
},
|
|
148
148
|
"account": {
|
|
149
149
|
"acceptedTerms": false,
|
|
@@ -244,7 +244,6 @@ describe('store state', () => {
|
|
|
244
244
|
"identity": {
|
|
245
245
|
"addressToDisplayName": {},
|
|
246
246
|
"addressToE164Number": {},
|
|
247
|
-
"addressToVerificationStatus": {},
|
|
248
247
|
"addressToVerifiedBy": {},
|
|
249
248
|
"askedContactsPermission": false,
|
|
250
249
|
"e164NumberToAddress": {},
|
package/src/redux/store.ts
CHANGED
|
@@ -30,7 +30,7 @@ const persistConfig: PersistConfig<ReducersRootState> = {
|
|
|
30
30
|
key: 'root',
|
|
31
31
|
// default is -1, increment as we make migrations
|
|
32
32
|
// See https://github.com/valora-xyz/wallet/tree/main/WALLET.md#redux-state-migration
|
|
33
|
-
version:
|
|
33
|
+
version: 257,
|
|
34
34
|
keyPrefix: `reduxStore-`, // the redux-persist default is `persist:` which doesn't work with some file systems.
|
|
35
35
|
storage: FSStorage(),
|
|
36
36
|
blacklist: ['networkInfo', 'alert', 'imports', 'keylessBackup', transactionFeedV2Api.reducerPath],
|
|
@@ -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 {
|