@lifi/widget 3.36.1 → 3.38.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/CHANGELOG.md +26 -0
- package/dist/esm/components/AmountInput/PriceFormHelperText.style.js +3 -0
- package/dist/esm/components/AmountInput/PriceFormHelperText.style.js.map +1 -1
- package/dist/esm/components/AppContainer.js +1 -0
- package/dist/esm/components/AppContainer.js.map +1 -1
- package/dist/esm/components/Avatar/AccountAvatar.js +3 -3
- package/dist/esm/components/Avatar/AccountAvatar.js.map +1 -1
- package/dist/esm/components/Avatar/Avatar.d.ts +2 -0
- package/dist/esm/components/Avatar/Avatar.js +4 -3
- package/dist/esm/components/Avatar/Avatar.js.map +1 -1
- package/dist/esm/components/Avatar/ChainBadgeContent.d.ts +7 -0
- package/dist/esm/components/Avatar/ChainBadgeContent.js +10 -0
- package/dist/esm/components/Avatar/ChainBadgeContent.js.map +1 -0
- package/dist/esm/components/Avatar/TokenAvatar.js +3 -3
- package/dist/esm/components/Avatar/TokenAvatar.js.map +1 -1
- package/dist/esm/components/Chains/AllChainsAvatar.js +31 -5
- package/dist/esm/components/Chains/AllChainsAvatar.js.map +1 -1
- package/dist/esm/components/Chains/ChainSearchInput.js +7 -1
- package/dist/esm/components/Chains/ChainSearchInput.js.map +1 -1
- package/dist/esm/components/Header/NavigationHeader.js +5 -1
- package/dist/esm/components/Header/NavigationHeader.js.map +1 -1
- package/dist/esm/components/RouteCard/RouteCard.js +2 -1
- package/dist/esm/components/RouteCard/RouteCard.js.map +1 -1
- package/dist/esm/components/SelectTokenButton/SelectTokenButton.js +3 -1
- package/dist/esm/components/SelectTokenButton/SelectTokenButton.js.map +1 -1
- package/dist/esm/components/TokenList/TokenListItem.js +8 -2
- package/dist/esm/components/TokenList/TokenListItem.js.map +1 -1
- package/dist/esm/config/version.d.ts +1 -1
- package/dist/esm/config/version.js +1 -1
- package/dist/esm/hooks/useFilteredByTokenBalances.js +8 -2
- package/dist/esm/hooks/useFilteredByTokenBalances.js.map +1 -1
- package/dist/esm/hooks/useRoutes.js +10 -19
- package/dist/esm/hooks/useRoutes.js.map +1 -1
- package/dist/esm/hooks/useTokenSearch.js +15 -8
- package/dist/esm/hooks/useTokenSearch.js.map +1 -1
- package/dist/esm/hooks/useTokens.js +33 -53
- package/dist/esm/hooks/useTokens.js.map +1 -1
- package/dist/esm/i18n/en.json +1 -0
- package/dist/esm/providers/I18nProvider/I18nProvider.js +27 -3
- package/dist/esm/providers/I18nProvider/I18nProvider.js.map +1 -1
- package/dist/esm/stores/StoreProvider.js +2 -1
- package/dist/esm/stores/StoreProvider.js.map +1 -1
- package/dist/esm/stores/chains/ChainOrderStore.js +15 -2
- package/dist/esm/stores/chains/ChainOrderStore.js.map +1 -1
- package/dist/esm/stores/settings/createSettingsStore.d.ts +2 -0
- package/dist/esm/stores/settings/types.d.ts +1 -0
- package/dist/esm/stores/settings/utils/getStateValues.js +1 -0
- package/dist/esm/stores/settings/utils/getStateValues.js.map +1 -1
- package/dist/esm/types/token.d.ts +6 -1
- package/dist/esm/types/widget.d.ts +12 -2
- package/dist/esm/types/widget.js +2 -0
- package/dist/esm/types/widget.js.map +1 -1
- package/dist/esm/utils/token.d.ts +12 -1
- package/dist/esm/utils/token.js +44 -0
- package/dist/esm/utils/token.js.map +1 -1
- package/dist/esm/utils/tokenList.js +2 -0
- package/dist/esm/utils/tokenList.js.map +1 -1
- package/dist/esm/utils/variant.d.ts +2 -0
- package/dist/esm/utils/variant.js +10 -0
- package/dist/esm/utils/variant.js.map +1 -0
- package/package.json +7 -7
- package/package.json.tmp +9 -9
- package/src/components/AmountInput/PriceFormHelperText.style.tsx +3 -0
- package/src/components/AppContainer.tsx +1 -0
- package/src/components/Avatar/AccountAvatar.tsx +3 -15
- package/src/components/Avatar/Avatar.tsx +6 -7
- package/src/components/Avatar/ChainBadgeContent.tsx +22 -0
- package/src/components/Avatar/TokenAvatar.tsx +3 -11
- package/src/components/Chains/AllChainsAvatar.tsx +56 -8
- package/src/components/Chains/ChainSearchInput.tsx +9 -1
- package/src/components/Header/NavigationHeader.tsx +5 -1
- package/src/components/RouteCard/RouteCard.tsx +2 -1
- package/src/components/SelectTokenButton/SelectTokenButton.tsx +9 -1
- package/src/components/TokenList/TokenListItem.tsx +23 -1
- package/src/config/version.ts +1 -1
- package/src/hooks/useFilteredByTokenBalances.ts +8 -4
- package/src/hooks/useRoutes.ts +17 -23
- package/src/hooks/useTokenSearch.ts +17 -10
- package/src/hooks/useTokens.ts +51 -83
- package/src/i18n/en.json +1 -0
- package/src/providers/I18nProvider/I18nProvider.tsx +39 -2
- package/src/stores/StoreProvider.tsx +2 -1
- package/src/stores/chains/ChainOrderStore.tsx +18 -2
- package/src/stores/settings/types.ts +1 -0
- package/src/stores/settings/utils/getStateValues.ts +1 -0
- package/src/types/token.ts +5 -0
- package/src/types/widget.ts +11 -1
- package/src/utils/token.ts +65 -1
- package/src/utils/tokenList.ts +2 -0
- package/src/utils/variant.ts +16 -0
package/src/hooks/useRoutes.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Route, Token
|
|
1
|
+
import type { Route, Token } from '@lifi/sdk'
|
|
2
2
|
import {
|
|
3
3
|
ChainType,
|
|
4
4
|
convertQuoteToRoute,
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
getRelayerQuote,
|
|
7
7
|
getRoutes,
|
|
8
8
|
isGaslessStep,
|
|
9
|
-
isTokenMessageSigningAllowed,
|
|
10
9
|
LiFiErrorCode,
|
|
11
10
|
} from '@lifi/sdk'
|
|
12
11
|
import { useAccount } from '@lifi/wallet-management'
|
|
@@ -20,8 +19,10 @@ import { useSetExecutableRoute } from '../stores/routes/useSetExecutableRoute.js
|
|
|
20
19
|
import { defaultSlippage } from '../stores/settings/createSettingsStore.js'
|
|
21
20
|
import { useSettings } from '../stores/settings/useSettings.js'
|
|
22
21
|
import { WidgetEvent } from '../types/events.js'
|
|
22
|
+
import type { TokensByChain } from '../types/token.js'
|
|
23
23
|
import { getChainTypeFromAddress } from '../utils/chainType.js'
|
|
24
24
|
import { getQueryKey } from '../utils/queries.js'
|
|
25
|
+
import { updateTokenInCache } from '../utils/token.js'
|
|
25
26
|
import { useChain } from './useChain.js'
|
|
26
27
|
import { useDebouncedWatch } from './useDebouncedWatch.js'
|
|
27
28
|
import { useGasRefuel } from './useGasRefuel.js'
|
|
@@ -352,8 +353,7 @@ export const useRoutes = ({ observableRoute }: RoutesProps = {}) => {
|
|
|
352
353
|
fromChain.nativeToken.address !== fromTokenAddress &&
|
|
353
354
|
useRelayerRoutes &&
|
|
354
355
|
!isBatchingSupported &&
|
|
355
|
-
(!observableRoute || isObservableRelayerRoute)
|
|
356
|
-
isTokenMessageSigningAllowed(fromToken!)
|
|
356
|
+
(!observableRoute || isObservableRelayerRoute)
|
|
357
357
|
|
|
358
358
|
const mainRoutesPromise = shouldUseMainRoutes
|
|
359
359
|
? getRoutes(
|
|
@@ -447,27 +447,21 @@ export const useRoutes = ({ observableRoute }: RoutesProps = {}) => {
|
|
|
447
447
|
// Update local tokens cache to keep priceUSD in sync
|
|
448
448
|
const { fromToken, toToken } = routesResult.routes[0]
|
|
449
449
|
;[fromToken, toToken].forEach((token) => {
|
|
450
|
-
|
|
450
|
+
// Update main tokens cache (verified)
|
|
451
|
+
queryClient.setQueriesData<TokensByChain>(
|
|
451
452
|
{ queryKey: [getQueryKey('tokens', keyPrefix)] },
|
|
452
|
-
(data) =>
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
clonedData.tokens[token.chainId][index] = {
|
|
463
|
-
...clonedData.tokens[token.chainId][index],
|
|
464
|
-
...token,
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
return clonedData
|
|
468
|
-
}
|
|
469
|
-
}
|
|
453
|
+
(data) => updateTokenInCache(data, token)
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
// Update search tokens cache (unverified) - matches any search query
|
|
457
|
+
queryClient.setQueriesData<TokensByChain>(
|
|
458
|
+
{
|
|
459
|
+
queryKey: [getQueryKey('tokens-search', keyPrefix)],
|
|
460
|
+
exact: false,
|
|
461
|
+
},
|
|
462
|
+
(data) => updateTokenInCache(data, token)
|
|
470
463
|
)
|
|
464
|
+
|
|
471
465
|
queryClient.setQueriesData<Token[]>(
|
|
472
466
|
{
|
|
473
467
|
queryKey: [
|
|
@@ -3,11 +3,11 @@ import {
|
|
|
3
3
|
type ChainId,
|
|
4
4
|
getToken,
|
|
5
5
|
type TokenExtended,
|
|
6
|
-
type TokensResponse,
|
|
7
6
|
} from '@lifi/sdk'
|
|
8
7
|
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
|
9
8
|
import { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js'
|
|
10
9
|
import type { FormType } from '../stores/form/types.js'
|
|
10
|
+
import type { TokensByChain } from '../types/token.js'
|
|
11
11
|
import { getConfigItemSets, isFormItemAllowed } from '../utils/item.js'
|
|
12
12
|
import { getQueryKey } from '../utils/queries.js'
|
|
13
13
|
|
|
@@ -49,21 +49,28 @@ export const useTokenSearch = (
|
|
|
49
49
|
return null
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
// Add token to main tokens cache
|
|
53
|
+
queryClient.setQueriesData<TokensByChain>(
|
|
53
54
|
{ queryKey: [getQueryKey('tokens', keyPrefix)] },
|
|
54
55
|
(data) => {
|
|
56
|
+
if (!data) {
|
|
57
|
+
return data
|
|
58
|
+
}
|
|
59
|
+
const chainTokens = data[chainId as number]
|
|
55
60
|
if (
|
|
56
|
-
|
|
57
|
-
!data.tokens[chainId as number]?.some(
|
|
61
|
+
chainTokens?.some(
|
|
58
62
|
(t) => t.address.toLowerCase() === token.address.toLowerCase()
|
|
59
63
|
)
|
|
60
64
|
) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
return data
|
|
66
|
+
}
|
|
67
|
+
// Mark token from search as unverified
|
|
68
|
+
return {
|
|
69
|
+
...data,
|
|
70
|
+
[chainId as number]: [
|
|
71
|
+
...(chainTokens ?? []),
|
|
72
|
+
{ ...token, verified: false },
|
|
73
|
+
],
|
|
67
74
|
}
|
|
68
75
|
}
|
|
69
76
|
)
|
package/src/hooks/useTokens.ts
CHANGED
|
@@ -4,17 +4,23 @@ import {
|
|
|
4
4
|
getTokens,
|
|
5
5
|
type TokensExtendedResponse,
|
|
6
6
|
} from '@lifi/sdk'
|
|
7
|
-
import { useQuery
|
|
7
|
+
import { useQuery } from '@tanstack/react-query'
|
|
8
8
|
import { useMemo } from 'react'
|
|
9
9
|
import { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js'
|
|
10
10
|
import type { FormType } from '../stores/form/types.js'
|
|
11
|
+
import type { TokensByChain } from '../types/token.js'
|
|
11
12
|
import {
|
|
12
13
|
defaultChainIdsByType,
|
|
13
14
|
getChainTypeFromAddress,
|
|
14
15
|
} from '../utils/chainType.js'
|
|
15
16
|
import { isItemAllowed } from '../utils/item.js'
|
|
16
17
|
import { getQueryKey } from '../utils/queries.js'
|
|
17
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
filterAllowedTokens,
|
|
20
|
+
mergeVerifiedWithSearchTokens,
|
|
21
|
+
} from '../utils/token.js'
|
|
22
|
+
|
|
23
|
+
const refetchInterval = 300_000
|
|
18
24
|
|
|
19
25
|
export const useTokens = (
|
|
20
26
|
formType?: FormType,
|
|
@@ -27,12 +33,8 @@ export const useTokens = (
|
|
|
27
33
|
keyPrefix,
|
|
28
34
|
} = useWidgetConfig()
|
|
29
35
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
chainId
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
const { data, isLoading } = useQuery({
|
|
36
|
+
// Main tokens cache - verified tokens from API
|
|
37
|
+
const { data: verifiedTokens, isLoading } = useQuery({
|
|
36
38
|
queryKey: [getQueryKey('tokens', keyPrefix)],
|
|
37
39
|
queryFn: async ({ signal }) => {
|
|
38
40
|
const chainTypes = [
|
|
@@ -41,6 +43,7 @@ export const useTokens = (
|
|
|
41
43
|
ChainType.UTXO,
|
|
42
44
|
ChainType.MVM,
|
|
43
45
|
].filter((chainType) => isItemAllowed(chainType, chainsConfig?.types))
|
|
46
|
+
|
|
44
47
|
const tokensResponse: TokensExtendedResponse = await getTokens(
|
|
45
48
|
{
|
|
46
49
|
chainTypes,
|
|
@@ -50,45 +53,37 @@ export const useTokens = (
|
|
|
50
53
|
},
|
|
51
54
|
{ signal }
|
|
52
55
|
)
|
|
53
|
-
return tokensResponse
|
|
54
|
-
},
|
|
55
|
-
refetchInterval: 300_000,
|
|
56
|
-
staleTime: 300_000,
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
const allTokens = useMemo(() => {
|
|
60
|
-
return filterAllowedTokens(
|
|
61
|
-
data?.tokens,
|
|
62
|
-
configTokens,
|
|
63
|
-
chainsConfig,
|
|
64
|
-
formType
|
|
65
|
-
)
|
|
66
|
-
}, [data?.tokens, configTokens, chainsConfig, formType])
|
|
67
56
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
57
|
+
// Mark all tokens as verified
|
|
58
|
+
const tokens: TokensByChain = Object.fromEntries(
|
|
59
|
+
Object.entries(tokensResponse.tokens).map(([chainId, tokens]) => [
|
|
60
|
+
chainId,
|
|
61
|
+
tokens.map((token) => ({ ...token, verified: true })),
|
|
62
|
+
])
|
|
63
|
+
)
|
|
74
64
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const queryClient = useQueryClient()
|
|
65
|
+
return tokens
|
|
66
|
+
},
|
|
67
|
+
refetchInterval,
|
|
68
|
+
staleTime: refetchInterval,
|
|
69
|
+
})
|
|
81
70
|
|
|
82
|
-
|
|
71
|
+
// Search tokens cache - unverified tokens from search
|
|
72
|
+
const { data: searchTokens, isLoading: isSearchLoading } = useQuery({
|
|
83
73
|
queryKey: [getQueryKey('tokens-search', keyPrefix), search, chainId],
|
|
84
74
|
queryFn: async ({ queryKey, signal }) => {
|
|
85
|
-
const [, searchQuery,
|
|
75
|
+
const [, searchQuery, searchChainId] = queryKey as [
|
|
76
|
+
string,
|
|
77
|
+
string,
|
|
78
|
+
number,
|
|
79
|
+
]
|
|
86
80
|
const chainTypes = [
|
|
87
81
|
ChainType.EVM,
|
|
88
82
|
ChainType.SVM,
|
|
89
83
|
ChainType.UTXO,
|
|
90
84
|
ChainType.MVM,
|
|
91
85
|
].filter((chainType) => isItemAllowed(chainType, chainsConfig?.types))
|
|
86
|
+
|
|
92
87
|
const tokensResponse: TokensExtendedResponse = await getTokens(
|
|
93
88
|
{
|
|
94
89
|
chainTypes,
|
|
@@ -101,7 +96,7 @@ const useBackgroundTokenSearch = (search?: string, chainId?: number) => {
|
|
|
101
96
|
)
|
|
102
97
|
|
|
103
98
|
// If the chainId is not provided, try to get it from the search query
|
|
104
|
-
let _chainId =
|
|
99
|
+
let _chainId = searchChainId
|
|
105
100
|
if (!_chainId) {
|
|
106
101
|
const chainType = getChainTypeFromAddress(searchQuery)
|
|
107
102
|
if (chainType) {
|
|
@@ -121,57 +116,30 @@ const useBackgroundTokenSearch = (search?: string, chainId?: number) => {
|
|
|
121
116
|
}
|
|
122
117
|
}
|
|
123
118
|
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const clonedData = { ...data, tokens: { ...data.tokens } }
|
|
134
|
-
|
|
135
|
-
Object.entries(tokensResponse.tokens).forEach(
|
|
136
|
-
([chainId, searchTokens]) => {
|
|
137
|
-
const chainIdNum = Number(chainId)
|
|
138
|
-
const existingTokens = clonedData.tokens[chainIdNum] || []
|
|
139
|
-
|
|
140
|
-
const existingTokenAddresses = new Set(
|
|
141
|
-
existingTokens.map((token) => token.address.toLowerCase())
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
// Find tokens in search results that don't exist in the main list
|
|
145
|
-
const newTokens = searchTokens.filter(
|
|
146
|
-
(searchToken) =>
|
|
147
|
-
!existingTokenAddresses.has(
|
|
148
|
-
searchToken.address.toLowerCase()
|
|
149
|
-
)
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
// Add new tokens to the main list
|
|
153
|
-
if (newTokens.length > 0) {
|
|
154
|
-
clonedData.tokens[chainIdNum] = [
|
|
155
|
-
...existingTokens,
|
|
156
|
-
...newTokens,
|
|
157
|
-
]
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
)
|
|
161
|
-
|
|
162
|
-
return clonedData
|
|
163
|
-
}
|
|
164
|
-
)
|
|
165
|
-
}
|
|
119
|
+
// Mark all search tokens as unverified
|
|
120
|
+
const tokens: TokensByChain = Object.fromEntries(
|
|
121
|
+
Object.entries(tokensResponse.tokens).map(([chainId, tokens]) => [
|
|
122
|
+
chainId,
|
|
123
|
+
tokens.map((token) => ({ ...token, verified: false })),
|
|
124
|
+
])
|
|
125
|
+
)
|
|
166
126
|
|
|
167
|
-
return
|
|
127
|
+
return tokens
|
|
168
128
|
},
|
|
169
129
|
enabled: !!search,
|
|
170
|
-
refetchInterval
|
|
171
|
-
staleTime:
|
|
130
|
+
refetchInterval,
|
|
131
|
+
staleTime: refetchInterval,
|
|
172
132
|
})
|
|
173
133
|
|
|
134
|
+
// Merge tokens at read time - single place where caches are combined
|
|
135
|
+
const allTokens = useMemo(() => {
|
|
136
|
+
const merged = mergeVerifiedWithSearchTokens(verifiedTokens, searchTokens)
|
|
137
|
+
return filterAllowedTokens(merged, configTokens, chainsConfig, formType)
|
|
138
|
+
}, [verifiedTokens, searchTokens, configTokens, chainsConfig, formType])
|
|
139
|
+
|
|
174
140
|
return {
|
|
175
|
-
|
|
141
|
+
allTokens,
|
|
142
|
+
isLoading,
|
|
143
|
+
isSearchLoading,
|
|
176
144
|
}
|
|
177
145
|
}
|
package/src/i18n/en.json
CHANGED
|
@@ -127,6 +127,7 @@
|
|
|
127
127
|
"deleteTransactionHistory": "Transaction history is only stored locally and can't be recovered if you delete it.",
|
|
128
128
|
"fundsLossPrevention": "Always ensure smart contract accounts are properly set up on the destination chain and avoid direct transfers to exchanges to prevent fund loss.",
|
|
129
129
|
"highValueLoss": "The value of the received tokens is significantly lower than the exchanged tokens and transaction cost.",
|
|
130
|
+
"unverifiedToken": "Unverified token. Always do your own research before proceeding.",
|
|
130
131
|
"insufficientFunds": "You don't have enough funds to complete the transaction.",
|
|
131
132
|
"insufficientGas": "You don't have enough gas to complete the transaction. You need to add at least:",
|
|
132
133
|
"minFromAmountUSD": "Minimum amount is {{amount, currencyExt(currency: USD)}}. Please enter a higher amount.",
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createInstance, type i18n } from 'i18next'
|
|
2
|
-
import { useMemo, useRef } from 'react'
|
|
2
|
+
import { useEffect, useMemo, useRef } from 'react'
|
|
3
3
|
import { I18nextProvider } from 'react-i18next'
|
|
4
|
+
import { useLanguages } from '../../hooks/useLanguages.js'
|
|
5
|
+
import { useSettingsStore } from '../../stores/settings/SettingsStore.js'
|
|
4
6
|
import { useSettings } from '../../stores/settings/useSettings.js'
|
|
5
7
|
import { compactNumberFormatter } from '../../utils/compactNumberFormatter.js'
|
|
6
8
|
import { currencyExtendedFormatter } from '../../utils/currencyExtendedFormatter.js'
|
|
@@ -92,5 +94,40 @@ export const I18nProvider: React.FC<React.PropsWithChildren> = ({
|
|
|
92
94
|
return i18n
|
|
93
95
|
}, [language, languageResources, languages?.default, languageCache])
|
|
94
96
|
|
|
95
|
-
return
|
|
97
|
+
return (
|
|
98
|
+
<I18nextProvider i18n={i18nInstance}>
|
|
99
|
+
{children}
|
|
100
|
+
<DefaultLanguageHandler />
|
|
101
|
+
</I18nextProvider>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Sync language settings internally when config.languages.default changes externally
|
|
106
|
+
const DefaultLanguageHandler: React.FC = () => {
|
|
107
|
+
const { languages } = useWidgetConfig()
|
|
108
|
+
const [lastDefaultLanguage, setValue] = useSettingsStore((state) => [
|
|
109
|
+
state.lastDefaultLanguage,
|
|
110
|
+
state.setValue,
|
|
111
|
+
])
|
|
112
|
+
const { setLanguageWithCode: setLanguage } = useLanguages()
|
|
113
|
+
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
const currentDefaultLanguage = languages?.default
|
|
116
|
+
|
|
117
|
+
const defaultLanguageChanged =
|
|
118
|
+
currentDefaultLanguage && currentDefaultLanguage !== lastDefaultLanguage
|
|
119
|
+
|
|
120
|
+
if (!defaultLanguageChanged) {
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const updateLanguage = async () => {
|
|
125
|
+
await setLanguage(currentDefaultLanguage)
|
|
126
|
+
setValue('lastDefaultLanguage', currentDefaultLanguage)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
updateLanguage()
|
|
130
|
+
}, [languages?.default, setValue, lastDefaultLanguage, setLanguage])
|
|
131
|
+
|
|
132
|
+
return null
|
|
96
133
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { PropsWithChildren } from 'react'
|
|
2
2
|
import type { WidgetConfigProps } from '../types/widget.js'
|
|
3
|
+
import { getSplitSubvariant } from '../utils/variant.js'
|
|
3
4
|
import { BookmarkStoreProvider } from './bookmarks/BookmarkStore.js'
|
|
4
5
|
import { ChainOrderStoreProvider } from './chains/ChainOrderStore.js'
|
|
5
6
|
import { FormStoreProvider } from './form/FormStore.js'
|
|
@@ -16,7 +17,7 @@ export const StoreProvider: React.FC<PropsWithChildren<WidgetConfigProps>> = ({
|
|
|
16
17
|
<SplitSubvariantStoreProvider
|
|
17
18
|
state={
|
|
18
19
|
config.subvariant === 'split'
|
|
19
|
-
? config.subvariantOptions?.split
|
|
20
|
+
? getSplitSubvariant(config.subvariantOptions?.split)
|
|
20
21
|
: undefined
|
|
21
22
|
}
|
|
22
23
|
>
|
|
@@ -3,8 +3,10 @@ import type { StoreApi } from 'zustand'
|
|
|
3
3
|
import { useShallow } from 'zustand/shallow'
|
|
4
4
|
import type { UseBoundStoreWithEqualityFn } from 'zustand/traditional'
|
|
5
5
|
import { useChains } from '../../hooks/useChains.js'
|
|
6
|
+
import { useSwapOnly } from '../../hooks/useSwapOnly.js'
|
|
6
7
|
import { useExternalWalletProvider } from '../../providers/WalletProvider/useExternalWalletProvider.js'
|
|
7
8
|
import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js'
|
|
9
|
+
import { HiddenUI } from '../../types/widget.js'
|
|
8
10
|
import { getConfigItemSets, isItemAllowedForSets } from '../../utils/item.js'
|
|
9
11
|
import type { FormType } from '../form/types.js'
|
|
10
12
|
import { useFieldActions } from '../form/useFieldActions.js'
|
|
@@ -20,10 +22,11 @@ export function ChainOrderStoreProvider({
|
|
|
20
22
|
children,
|
|
21
23
|
...props
|
|
22
24
|
}: PersistStoreProviderProps) {
|
|
23
|
-
const { chains: chainsConfig } = useWidgetConfig()
|
|
25
|
+
const { chains: chainsConfig, hiddenUI } = useWidgetConfig()
|
|
24
26
|
const storeRef = useRef<ChainOrderStore>(null)
|
|
25
27
|
const { chains } = useChains()
|
|
26
28
|
const { setFieldValue, getFieldValues } = useFieldActions()
|
|
29
|
+
const swapOnly = useSwapOnly()
|
|
27
30
|
const { variant, subvariantOptions } = useWidgetConfig()
|
|
28
31
|
const { externalChainTypes, useExternalWalletProvidersOnly } =
|
|
29
32
|
useExternalWalletProvider()
|
|
@@ -62,13 +65,24 @@ export function ChainOrderStoreProvider({
|
|
|
62
65
|
key
|
|
63
66
|
)
|
|
64
67
|
|
|
68
|
+
const isSwapTo = swapOnly && key === 'to'
|
|
69
|
+
|
|
65
70
|
// Show "All networks" button if there are multiple networks
|
|
66
|
-
const showAllNetworks =
|
|
71
|
+
const showAllNetworks =
|
|
72
|
+
filteredChains.length > 1 &&
|
|
73
|
+
!hiddenUI?.includes(HiddenUI.AllNetworks) &&
|
|
74
|
+
!isSwapTo
|
|
67
75
|
if (!showAllNetworks) {
|
|
68
76
|
storeRef.current?.getState().setIsAllNetworks(false, key)
|
|
69
77
|
}
|
|
70
78
|
storeRef.current?.getState().setShowAllNetworks(showAllNetworks, key)
|
|
71
79
|
|
|
80
|
+
// If swap only, set the to chain to the from chain
|
|
81
|
+
if (isSwapTo) {
|
|
82
|
+
const [fromChainValue] = getFieldValues('fromChain')
|
|
83
|
+
setFieldValue('toChain', fromChainValue)
|
|
84
|
+
}
|
|
85
|
+
|
|
72
86
|
const [chainValue] = getFieldValues(`${key}Chain`)
|
|
73
87
|
if (chainValue) {
|
|
74
88
|
return
|
|
@@ -99,6 +113,8 @@ export function ChainOrderStoreProvider({
|
|
|
99
113
|
useExternalWalletProvidersOnly,
|
|
100
114
|
variant,
|
|
101
115
|
subvariantOptions?.wide?.enableChainSidebar,
|
|
116
|
+
hiddenUI,
|
|
117
|
+
swapOnly,
|
|
102
118
|
])
|
|
103
119
|
|
|
104
120
|
return (
|
|
@@ -4,6 +4,7 @@ export const getStateValues = (state: SettingsState): SettingsProps => ({
|
|
|
4
4
|
gasPrice: state.gasPrice,
|
|
5
5
|
language: state.language,
|
|
6
6
|
languageCache: state.languageCache,
|
|
7
|
+
lastDefaultLanguage: state.lastDefaultLanguage,
|
|
7
8
|
routePriority: state.routePriority,
|
|
8
9
|
enabledAutoRefuel: state.enabledAutoRefuel,
|
|
9
10
|
slippage: state.slippage,
|
package/src/types/token.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
TokenAmount as SDKTokenAmount,
|
|
3
3
|
TokenAmountExtended as SDKTokenAmountExtended,
|
|
4
|
+
TokenExtended,
|
|
4
5
|
} from '@lifi/sdk'
|
|
5
6
|
|
|
6
7
|
interface TokenFlags {
|
|
7
8
|
featured?: boolean
|
|
8
9
|
popular?: boolean
|
|
10
|
+
verified?: boolean
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
export interface TokenAmount extends SDKTokenAmount, TokenFlags {}
|
|
@@ -13,3 +15,6 @@ export interface TokenAmount extends SDKTokenAmount, TokenFlags {}
|
|
|
13
15
|
export interface TokenAmountExtended
|
|
14
16
|
extends SDKTokenAmountExtended,
|
|
15
17
|
TokenFlags {}
|
|
18
|
+
|
|
19
|
+
export type TokenWithVerified = TokenExtended & { verified?: boolean }
|
|
20
|
+
export type TokensByChain = Record<number, TokenWithVerified[]>
|
package/src/types/widget.ts
CHANGED
|
@@ -38,12 +38,20 @@ import type { DefaultFieldValues } from '../stores/form/types.js'
|
|
|
38
38
|
export type WidgetVariant = 'compact' | 'wide' | 'drawer'
|
|
39
39
|
export type WidgetSubvariant = 'default' | 'split' | 'custom' | 'refuel'
|
|
40
40
|
export type SplitSubvariant = 'bridge' | 'swap'
|
|
41
|
+
export type SplitSubvariantOptions = {
|
|
42
|
+
defaultTab: SplitSubvariant
|
|
43
|
+
}
|
|
41
44
|
export type CustomSubvariant = 'checkout' | 'deposit'
|
|
42
45
|
export type WideSubvariant = {
|
|
43
46
|
enableChainSidebar?: boolean
|
|
44
47
|
}
|
|
45
48
|
export interface SubvariantOptions {
|
|
46
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Configure split subvariant behavior:
|
|
51
|
+
* - 'bridge' | 'swap': Single mode without tabs
|
|
52
|
+
* - { defaultTab: 'bridge' | 'swap' }: Tabs mode with configurable default tab
|
|
53
|
+
*/
|
|
54
|
+
split?: SplitSubvariant | SplitSubvariantOptions
|
|
47
55
|
custom?: CustomSubvariant
|
|
48
56
|
wide?: WideSubvariant
|
|
49
57
|
}
|
|
@@ -116,6 +124,7 @@ export enum HiddenUI {
|
|
|
116
124
|
IntegratorStepDetails = 'integratorStepDetails',
|
|
117
125
|
ReverseTokensButton = 'reverseTokensButton',
|
|
118
126
|
RouteTokenDescription = 'routeTokenDescription',
|
|
127
|
+
RouteCardPriceImpact = 'routeCardPriceImpact',
|
|
119
128
|
ChainSelect = 'chainSelect',
|
|
120
129
|
BridgesSettings = 'bridgesSettings',
|
|
121
130
|
AddressBookConnectedWallets = 'addressBookConnectedWallets',
|
|
@@ -124,6 +133,7 @@ export enum HiddenUI {
|
|
|
124
133
|
SearchTokenInput = 'searchTokenInput',
|
|
125
134
|
InsufficientGasMessage = 'insufficientGasMessage',
|
|
126
135
|
ContactSupport = 'contactSupport',
|
|
136
|
+
AllNetworks = 'allNetworks',
|
|
127
137
|
}
|
|
128
138
|
export type HiddenUIType = `${HiddenUI}`
|
|
129
139
|
|
package/src/utils/token.ts
CHANGED
|
@@ -1,8 +1,72 @@
|
|
|
1
|
-
import type { BaseToken, TokenExtended } from '@lifi/sdk'
|
|
1
|
+
import type { BaseToken, Token, TokenExtended } from '@lifi/sdk'
|
|
2
2
|
import type { FormType } from '../stores/form/types.js'
|
|
3
|
+
import type { TokensByChain } from '../types/token.js'
|
|
3
4
|
import type { WidgetChains, WidgetTokens } from '../types/widget.js'
|
|
4
5
|
import { getConfigItemSets, isFormItemAllowed } from './item.js'
|
|
5
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Merges verified tokens with search tokens.
|
|
9
|
+
* Verified tokens take priority - search tokens are only added if they don't already exist.
|
|
10
|
+
*/
|
|
11
|
+
export const mergeVerifiedWithSearchTokens = (
|
|
12
|
+
verifiedTokens?: TokensByChain,
|
|
13
|
+
searchTokens?: TokensByChain
|
|
14
|
+
): TokensByChain | undefined => {
|
|
15
|
+
if (!verifiedTokens) {
|
|
16
|
+
return searchTokens
|
|
17
|
+
}
|
|
18
|
+
if (!searchTokens) {
|
|
19
|
+
return verifiedTokens
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const result = { ...verifiedTokens }
|
|
23
|
+
|
|
24
|
+
for (const [chainId, tokens] of Object.entries(searchTokens)) {
|
|
25
|
+
const chainIdNum = Number(chainId)
|
|
26
|
+
const existingTokens = result[chainIdNum] || []
|
|
27
|
+
const existingAddresses = new Set(
|
|
28
|
+
existingTokens.map((t) => t.address.toLowerCase())
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
const newTokens = tokens.filter(
|
|
32
|
+
(t) => !existingAddresses.has(t.address.toLowerCase())
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
if (newTokens.length) {
|
|
36
|
+
result[chainIdNum] = [...existingTokens, ...newTokens]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return result
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Updates a token in the cache by chainId and address.
|
|
45
|
+
* Returns a new cache object with the token updated, or the original if not found.
|
|
46
|
+
*/
|
|
47
|
+
export const updateTokenInCache = (
|
|
48
|
+
data: TokensByChain | undefined,
|
|
49
|
+
token: Token
|
|
50
|
+
): TokensByChain | undefined => {
|
|
51
|
+
if (!data) {
|
|
52
|
+
return data
|
|
53
|
+
}
|
|
54
|
+
const chainTokens = data[token.chainId]
|
|
55
|
+
if (!chainTokens) {
|
|
56
|
+
return data
|
|
57
|
+
}
|
|
58
|
+
const index = chainTokens.findIndex((t) => t.address === token.address)
|
|
59
|
+
if (index < 0) {
|
|
60
|
+
return data
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
...data,
|
|
64
|
+
[token.chainId]: chainTokens.map((t, i) =>
|
|
65
|
+
i === index ? { ...t, ...token } : t
|
|
66
|
+
),
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
6
70
|
export const filterAllowedTokens = (
|
|
7
71
|
dataTokens: { [chainId: number]: TokenExtended[] } | undefined,
|
|
8
72
|
configTokens?: WidgetTokens,
|
package/src/utils/tokenList.ts
CHANGED
|
@@ -91,6 +91,8 @@ const processedTypedTokens = (
|
|
|
91
91
|
typedTokens?.forEach((token) => {
|
|
92
92
|
const tokenAmount = { ...token } as TokenAmount
|
|
93
93
|
tokenAmount[tokenType] = true
|
|
94
|
+
// Config tokens are explicitly set by integrator, mark as verified
|
|
95
|
+
tokenAmount.verified = true
|
|
94
96
|
|
|
95
97
|
const match = filteredTokensMap.get(token.address)
|
|
96
98
|
if (match?.priceUSD) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SplitSubvariant,
|
|
3
|
+
SplitSubvariantOptions,
|
|
4
|
+
} from '../types/widget.js'
|
|
5
|
+
|
|
6
|
+
export const getSplitSubvariant = (
|
|
7
|
+
split?: SplitSubvariant | SplitSubvariantOptions
|
|
8
|
+
): SplitSubvariant => {
|
|
9
|
+
if (!split) {
|
|
10
|
+
return 'swap'
|
|
11
|
+
}
|
|
12
|
+
if (typeof split === 'string') {
|
|
13
|
+
return split
|
|
14
|
+
}
|
|
15
|
+
return split.defaultTab
|
|
16
|
+
}
|