@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.
Files changed (118) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/esm/components/Chains/ChainList.d.ts +2 -3
  3. package/dist/esm/components/Chains/ChainList.js +4 -3
  4. package/dist/esm/components/Chains/ChainList.js.map +1 -1
  5. package/dist/esm/components/Chains/ChainList.style.js +10 -3
  6. package/dist/esm/components/Chains/ChainList.style.js.map +1 -1
  7. package/dist/esm/components/Chains/ChainListItem.d.ts +4 -1
  8. package/dist/esm/components/Chains/ChainListItem.js +14 -2
  9. package/dist/esm/components/Chains/ChainListItem.js.map +1 -1
  10. package/dist/esm/components/Chains/PinChainButton.d.ts +7 -0
  11. package/dist/esm/components/Chains/PinChainButton.js +34 -0
  12. package/dist/esm/components/Chains/PinChainButton.js.map +1 -0
  13. package/dist/esm/components/Chains/SelectChainContent.js +15 -16
  14. package/dist/esm/components/Chains/SelectChainContent.js.map +1 -1
  15. package/dist/esm/components/Chains/VirtualizedChainList.d.ts +2 -1
  16. package/dist/esm/components/Chains/VirtualizedChainList.js +43 -9
  17. package/dist/esm/components/Chains/VirtualizedChainList.js.map +1 -1
  18. package/dist/esm/components/Messages/MinFromAmountUSDMessage.d.ts +6 -0
  19. package/dist/esm/components/Messages/MinFromAmountUSDMessage.js +15 -0
  20. package/dist/esm/components/Messages/MinFromAmountUSDMessage.js.map +1 -0
  21. package/dist/esm/components/Messages/WarningMessages.js +3 -0
  22. package/dist/esm/components/Messages/WarningMessages.js.map +1 -1
  23. package/dist/esm/components/Messages/useMessageQueue.js +15 -4
  24. package/dist/esm/components/Messages/useMessageQueue.js.map +1 -1
  25. package/dist/esm/components/TokenList/TokenDetailsSheet.js +0 -2
  26. package/dist/esm/components/TokenList/TokenDetailsSheet.js.map +1 -1
  27. package/dist/esm/components/TokenList/TokenDetailsSheetContent.js +26 -2
  28. package/dist/esm/components/TokenList/TokenDetailsSheetContent.js.map +1 -1
  29. package/dist/esm/components/TokenList/TokenList.js +10 -1
  30. package/dist/esm/components/TokenList/TokenList.js.map +1 -1
  31. package/dist/esm/components/TokenList/TokenListItem.js +3 -1
  32. package/dist/esm/components/TokenList/TokenListItem.js.map +1 -1
  33. package/dist/esm/config/version.d.ts +1 -1
  34. package/dist/esm/config/version.js +1 -1
  35. package/dist/esm/config/version.js.map +1 -1
  36. package/dist/esm/hooks/useFromAmountThreshold.d.ts +4 -0
  37. package/dist/esm/hooks/useFromAmountThreshold.js +23 -0
  38. package/dist/esm/hooks/useFromAmountThreshold.js.map +1 -0
  39. package/dist/esm/hooks/useLongPress.d.ts +7 -0
  40. package/dist/esm/hooks/useLongPress.js +41 -0
  41. package/dist/esm/hooks/useLongPress.js.map +1 -0
  42. package/dist/esm/hooks/useTokenBalances.d.ts +1 -2
  43. package/dist/esm/hooks/useTokenBalances.js +2 -3
  44. package/dist/esm/hooks/useTokenBalances.js.map +1 -1
  45. package/dist/esm/hooks/useTokenSearch.d.ts +2 -2
  46. package/dist/esm/hooks/useTokenSearch.js.map +1 -1
  47. package/dist/esm/hooks/useTokens.d.ts +1 -2
  48. package/dist/esm/hooks/useTokens.js +1 -10
  49. package/dist/esm/hooks/useTokens.js.map +1 -1
  50. package/dist/esm/i18n/bn.json +4 -1
  51. package/dist/esm/i18n/de.json +4 -1
  52. package/dist/esm/i18n/en.json +4 -1
  53. package/dist/esm/i18n/es.json +4 -1
  54. package/dist/esm/i18n/fr.json +4 -1
  55. package/dist/esm/i18n/hi.json +4 -1
  56. package/dist/esm/i18n/id.json +4 -1
  57. package/dist/esm/i18n/it.json +4 -1
  58. package/dist/esm/i18n/ja.json +4 -1
  59. package/dist/esm/i18n/ko.json +4 -1
  60. package/dist/esm/i18n/pl.json +4 -1
  61. package/dist/esm/i18n/pt.json +4 -1
  62. package/dist/esm/i18n/th.json +4 -1
  63. package/dist/esm/i18n/tr.json +4 -1
  64. package/dist/esm/i18n/uk.json +4 -1
  65. package/dist/esm/i18n/vi.json +4 -1
  66. package/dist/esm/i18n/zh.json +4 -1
  67. package/dist/esm/stores/chains/ChainOrderStore.js +17 -5
  68. package/dist/esm/stores/chains/ChainOrderStore.js.map +1 -1
  69. package/dist/esm/stores/chains/createChainOrderStore.js +19 -1
  70. package/dist/esm/stores/chains/createChainOrderStore.js.map +1 -1
  71. package/dist/esm/stores/chains/types.d.ts +2 -0
  72. package/dist/esm/stores/header/useHeaderStore.js +2 -3
  73. package/dist/esm/stores/header/useHeaderStore.js.map +1 -1
  74. package/dist/esm/types/widget.d.ts +1 -0
  75. package/dist/esm/types/widget.js.map +1 -1
  76. package/package.json +4 -4
  77. package/package.json.tmp +3 -3
  78. package/src/components/Chains/ChainList.style.tsx +10 -2
  79. package/src/components/Chains/ChainList.tsx +6 -5
  80. package/src/components/Chains/ChainListItem.tsx +29 -0
  81. package/src/components/Chains/PinChainButton.tsx +51 -0
  82. package/src/components/Chains/SelectChainContent.tsx +18 -21
  83. package/src/components/Chains/VirtualizedChainList.tsx +68 -20
  84. package/src/components/Messages/MinFromAmountUSDMessage.tsx +35 -0
  85. package/src/components/Messages/WarningMessages.tsx +8 -0
  86. package/src/components/Messages/useMessageQueue.ts +16 -4
  87. package/src/components/TokenList/TokenDetailsSheet.tsx +0 -2
  88. package/src/components/TokenList/TokenDetailsSheetContent.tsx +49 -1
  89. package/src/components/TokenList/TokenList.tsx +24 -1
  90. package/src/components/TokenList/TokenListItem.tsx +6 -0
  91. package/src/config/version.ts +1 -1
  92. package/src/hooks/useFromAmountThreshold.ts +31 -0
  93. package/src/hooks/useLongPress.ts +51 -0
  94. package/src/hooks/useTokenBalances.ts +3 -10
  95. package/src/hooks/useTokenSearch.ts +3 -3
  96. package/src/hooks/useTokens.ts +2 -29
  97. package/src/i18n/bn.json +4 -1
  98. package/src/i18n/de.json +4 -1
  99. package/src/i18n/en.json +4 -1
  100. package/src/i18n/es.json +4 -1
  101. package/src/i18n/fr.json +4 -1
  102. package/src/i18n/hi.json +4 -1
  103. package/src/i18n/id.json +4 -1
  104. package/src/i18n/it.json +4 -1
  105. package/src/i18n/ja.json +4 -1
  106. package/src/i18n/ko.json +4 -1
  107. package/src/i18n/pl.json +4 -1
  108. package/src/i18n/pt.json +4 -1
  109. package/src/i18n/th.json +4 -1
  110. package/src/i18n/tr.json +4 -1
  111. package/src/i18n/uk.json +4 -1
  112. package/src/i18n/vi.json +4 -1
  113. package/src/i18n/zh.json +4 -1
  114. package/src/stores/chains/ChainOrderStore.tsx +23 -5
  115. package/src/stores/chains/createChainOrderStore.ts +19 -1
  116. package/src/stores/chains/types.ts +2 -0
  117. package/src/stores/header/useHeaderStore.tsx +2 -9
  118. 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 initialSelectedChainIdRef = useRef(selectedChainId)
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
- const selectedChain = chains.find(
26
- (chain) => chain.id === initialSelectedChainIdRef.current
27
- )
28
- const otherChains = chains.filter(
29
- (chain) => chain.id !== initialSelectedChainIdRef.current
30
- )
31
- return selectedChain ? [selectedChain, ...otherChains] : chains
32
- }, [chains])
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 } = useVirtualizer({
42
- count: sortedChains.length,
43
- overscan: 3,
44
- paddingEnd: 0,
45
- getScrollElement: () => scrollElementRef.current,
46
- estimateSize: () => {
47
- return itemsSize === 'small' ? 48 : 60
48
- },
49
- getItemKey,
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: 3,
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: 4,
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: 5,
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: 6,
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 {
@@ -29,8 +29,6 @@ export const TokenDetailsSheet = forwardRef<
29
29
  },
30
30
  close: () => {
31
31
  bottomSheetRef.current?.close()
32
- setTokenAddress(undefined)
33
- setWithoutContractAddress(false)
34
32
  },
35
33
  }),
36
34
  []
@@ -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, formType)
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={{
@@ -1,2 +1,2 @@
1
1
  export const name = '@lifi/widget'
2
- export const version = '3.25.0-beta.4'
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
- selectedChainId?: number,
15
- formType?: FormType
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 TokenAmount)
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 TokenAmount
71
+ return token as TokenExtended
72
72
  },
73
73
 
74
74
  enabled: Boolean(chainId && tokenQuery && enabled),
@@ -1,14 +1,12 @@
1
- import { type BaseToken, ChainType, getTokens } from '@lifi/sdk'
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, formType?: FormType) => {
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
  }