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

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.
@@ -0,0 +1,139 @@
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
+ updateE164PhoneNumberAddresses,
9
+ } from 'src/identity/actions'
10
+ import { Recipient, RecipientType } from 'src/recipients/recipient'
11
+ import { setupStore } from 'src/redux/store'
12
+ import { useRecipientLookup } from 'src/send/useRecipientLookup'
13
+ import { getMockStoreData } from 'test/utils'
14
+ import { mockAccount, mockAccount2, 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 & { address: string } = {
22
+ name: mockName,
23
+ contactId: 'contactId',
24
+ e164PhoneNumber: mockE164Number,
25
+ recipientType: RecipientType.PhoneNumber,
26
+ address: mockAccount,
27
+ }
28
+
29
+ const addressRecipient: Recipient & { address: string } = {
30
+ address: mockAccount,
31
+ recipientType: RecipientType.Address,
32
+ }
33
+
34
+ function setupHook(
35
+ recipient: Recipient & { address: string },
36
+ options?: { skipFetch?: boolean },
37
+ storeOverrides: Parameters<typeof getMockStoreData>[0] = {}
38
+ ) {
39
+ const { store } = setupStore(getMockStoreData(storeOverrides))
40
+ const dispatchSpy = jest.spyOn(store, 'dispatch')
41
+ const { result, rerender } = renderHook(() => useRecipientLookup(recipient, options), {
42
+ wrapper: ({ children }: { children: React.ReactNode }) => (
43
+ <Provider store={store}>{children}</Provider>
44
+ ),
45
+ })
46
+ return { result, rerender, store, dispatchSpy }
47
+ }
48
+
49
+ describe('useRecipientLookup', () => {
50
+ beforeEach(() => {
51
+ jest.clearAllMocks()
52
+ })
53
+
54
+ it('dispatches fetchAddressesAndValidate for phone recipients', () => {
55
+ const { dispatchSpy } = setupHook(phoneRecipient)
56
+ expect(dispatchSpy).toHaveBeenCalledWith(fetchAddressesAndValidate(mockE164Number))
57
+ })
58
+
59
+ it('dispatches fetchAddressVerification for address-only recipients', () => {
60
+ const { dispatchSpy } = setupHook(addressRecipient)
61
+ expect(dispatchSpy).toHaveBeenCalledWith(fetchAddressVerification(mockAccount.toLowerCase()))
62
+ })
63
+
64
+ it('does not dispatch when skipFetch is set (caller already triggered the lookup)', () => {
65
+ const { dispatchSpy } = setupHook(phoneRecipient, { skipFetch: true })
66
+ expect(dispatchSpy).not.toHaveBeenCalledWith(fetchAddressesAndValidate(mockE164Number))
67
+ })
68
+
69
+ it('reports loading while the lookup is in flight and resolves to unknown when no verifier is recorded', () => {
70
+ const { result, store } = setupHook(addressRecipient)
71
+ expect(result.current.status).toBe('loading')
72
+
73
+ act(() => {
74
+ store.dispatch(recipientLookupResolved())
75
+ })
76
+ expect(result.current.status).toBe('unknown')
77
+ })
78
+
79
+ it('returns "unverified" for a phone recipient when the lookup resolved with no verified addresses', () => {
80
+ const { result, store } = setupHook(phoneRecipient, { skipFetch: true })
81
+ act(() => {
82
+ store.dispatch(updateE164PhoneNumberAddresses({ [mockE164Number]: null }, {}))
83
+ })
84
+ expect(result.current.status).toBe('unverified')
85
+ })
86
+
87
+ it('returns "verified" when the address has a known verifier', () => {
88
+ const { result } = setupHook(
89
+ addressRecipient,
90
+ { skipFetch: true },
91
+ {
92
+ identity: {
93
+ addressToVerifiedBy: { [mockAccount.toLowerCase()]: 'valora' },
94
+ },
95
+ }
96
+ )
97
+ expect(result.current.status).toBe('verified')
98
+ })
99
+
100
+ it('returns "unverified" when the address is known to be unverified (verifier=null)', () => {
101
+ const { result } = setupHook(
102
+ addressRecipient,
103
+ { skipFetch: true },
104
+ {
105
+ identity: {
106
+ addressToVerifiedBy: { [mockAccount.toLowerCase()]: null },
107
+ },
108
+ }
109
+ )
110
+ expect(result.current.status).toBe('unverified')
111
+ })
112
+
113
+ it('returns the verified addresses linked to a phone recipient, filtering out unverified ones', () => {
114
+ const { result, store } = setupHook(
115
+ phoneRecipient,
116
+ { skipFetch: true },
117
+ {
118
+ identity: {
119
+ addressToVerifiedBy: {
120
+ [mockAccount.toLowerCase()]: 'valora',
121
+ [mockAccount2.toLowerCase()]: null,
122
+ },
123
+ },
124
+ }
125
+ )
126
+ act(() => {
127
+ store.dispatch(
128
+ updateE164PhoneNumberAddresses(
129
+ { [mockE164Number]: [mockAccount.toLowerCase(), mockAccount2.toLowerCase()] },
130
+ {}
131
+ )
132
+ )
133
+ })
134
+
135
+ expect(result.current.verifiedAddresses).toEqual([
136
+ { address: mockAccount.toLowerCase(), verifier: 'valora' },
137
+ ])
138
+ })
139
+ })
@@ -0,0 +1,74 @@
1
+ import { useEffect } from 'react'
2
+ import { fetchAddressVerification, fetchAddressesAndValidate } from 'src/identity/actions'
3
+ import type { AddressToVerifiedByType } from 'src/identity/reducer'
4
+ import {
5
+ addressToVerifiedBySelector,
6
+ e164NumberToAddressSelector,
7
+ recipientLookupLoadingSelector,
8
+ } from 'src/identity/selectors'
9
+ import { Recipient } from 'src/recipients/recipient'
10
+ import { type Verifier, isKnownVerifier } from 'src/recipients/verifier'
11
+ import { useDispatch, useSelector } from 'src/redux/hooks'
12
+
13
+ export type RecipientLookupStatus = 'unknown' | 'loading' | 'verified' | 'unverified'
14
+
15
+ export interface VerifiedAddressEntry {
16
+ address: string
17
+ verifier: Verifier
18
+ }
19
+
20
+ function getVerifiedAddresses(
21
+ addresses: readonly string[],
22
+ addressToVerifiedBy: AddressToVerifiedByType
23
+ ): VerifiedAddressEntry[] {
24
+ return addresses
25
+ .map((address) => ({ address, verifier: addressToVerifiedBy[address] }))
26
+ .filter((entry): entry is VerifiedAddressEntry => isKnownVerifier(entry.verifier))
27
+ }
28
+
29
+ /**
30
+ * Dispatches a verification lookup for `recipient` and returns the status plus any verified
31
+ * addresses linked to the phone number. Pass `skipFetch` when the caller has already triggered
32
+ * a fresh lookup and re-fetching would be wasteful.
33
+ */
34
+ export function useRecipientLookup(
35
+ recipient: Recipient & { address: string },
36
+ options: { skipFetch?: boolean } = {}
37
+ ): { status: RecipientLookupStatus; verifiedAddresses: VerifiedAddressEntry[] } {
38
+ const dispatch = useDispatch()
39
+ const e164PhoneNumber = recipient.e164PhoneNumber
40
+ const recipientAddress = recipient.address.toLowerCase()
41
+ const skipFetch = !!options.skipFetch
42
+
43
+ const e164NumberToAddress = useSelector(e164NumberToAddressSelector)
44
+ const addressToVerifiedBy = useSelector(addressToVerifiedBySelector)
45
+ const recipientLookupLoading = useSelector(recipientLookupLoadingSelector)
46
+
47
+ useEffect(() => {
48
+ if (skipFetch || !e164PhoneNumber) return
49
+ dispatch(fetchAddressesAndValidate(e164PhoneNumber))
50
+ }, [dispatch, e164PhoneNumber, skipFetch])
51
+
52
+ useEffect(() => {
53
+ if (skipFetch || e164PhoneNumber) return
54
+ dispatch(fetchAddressVerification(recipientAddress))
55
+ }, [dispatch, e164PhoneNumber, recipientAddress, skipFetch])
56
+
57
+ const cachedAddresses = e164PhoneNumber ? e164NumberToAddress[e164PhoneNumber] : undefined
58
+ const verifiedAddresses = getVerifiedAddresses(cachedAddresses ?? [], addressToVerifiedBy)
59
+
60
+ const verifier = addressToVerifiedBy[recipientAddress]
61
+
62
+ let status: RecipientLookupStatus
63
+ if (recipientLookupLoading) {
64
+ status = 'loading'
65
+ } else if (isKnownVerifier(verifier)) {
66
+ status = 'verified'
67
+ } else if (verifier === null || cachedAddresses === null) {
68
+ status = 'unverified'
69
+ } else {
70
+ status = 'unknown'
71
+ }
72
+
73
+ return { status, verifiedAddresses }
74
+ }