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

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.135",
3
+ "version": "1.0.0-alpha.136",
4
4
  "author": "Valora Inc",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -15,6 +15,7 @@ export enum Actions {
15
15
  CANCEL_IMPORT_CONTACTS = 'IDENTITY/CANCEL_IMPORT_CONTACTS',
16
16
  END_IMPORT_CONTACTS = 'IDENTITY/END_IMPORT_CONTACTS',
17
17
  FETCH_ADDRESS_VERIFICATION_STATUS = 'IDENTITY/FETCH_ADDRESS_VERIFICATION_STATUS',
18
+ RECIPIENT_LOOKUP_RESOLVED = 'IDENTITY/RECIPIENT_LOOKUP_RESOLVED',
18
19
  CONTACTS_SAVED = 'IDENTITY/CONTACTS_SAVED',
19
20
  STORED_PASSWORD_REFRESHED = 'IDENTITY/STORED_PASSWORD_REFRESHED',
20
21
  }
@@ -57,6 +58,10 @@ export interface FetchAddressVerificationAction {
57
58
  address: string
58
59
  }
59
60
 
61
+ interface RecipientLookupResolvedAction {
62
+ type: Actions.RECIPIENT_LOOKUP_RESOLVED
63
+ }
64
+
60
65
  interface ContactsSavedAction {
61
66
  type: Actions.CONTACTS_SAVED
62
67
  hash: string
@@ -74,6 +79,7 @@ export type ActionTypes =
74
79
  | EndImportContactsAction
75
80
  | FetchAddressesAndValidateAction
76
81
  | FetchAddressVerificationAction
82
+ | RecipientLookupResolvedAction
77
83
  | ContactsSavedAction
78
84
  | StoredPasswordRefreshedAction
79
85
 
@@ -87,6 +93,10 @@ export const fetchAddressVerification = (address: string): FetchAddressVerificat
87
93
  address,
88
94
  })
89
95
 
96
+ export const recipientLookupResolved = (): RecipientLookupResolvedAction => ({
97
+ type: Actions.RECIPIENT_LOOKUP_RESOLVED,
98
+ })
99
+
90
100
  export const updateE164PhoneNumberAddresses = (
91
101
  e164NumberToAddress: E164NumberToAddressType,
92
102
  addressToE164Number: AddressToE164NumberType,
@@ -15,6 +15,7 @@ import {
15
15
  contactsSaved,
16
16
  fetchAddressVerification,
17
17
  fetchAddressesAndValidate,
18
+ recipientLookupResolved,
18
19
  updateE164PhoneNumberAddresses,
19
20
  } from 'src/identity/actions'
20
21
  import {
@@ -118,6 +119,7 @@ describe('Fetch Addresses Saga', () => {
118
119
  {}
119
120
  )
120
121
  )
122
+ .put(recipientLookupResolved())
121
123
  .run()
122
124
 
123
125
  expect(mockFetch).toHaveBeenCalledTimes(1)
@@ -228,6 +230,7 @@ describe('Fetch Addresses Saga', () => {
228
230
  [call(retrieveSignedMessage), 'some signed message'],
229
231
  ])
230
232
  .put(showErrorOrFallback(expect.anything(), ErrorMessages.ADDRESS_LOOKUP_FAILURE))
233
+ .put(recipientLookupResolved())
231
234
  .run()
232
235
  })
233
236
  })
@@ -249,6 +252,7 @@ describe('Fetch Address Verification Saga', () => {
249
252
  [call(retrieveSignedMessage), 'some signed message'],
250
253
  ])
251
254
  .put(updateE164PhoneNumberAddresses({}, {}, { [mockAccount.toLowerCase()]: 'minipay' }))
255
+ .put(recipientLookupResolved())
252
256
  .run()
253
257
 
254
258
  expect(mockFetch).toHaveBeenCalledTimes(1)
@@ -296,6 +300,7 @@ describe('Fetch Address Verification Saga', () => {
296
300
  ])
297
301
  .not.put.actionType(Actions.UPDATE_E164_PHONE_NUMBER_ADDRESSES)
298
302
  .put(showErrorOrFallback(expect.anything(), ErrorMessages.ADDRESS_LOOKUP_FAILURE))
303
+ .put(recipientLookupResolved())
299
304
  .run()
300
305
  expect(AppAnalytics.track).toHaveBeenCalledWith(IdentityEvents.address_lookup_error, {
301
306
  error: 'Unable to fetch verification status for this address',
@@ -13,6 +13,7 @@ import {
13
13
  FetchAddressesAndValidateAction,
14
14
  contactsSaved,
15
15
  endImportContacts,
16
+ recipientLookupResolved,
16
17
  updateE164PhoneNumberAddresses,
17
18
  updateImportContactsProgress,
18
19
  } from 'src/identity/actions'
@@ -212,6 +213,8 @@ export function* fetchAddressesAndValidateSaga({ e164Number }: FetchAddressesAnd
212
213
  AppAnalytics.track(IdentityEvents.phone_number_lookup_error, {
213
214
  error: error.message,
214
215
  })
216
+ } finally {
217
+ yield* put(recipientLookupResolved())
215
218
  }
216
219
  }
217
220
 
@@ -239,6 +242,8 @@ export function* fetchAddressVerificationSaga({ address }: FetchAddressVerificat
239
242
  )
240
243
  yield* put(showErrorOrFallback(error, ErrorMessages.ADDRESS_LOOKUP_FAILURE))
241
244
  AppAnalytics.track(IdentityEvents.address_lookup_error, { error: error.message })
245
+ } finally {
246
+ yield* put(recipientLookupResolved())
242
247
  }
243
248
  }
244
249
 
@@ -0,0 +1,29 @@
1
+ import {
2
+ Actions,
3
+ fetchAddressVerification,
4
+ fetchAddressesAndValidate,
5
+ recipientLookupResolved,
6
+ } from 'src/identity/actions'
7
+ import { reducer } from 'src/identity/reducer'
8
+
9
+ const initialState = reducer(undefined, { type: 'INIT' } as any)
10
+
11
+ describe('identity reducer', () => {
12
+ describe('recipientLookupLoading', () => {
13
+ it(`is set true on ${Actions.FETCH_ADDRESSES_AND_VALIDATION_STATUS}`, () => {
14
+ const next = reducer(initialState, fetchAddressesAndValidate('+15551234567'))
15
+ expect(next.recipientLookupLoading).toBe(true)
16
+ })
17
+
18
+ it(`is set true on ${Actions.FETCH_ADDRESS_VERIFICATION_STATUS}`, () => {
19
+ const next = reducer(initialState, fetchAddressVerification('0xabc'))
20
+ expect(next.recipientLookupLoading).toBe(true)
21
+ })
22
+
23
+ it(`is cleared on ${Actions.RECIPIENT_LOOKUP_RESOLVED}`, () => {
24
+ const loadingState = { ...initialState, recipientLookupLoading: true }
25
+ const next = reducer(loadingState, recipientLookupResolved())
26
+ expect(next.recipientLookupLoading).toBe(false)
27
+ })
28
+ })
29
+ })
@@ -49,6 +49,8 @@ interface State {
49
49
  importContactsProgress: ImportContactProgress
50
50
  // Mapping of address to the entity that verified it (e.g. "valora", "minipay")
51
51
  addressToVerifiedBy: AddressToVerifiedByType
52
+ // Single boolean is safe because both lookup sagas use `takeLatest` — at most one in flight.
53
+ recipientLookupLoading: boolean
52
54
  lastSavedContactsHash: string | null
53
55
  shouldRefreshStoredPasswordHash: boolean
54
56
  }
@@ -64,6 +66,7 @@ const initialState: State = {
64
66
  total: 0,
65
67
  },
66
68
  addressToVerifiedBy: {},
69
+ recipientLookupLoading: false,
67
70
  lastSavedContactsHash: null,
68
71
  shouldRefreshStoredPasswordHash: false,
69
72
  }
@@ -85,6 +88,7 @@ export const reducer = (
85
88
  current: 0,
86
89
  total: 0,
87
90
  },
91
+ recipientLookupLoading: false,
88
92
  }
89
93
  }
90
94
  case Actions.UPDATE_E164_PHONE_NUMBER_ADDRESSES:
@@ -139,6 +143,11 @@ export const reducer = (
139
143
  addressToE164Number: state.addressToE164Number,
140
144
  e164NumberToAddress: state.e164NumberToAddress,
141
145
  }
146
+ case Actions.FETCH_ADDRESSES_AND_VALIDATION_STATUS:
147
+ return {
148
+ ...state,
149
+ recipientLookupLoading: true,
150
+ }
142
151
  case Actions.FETCH_ADDRESS_VERIFICATION_STATUS:
143
152
  return {
144
153
  ...state,
@@ -146,6 +155,12 @@ export const reducer = (
146
155
  ...state.addressToVerifiedBy,
147
156
  [action.address]: undefined,
148
157
  },
158
+ recipientLookupLoading: true,
159
+ }
160
+ case Actions.RECIPIENT_LOOKUP_RESOLVED:
161
+ return {
162
+ ...state,
163
+ recipientLookupLoading: false,
149
164
  }
150
165
  case Actions.CONTACTS_SAVED:
151
166
  return {
@@ -7,7 +7,7 @@ import {
7
7
  import { Actions } from 'src/identity/actions'
8
8
  import Logger from 'src/utils/Logger'
9
9
  import { safely } from 'src/utils/safely'
10
- import { cancelled, spawn, takeEvery, takeLatest, takeLeading } from 'typed-redux-saga'
10
+ import { cancelled, spawn, takeLatest, takeLeading } from 'typed-redux-saga'
11
11
 
12
12
  const TAG = 'identity/saga'
13
13
 
@@ -20,7 +20,7 @@ function* watchContactMapping() {
20
20
  }
21
21
 
22
22
  function* watchFetchAddressVerification() {
23
- yield* takeEvery(Actions.FETCH_ADDRESS_VERIFICATION_STATUS, safely(fetchAddressVerificationSaga))
23
+ yield* takeLatest(Actions.FETCH_ADDRESS_VERIFICATION_STATUS, safely(fetchAddressVerificationSaga))
24
24
  }
25
25
 
26
26
  export function* identitySaga() {
@@ -3,6 +3,8 @@ import { RootState } from 'src/redux/reducers'
3
3
  export const e164NumberToAddressSelector = (state: RootState) => state.identity.e164NumberToAddress
4
4
  export const addressToE164NumberSelector = (state: RootState) => state.identity.addressToE164Number
5
5
  export const addressToVerifiedBySelector = (state: RootState) => state.identity.addressToVerifiedBy
6
+ export const recipientLookupLoadingSelector = (state: RootState) =>
7
+ state.identity.recipientLookupLoading
6
8
  export const importContactsProgressSelector = (state: RootState) =>
7
9
  state.identity.importContactsProgress
8
10
  export const addressToDisplayNameSelector = (state: RootState) =>
@@ -2102,4 +2102,11 @@ export const migrations = {
2102
2102
  },
2103
2103
  }
2104
2104
  },
2105
+ 258: (state: any) => ({
2106
+ ...state,
2107
+ identity: {
2108
+ ...state.identity,
2109
+ recipientLookupLoading: false,
2110
+ },
2111
+ }),
2105
2112
  }
@@ -143,7 +143,7 @@ describe('store state', () => {
143
143
  {
144
144
  "_persist": {
145
145
  "rehydrated": true,
146
- "version": 257,
146
+ "version": 258,
147
147
  },
148
148
  "account": {
149
149
  "acceptedTerms": false,
@@ -253,6 +253,7 @@ describe('store state', () => {
253
253
  "total": 0,
254
254
  },
255
255
  "lastSavedContactsHash": null,
256
+ "recipientLookupLoading": false,
256
257
  "shouldRefreshStoredPasswordHash": true,
257
258
  },
258
259
  "imports": {
@@ -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: 257,
33
+ version: 258,
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],
@@ -6,14 +6,19 @@ import AppAnalytics from 'src/analytics/AppAnalytics'
6
6
  import { SendEvents } from 'src/analytics/Events'
7
7
  import { SendOrigin } from 'src/analytics/types'
8
8
  import { getAppConfig } from 'src/appConfig'
9
- import { fetchAddressVerification, fetchAddressesAndValidate } from 'src/identity/actions'
9
+ import {
10
+ fetchAddressVerification,
11
+ fetchAddressesAndValidate,
12
+ recipientLookupResolved,
13
+ } from 'src/identity/actions'
10
14
  import { navigate } from 'src/navigator/NavigationService'
11
15
  import { Screens } from 'src/navigator/Screens'
12
16
  import { RecipientType } from 'src/recipients/recipient'
13
17
  import SendSelectRecipient from 'src/send/SendSelectRecipient'
14
18
  import { getDynamicConfigParams } from 'src/statsig'
15
19
  import { StatsigDynamicConfigs } from 'src/statsig/types'
16
- import { createMockStore, getMockStackScreenProps } from 'test/utils'
20
+ import { setupStore } from 'src/redux/store'
21
+ import { createMockStore, getMockStackScreenProps, getMockStoreData } from 'test/utils'
17
22
  import {
18
23
  mockAccount,
19
24
  mockAccount2,
@@ -33,6 +38,10 @@ jest.mock('src/recipients/resolve-id')
33
38
 
34
39
  jest.mock('react-native-device-info', () => ({ getFontScaleSync: () => 1 }))
35
40
  jest.mock('src/statsig')
41
+ jest.mock('src/redux/sagas', () => ({
42
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
43
+ rootSaga: jest.fn(function* () {}),
44
+ }))
36
45
 
37
46
  const mockScreenProps = ({
38
47
  defaultTokenIdOverride,
@@ -197,6 +206,37 @@ describe('SendSelectRecipient', () => {
197
206
  })
198
207
  expect(getByTestId('SelectRecipient/NoResults')).toBeTruthy()
199
208
  })
209
+ describe('selection spinner', () => {
210
+ it('shows the spinner while the lookup is in flight and hides it once the saga resolves (error path)', async () => {
211
+ // Reducer-backed store: tapping a recipient dispatches `fetchAddressesAndValidate`
212
+ // which flips `recipientLookupLoading` to true; dispatching `recipientLookupResolved`
213
+ // (the saga's `finally` branch) flips it back to false. We assert the row spinner
214
+ // tracks the actual state transition, including the no-mapping (error) end state.
215
+ const { store } = setupStore(getMockStoreData(storeWithPhoneVerified))
216
+
217
+ const { getByTestId, queryByTestId } = render(
218
+ <Provider store={store}>
219
+ <SendSelectRecipient {...mockScreenProps({})} />
220
+ </Provider>
221
+ )
222
+
223
+ await act(() => {
224
+ fireEvent.changeText(getByTestId('SendSelectRecipientSearchInput'), 'George Bogart')
225
+ })
226
+ await act(() => {
227
+ fireEvent.press(getByTestId('RecipientItem'))
228
+ })
229
+
230
+ expect(queryByTestId('RecipientItem/ActivityIndicator')).toBeTruthy()
231
+
232
+ await act(() => {
233
+ store.dispatch(recipientLookupResolved())
234
+ })
235
+
236
+ expect(queryByTestId('RecipientItem/ActivityIndicator')).toBeFalsy()
237
+ })
238
+ })
239
+
200
240
  it('navigates to send amount when a verified phone recipient is tapped in search results', async () => {
201
241
  const store = createMockStore({
202
242
  ...storeWithPhoneVerified,
@@ -184,8 +184,13 @@ function SendSelectRecipient({ route }: Props) {
184
184
  const { contactRecipients, recentRecipients } = useSendRecipients()
185
185
  const { mergedRecipients, searchQuery, setSearchQuery } = useMergedSearchRecipients(onSearch)
186
186
 
187
- const { recipientVerificationStatus, recipient, setSelectedRecipient, unsetSelectedRecipient } =
188
- useFetchRecipientVerificationStatus()
187
+ const {
188
+ recipientVerificationStatus,
189
+ recipient,
190
+ setSelectedRecipient,
191
+ unsetSelectedRecipient,
192
+ isSelectedRecipientLoading,
193
+ } = useFetchRecipientVerificationStatus()
189
194
 
190
195
  useEffect(() => {
191
196
  // Auto-navigate once verification resolves. The picker stays mounted so the
@@ -280,9 +285,7 @@ function SendSelectRecipient({ route }: Props) {
280
285
  recipients={mergedRecipients}
281
286
  onSelectRecipient={setSelectedRecipient}
282
287
  selectedRecipient={recipient}
283
- isSelectedRecipientLoading={
284
- !!recipient && recipientVerificationStatus === RecipientVerificationStatus.UNKNOWN
285
- }
288
+ isSelectedRecipientLoading={isSelectedRecipientLoading}
286
289
  />
287
290
  </>
288
291
  )
@@ -327,9 +330,7 @@ function SendSelectRecipient({ route }: Props) {
327
330
  recipients={contactRecipients}
328
331
  onSelectRecipient={setSelectedRecipient}
329
332
  selectedRecipient={recipient}
330
- isSelectedRecipientLoading={
331
- !!recipient && recipientVerificationStatus === RecipientVerificationStatus.UNKNOWN
332
- }
333
+ isSelectedRecipientLoading={isSelectedRecipientLoading}
333
334
  />
334
335
  ) : (
335
336
  <>
@@ -344,9 +345,7 @@ function SendSelectRecipient({ route }: Props) {
344
345
  title={t('sendSelectRecipient.recents')}
345
346
  onSelectRecipient={onSelectRecentRecipient}
346
347
  selectedRecipient={recipient}
347
- isSelectedRecipientLoading={
348
- !!recipient && recipientVerificationStatus === RecipientVerificationStatus.UNKNOWN
349
- }
348
+ isSelectedRecipientLoading={isSelectedRecipientLoading}
350
349
  style={styles.recentRecipientPicker}
351
350
  />
352
351
  ) : (
@@ -0,0 +1,131 @@
1
+ import { act, renderHook } from '@testing-library/react-native'
2
+ import * as React from 'react'
3
+ import { Provider } from 'react-redux'
4
+ import {
5
+ fetchAddressVerification,
6
+ fetchAddressesAndValidate,
7
+ recipientLookupResolved,
8
+ } from 'src/identity/actions'
9
+ import { RecipientVerificationStatus } from 'src/identity/types'
10
+ import { Recipient, RecipientType } from 'src/recipients/recipient'
11
+ import { setupStore } from 'src/redux/store'
12
+ import useFetchRecipientVerificationStatus from 'src/send/useFetchRecipientVerificationStatus'
13
+ import { getMockStoreData } from 'test/utils'
14
+ import { mockAccount, mockE164Number, mockName } from 'test/values'
15
+
16
+ jest.mock('src/redux/sagas', () => ({
17
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
18
+ rootSaga: jest.fn(function* () {}),
19
+ }))
20
+
21
+ const phoneRecipient: Recipient = {
22
+ name: mockName,
23
+ contactId: 'contactId',
24
+ e164PhoneNumber: mockE164Number,
25
+ recipientType: RecipientType.PhoneNumber,
26
+ }
27
+
28
+ const addressRecipient: Recipient = {
29
+ address: mockAccount,
30
+ recipientType: RecipientType.Address,
31
+ }
32
+
33
+ function setupHook(storeOverrides: Parameters<typeof getMockStoreData>[0] = {}) {
34
+ const { store } = setupStore(getMockStoreData(storeOverrides))
35
+ const dispatchSpy = jest.spyOn(store, 'dispatch')
36
+ const { result, rerender } = renderHook(() => useFetchRecipientVerificationStatus(), {
37
+ wrapper: ({ children }: { children: React.ReactNode }) => (
38
+ <Provider store={store}>{children}</Provider>
39
+ ),
40
+ })
41
+ return { result, rerender, store, dispatchSpy }
42
+ }
43
+
44
+ describe('useFetchRecipientVerificationStatus', () => {
45
+ beforeEach(() => {
46
+ jest.clearAllMocks()
47
+ })
48
+
49
+ it('starts with no recipient and not loading', () => {
50
+ const { result } = setupHook()
51
+ expect(result.current.recipient).toBeNull()
52
+ expect(result.current.recipientVerificationStatus).toBe(RecipientVerificationStatus.UNKNOWN)
53
+ expect(result.current.isSelectedRecipientLoading).toBe(false)
54
+ })
55
+
56
+ describe('phone-number recipient', () => {
57
+ it('dispatches fetchAddressesAndValidate when selected', () => {
58
+ const { result, dispatchSpy } = setupHook()
59
+ act(() => {
60
+ result.current.setSelectedRecipient(phoneRecipient)
61
+ })
62
+ expect(dispatchSpy).toHaveBeenCalledWith(fetchAddressesAndValidate(mockE164Number))
63
+ })
64
+
65
+ it('reports loading once the lookup is dispatched and stops once it resolves', () => {
66
+ const { result, store } = setupHook()
67
+
68
+ act(() => {
69
+ result.current.setSelectedRecipient(phoneRecipient)
70
+ })
71
+ expect(result.current.isSelectedRecipientLoading).toBe(true)
72
+
73
+ act(() => {
74
+ store.dispatch(recipientLookupResolved())
75
+ })
76
+ expect(result.current.isSelectedRecipientLoading).toBe(false)
77
+ })
78
+ })
79
+
80
+ describe('address recipient', () => {
81
+ it('dispatches fetchAddressVerification when phone is verified', () => {
82
+ const { result, dispatchSpy } = setupHook({
83
+ app: { phoneNumberVerified: true },
84
+ })
85
+ act(() => {
86
+ result.current.setSelectedRecipient(addressRecipient)
87
+ })
88
+ expect(dispatchSpy).toHaveBeenCalledWith(fetchAddressVerification(mockAccount))
89
+ })
90
+
91
+ it('marks address recipient as unverified when phone is not verified', () => {
92
+ const { result } = setupHook({ app: { phoneNumberVerified: false } })
93
+ act(() => {
94
+ result.current.setSelectedRecipient(addressRecipient)
95
+ })
96
+ expect(result.current.recipientVerificationStatus).toBe(
97
+ RecipientVerificationStatus.UNVERIFIED
98
+ )
99
+ expect(result.current.isSelectedRecipientLoading).toBe(false)
100
+ })
101
+
102
+ it('reports loading once the lookup is dispatched and stops once it resolves', () => {
103
+ const { result, store } = setupHook({ app: { phoneNumberVerified: true } })
104
+
105
+ act(() => {
106
+ result.current.setSelectedRecipient(addressRecipient)
107
+ })
108
+ expect(result.current.isSelectedRecipientLoading).toBe(true)
109
+
110
+ act(() => {
111
+ store.dispatch(recipientLookupResolved())
112
+ })
113
+ expect(result.current.isSelectedRecipientLoading).toBe(false)
114
+ })
115
+ })
116
+
117
+ it('clears recipient and verification status on unset', () => {
118
+ const { result } = setupHook()
119
+ act(() => {
120
+ result.current.setSelectedRecipient(phoneRecipient)
121
+ })
122
+ expect(result.current.isSelectedRecipientLoading).toBe(true)
123
+
124
+ act(() => {
125
+ result.current.unsetSelectedRecipient()
126
+ })
127
+ expect(result.current.recipient).toBeNull()
128
+ expect(result.current.recipientVerificationStatus).toBe(RecipientVerificationStatus.UNKNOWN)
129
+ expect(result.current.isSelectedRecipientLoading).toBe(false)
130
+ })
131
+ })
@@ -1,7 +1,11 @@
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 { addressToVerifiedBySelector, e164NumberToAddressSelector } from 'src/identity/selectors'
4
+ import {
5
+ addressToVerifiedBySelector,
6
+ e164NumberToAddressSelector,
7
+ recipientLookupLoadingSelector,
8
+ } from 'src/identity/selectors'
5
9
  import { RecipientVerificationStatus } from 'src/identity/types'
6
10
  import { Recipient, RecipientType, getRecipientVerificationStatus } from 'src/recipients/recipient'
7
11
  import { useDispatch, useSelector } from 'src/redux/hooks'
@@ -14,6 +18,7 @@ const useFetchRecipientVerificationStatus = () => {
14
18
 
15
19
  const e164NumberToAddress = useSelector(e164NumberToAddressSelector)
16
20
  const addressToVerifiedBy = useSelector(addressToVerifiedBySelector)
21
+ const recipientLookupLoading = useSelector(recipientLookupLoadingSelector)
17
22
  const phoneNumberVerified = useSelector(phoneNumberVerifiedSelector)
18
23
  const dispatch = useDispatch()
19
24
 
@@ -51,11 +56,14 @@ const useFetchRecipientVerificationStatus = () => {
51
56
  }
52
57
  }, [e164NumberToAddress, addressToVerifiedBy, recipient, recipientVerificationStatus])
53
58
 
59
+ const isSelectedRecipientLoading = !!recipient && recipientLookupLoading
60
+
54
61
  return {
55
62
  recipient,
56
63
  setSelectedRecipient,
57
64
  unsetSelectedRecipient,
58
65
  recipientVerificationStatus,
66
+ isSelectedRecipientLoading,
59
67
  }
60
68
  }
61
69