@lifi/widget 4.7.0 → 4.8.0
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/dist/esm/components/AmountInput/AmountInput.style.d.ts.map +1 -1
- package/dist/esm/components/Card/Card.d.ts.map +1 -1
- package/dist/esm/components/Card/CardIconButton.d.ts.map +1 -1
- package/dist/esm/components/ContractComponent/ItemPrice.d.ts.map +1 -1
- package/dist/esm/components/ListItem/ListItemButton.d.ts.map +1 -1
- package/dist/esm/components/ListItemButton.d.ts.map +1 -1
- package/dist/esm/components/Token/Token.d.ts.map +1 -1
- package/dist/esm/components/TokenList/types.d.ts.map +1 -1
- package/dist/esm/config/version.d.ts +1 -1
- package/dist/esm/config/version.js +1 -1
- package/dist/esm/config/version.js.map +1 -1
- package/dist/esm/hooks/useAvailableChains.d.ts.map +1 -1
- package/dist/esm/hooks/useAvailableChains.js +21 -38
- package/dist/esm/hooks/useAvailableChains.js.map +1 -1
- package/dist/esm/hooks/useExplorer.d.ts +1 -1
- package/dist/esm/hooks/useExplorer.d.ts.map +1 -1
- package/dist/esm/hooks/useExplorer.js +7 -3
- package/dist/esm/hooks/useExplorer.js.map +1 -1
- package/dist/esm/hooks/useFromTokenSufficiency.js +3 -3
- package/dist/esm/hooks/useFromTokenSufficiency.js.map +1 -1
- package/dist/esm/hooks/useGasRecommendation.js +3 -3
- package/dist/esm/hooks/useGasRecommendation.js.map +1 -1
- package/dist/esm/hooks/useGasSufficiency.js +3 -3
- package/dist/esm/hooks/useGasSufficiency.js.map +1 -1
- package/dist/esm/hooks/useIsBatchingSupported.js +2 -2
- package/dist/esm/hooks/useIsBatchingSupported.js.map +1 -1
- package/dist/esm/hooks/useRouteExecution.js +6 -6
- package/dist/esm/hooks/useRouteExecution.js.map +1 -1
- package/dist/esm/hooks/useRoutes.js +7 -7
- package/dist/esm/hooks/useRoutes.js.map +1 -1
- package/dist/esm/hooks/useTokenBalance.js +4 -4
- package/dist/esm/hooks/useTokenBalance.js.map +1 -1
- package/dist/esm/hooks/useTokenBalancesQueries.js +3 -3
- package/dist/esm/hooks/useTokenBalancesQueries.js.map +1 -1
- package/dist/esm/hooks/useTokenSearch.js +3 -3
- package/dist/esm/hooks/useTokenSearch.js.map +1 -1
- package/dist/esm/hooks/useTokens.js +14 -14
- package/dist/esm/hooks/useTokens.js.map +1 -1
- package/dist/esm/hooks/useTools.d.ts.map +1 -1
- package/dist/esm/hooks/useTools.js +26 -27
- package/dist/esm/hooks/useTools.js.map +1 -1
- package/dist/esm/hooks/useTransactionDetails.js +2 -2
- package/dist/esm/hooks/useTransactionDetails.js.map +1 -1
- package/dist/esm/hooks/useTransactionHistory.js +3 -3
- package/dist/esm/hooks/useTransactionHistory.js.map +1 -1
- package/dist/esm/index.d.ts +5 -1
- package/dist/esm/index.js +3 -1
- package/dist/esm/pages/RoutesPage/RoutesPage.style.d.ts.map +1 -1
- package/dist/esm/pages/SendToWallet/BookmarksPage.js +2 -1
- package/dist/esm/pages/SendToWallet/BookmarksPage.js.map +1 -1
- package/dist/esm/pages/SendToWallet/ConnectedWalletsPage.js +2 -1
- package/dist/esm/pages/SendToWallet/ConnectedWalletsPage.js.map +1 -1
- package/dist/esm/pages/SendToWallet/RecentWalletsPage.js +2 -1
- package/dist/esm/pages/SendToWallet/RecentWalletsPage.js.map +1 -1
- package/dist/esm/pages/SendToWallet/SendToConfiguredWalletPage.js +2 -1
- package/dist/esm/pages/SendToWallet/SendToConfiguredWalletPage.js.map +1 -1
- package/dist/esm/pages/TransactionDetailsPage/TransactionDetailsPage.js +2 -1
- package/dist/esm/pages/TransactionDetailsPage/TransactionDetailsPage.js.map +1 -1
- package/dist/esm/pages/TransactionDetailsPage/TransferIdCard.js +2 -1
- package/dist/esm/pages/TransactionDetailsPage/TransferIdCard.js.map +1 -1
- package/dist/esm/providers/I18nProvider/types.d.ts.map +1 -1
- package/dist/esm/providers/WidgetProvider/WidgetProvider.d.ts.map +1 -1
- package/dist/esm/providers/WidgetProvider/WidgetProvider.js +9 -1
- package/dist/esm/providers/WidgetProvider/WidgetProvider.js.map +1 -1
- package/dist/esm/providers/WidgetProvider/types.d.ts +2 -0
- package/dist/esm/providers/WidgetProvider/types.d.ts.map +1 -1
- package/dist/esm/queries/getChains.d.ts +38 -0
- package/dist/esm/queries/getChains.d.ts.map +1 -0
- package/dist/esm/queries/getChains.js +51 -0
- package/dist/esm/queries/getChains.js.map +1 -0
- package/dist/esm/queries/getTools.d.ts +26 -0
- package/dist/esm/queries/getTools.d.ts.map +1 -0
- package/dist/esm/queries/getTools.js +30 -0
- package/dist/esm/queries/getTools.js.map +1 -0
- package/dist/esm/queries/index.d.ts +4 -0
- package/dist/esm/queries/index.js +3 -0
- package/dist/esm/queries/types.d.ts +25 -0
- package/dist/esm/queries/types.d.ts.map +1 -0
- package/dist/esm/shared.d.ts +3 -1
- package/dist/esm/shared.js +3 -1
- package/dist/esm/stores/StoreProvider.d.ts.map +1 -1
- package/dist/esm/stores/StoreProvider.js +16 -17
- package/dist/esm/stores/StoreProvider.js.map +1 -1
- package/dist/esm/stores/header/useHeaderStore.d.ts +1 -1
- package/dist/esm/stores/header/useHeaderStore.d.ts.map +1 -1
- package/dist/esm/stores/header/useHeaderStore.js.map +1 -1
- package/dist/esm/themes/createTheme.d.ts.map +1 -1
- package/dist/esm/types/events.d.ts.map +1 -1
- package/dist/esm/types/token.d.ts.map +1 -1
- package/dist/esm/types/widget.d.ts +30 -0
- package/dist/esm/types/widget.d.ts.map +1 -1
- package/dist/esm/types/widget.js.map +1 -1
- package/dist/esm/utils/queries.js.map +1 -1
- package/dist/esm/utils/scopeKeys.d.ts +6 -0
- package/dist/esm/utils/scopeKeys.d.ts.map +1 -0
- package/dist/esm/utils/scopeKeys.js +8 -0
- package/dist/esm/utils/scopeKeys.js.map +1 -0
- package/dist/esm/utils/url.d.ts +5 -0
- package/dist/esm/utils/url.d.ts.map +1 -0
- package/dist/esm/utils/url.js +17 -0
- package/dist/esm/utils/url.js.map +1 -0
- package/package.json +12 -4
- package/src/config/version.ts +1 -1
- package/src/hooks/useAvailableChains.test.tsx +170 -0
- package/src/hooks/useAvailableChains.ts +29 -47
- package/src/hooks/useExplorer.test.tsx +106 -0
- package/src/hooks/useExplorer.ts +11 -5
- package/src/hooks/useFromTokenSufficiency.ts +2 -2
- package/src/hooks/useGasRecommendation.ts +2 -2
- package/src/hooks/useGasSufficiency.ts +2 -2
- package/src/hooks/useIsBatchingSupported.ts +2 -2
- package/src/hooks/useRouteExecution.ts +5 -5
- package/src/hooks/useRoutes.ts +6 -6
- package/src/hooks/useTokenBalance.ts +4 -4
- package/src/hooks/useTokenBalancesQueries.ts +3 -3
- package/src/hooks/useTokenSearch.ts +7 -3
- package/src/hooks/useTokens.ts +13 -14
- package/src/hooks/useTools.sharedEntry.test.tsx +155 -0
- package/src/hooks/useTools.ts +42 -37
- package/src/hooks/useTransactionDetails.ts +3 -3
- package/src/hooks/useTransactionHistory.ts +2 -2
- package/src/index.ts +2 -1
- package/src/pages/SendToWallet/BookmarksPage.tsx +3 -3
- package/src/pages/SendToWallet/ConnectedWalletsPage.tsx +3 -3
- package/src/pages/SendToWallet/RecentWalletsPage.tsx +3 -3
- package/src/pages/SendToWallet/SendToConfiguredWalletPage.tsx +3 -3
- package/src/pages/TransactionDetailsPage/TransactionDetailsPage.tsx +5 -5
- package/src/pages/TransactionDetailsPage/TransferIdCard.tsx +2 -1
- package/src/providers/WidgetProvider/WidgetProvider.test.tsx +73 -0
- package/src/providers/WidgetProvider/WidgetProvider.tsx +13 -0
- package/src/providers/WidgetProvider/types.ts +2 -0
- package/src/queries/getChains.ts +93 -0
- package/src/queries/getTools.ts +62 -0
- package/src/queries/index.ts +30 -0
- package/src/queries/sharedCache.test.tsx +223 -0
- package/src/queries/types.ts +38 -0
- package/src/shared.ts +2 -0
- package/src/stores/StoreProvider.test.tsx +107 -0
- package/src/stores/StoreProvider.tsx +8 -6
- package/src/stores/header/useHeaderStore.tsx +3 -4
- package/src/types/widget.ts +30 -0
- package/src/utils/queries.ts +1 -0
- package/src/utils/scopeKeys.ts +18 -0
- package/src/utils/url.ts +17 -0
package/src/hooks/useTokens.ts
CHANGED
|
@@ -25,6 +25,14 @@ import { useAvailableChains } from './useAvailableChains.js'
|
|
|
25
25
|
|
|
26
26
|
const refetchInterval = 300_000
|
|
27
27
|
|
|
28
|
+
// The main list and the search send the same list parameters.
|
|
29
|
+
const tokenListParams = {
|
|
30
|
+
orderBy: 'volumeUSD24H',
|
|
31
|
+
extended: true,
|
|
32
|
+
limit: 1100,
|
|
33
|
+
minPriceUSD: 0.000001,
|
|
34
|
+
} as const
|
|
35
|
+
|
|
28
36
|
export const useTokens = (
|
|
29
37
|
formType?: FormType,
|
|
30
38
|
search?: string,
|
|
@@ -39,7 +47,7 @@ export const useTokens = (
|
|
|
39
47
|
const {
|
|
40
48
|
tokens: configTokens,
|
|
41
49
|
chains: chainsConfig,
|
|
42
|
-
|
|
50
|
+
storageScopeKey,
|
|
43
51
|
} = useWidgetConfig()
|
|
44
52
|
const sdkClient = useSDKClient()
|
|
45
53
|
const { getChainTypeFromTokenAddress } = useChainTypeFromAddress()
|
|
@@ -55,7 +63,7 @@ export const useTokens = (
|
|
|
55
63
|
|
|
56
64
|
// Main tokens cache - verified tokens from API
|
|
57
65
|
const { data: verifiedTokens, isLoading } = useQuery({
|
|
58
|
-
queryKey: [getQueryKey('tokens',
|
|
66
|
+
queryKey: [getQueryKey('tokens', storageScopeKey)],
|
|
59
67
|
queryFn: async ({ signal }) => {
|
|
60
68
|
const chainTypes = [
|
|
61
69
|
ChainType.EVM,
|
|
@@ -68,13 +76,7 @@ export const useTokens = (
|
|
|
68
76
|
|
|
69
77
|
const tokensResponse: TokensExtendedResponse = await getTokens(
|
|
70
78
|
sdkClient,
|
|
71
|
-
{
|
|
72
|
-
chainTypes,
|
|
73
|
-
orderBy: 'volumeUSD24H',
|
|
74
|
-
extended: true,
|
|
75
|
-
limit: 1000,
|
|
76
|
-
minPriceUSD: 0.000001,
|
|
77
|
-
},
|
|
79
|
+
{ ...tokenListParams, chainTypes },
|
|
78
80
|
{ signal }
|
|
79
81
|
)
|
|
80
82
|
|
|
@@ -95,7 +97,7 @@ export const useTokens = (
|
|
|
95
97
|
// Search tokens cache - unverified tokens from search
|
|
96
98
|
const { data: searchTokens, isLoading: isSearchLoading } = useQuery({
|
|
97
99
|
queryKey: [
|
|
98
|
-
getQueryKey('tokens-search',
|
|
100
|
+
getQueryKey('tokens-search', storageScopeKey),
|
|
99
101
|
trimmedSearch,
|
|
100
102
|
chainId,
|
|
101
103
|
isAddressSearch,
|
|
@@ -114,12 +116,9 @@ export const useTokens = (
|
|
|
114
116
|
const tokensResponse: TokensExtendedResponse = await getTokens(
|
|
115
117
|
sdkClient,
|
|
116
118
|
{
|
|
119
|
+
...tokenListParams,
|
|
117
120
|
chainTypes,
|
|
118
|
-
orderBy: 'volumeUSD24H',
|
|
119
|
-
extended: true,
|
|
120
121
|
search: searchQuery,
|
|
121
|
-
limit: 1000,
|
|
122
|
-
minPriceUSD: 0.000001,
|
|
123
122
|
},
|
|
124
123
|
{ signal }
|
|
125
124
|
)
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/** @vitest-environment happy-dom */
|
|
2
|
+
|
|
3
|
+
import type * as LifiSdk from '@lifi/sdk'
|
|
4
|
+
import type { SDKClient, ToolsResponse } from '@lifi/sdk'
|
|
5
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
6
|
+
import { act } from 'react'
|
|
7
|
+
import { createRoot, type Root } from 'react-dom/client'
|
|
8
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
9
|
+
import type { WidgetConfig } from '../types/widget.js'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The tools entry is shared with the host app through
|
|
13
|
+
* `getToolsQueryOptions`, keyed by the client's API base and the widget's
|
|
14
|
+
* `queryScopeKey`. When the host fetched first, the widget must read that entry
|
|
15
|
+
* — no request of its own — and still seed its settings store from the
|
|
16
|
+
* filtered view.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const API_URL = 'https://api.example/v1'
|
|
20
|
+
|
|
21
|
+
const mocks = vi.hoisted(() => ({
|
|
22
|
+
getTools: vi.fn(),
|
|
23
|
+
initializeTools: vi.fn(),
|
|
24
|
+
}))
|
|
25
|
+
|
|
26
|
+
vi.mock('@lifi/sdk', async (importOriginal) => ({
|
|
27
|
+
...(await importOriginal<typeof LifiSdk>()),
|
|
28
|
+
getTools: mocks.getTools,
|
|
29
|
+
}))
|
|
30
|
+
|
|
31
|
+
let widgetConfig: Partial<WidgetConfig> = {}
|
|
32
|
+
|
|
33
|
+
vi.mock('../providers/WidgetProvider/WidgetProvider.js', () => ({
|
|
34
|
+
useWidgetConfig: () => widgetConfig,
|
|
35
|
+
}))
|
|
36
|
+
|
|
37
|
+
const client = { config: { apiUrl: API_URL } } as unknown as SDKClient
|
|
38
|
+
vi.mock('../providers/SDKClientProvider.js', () => ({
|
|
39
|
+
useSDKClient: () => client,
|
|
40
|
+
}))
|
|
41
|
+
|
|
42
|
+
const settingsStore = {
|
|
43
|
+
getState: () => ({ initializeTools: mocks.initializeTools }),
|
|
44
|
+
}
|
|
45
|
+
vi.mock('../stores/settings/SettingsStore.js', () => ({
|
|
46
|
+
useSettingsStoreContext: () => settingsStore,
|
|
47
|
+
}))
|
|
48
|
+
|
|
49
|
+
import { getToolsQueryKey } from '../queries/getTools.js'
|
|
50
|
+
import { useTools } from './useTools.js'
|
|
51
|
+
|
|
52
|
+
const TOOLS = {
|
|
53
|
+
bridges: [{ key: 'across' }, { key: 'stargate' }, { key: 'hop' }],
|
|
54
|
+
exchanges: [{ key: '1inch' }, { key: 'paraswap' }],
|
|
55
|
+
} as unknown as ToolsResponse
|
|
56
|
+
|
|
57
|
+
const Probe = () => {
|
|
58
|
+
useTools()
|
|
59
|
+
return null
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let root: Root
|
|
63
|
+
let queryClient: QueryClient
|
|
64
|
+
|
|
65
|
+
const render = async () => {
|
|
66
|
+
await act(async () => {
|
|
67
|
+
root.render(
|
|
68
|
+
<QueryClientProvider client={queryClient}>
|
|
69
|
+
<Probe />
|
|
70
|
+
</QueryClientProvider>
|
|
71
|
+
)
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
describe('useTools with a shared tools entry', () => {
|
|
76
|
+
beforeEach(() => {
|
|
77
|
+
vi.clearAllMocks()
|
|
78
|
+
mocks.getTools.mockResolvedValue(TOOLS)
|
|
79
|
+
widgetConfig = {
|
|
80
|
+
integrator: 'test',
|
|
81
|
+
queryScopeKey: 'host',
|
|
82
|
+
bridges: { allow: ['across', 'hop'] },
|
|
83
|
+
exchanges: { deny: ['paraswap'] },
|
|
84
|
+
}
|
|
85
|
+
queryClient = new QueryClient({
|
|
86
|
+
defaultOptions: { queries: { retry: false } },
|
|
87
|
+
})
|
|
88
|
+
const container = document.createElement('div')
|
|
89
|
+
document.body.appendChild(container)
|
|
90
|
+
root = createRoot(container)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
afterEach(async () => {
|
|
94
|
+
await act(async () => root.unmount())
|
|
95
|
+
queryClient.clear()
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('initialises the settings store when the host already fetched the tools', async () => {
|
|
99
|
+
// The host app fetched first and filled the shared entry.
|
|
100
|
+
queryClient.setQueryData(
|
|
101
|
+
getToolsQueryKey({ apiUrl: API_URL, scopeKey: 'host' }),
|
|
102
|
+
TOOLS
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
await render()
|
|
106
|
+
|
|
107
|
+
await vi.waitFor(() => {
|
|
108
|
+
expect(mocks.initializeTools).toHaveBeenCalledWith('Bridges', [
|
|
109
|
+
'across',
|
|
110
|
+
'hop',
|
|
111
|
+
])
|
|
112
|
+
})
|
|
113
|
+
expect(mocks.initializeTools).toHaveBeenCalledWith('Exchanges', ['1inch'])
|
|
114
|
+
// The widget read the host's entry instead of sending its own request.
|
|
115
|
+
expect(mocks.getTools).not.toHaveBeenCalled()
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('initialises the settings store when the widget fetched the tools itself', async () => {
|
|
119
|
+
await render()
|
|
120
|
+
|
|
121
|
+
await vi.waitFor(() => {
|
|
122
|
+
expect(mocks.initializeTools).toHaveBeenCalledWith('Bridges', [
|
|
123
|
+
'across',
|
|
124
|
+
'hop',
|
|
125
|
+
])
|
|
126
|
+
})
|
|
127
|
+
expect(mocks.getTools).toHaveBeenCalledTimes(1)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('initialises with the filtered view while the cache keeps the raw list', async () => {
|
|
131
|
+
await render()
|
|
132
|
+
|
|
133
|
+
await vi.waitFor(() => {
|
|
134
|
+
expect(mocks.initializeTools).toHaveBeenCalled()
|
|
135
|
+
})
|
|
136
|
+
const cached = queryClient.getQueryData<ToolsResponse>(
|
|
137
|
+
getToolsQueryKey({ apiUrl: API_URL, scopeKey: 'host' })
|
|
138
|
+
)
|
|
139
|
+
expect(cached?.bridges).toHaveLength(3)
|
|
140
|
+
expect(cached?.exchanges).toHaveLength(2)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('refreshes the tools every three minutes', async () => {
|
|
144
|
+
await render()
|
|
145
|
+
await vi.waitFor(() => {
|
|
146
|
+
expect(mocks.initializeTools).toHaveBeenCalled()
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
const [observer] = queryClient.getQueryCache().find({
|
|
150
|
+
queryKey: getToolsQueryKey({ apiUrl: API_URL, scopeKey: 'host' }),
|
|
151
|
+
})!.observers
|
|
152
|
+
expect(observer.options.refetchInterval).toBe(180_000)
|
|
153
|
+
expect(observer.options.staleTime).toBe(180_000)
|
|
154
|
+
})
|
|
155
|
+
})
|
package/src/hooks/useTools.ts
CHANGED
|
@@ -1,56 +1,61 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ToolsResponse } from '@lifi/sdk'
|
|
2
2
|
import { useQuery } from '@tanstack/react-query'
|
|
3
|
+
import { useCallback, useEffect } from 'react'
|
|
3
4
|
import { useSDKClient } from '../providers/SDKClientProvider.js'
|
|
4
5
|
import { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js'
|
|
6
|
+
import { getToolsQueryOptions } from '../queries/getTools.js'
|
|
5
7
|
import { useSettingsStoreContext } from '../stores/settings/SettingsStore.js'
|
|
6
8
|
import { getConfigItemSets, isItemAllowedForSets } from '../utils/item.js'
|
|
7
|
-
|
|
9
|
+
|
|
10
|
+
const refetchInterval = 180_000
|
|
8
11
|
|
|
9
12
|
export const useTools = (): { tools: ToolsResponse | undefined } => {
|
|
10
|
-
const { bridges, exchanges,
|
|
13
|
+
const { bridges, exchanges, queryScopeKey } = useWidgetConfig()
|
|
11
14
|
const settingsStore = useSettingsStoreContext()
|
|
12
15
|
const sdkClient = useSDKClient()
|
|
13
16
|
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
exchanges?.allow,
|
|
20
|
-
exchanges?.deny,
|
|
21
|
-
],
|
|
22
|
-
queryFn: async (): Promise<ToolsResponse> => {
|
|
23
|
-
const tools = await getTools(sdkClient)
|
|
24
|
-
const bridgesConfigSets = getConfigItemSets(
|
|
25
|
-
bridges,
|
|
26
|
-
(bridges) => new Set(bridges)
|
|
27
|
-
)
|
|
28
|
-
const exchangesConfigSets = getConfigItemSets(
|
|
29
|
-
exchanges,
|
|
30
|
-
(exchanges) => new Set(exchanges)
|
|
31
|
-
)
|
|
32
|
-
const result = {
|
|
17
|
+
const selectAllowedTools = useCallback(
|
|
18
|
+
(tools: ToolsResponse): ToolsResponse => {
|
|
19
|
+
const bridgeSets = getConfigItemSets(bridges, (keys) => new Set(keys))
|
|
20
|
+
const exchangeSets = getConfigItemSets(exchanges, (keys) => new Set(keys))
|
|
21
|
+
return {
|
|
33
22
|
bridges: tools.bridges.filter((bridge) =>
|
|
34
|
-
isItemAllowedForSets(bridge.key,
|
|
23
|
+
isItemAllowedForSets(bridge.key, bridgeSets)
|
|
35
24
|
),
|
|
36
25
|
exchanges: tools.exchanges.filter((exchange) =>
|
|
37
|
-
isItemAllowedForSets(exchange.key,
|
|
26
|
+
isItemAllowedForSets(exchange.key, exchangeSets)
|
|
38
27
|
),
|
|
39
28
|
}
|
|
40
|
-
const { initializeTools } = settingsStore.getState()
|
|
41
|
-
initializeTools(
|
|
42
|
-
'Bridges',
|
|
43
|
-
result.bridges.map((bridge) => bridge.key)
|
|
44
|
-
)
|
|
45
|
-
initializeTools(
|
|
46
|
-
'Exchanges',
|
|
47
|
-
result.exchanges.map((exchange) => exchange.key)
|
|
48
|
-
)
|
|
49
|
-
return result
|
|
50
29
|
},
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
30
|
+
[bridges, exchanges]
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
const { data } = useQuery(
|
|
34
|
+
getToolsQueryOptions(sdkClient, {
|
|
35
|
+
scopeKey: queryScopeKey,
|
|
36
|
+
query: {
|
|
37
|
+
refetchInterval,
|
|
38
|
+
staleTime: refetchInterval,
|
|
39
|
+
select: selectAllowedTools,
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
// A host may fill the shared entry, so seed from the data, not in a queryFn.
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (!data) {
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
const { initializeTools } = settingsStore.getState()
|
|
50
|
+
initializeTools(
|
|
51
|
+
'Bridges',
|
|
52
|
+
data.bridges.map((bridge) => bridge.key)
|
|
53
|
+
)
|
|
54
|
+
initializeTools(
|
|
55
|
+
'Exchanges',
|
|
56
|
+
data.exchanges.map((exchange) => exchange.key)
|
|
57
|
+
)
|
|
58
|
+
}, [data, settingsStore])
|
|
54
59
|
|
|
55
60
|
return { tools: data }
|
|
56
61
|
}
|
|
@@ -19,12 +19,12 @@ export const useTransactionDetails = (
|
|
|
19
19
|
} => {
|
|
20
20
|
const { account, accounts } = useAccount()
|
|
21
21
|
const queryClient = useQueryClient()
|
|
22
|
-
const {
|
|
22
|
+
const { storageScopeKey } = useWidgetConfig()
|
|
23
23
|
const sdkClient = useSDKClient()
|
|
24
24
|
|
|
25
25
|
const transactionHistoryQueryKey = useMemo(
|
|
26
|
-
() => getQueryKey('transaction-history',
|
|
27
|
-
[
|
|
26
|
+
() => getQueryKey('transaction-history', storageScopeKey),
|
|
27
|
+
[storageScopeKey]
|
|
28
28
|
)
|
|
29
29
|
|
|
30
30
|
const { data, isLoading } = useQuery({
|
|
@@ -22,14 +22,14 @@ export const useTransactionHistory = (): {
|
|
|
22
22
|
} => {
|
|
23
23
|
const store = useRouteExecutionStoreContext()
|
|
24
24
|
const { accounts } = useAccount()
|
|
25
|
-
const {
|
|
25
|
+
const { storageScopeKey } = useWidgetConfig()
|
|
26
26
|
const sdkClient = useSDKClient()
|
|
27
27
|
const { tools } = useTools()
|
|
28
28
|
|
|
29
29
|
const { data: routeExecutions, isLoading } = useQueries({
|
|
30
30
|
queries: accounts.map((account) => ({
|
|
31
31
|
queryKey: [
|
|
32
|
-
getQueryKey('transaction-history',
|
|
32
|
+
getQueryKey('transaction-history', storageScopeKey),
|
|
33
33
|
account.address,
|
|
34
34
|
],
|
|
35
35
|
queryFn: (async ({ queryKey: [, accountAddress], signal }) => {
|
package/src/index.ts
CHANGED
|
@@ -8,13 +8,14 @@ export * from './components/ContractComponent/NFT/NFTBase.js'
|
|
|
8
8
|
export * from './components/ContractComponent/NFT/types.js'
|
|
9
9
|
export * from './components/Skeleton/WidgetSkeleton.js'
|
|
10
10
|
export { defaultMaxHeight } from './config/constants.js'
|
|
11
|
-
export
|
|
11
|
+
export { name, version } from './config/version.js'
|
|
12
12
|
export { useWidgetChains } from './hooks/useWidgetChains.js'
|
|
13
13
|
export {
|
|
14
14
|
useWidgetEvents,
|
|
15
15
|
type WidgetEventEmitter,
|
|
16
16
|
widgetEvents,
|
|
17
17
|
} from './hooks/useWidgetEvents.js'
|
|
18
|
+
export * from './queries/index.js'
|
|
18
19
|
export * from './stores/bookmarks/types.js'
|
|
19
20
|
export * from './stores/form/types.js'
|
|
20
21
|
export { useFieldActions } from './stores/form/useFieldActions.js'
|
|
@@ -23,6 +23,7 @@ import { useBookmarks } from '../../stores/bookmarks/useBookmarks.js'
|
|
|
23
23
|
import { useFieldActions } from '../../stores/form/useFieldActions.js'
|
|
24
24
|
import { defaultChainIdsByType } from '../../utils/chainType.js'
|
|
25
25
|
import { navigationRoutes } from '../../utils/navigationRoutes.js'
|
|
26
|
+
import { openInNewTab } from '../../utils/url.js'
|
|
26
27
|
import { shortenAddress } from '../../utils/wallet.js'
|
|
27
28
|
import { BookmarkAddressSheet } from './BookmarkAddressSheet.js'
|
|
28
29
|
import { BookmarkButtonContainer } from './SendToWalletPage.style.js'
|
|
@@ -106,12 +107,11 @@ export const BookmarksPage = (): JSX.Element => {
|
|
|
106
107
|
icon: <OpenInNewRounded />,
|
|
107
108
|
label: t('button.viewOnExplorer'),
|
|
108
109
|
onClick: () =>
|
|
109
|
-
|
|
110
|
+
openInNewTab(
|
|
110
111
|
getAddressLink(
|
|
111
112
|
bookmark.address,
|
|
112
113
|
defaultChainIdsByType[bookmark.chainType]
|
|
113
|
-
)
|
|
114
|
-
'_blank'
|
|
114
|
+
)
|
|
115
115
|
),
|
|
116
116
|
},
|
|
117
117
|
{
|
|
@@ -20,6 +20,7 @@ import { useToAddressRequirements } from '../../hooks/useToAddressRequirements.j
|
|
|
20
20
|
import { useBookmarkActions } from '../../stores/bookmarks/useBookmarkActions.js'
|
|
21
21
|
import { useFieldActions } from '../../stores/form/useFieldActions.js'
|
|
22
22
|
import { navigationRoutes } from '../../utils/navigationRoutes.js'
|
|
23
|
+
import { openInNewTab } from '../../utils/url.js'
|
|
23
24
|
import { shortenAddress } from '../../utils/wallet.js'
|
|
24
25
|
|
|
25
26
|
export const ConnectedWalletsPage = (): JSX.Element => {
|
|
@@ -81,9 +82,8 @@ export const ConnectedWalletsPage = (): JSX.Element => {
|
|
|
81
82
|
label: t('button.viewOnExplorer'),
|
|
82
83
|
onClick: () => {
|
|
83
84
|
if (account.address) {
|
|
84
|
-
|
|
85
|
-
getAddressLink(account.address, account.chainId)
|
|
86
|
-
'_blank'
|
|
85
|
+
openInNewTab(
|
|
86
|
+
getAddressLink(account.address, account.chainId)
|
|
87
87
|
)
|
|
88
88
|
}
|
|
89
89
|
},
|
|
@@ -24,6 +24,7 @@ import { useBookmarks } from '../../stores/bookmarks/useBookmarks.js'
|
|
|
24
24
|
import { useFieldActions } from '../../stores/form/useFieldActions.js'
|
|
25
25
|
import { defaultChainIdsByType } from '../../utils/chainType.js'
|
|
26
26
|
import { navigationRoutes } from '../../utils/navigationRoutes.js'
|
|
27
|
+
import { openInNewTab } from '../../utils/url.js'
|
|
27
28
|
import { shortenAddress } from '../../utils/wallet.js'
|
|
28
29
|
import { BookmarkAddressSheet } from './BookmarkAddressSheet.js'
|
|
29
30
|
|
|
@@ -118,12 +119,11 @@ export const RecentWalletsPage = (): JSX.Element => {
|
|
|
118
119
|
icon: <OpenInNewRounded />,
|
|
119
120
|
label: t('button.viewOnExplorer'),
|
|
120
121
|
onClick: () =>
|
|
121
|
-
|
|
122
|
+
openInNewTab(
|
|
122
123
|
getAddressLink(
|
|
123
124
|
recentWallet.address,
|
|
124
125
|
defaultChainIdsByType[recentWallet.chainType]
|
|
125
|
-
)
|
|
126
|
-
'_blank'
|
|
126
|
+
)
|
|
127
127
|
),
|
|
128
128
|
},
|
|
129
129
|
{
|
|
@@ -18,6 +18,7 @@ import { useBookmarkActions } from '../../stores/bookmarks/useBookmarkActions.js
|
|
|
18
18
|
import { useFieldActions } from '../../stores/form/useFieldActions.js'
|
|
19
19
|
import type { ToAddress } from '../../types/widget.js'
|
|
20
20
|
import { defaultChainIdsByType } from '../../utils/chainType.js'
|
|
21
|
+
import { openInNewTab } from '../../utils/url.js'
|
|
21
22
|
import { shortenAddress } from '../../utils/wallet.js'
|
|
22
23
|
|
|
23
24
|
export const SendToConfiguredWalletPage = (): JSX.Element => {
|
|
@@ -92,12 +93,11 @@ export const SendToConfiguredWalletPage = (): JSX.Element => {
|
|
|
92
93
|
icon: <OpenInNewRounded />,
|
|
93
94
|
label: t('button.viewOnExplorer'),
|
|
94
95
|
onClick: () =>
|
|
95
|
-
|
|
96
|
+
openInNewTab(
|
|
96
97
|
getAddressLink(
|
|
97
98
|
toAddress.address,
|
|
98
99
|
defaultChainIdsByType[toAddress.chainType]
|
|
99
|
-
)
|
|
100
|
-
'_blank'
|
|
100
|
+
)
|
|
101
101
|
),
|
|
102
102
|
},
|
|
103
103
|
]}
|
|
@@ -15,6 +15,7 @@ import { useRouteExecutionStore } from '../../stores/routes/RouteExecutionStore.
|
|
|
15
15
|
import { getSourceTxHash } from '../../stores/routes/utils.js'
|
|
16
16
|
import { buildRouteFromTxHistory } from '../../utils/converters.js'
|
|
17
17
|
import { navigationRoutes } from '../../utils/navigationRoutes.js'
|
|
18
|
+
import { isHttpUrl } from '../../utils/url.js'
|
|
18
19
|
import { ReceiptsCard } from './ReceiptsCard.js'
|
|
19
20
|
import { TransactionDetailsSkeleton } from './TransactionDetailsSkeleton.js'
|
|
20
21
|
import { TransferIdCard } from './TransferIdCard.js'
|
|
@@ -70,11 +71,10 @@ export const TransactionDetailsPage: React.FC = () => {
|
|
|
70
71
|
routeExecution?.route?.steps?.at(-1)?.execution?.externalTxLink
|
|
71
72
|
|
|
72
73
|
const txLink =
|
|
73
|
-
|
|
74
|
-
? internalTxLink?.replace(internalExplorerUrl, url)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
getTransactionLink({ txHash: supportId })
|
|
74
|
+
[
|
|
75
|
+
url ? internalTxLink?.replace(internalExplorerUrl, url) : internalTxLink,
|
|
76
|
+
externalTxLink,
|
|
77
|
+
].find(isHttpUrl) ?? getTransactionLink({ txHash: supportId })
|
|
78
78
|
|
|
79
79
|
if (process.env.NODE_ENV === 'development') {
|
|
80
80
|
supportId += `_${routeExecution?.route.id}`
|
|
@@ -7,6 +7,7 @@ import { Card } from '../../components/Card/Card.js'
|
|
|
7
7
|
import { CardIconButton } from '../../components/Card/CardIconButton.js'
|
|
8
8
|
import { CardTitle } from '../../components/Card/CardTitle.js'
|
|
9
9
|
import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js'
|
|
10
|
+
import { openInNewTab } from '../../utils/url.js'
|
|
10
11
|
import { ContactSupportButton } from './ContactSupportButton.js'
|
|
11
12
|
|
|
12
13
|
interface TransferIdCardProps {
|
|
@@ -26,7 +27,7 @@ export const TransferIdCard = ({
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
const openTransferIdInExplorer = () => {
|
|
29
|
-
|
|
30
|
+
openInNewTab(txLink)
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
return (
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** @vitest-environment happy-dom */
|
|
2
|
+
|
|
3
|
+
import { act } from 'react'
|
|
4
|
+
import { createRoot, type Root } from 'react-dom/client'
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
6
|
+
import type { WidgetConfig } from '../../types/widget.js'
|
|
7
|
+
import type { WidgetContextProps } from './types.js'
|
|
8
|
+
|
|
9
|
+
const settingsActions = { setDefaultSettings: vi.fn() }
|
|
10
|
+
vi.mock('../../stores/settings/useSettingsActions.js', () => ({
|
|
11
|
+
useSettingsActions: () => settingsActions,
|
|
12
|
+
}))
|
|
13
|
+
|
|
14
|
+
import { useWidgetConfig, WidgetProvider } from './WidgetProvider.js'
|
|
15
|
+
|
|
16
|
+
let root: Root
|
|
17
|
+
let seen: WidgetContextProps | undefined
|
|
18
|
+
|
|
19
|
+
const Probe = () => {
|
|
20
|
+
seen = useWidgetConfig()
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const render = async (config: WidgetConfig) => {
|
|
25
|
+
await act(async () => {
|
|
26
|
+
root.render(
|
|
27
|
+
<WidgetProvider config={config}>
|
|
28
|
+
<Probe />
|
|
29
|
+
</WidgetProvider>
|
|
30
|
+
)
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe('WidgetProvider scope keys', () => {
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
seen = undefined
|
|
37
|
+
const container = document.createElement('div')
|
|
38
|
+
document.body.appendChild(container)
|
|
39
|
+
root = createRoot(container)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
afterEach(async () => {
|
|
43
|
+
await act(async () => root.unmount())
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
// An integration that only ever set the deprecated keyPrefix keeps its
|
|
47
|
+
// isolation: everything below reads the resolved keys, never keyPrefix.
|
|
48
|
+
it('resolves both scopes from keyPrefix alone', async () => {
|
|
49
|
+
await render({ integrator: 'test', keyPrefix: 'legacy' })
|
|
50
|
+
|
|
51
|
+
expect(seen?.storageScopeKey).toBe('legacy')
|
|
52
|
+
expect(seen?.queryScopeKey).toBe('legacy')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('prefers the explicit scopes over keyPrefix', async () => {
|
|
56
|
+
await render({
|
|
57
|
+
integrator: 'test',
|
|
58
|
+
keyPrefix: 'legacy',
|
|
59
|
+
storageScopeKey: 'store',
|
|
60
|
+
queryScopeKey: 'query',
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
expect(seen?.storageScopeKey).toBe('store')
|
|
64
|
+
expect(seen?.queryScopeKey).toBe('query')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('keeps the historical storage names and the default query scope when nothing is set', async () => {
|
|
68
|
+
await render({ integrator: 'test' })
|
|
69
|
+
|
|
70
|
+
expect(seen?.storageScopeKey).toBeUndefined()
|
|
71
|
+
expect(seen?.queryScopeKey).toBe('li.fi')
|
|
72
|
+
})
|
|
73
|
+
})
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import type { Context } from 'react'
|
|
2
2
|
import { createContext, use, useId, useMemo } from 'react'
|
|
3
3
|
import { useSettingsActions } from '../../stores/settings/useSettingsActions.js'
|
|
4
|
+
import {
|
|
5
|
+
defaultQueryScopeKey,
|
|
6
|
+
resolveQueryScopeKey,
|
|
7
|
+
resolveStorageScopeKey,
|
|
8
|
+
} from '../../utils/scopeKeys.js'
|
|
4
9
|
import type { WidgetContextProps, WidgetProviderProps } from './types.js'
|
|
5
10
|
|
|
6
11
|
const initialContext: WidgetContextProps = {
|
|
7
12
|
elementId: '',
|
|
8
13
|
integrator: '',
|
|
14
|
+
queryScopeKey: defaultQueryScopeKey,
|
|
9
15
|
}
|
|
10
16
|
|
|
11
17
|
export const WidgetContext: Context<WidgetContextProps> =
|
|
@@ -24,10 +30,16 @@ export const WidgetProvider: React.FC<
|
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
const value = useMemo((): WidgetContextProps => {
|
|
33
|
+
// Resolved once, so nothing below reads the deprecated keyPrefix.
|
|
34
|
+
const scopeKeys = {
|
|
35
|
+
storageScopeKey: resolveStorageScopeKey(widgetConfig),
|
|
36
|
+
queryScopeKey: resolveQueryScopeKey(widgetConfig),
|
|
37
|
+
}
|
|
27
38
|
try {
|
|
28
39
|
// Create widget configuration object
|
|
29
40
|
const value = {
|
|
30
41
|
...widgetConfig,
|
|
42
|
+
...scopeKeys,
|
|
31
43
|
elementId,
|
|
32
44
|
} as WidgetContextProps
|
|
33
45
|
|
|
@@ -39,6 +51,7 @@ export const WidgetProvider: React.FC<
|
|
|
39
51
|
console.warn(e)
|
|
40
52
|
return {
|
|
41
53
|
...widgetConfig,
|
|
54
|
+
...scopeKeys,
|
|
42
55
|
elementId,
|
|
43
56
|
integrator: widgetConfig.integrator,
|
|
44
57
|
}
|
|
@@ -2,6 +2,8 @@ import type { WidgetConfig } from '../../types/widget.js'
|
|
|
2
2
|
|
|
3
3
|
export type WidgetContextProps = WidgetConfig & {
|
|
4
4
|
elementId: string
|
|
5
|
+
/** Always resolved by `WidgetProvider` — see `resolveQueryScopeKey`. */
|
|
6
|
+
queryScopeKey: string
|
|
5
7
|
}
|
|
6
8
|
|
|
7
9
|
export interface WidgetProviderProps {
|