@lifi/widget 3.25.0-beta.4 → 3.25.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 +23 -0
- package/dist/esm/components/Chains/ChainList.d.ts +2 -3
- package/dist/esm/components/Chains/ChainList.js +4 -3
- package/dist/esm/components/Chains/ChainList.js.map +1 -1
- package/dist/esm/components/Chains/ChainList.style.js +10 -3
- package/dist/esm/components/Chains/ChainList.style.js.map +1 -1
- package/dist/esm/components/Chains/ChainListItem.d.ts +4 -1
- package/dist/esm/components/Chains/ChainListItem.js +14 -2
- package/dist/esm/components/Chains/ChainListItem.js.map +1 -1
- package/dist/esm/components/Chains/PinChainButton.d.ts +7 -0
- package/dist/esm/components/Chains/PinChainButton.js +34 -0
- package/dist/esm/components/Chains/PinChainButton.js.map +1 -0
- package/dist/esm/components/Chains/SelectChainContent.js +15 -16
- package/dist/esm/components/Chains/SelectChainContent.js.map +1 -1
- package/dist/esm/components/Chains/VirtualizedChainList.d.ts +2 -1
- package/dist/esm/components/Chains/VirtualizedChainList.js +43 -9
- package/dist/esm/components/Chains/VirtualizedChainList.js.map +1 -1
- package/dist/esm/components/Messages/MinFromAmountUSDMessage.d.ts +6 -0
- package/dist/esm/components/Messages/MinFromAmountUSDMessage.js +15 -0
- package/dist/esm/components/Messages/MinFromAmountUSDMessage.js.map +1 -0
- package/dist/esm/components/Messages/WarningMessages.js +3 -0
- package/dist/esm/components/Messages/WarningMessages.js.map +1 -1
- package/dist/esm/components/Messages/useMessageQueue.js +15 -4
- package/dist/esm/components/Messages/useMessageQueue.js.map +1 -1
- package/dist/esm/components/TokenList/TokenDetailsSheet.js +0 -2
- package/dist/esm/components/TokenList/TokenDetailsSheet.js.map +1 -1
- package/dist/esm/components/TokenList/TokenDetailsSheetContent.js +26 -2
- package/dist/esm/components/TokenList/TokenDetailsSheetContent.js.map +1 -1
- package/dist/esm/components/TokenList/TokenList.js +10 -1
- package/dist/esm/components/TokenList/TokenList.js.map +1 -1
- package/dist/esm/components/TokenList/TokenListItem.js +3 -1
- 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/config/version.js.map +1 -1
- package/dist/esm/hooks/useFromAmountThreshold.d.ts +4 -0
- package/dist/esm/hooks/useFromAmountThreshold.js +23 -0
- package/dist/esm/hooks/useFromAmountThreshold.js.map +1 -0
- package/dist/esm/hooks/useLongPress.d.ts +7 -0
- package/dist/esm/hooks/useLongPress.js +41 -0
- package/dist/esm/hooks/useLongPress.js.map +1 -0
- package/dist/esm/hooks/useTokenBalances.d.ts +1 -2
- package/dist/esm/hooks/useTokenBalances.js +2 -3
- package/dist/esm/hooks/useTokenBalances.js.map +1 -1
- package/dist/esm/hooks/useTokenSearch.d.ts +2 -2
- package/dist/esm/hooks/useTokenSearch.js.map +1 -1
- package/dist/esm/hooks/useTokens.d.ts +1 -2
- package/dist/esm/hooks/useTokens.js +1 -10
- package/dist/esm/hooks/useTokens.js.map +1 -1
- package/dist/esm/i18n/bn.json +4 -1
- package/dist/esm/i18n/de.json +4 -1
- package/dist/esm/i18n/en.json +4 -1
- package/dist/esm/i18n/es.json +4 -1
- package/dist/esm/i18n/fr.json +4 -1
- package/dist/esm/i18n/hi.json +4 -1
- package/dist/esm/i18n/id.json +4 -1
- package/dist/esm/i18n/it.json +4 -1
- package/dist/esm/i18n/ja.json +4 -1
- package/dist/esm/i18n/ko.json +4 -1
- package/dist/esm/i18n/pl.json +4 -1
- package/dist/esm/i18n/pt.json +4 -1
- package/dist/esm/i18n/th.json +4 -1
- package/dist/esm/i18n/tr.json +4 -1
- package/dist/esm/i18n/uk.json +4 -1
- package/dist/esm/i18n/vi.json +4 -1
- package/dist/esm/i18n/zh.json +4 -1
- package/dist/esm/stores/chains/ChainOrderStore.js +17 -5
- package/dist/esm/stores/chains/ChainOrderStore.js.map +1 -1
- package/dist/esm/stores/chains/createChainOrderStore.js +19 -1
- package/dist/esm/stores/chains/createChainOrderStore.js.map +1 -1
- package/dist/esm/stores/chains/types.d.ts +2 -0
- package/dist/esm/stores/header/useHeaderStore.js +2 -3
- package/dist/esm/stores/header/useHeaderStore.js.map +1 -1
- package/dist/esm/types/widget.d.ts +1 -0
- package/dist/esm/types/widget.js.map +1 -1
- package/package.json +4 -4
- package/package.json.tmp +3 -3
- package/src/components/Chains/ChainList.style.tsx +10 -2
- package/src/components/Chains/ChainList.tsx +6 -5
- package/src/components/Chains/ChainListItem.tsx +29 -0
- package/src/components/Chains/PinChainButton.tsx +51 -0
- package/src/components/Chains/SelectChainContent.tsx +18 -21
- package/src/components/Chains/VirtualizedChainList.tsx +68 -20
- package/src/components/Messages/MinFromAmountUSDMessage.tsx +35 -0
- package/src/components/Messages/WarningMessages.tsx +8 -0
- package/src/components/Messages/useMessageQueue.ts +16 -4
- package/src/components/TokenList/TokenDetailsSheet.tsx +0 -2
- package/src/components/TokenList/TokenDetailsSheetContent.tsx +49 -1
- package/src/components/TokenList/TokenList.tsx +24 -1
- package/src/components/TokenList/TokenListItem.tsx +6 -0
- package/src/config/version.ts +1 -1
- package/src/hooks/useFromAmountThreshold.ts +31 -0
- package/src/hooks/useLongPress.ts +51 -0
- package/src/hooks/useTokenBalances.ts +3 -10
- package/src/hooks/useTokenSearch.ts +3 -3
- package/src/hooks/useTokens.ts +2 -29
- package/src/i18n/bn.json +4 -1
- package/src/i18n/de.json +4 -1
- package/src/i18n/en.json +4 -1
- package/src/i18n/es.json +4 -1
- package/src/i18n/fr.json +4 -1
- package/src/i18n/hi.json +4 -1
- package/src/i18n/id.json +4 -1
- package/src/i18n/it.json +4 -1
- package/src/i18n/ja.json +4 -1
- package/src/i18n/ko.json +4 -1
- package/src/i18n/pl.json +4 -1
- package/src/i18n/pt.json +4 -1
- package/src/i18n/th.json +4 -1
- package/src/i18n/tr.json +4 -1
- package/src/i18n/uk.json +4 -1
- package/src/i18n/vi.json +4 -1
- package/src/i18n/zh.json +4 -1
- package/src/stores/chains/ChainOrderStore.tsx +23 -5
- package/src/stores/chains/createChainOrderStore.ts +19 -1
- package/src/stores/chains/types.ts +2 -0
- package/src/stores/header/useHeaderStore.tsx +2 -9
- package/src/types/widget.ts +1 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { ExtendedChain } from '@lifi/sdk'
|
|
2
2
|
import { useVirtualizer } from '@tanstack/react-virtual'
|
|
3
3
|
import type { RefObject } from 'react'
|
|
4
|
-
import { useCallback, useEffect, useMemo, useRef } from 'react'
|
|
4
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react'
|
|
5
|
+
import { shallow } from 'zustand/shallow'
|
|
6
|
+
import { useChainOrderStore } from '../../stores/chains/ChainOrderStore'
|
|
5
7
|
import { List } from './ChainList.style'
|
|
6
8
|
import { ChainListItem } from './ChainListItem'
|
|
7
9
|
|
|
@@ -11,6 +13,7 @@ interface VirtualizedChainListProps {
|
|
|
11
13
|
onSelect: (chain: ExtendedChain) => void
|
|
12
14
|
selectedChainId?: number
|
|
13
15
|
itemsSize: 'small' | 'medium'
|
|
16
|
+
withPinnedChains: boolean
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
export const VirtualizedChainList = ({
|
|
@@ -19,17 +22,33 @@ export const VirtualizedChainList = ({
|
|
|
19
22
|
selectedChainId,
|
|
20
23
|
itemsSize,
|
|
21
24
|
scrollElementRef,
|
|
25
|
+
withPinnedChains,
|
|
22
26
|
}: VirtualizedChainListProps) => {
|
|
23
|
-
const
|
|
27
|
+
const selectedChainIdRef = useRef(selectedChainId) // Store the initial selected chain ID to scroll to it once chains are loaded
|
|
28
|
+
const hasScrolledRef = useRef(false)
|
|
29
|
+
const [pinnedChains, setPinnedChain] = useChainOrderStore(
|
|
30
|
+
(state) => [state.pinnedChains, state.setPinnedChain],
|
|
31
|
+
shallow
|
|
32
|
+
)
|
|
33
|
+
const onPin = useCallback(
|
|
34
|
+
(chainId: number) => {
|
|
35
|
+
setPinnedChain(chainId)
|
|
36
|
+
},
|
|
37
|
+
[setPinnedChain]
|
|
38
|
+
)
|
|
39
|
+
|
|
24
40
|
const sortedChains = useMemo(() => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
41
|
+
if (!pinnedChains.length) {
|
|
42
|
+
return chains
|
|
43
|
+
}
|
|
44
|
+
// Pinning logic: move pinned chains to the top of the list
|
|
45
|
+
const pinned = pinnedChains
|
|
46
|
+
.map((id) => chains.find((c) => c.id === id))
|
|
47
|
+
.filter(Boolean) as ExtendedChain[]
|
|
48
|
+
const pinnedIds = new Set(pinned.map((c) => c.id))
|
|
49
|
+
const rest = chains.filter((c) => !pinnedIds.has(c.id))
|
|
50
|
+
return [...pinned, ...rest]
|
|
51
|
+
}, [chains, pinnedChains])
|
|
33
52
|
|
|
34
53
|
const getItemKey = useCallback(
|
|
35
54
|
(index: number) => {
|
|
@@ -38,16 +57,17 @@ export const VirtualizedChainList = ({
|
|
|
38
57
|
[sortedChains]
|
|
39
58
|
)
|
|
40
59
|
|
|
41
|
-
const { getVirtualItems, getTotalSize, measure } =
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
60
|
+
const { getVirtualItems, getTotalSize, measure, scrollToIndex, range } =
|
|
61
|
+
useVirtualizer({
|
|
62
|
+
count: sortedChains.length,
|
|
63
|
+
overscan: 3,
|
|
64
|
+
paddingEnd: 0,
|
|
65
|
+
getScrollElement: () => scrollElementRef.current,
|
|
66
|
+
estimateSize: () => {
|
|
67
|
+
return itemsSize === 'small' ? 48 : 60
|
|
68
|
+
},
|
|
69
|
+
getItemKey,
|
|
70
|
+
})
|
|
51
71
|
|
|
52
72
|
// Using mountOnEnter of the ExpansionTransition component
|
|
53
73
|
// leads to a short delay for setting up scrollElementRef,
|
|
@@ -59,6 +79,31 @@ export const VirtualizedChainList = ({
|
|
|
59
79
|
}
|
|
60
80
|
}, [measure, scrollElementRef.current])
|
|
61
81
|
|
|
82
|
+
useLayoutEffect(() => {
|
|
83
|
+
// Only scroll if sortedChains is not empty and we haven't scrolled yet
|
|
84
|
+
if (!hasScrolledRef.current && sortedChains.length > 0 && range) {
|
|
85
|
+
const selectedChainIndex = sortedChains.findIndex(
|
|
86
|
+
(chain) => chain.id === selectedChainIdRef.current
|
|
87
|
+
)
|
|
88
|
+
if (selectedChainIndex !== -1) {
|
|
89
|
+
// Only scroll if the selected chain is not in the visible range
|
|
90
|
+
// +1 and -1 to account for partially visible items
|
|
91
|
+
if (
|
|
92
|
+
range.startIndex + 1 > selectedChainIndex ||
|
|
93
|
+
range.endIndex - 1 < selectedChainIndex
|
|
94
|
+
) {
|
|
95
|
+
requestAnimationFrame(() => {
|
|
96
|
+
scrollToIndex(selectedChainIndex, {
|
|
97
|
+
align: 'center',
|
|
98
|
+
behavior: 'smooth',
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
hasScrolledRef.current = true // Mark as scrolled (when needed)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}, [sortedChains, scrollToIndex, range])
|
|
106
|
+
|
|
62
107
|
return (
|
|
63
108
|
<List
|
|
64
109
|
className="long-list"
|
|
@@ -76,6 +121,9 @@ export const VirtualizedChainList = ({
|
|
|
76
121
|
itemsSize={itemsSize}
|
|
77
122
|
size={item.size}
|
|
78
123
|
start={item.start}
|
|
124
|
+
withPin={withPinnedChains}
|
|
125
|
+
isPinned={pinnedChains.includes(chain.id)}
|
|
126
|
+
onPin={onPin}
|
|
79
127
|
/>
|
|
80
128
|
)
|
|
81
129
|
})}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import WarningRounded from '@mui/icons-material/WarningRounded'
|
|
2
|
+
import { type BoxProps, Typography } from '@mui/material'
|
|
3
|
+
import { useTranslation } from 'react-i18next'
|
|
4
|
+
import { AlertMessage } from './AlertMessage.js'
|
|
5
|
+
|
|
6
|
+
type MinFromAmountUSDMessageProps = BoxProps & {
|
|
7
|
+
minFromAmountUSD: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const MinFromAmountUSDMessage: React.FC<
|
|
11
|
+
MinFromAmountUSDMessageProps
|
|
12
|
+
> = ({ minFromAmountUSD, ...props }) => {
|
|
13
|
+
const { t } = useTranslation()
|
|
14
|
+
return (
|
|
15
|
+
<AlertMessage
|
|
16
|
+
severity="warning"
|
|
17
|
+
icon={<WarningRounded />}
|
|
18
|
+
title={
|
|
19
|
+
<Typography
|
|
20
|
+
variant="body2"
|
|
21
|
+
sx={{
|
|
22
|
+
color: 'text.primary',
|
|
23
|
+
}}
|
|
24
|
+
>
|
|
25
|
+
{t('warning.message.minFromAmountUSD', {
|
|
26
|
+
amount: minFromAmountUSD,
|
|
27
|
+
minimumFractionDigits: 0,
|
|
28
|
+
})}
|
|
29
|
+
</Typography>
|
|
30
|
+
}
|
|
31
|
+
multiline
|
|
32
|
+
{...props}
|
|
33
|
+
/>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
@@ -5,6 +5,7 @@ import { AccountDeployedMessage } from './AccountDeployedMessage.js'
|
|
|
5
5
|
import { AccountNotDeployedMessage } from './AccountNotDeployedMessage.js'
|
|
6
6
|
import { FundsSufficiencyMessage } from './FundsSufficiencyMessage.js'
|
|
7
7
|
import { GasSufficiencyMessage } from './GasSufficiencyMessage.js'
|
|
8
|
+
import { MinFromAmountUSDMessage } from './MinFromAmountUSDMessage.js'
|
|
8
9
|
import { MissingRouteRequiredAccountMessage } from './MissingRouteRequiredAccountMessage.js'
|
|
9
10
|
import { ToAddressRequiredMessage } from './ToAddressRequiredMessage.js'
|
|
10
11
|
import { useMessageQueue } from './useMessageQueue.js'
|
|
@@ -40,6 +41,13 @@ export const WarningMessages: React.FC<WarningMessagesProps> = ({
|
|
|
40
41
|
{...props}
|
|
41
42
|
/>
|
|
42
43
|
)
|
|
44
|
+
case 'MIN_FROM_AMOUNT_USD':
|
|
45
|
+
return (
|
|
46
|
+
<MinFromAmountUSDMessage
|
|
47
|
+
minFromAmountUSD={messages[0].props?.minFromAmountUSD}
|
|
48
|
+
{...props}
|
|
49
|
+
/>
|
|
50
|
+
)
|
|
43
51
|
case 'ACCOUNT_NOT_DEPLOYED':
|
|
44
52
|
return <AccountNotDeployedMessage {...props} />
|
|
45
53
|
case 'ACCOUNT_DEPLOYED':
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Route } from '@lifi/sdk'
|
|
2
2
|
import { useMemo } from 'react'
|
|
3
|
+
import { useFromAmountThreshold } from '../../hooks/useFromAmountThreshold.js'
|
|
3
4
|
import { useFromTokenSufficiency } from '../../hooks/useFromTokenSufficiency.js'
|
|
4
5
|
import { useGasSufficiency } from '../../hooks/useGasSufficiency.js'
|
|
5
6
|
import { useRouteRequiredAccountConnection } from '../../hooks/useRouteRequiredAccountConnection.js'
|
|
@@ -25,6 +26,7 @@ export const useMessageQueue = (route?: Route, allowInteraction?: boolean) => {
|
|
|
25
26
|
useGasSufficiency(route)
|
|
26
27
|
const { missingChain, missingAccountAddress } =
|
|
27
28
|
useRouteRequiredAccountConnection(route)
|
|
29
|
+
const { belowMinFromAmountUSD, minFromAmountUSD } = useFromAmountThreshold()
|
|
28
30
|
|
|
29
31
|
const messageQueue = useMemo(() => {
|
|
30
32
|
const queue: QueuedMessage[] = []
|
|
@@ -44,10 +46,18 @@ export const useMessageQueue = (route?: Route, allowInteraction?: boolean) => {
|
|
|
44
46
|
})
|
|
45
47
|
}
|
|
46
48
|
|
|
49
|
+
if (belowMinFromAmountUSD) {
|
|
50
|
+
queue.push({
|
|
51
|
+
id: 'MIN_FROM_AMOUNT_USD',
|
|
52
|
+
priority: 3,
|
|
53
|
+
props: { minFromAmountUSD },
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
47
57
|
if (insufficientGas?.length) {
|
|
48
58
|
queue.push({
|
|
49
59
|
id: 'INSUFFICIENT_GAS',
|
|
50
|
-
priority:
|
|
60
|
+
priority: 4,
|
|
51
61
|
props: { insufficientGas },
|
|
52
62
|
})
|
|
53
63
|
}
|
|
@@ -55,21 +65,21 @@ export const useMessageQueue = (route?: Route, allowInteraction?: boolean) => {
|
|
|
55
65
|
if (accountNotDeployedAtDestination && !allowInteraction) {
|
|
56
66
|
queue.push({
|
|
57
67
|
id: 'ACCOUNT_NOT_DEPLOYED',
|
|
58
|
-
priority:
|
|
68
|
+
priority: 5,
|
|
59
69
|
})
|
|
60
70
|
}
|
|
61
71
|
|
|
62
72
|
if (requiredToAddress && !toAddress) {
|
|
63
73
|
queue.push({
|
|
64
74
|
id: 'TO_ADDRESS_REQUIRED',
|
|
65
|
-
priority:
|
|
75
|
+
priority: 6,
|
|
66
76
|
})
|
|
67
77
|
}
|
|
68
78
|
|
|
69
79
|
if (accountDeployedAtDestination && !allowInteraction) {
|
|
70
80
|
queue.push({
|
|
71
81
|
id: 'ACCOUNT_DEPLOYED',
|
|
72
|
-
priority:
|
|
82
|
+
priority: 7,
|
|
73
83
|
})
|
|
74
84
|
}
|
|
75
85
|
|
|
@@ -84,6 +94,8 @@ export const useMessageQueue = (route?: Route, allowInteraction?: boolean) => {
|
|
|
84
94
|
toAddress,
|
|
85
95
|
missingChain,
|
|
86
96
|
missingAccountAddress,
|
|
97
|
+
belowMinFromAmountUSD,
|
|
98
|
+
minFromAmountUSD,
|
|
87
99
|
])
|
|
88
100
|
|
|
89
101
|
return {
|
|
@@ -117,7 +117,7 @@ export const TokenDetailsSheetContent = forwardRef<
|
|
|
117
117
|
color: 'text.primary',
|
|
118
118
|
}}
|
|
119
119
|
>
|
|
120
|
-
{token
|
|
120
|
+
{token?.priceUSD
|
|
121
121
|
? t('format.currency', {
|
|
122
122
|
value: formatTokenPrice('1', token.priceUSD, token.decimals),
|
|
123
123
|
})
|
|
@@ -169,6 +169,54 @@ export const TokenDetailsSheetContent = forwardRef<
|
|
|
169
169
|
</Box>
|
|
170
170
|
</MetricWithSkeleton>
|
|
171
171
|
)}
|
|
172
|
+
<MetricWithSkeleton
|
|
173
|
+
isLoading={isLoading}
|
|
174
|
+
label={t('tokenMetric.marketCap')}
|
|
175
|
+
width={200}
|
|
176
|
+
height={24}
|
|
177
|
+
>
|
|
178
|
+
<Typography
|
|
179
|
+
sx={{
|
|
180
|
+
fontWeight: 700,
|
|
181
|
+
fontSize: '18px',
|
|
182
|
+
lineHeight: '24px',
|
|
183
|
+
color: 'text.primary',
|
|
184
|
+
}}
|
|
185
|
+
>
|
|
186
|
+
{token?.marketCapUSD
|
|
187
|
+
? t('format.currency', {
|
|
188
|
+
value: token.marketCapUSD,
|
|
189
|
+
notation: 'compact',
|
|
190
|
+
compactDisplay: 'short',
|
|
191
|
+
maximumFractionDigits: 2,
|
|
192
|
+
})
|
|
193
|
+
: noDataLabel}
|
|
194
|
+
</Typography>
|
|
195
|
+
</MetricWithSkeleton>
|
|
196
|
+
<MetricWithSkeleton
|
|
197
|
+
isLoading={isLoading}
|
|
198
|
+
label={t('tokenMetric.volume24h')}
|
|
199
|
+
width={200}
|
|
200
|
+
height={24}
|
|
201
|
+
>
|
|
202
|
+
<Typography
|
|
203
|
+
sx={{
|
|
204
|
+
fontWeight: 700,
|
|
205
|
+
fontSize: '18px',
|
|
206
|
+
lineHeight: '24px',
|
|
207
|
+
color: 'text.primary',
|
|
208
|
+
}}
|
|
209
|
+
>
|
|
210
|
+
{token?.volumeUSD24H
|
|
211
|
+
? t('format.currency', {
|
|
212
|
+
value: token.volumeUSD24H,
|
|
213
|
+
notation: 'compact',
|
|
214
|
+
compactDisplay: 'short',
|
|
215
|
+
maximumFractionDigits: 2,
|
|
216
|
+
})
|
|
217
|
+
: noDataLabel}
|
|
218
|
+
</Typography>
|
|
219
|
+
</MetricWithSkeleton>
|
|
172
220
|
</TokenDetailsSheetContainer>
|
|
173
221
|
)
|
|
174
222
|
})
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { BaseToken } from '@lifi/sdk'
|
|
1
2
|
import { useAccount } from '@lifi/wallet-management'
|
|
2
3
|
import { Box } from '@mui/material'
|
|
3
4
|
import { type FC, useEffect } from 'react'
|
|
@@ -6,10 +7,12 @@ import { useDebouncedWatch } from '../../hooks/useDebouncedWatch.js'
|
|
|
6
7
|
import { useTokenBalances } from '../../hooks/useTokenBalances.js'
|
|
7
8
|
import { useTokenSearch } from '../../hooks/useTokenSearch.js'
|
|
8
9
|
import { useWidgetEvents } from '../../hooks/useWidgetEvents.js'
|
|
10
|
+
import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js'
|
|
9
11
|
import { FormKeyHelper } from '../../stores/form/types.js'
|
|
10
12
|
import { useFieldValues } from '../../stores/form/useFieldValues.js'
|
|
11
13
|
import { WidgetEvent } from '../../types/events.js'
|
|
12
14
|
import type { TokenAmount } from '../../types/token.js'
|
|
15
|
+
import { getConfigItemSets, isFormItemAllowed } from '../../utils/item.js'
|
|
13
16
|
import { TokenNotFound } from './TokenNotFound.js'
|
|
14
17
|
import type { TokenListProps } from './types.js'
|
|
15
18
|
import { useTokenSelect } from './useTokenSelect.js'
|
|
@@ -32,6 +35,8 @@ export const TokenList: FC<TokenListProps> = ({
|
|
|
32
35
|
'tokenSearchFilter'
|
|
33
36
|
)
|
|
34
37
|
|
|
38
|
+
const { tokens: configTokens } = useWidgetConfig()
|
|
39
|
+
|
|
35
40
|
const { chain: selectedChain, isLoading: isSelectedChainLoading } =
|
|
36
41
|
useChain(selectedChainId)
|
|
37
42
|
const { account } = useAccount({
|
|
@@ -45,12 +50,30 @@ export const TokenList: FC<TokenListProps> = ({
|
|
|
45
50
|
isBalanceLoading,
|
|
46
51
|
featuredTokens,
|
|
47
52
|
popularTokens,
|
|
48
|
-
} = useTokenBalances(selectedChainId
|
|
53
|
+
} = useTokenBalances(selectedChainId)
|
|
49
54
|
|
|
50
55
|
let filteredTokens = (tokensWithBalance ?? chainTokens ?? []) as TokenAmount[]
|
|
51
56
|
const normalizedSearchFilter = tokenSearchFilter?.replaceAll('$', '')
|
|
52
57
|
const searchFilter = normalizedSearchFilter?.toUpperCase() ?? ''
|
|
53
58
|
|
|
59
|
+
const filteredConfigTokens = getConfigItemSets(
|
|
60
|
+
configTokens,
|
|
61
|
+
(tokens: BaseToken[]) =>
|
|
62
|
+
new Set(
|
|
63
|
+
tokens
|
|
64
|
+
.filter((t) => t.chainId === selectedChainId)
|
|
65
|
+
.map((t) => t.address)
|
|
66
|
+
),
|
|
67
|
+
formType
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
// Get the appropriate allow/deny lists based on formType
|
|
71
|
+
filteredTokens = filteredTokens.filter(
|
|
72
|
+
(token) =>
|
|
73
|
+
token.chainId === selectedChainId &&
|
|
74
|
+
isFormItemAllowed(token, filteredConfigTokens, formType, (t) => t.address)
|
|
75
|
+
)
|
|
76
|
+
|
|
54
77
|
filteredTokens = tokenSearchFilter
|
|
55
78
|
? filteredTokens
|
|
56
79
|
.filter(
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import type { MouseEventHandler } from 'react'
|
|
13
13
|
import { memo, useRef, useState } from 'react'
|
|
14
14
|
import { useTranslation } from 'react-i18next'
|
|
15
|
+
import { useLongPress } from '../../hooks/useLongPress.js'
|
|
15
16
|
import { formatTokenAmount, formatTokenPrice } from '../../utils/format.js'
|
|
16
17
|
import { shortenAddress } from '../../utils/wallet.js'
|
|
17
18
|
import { ListItemButton } from '../ListItem/ListItemButton.js'
|
|
@@ -147,11 +148,16 @@ export const TokenListItemButton: React.FC<TokenListItemButtonProps> = ({
|
|
|
147
148
|
token.decimals
|
|
148
149
|
)
|
|
149
150
|
|
|
151
|
+
const longPressEvents = useLongPress(() =>
|
|
152
|
+
onShowTokenDetails(token.address, withoutContractAddress)
|
|
153
|
+
)
|
|
154
|
+
|
|
150
155
|
return (
|
|
151
156
|
<ListItemButton
|
|
152
157
|
onClick={onClick}
|
|
153
158
|
onMouseEnter={onMouseEnter}
|
|
154
159
|
onMouseLeave={onMouseLeave}
|
|
160
|
+
{...longPressEvents}
|
|
155
161
|
dense
|
|
156
162
|
selected={selected}
|
|
157
163
|
sx={{
|
package/src/config/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export const name = '@lifi/widget'
|
|
2
|
-
export const version = '3.25.0
|
|
2
|
+
export const version = '3.25.0'
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
import { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider'
|
|
3
|
+
import { FormKeyHelper } from '../stores/form/types'
|
|
4
|
+
import { useFieldValues } from '../stores/form/useFieldValues'
|
|
5
|
+
import { formatTokenPrice } from '../utils/format'
|
|
6
|
+
import { useToken } from './useToken'
|
|
7
|
+
|
|
8
|
+
export const useFromAmountThreshold = () => {
|
|
9
|
+
const { minFromAmountUSD } = useWidgetConfig()
|
|
10
|
+
|
|
11
|
+
const [chainId, tokenAddress, fromAmount] = useFieldValues(
|
|
12
|
+
FormKeyHelper.getChainKey('from'),
|
|
13
|
+
FormKeyHelper.getTokenKey('from'),
|
|
14
|
+
FormKeyHelper.getAmountKey('from')
|
|
15
|
+
)
|
|
16
|
+
const { token } = useToken(chainId, tokenAddress)
|
|
17
|
+
|
|
18
|
+
const belowMinFromAmountUSD = useMemo(() => {
|
|
19
|
+
const fromAmountUSD = formatTokenPrice(fromAmount, token?.priceUSD)
|
|
20
|
+
|
|
21
|
+
if (!minFromAmountUSD || !fromAmountUSD) {
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
24
|
+
return fromAmountUSD < minFromAmountUSD
|
|
25
|
+
}, [minFromAmountUSD, fromAmount, token?.priceUSD])
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
belowMinFromAmountUSD,
|
|
29
|
+
minFromAmountUSD,
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useCallback, useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
export const useLongPress = (callback = () => {}, ms = 500) => {
|
|
4
|
+
const timerRef = useRef<NodeJS.Timeout | null>(null)
|
|
5
|
+
const isPressedRef = useRef(false)
|
|
6
|
+
const startPosRef = useRef<{ x: number; y: number } | null>(null)
|
|
7
|
+
|
|
8
|
+
const start = useCallback(
|
|
9
|
+
(e: React.PointerEvent) => {
|
|
10
|
+
isPressedRef.current = true
|
|
11
|
+
startPosRef.current = { x: e.clientX, y: e.clientY }
|
|
12
|
+
timerRef.current = setTimeout(() => {
|
|
13
|
+
if (isPressedRef.current) {
|
|
14
|
+
callback()
|
|
15
|
+
}
|
|
16
|
+
}, ms)
|
|
17
|
+
},
|
|
18
|
+
[callback, ms]
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
const clear = useCallback(() => {
|
|
22
|
+
isPressedRef.current = false
|
|
23
|
+
startPosRef.current = null
|
|
24
|
+
if (timerRef.current) {
|
|
25
|
+
clearTimeout(timerRef.current)
|
|
26
|
+
}
|
|
27
|
+
}, [])
|
|
28
|
+
|
|
29
|
+
// Based on https://github.com/minwork/react/tree/main/packages/use-long-press
|
|
30
|
+
const move = useCallback(
|
|
31
|
+
(e: React.PointerEvent) => {
|
|
32
|
+
if (isPressedRef.current && startPosRef.current) {
|
|
33
|
+
const dx = Math.abs(e.clientX - startPosRef.current.x)
|
|
34
|
+
const dy = Math.abs(e.clientY - startPosRef.current.y)
|
|
35
|
+
const limit = 25
|
|
36
|
+
if (dx > limit || dy > limit) {
|
|
37
|
+
clear() // cancel on movement
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
[clear]
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
onPointerDown: start,
|
|
46
|
+
onPointerUp: clear,
|
|
47
|
+
onPointerLeave: clear,
|
|
48
|
+
onPointerCancel: clear,
|
|
49
|
+
onPointerMove: move,
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -3,21 +3,15 @@ import { useAccount } from '@lifi/wallet-management'
|
|
|
3
3
|
import { useQuery } from '@tanstack/react-query'
|
|
4
4
|
import { formatUnits } from 'viem'
|
|
5
5
|
import { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js'
|
|
6
|
-
import type { FormType } from '../stores/form/types.js'
|
|
7
6
|
import type { TokenAmount } from '../types/token.js'
|
|
8
7
|
import { getQueryKey } from '../utils/queries.js'
|
|
9
8
|
import { useTokens } from './useTokens.js'
|
|
10
9
|
|
|
11
10
|
const defaultRefetchInterval = 32_000
|
|
12
11
|
|
|
13
|
-
export const useTokenBalances = (
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
) => {
|
|
17
|
-
const { tokens, featuredTokens, popularTokens, chain, isLoading } = useTokens(
|
|
18
|
-
selectedChainId,
|
|
19
|
-
formType
|
|
20
|
-
)
|
|
12
|
+
export const useTokenBalances = (selectedChainId?: number) => {
|
|
13
|
+
const { tokens, featuredTokens, popularTokens, chain, isLoading } =
|
|
14
|
+
useTokens(selectedChainId)
|
|
21
15
|
const { account } = useAccount({ chainType: chain?.chainType })
|
|
22
16
|
const { keyPrefix } = useWidgetConfig()
|
|
23
17
|
|
|
@@ -36,7 +30,6 @@ export const useTokenBalances = (
|
|
|
36
30
|
account.address,
|
|
37
31
|
selectedChainId,
|
|
38
32
|
tokens?.length,
|
|
39
|
-
formType,
|
|
40
33
|
],
|
|
41
34
|
queryFn: async ({ queryKey: [, accountAddress] }) => {
|
|
42
35
|
const tokensWithBalance: TokenAmount[] = await getTokenBalances(
|
|
@@ -2,12 +2,12 @@ import {
|
|
|
2
2
|
type BaseToken,
|
|
3
3
|
type ChainId,
|
|
4
4
|
getToken,
|
|
5
|
+
type TokenExtended,
|
|
5
6
|
type TokensResponse,
|
|
6
7
|
} from '@lifi/sdk'
|
|
7
8
|
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
|
8
9
|
import { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js'
|
|
9
10
|
import type { FormType } from '../stores/form/types.js'
|
|
10
|
-
import type { TokenAmount } from '../types/token.js'
|
|
11
11
|
import { getConfigItemSets, isFormItemAllowed } from '../utils/item.js'
|
|
12
12
|
import { getQueryKey } from '../utils/queries.js'
|
|
13
13
|
|
|
@@ -62,13 +62,13 @@ export const useTokenSearch = (
|
|
|
62
62
|
)
|
|
63
63
|
) {
|
|
64
64
|
const clonedData = { ...data }
|
|
65
|
-
clonedData.tokens[chainId as number]?.push(token as
|
|
65
|
+
clonedData.tokens[chainId as number]?.push(token as TokenExtended)
|
|
66
66
|
return clonedData
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
)
|
|
70
70
|
}
|
|
71
|
-
return token as
|
|
71
|
+
return token as TokenExtended
|
|
72
72
|
},
|
|
73
73
|
|
|
74
74
|
enabled: Boolean(chainId && tokenQuery && enabled),
|
package/src/hooks/useTokens.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ChainType, getTokens } from '@lifi/sdk'
|
|
2
2
|
import { useQuery } from '@tanstack/react-query'
|
|
3
3
|
import { useMemo } from 'react'
|
|
4
4
|
import { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js'
|
|
5
|
-
import type { FormType } from '../stores/form/types.js'
|
|
6
5
|
import type { TokenAmount } from '../types/token.js'
|
|
7
|
-
import { getConfigItemSets, isFormItemAllowed } from '../utils/item.js'
|
|
8
6
|
import { getQueryKey } from '../utils/queries.js'
|
|
9
7
|
import { useChains } from './useChains.js'
|
|
10
8
|
|
|
11
|
-
export const useTokens = (selectedChainId?: number
|
|
9
|
+
export const useTokens = (selectedChainId?: number) => {
|
|
12
10
|
const { tokens: configTokens, keyPrefix } = useWidgetConfig()
|
|
13
11
|
const { data, isLoading } = useQuery({
|
|
14
12
|
queryKey: [getQueryKey('tokens', keyPrefix)],
|
|
@@ -47,30 +45,6 @@ export const useTokens = (selectedChainId?: number, formType?: FormType) => {
|
|
|
47
45
|
filteredTokens = [...includedTokens, ...filteredTokens]
|
|
48
46
|
}
|
|
49
47
|
|
|
50
|
-
// Filter config tokens by chain before checking if token is allowed
|
|
51
|
-
const filteredConfigTokens = getConfigItemSets(
|
|
52
|
-
configTokens,
|
|
53
|
-
(tokens: BaseToken[]) =>
|
|
54
|
-
new Set(
|
|
55
|
-
tokens
|
|
56
|
-
.filter((t) => t.chainId === selectedChainId)
|
|
57
|
-
.map((t) => t.address)
|
|
58
|
-
),
|
|
59
|
-
formType
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
// Get the appropriate allow/deny lists based on formType
|
|
63
|
-
filteredTokens = filteredTokens.filter(
|
|
64
|
-
(token) =>
|
|
65
|
-
token.chainId === selectedChainId &&
|
|
66
|
-
isFormItemAllowed(
|
|
67
|
-
token,
|
|
68
|
-
filteredConfigTokens,
|
|
69
|
-
formType,
|
|
70
|
-
(t) => t.address
|
|
71
|
-
)
|
|
72
|
-
)
|
|
73
|
-
|
|
74
48
|
const filteredTokensMap = new Map(
|
|
75
49
|
filteredTokens.map((token) => [token.address, token])
|
|
76
50
|
)
|
|
@@ -123,7 +97,6 @@ export const useTokens = (selectedChainId?: number, formType?: FormType) => {
|
|
|
123
97
|
getChainById,
|
|
124
98
|
isSupportedChainsLoading,
|
|
125
99
|
selectedChainId,
|
|
126
|
-
formType,
|
|
127
100
|
])
|
|
128
101
|
|
|
129
102
|
return {
|
package/src/i18n/bn.json
CHANGED
|
@@ -129,6 +129,7 @@
|
|
|
129
129
|
"highValueLoss": "প্রাপ্ত টোকেনগুলির মান সোওয়াপ করা টোকেন এবং লেনদেনের খরচের তুলনায় উল্লেখযোগ্যভাবে কম।",
|
|
130
130
|
"insufficientFunds": "সোওয়াপ সম্পূর্ণ করার জন্য আপনার কাছে পর্যাপ্ত তহবিল নেই।",
|
|
131
131
|
"insufficientGas": "সোওয়াপ সম্পূর্ণ করার জন্য আপনার কাছে পর্যাপ্ত গ্যাস নেই। আপনাকে অন্তত যোগ করতে হবে:",
|
|
132
|
+
"minFromAmountUSD": "",
|
|
132
133
|
"rateChanged": "বিনিময় হার পরিবর্তিত হয়েছে। সোওয়াপ চালিয়ে যাওয়ার মাধ্যমে, আপনি নতুন হার গ্রহণ করবেন।",
|
|
133
134
|
"resetSettings": "এটি আপনার রুটের অগ্রাধিকার, স্লিপেজ, গ্যাসের দাম, সক্ষম ব্রিজ এবং এক্সচেঞ্জ রিসেট করবে।",
|
|
134
135
|
"slippageOutsideRecommendedLimits": "",
|
|
@@ -345,6 +346,8 @@
|
|
|
345
346
|
},
|
|
346
347
|
"tokenMetric": {
|
|
347
348
|
"currentPrice": "",
|
|
348
|
-
"contractAddress": ""
|
|
349
|
+
"contractAddress": "",
|
|
350
|
+
"marketCap": "",
|
|
351
|
+
"volume24h": ""
|
|
349
352
|
}
|
|
350
353
|
}
|
package/src/i18n/de.json
CHANGED
|
@@ -129,6 +129,7 @@
|
|
|
129
129
|
"highValueLoss": "Der Wert der erhaltenen Token ist deutlich niedriger als die getauschten Token und die Transaktionskosten.",
|
|
130
130
|
"insufficientFunds": "Sie haben nicht genug Token, um diesen Swap abzuschließen.",
|
|
131
131
|
"insufficientGas": "Sie haben nicht genug Gas um diesen Swap abzuschließen. Sie müssen mindestens hinzufügen:",
|
|
132
|
+
"minFromAmountUSD": "",
|
|
132
133
|
"rateChanged": "Der Wechselkurs hat sich geändert. Durch Fortsetzen des Swaps akzeptieren Sie den neuen Kurs.",
|
|
133
134
|
"resetSettings": "Dies wird Ihre Routenpriorität, Slippage, Gaspreis, aktivierte Brücken und Exchanges zurücksetzen.",
|
|
134
135
|
"slippageOutsideRecommendedLimits": "",
|
|
@@ -345,6 +346,8 @@
|
|
|
345
346
|
},
|
|
346
347
|
"tokenMetric": {
|
|
347
348
|
"currentPrice": "",
|
|
348
|
-
"contractAddress": ""
|
|
349
|
+
"contractAddress": "",
|
|
350
|
+
"marketCap": "",
|
|
351
|
+
"volume24h": ""
|
|
349
352
|
}
|
|
350
353
|
}
|