@lifi/widget 3.13.2 → 3.14.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 (122) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/esm/AppDrawer.style.d.ts +1 -1
  3. package/dist/esm/components/ActiveTransactions/ActiveTransactions.style.d.ts +1 -1
  4. package/dist/esm/components/AlertMessage/AlertMessage.d.ts +2 -2
  5. package/dist/esm/components/AlertMessage/AlertMessage.js +1 -1
  6. package/dist/esm/components/AlertMessage/AlertMessage.js.map +1 -1
  7. package/dist/esm/components/AmountInput/AmountInputAdornment.style.d.ts +1 -1
  8. package/dist/esm/components/AmountInput/PriceFormHelperText.js +7 -4
  9. package/dist/esm/components/AmountInput/PriceFormHelperText.js.map +1 -1
  10. package/dist/esm/components/BaseTransactionButton/BaseTransactionButton.js +2 -2
  11. package/dist/esm/components/BaseTransactionButton/BaseTransactionButton.js.map +1 -1
  12. package/dist/esm/components/ButtonTertiary.d.ts +1 -1
  13. package/dist/esm/components/ButtonTertiary.js +4 -5
  14. package/dist/esm/components/ButtonTertiary.js.map +1 -1
  15. package/dist/esm/components/Card/CardHeader.d.ts +1 -1
  16. package/dist/esm/components/Card/CardIconButton.d.ts +1 -1
  17. package/dist/esm/components/Card/CardLabel.js +0 -1
  18. package/dist/esm/components/Card/CardLabel.js.map +1 -1
  19. package/dist/esm/components/FeeBreakdownTooltip.js +3 -4
  20. package/dist/esm/components/FeeBreakdownTooltip.js.map +1 -1
  21. package/dist/esm/components/GasMessage/FundsSufficiencyMessage.js +1 -1
  22. package/dist/esm/components/GasMessage/FundsSufficiencyMessage.js.map +1 -1
  23. package/dist/esm/components/Header/Header.style.d.ts +1 -1
  24. package/dist/esm/components/Header/SettingsButton.style.d.ts +1 -1
  25. package/dist/esm/components/RouteCard/RouteCard.js +8 -3
  26. package/dist/esm/components/RouteCard/RouteCard.js.map +1 -1
  27. package/dist/esm/components/RouteCard/getMatchingLabels.d.ts +3 -0
  28. package/dist/esm/components/RouteCard/getMatchingLabels.js +34 -0
  29. package/dist/esm/components/RouteCard/getMatchingLabels.js.map +1 -0
  30. package/dist/esm/components/SelectTokenButton/SelectTokenButton.style.d.ts +1 -1
  31. package/dist/esm/components/SendToWallet/SendToWallet.style.d.ts +1 -1
  32. package/dist/esm/components/Skeleton/WidgetSkeleton.style.d.ts +2 -2
  33. package/dist/esm/components/Step/Step.js +17 -8
  34. package/dist/esm/components/Step/Step.js.map +1 -1
  35. package/dist/esm/components/StepActions/StepActions.js +8 -8
  36. package/dist/esm/components/StepActions/StepActions.js.map +1 -1
  37. package/dist/esm/components/ToAddressRequiredMessage.js +1 -1
  38. package/dist/esm/components/ToAddressRequiredMessage.js.map +1 -1
  39. package/dist/esm/components/Token/Token.js +3 -3
  40. package/dist/esm/components/Token/Token.js.map +1 -1
  41. package/dist/esm/components/TokenList/TokenList.style.d.ts +1 -1
  42. package/dist/esm/components/TokenList/TokenListItem.js +4 -6
  43. package/dist/esm/components/TokenList/TokenListItem.js.map +1 -1
  44. package/dist/esm/components/TokenRate/TokenRate.js +5 -7
  45. package/dist/esm/components/TokenRate/TokenRate.js.map +1 -1
  46. package/dist/esm/components/TransactionDetails.js +3 -7
  47. package/dist/esm/components/TransactionDetails.js.map +1 -1
  48. package/dist/esm/config/version.d.ts +1 -1
  49. package/dist/esm/config/version.js +1 -1
  50. package/dist/esm/i18n/en.json +4 -2
  51. package/dist/esm/pages/SendToWallet/BookmarkAddressSheet.js +1 -2
  52. package/dist/esm/pages/SendToWallet/BookmarkAddressSheet.js.map +1 -1
  53. package/dist/esm/pages/SendToWallet/ConfirmAddressSheet.d.ts +1 -0
  54. package/dist/esm/pages/SendToWallet/ConfirmAddressSheet.js +6 -4
  55. package/dist/esm/pages/SendToWallet/ConfirmAddressSheet.js.map +1 -1
  56. package/dist/esm/pages/SendToWallet/SendToWalletPage.js +5 -3
  57. package/dist/esm/pages/SendToWallet/SendToWalletPage.js.map +1 -1
  58. package/dist/esm/pages/SendToWallet/SendToWalletPage.style.d.ts +2 -2
  59. package/dist/esm/pages/TransactionPage/ExchangeRateBottomSheet.js +2 -2
  60. package/dist/esm/pages/TransactionPage/ExchangeRateBottomSheet.js.map +1 -1
  61. package/dist/esm/providers/I18nProvider/I18nProvider.js +3 -1
  62. package/dist/esm/providers/I18nProvider/I18nProvider.js.map +1 -1
  63. package/dist/esm/stores/routes/useExecutingRoutesIds.js +2 -2
  64. package/dist/esm/stores/routes/useExecutingRoutesIds.js.map +1 -1
  65. package/dist/esm/themes/createTheme.js +4 -5
  66. package/dist/esm/themes/createTheme.js.map +1 -1
  67. package/dist/esm/types/widget.d.ts +18 -1
  68. package/dist/esm/types/widget.js.map +1 -1
  69. package/dist/esm/utils/compactNumberFormatter.d.ts +10 -0
  70. package/dist/esm/utils/compactNumberFormatter.js +81 -0
  71. package/dist/esm/utils/compactNumberFormatter.js.map +1 -0
  72. package/dist/esm/utils/compactNumberFormatter.test.d.ts +1 -0
  73. package/dist/esm/utils/compactNumberFormatter.test.js +64 -0
  74. package/dist/esm/utils/compactNumberFormatter.test.js.map +1 -0
  75. package/dist/esm/utils/converters.js +2 -2
  76. package/dist/esm/utils/converters.js.map +1 -1
  77. package/dist/esm/utils/fees.js +2 -3
  78. package/dist/esm/utils/fees.js.map +1 -1
  79. package/dist/esm/utils/format.d.ts +1 -10
  80. package/dist/esm/utils/format.js +8 -47
  81. package/dist/esm/utils/format.js.map +1 -1
  82. package/dist/esm/utils/getPriceImpact.js +7 -6
  83. package/dist/esm/utils/getPriceImpact.js.map +1 -1
  84. package/dist/esm/utils/percentFormatter.js.map +1 -0
  85. package/package.json +13 -14
  86. package/src/components/AlertMessage/AlertMessage.tsx +3 -3
  87. package/src/components/AmountInput/PriceFormHelperText.tsx +8 -4
  88. package/src/components/BaseTransactionButton/BaseTransactionButton.tsx +3 -3
  89. package/src/components/ButtonTertiary.tsx +4 -5
  90. package/src/components/Card/CardLabel.tsx +0 -1
  91. package/src/components/FeeBreakdownTooltip.tsx +3 -4
  92. package/src/components/GasMessage/FundsSufficiencyMessage.tsx +1 -1
  93. package/src/components/RouteCard/RouteCard.tsx +12 -2
  94. package/src/components/RouteCard/getMatchingLabels.ts +53 -0
  95. package/src/components/Step/Step.tsx +23 -12
  96. package/src/components/StepActions/StepActions.tsx +16 -13
  97. package/src/components/ToAddressRequiredMessage.tsx +1 -1
  98. package/src/components/Token/Token.tsx +7 -2
  99. package/src/components/TokenList/TokenListItem.tsx +9 -9
  100. package/src/components/TokenRate/TokenRate.tsx +5 -11
  101. package/src/components/TransactionDetails.tsx +4 -8
  102. package/src/config/version.ts +1 -1
  103. package/src/i18n/en.json +4 -2
  104. package/src/pages/SendToWallet/BookmarkAddressSheet.tsx +2 -3
  105. package/src/pages/SendToWallet/ConfirmAddressSheet.tsx +28 -6
  106. package/src/pages/SendToWallet/SendToWalletPage.tsx +8 -2
  107. package/src/pages/TransactionPage/ExchangeRateBottomSheet.tsx +2 -2
  108. package/src/providers/I18nProvider/I18nProvider.tsx +3 -1
  109. package/src/stores/routes/useExecutingRoutesIds.ts +2 -2
  110. package/src/themes/createTheme.ts +6 -8
  111. package/src/themes/types.ts +0 -1
  112. package/src/types/widget.ts +22 -0
  113. package/src/utils/compactNumberFormatter.test.ts +67 -0
  114. package/src/utils/compactNumberFormatter.ts +91 -0
  115. package/src/utils/converters.ts +6 -4
  116. package/src/utils/fees.ts +6 -4
  117. package/src/utils/format.ts +14 -60
  118. package/src/utils/getPriceImpact.ts +15 -6
  119. package/dist/esm/providers/I18nProvider/percentFormatter.js.map +0 -1
  120. /package/dist/esm/{providers/I18nProvider → utils}/percentFormatter.d.ts +0 -0
  121. /package/dist/esm/{providers/I18nProvider → utils}/percentFormatter.js +0 -0
  122. /package/src/{providers/I18nProvider → utils}/percentFormatter.ts +0 -0
@@ -13,7 +13,6 @@ import {
13
13
  import type { MouseEventHandler } from 'react'
14
14
  import { useRef, useState } from 'react'
15
15
  import { useTranslation } from 'react-i18next'
16
- import { formatUnits } from 'viem'
17
16
  import { useExplorer } from '../../hooks/useExplorer.js'
18
17
  import { formatTokenAmount, formatTokenPrice } from '../../utils/format.js'
19
18
  import { shortenAddress } from '../../utils/wallet.js'
@@ -66,12 +65,6 @@ export const TokenListItemButton: React.FC<TokenListItemButtonProps> = ({
66
65
  const { t } = useTranslation()
67
66
  const { getAddressLink } = useExplorer()
68
67
 
69
- const tokenPrice = token.amount
70
- ? formatTokenPrice(
71
- formatUnits(token.amount, token.decimals),
72
- token.priceUSD
73
- )
74
- : undefined
75
68
  const container = useRef(null)
76
69
  const timeoutId = useRef<ReturnType<typeof setTimeout>>(undefined)
77
70
  const [showAddress, setShowAddress] = useState(false)
@@ -93,6 +86,12 @@ export const TokenListItemButton: React.FC<TokenListItemButtonProps> = ({
93
86
  setShowAddress(false)
94
87
  }
95
88
  }
89
+ const tokenAmount = formatTokenAmount(token.amount, token.decimals)
90
+ const tokenPrice = formatTokenPrice(
91
+ token.amount,
92
+ token.priceUSD,
93
+ token.decimals
94
+ )
96
95
 
97
96
  return (
98
97
  <ListItemButton
@@ -186,9 +185,10 @@ export const TokenListItemButton: React.FC<TokenListItemButtonProps> = ({
186
185
  sx={{
187
186
  fontWeight: 600,
188
187
  }}
188
+ title={tokenAmount}
189
189
  >
190
- {t('format.number', {
191
- value: formatTokenAmount(token.amount, token.decimals),
190
+ {t('format.tokenAmount', {
191
+ value: tokenAmount,
192
192
  })}
193
193
  </Typography>
194
194
  ) : null}
@@ -1,12 +1,9 @@
1
1
  import type { RouteExtended } from '@lifi/sdk'
2
2
  import type { TypographyProps } from '@mui/material'
3
3
  import type { MouseEventHandler } from 'react'
4
+ import { useTranslation } from 'react-i18next'
4
5
  import { formatUnits } from 'viem'
5
6
  import { create } from 'zustand'
6
- import {
7
- convertToSubscriptFormat,
8
- precisionFormatter,
9
- } from '../../utils/format.js'
10
7
  import { TokenRateTypography } from './TokenRate.style.js'
11
8
 
12
9
  interface TokenRateProps extends TypographyProps {
@@ -24,6 +21,7 @@ const useTokenRate = create<TokenRateState>((set) => ({
24
21
  }))
25
22
 
26
23
  export const TokenRate: React.FC<TokenRateProps> = ({ route }) => {
24
+ const { t } = useTranslation()
27
25
  const { isForward, toggleIsForward } = useTokenRate()
28
26
 
29
27
  const toggleRate: MouseEventHandler<HTMLSpanElement> = (e) => {
@@ -54,16 +52,12 @@ export const TokenRate: React.FC<TokenRateProps> = ({ route }) => {
54
52
  Number.parseFloat(formatUnits(toToken.amount!, toToken.decimals))
55
53
 
56
54
  const rateText = isForward
57
- ? `1 ${fromToken.symbol} ≈ ${convertToSubscriptFormat(fromToRate)} ${toToken.symbol}`
58
- : `1 ${toToken.symbol} ≈ ${convertToSubscriptFormat(toFromRate)} ${fromToken.symbol}`
59
-
60
- const rateTitle = isForward
61
- ? `1 ${fromToken.symbol} ≈ ${precisionFormatter.format(fromToRate)} ${toToken.symbol}`
62
- : `1 ${toToken.symbol} ≈ ${precisionFormatter.format(toFromRate)} ${fromToken.symbol}`
55
+ ? `1 ${fromToken.symbol} ≈ ${t('format.tokenAmount', { value: fromToRate })} ${toToken.symbol}`
56
+ : `1 ${toToken.symbol} ≈ ${t('format.tokenAmount', { value: toFromRate })} ${fromToken.symbol}`
63
57
 
64
58
  return (
65
59
  // biome-ignore lint/a11y/useSemanticElements:
66
- <TokenRateTypography onClick={toggleRate} role="button" title={rateTitle}>
60
+ <TokenRateTypography onClick={toggleRate} role="button">
67
61
  {rateText}
68
62
  </TokenRateTypography>
69
63
  )
@@ -8,11 +8,10 @@ import type { CardProps } from '@mui/material'
8
8
  import { Box, Collapse, Tooltip, Typography } from '@mui/material'
9
9
  import { useState } from 'react'
10
10
  import { useTranslation } from 'react-i18next'
11
- import { formatUnits } from 'viem'
12
11
  import { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js'
13
12
  import { isRouteDone } from '../stores/routes/utils.js'
14
13
  import { getAccumulatedFeeCostsBreakdown } from '../utils/fees.js'
15
- import { formatTokenAmount } from '../utils/format.js'
14
+ import { formatTokenAmount, formatTokenPrice } from '../utils/format.js'
16
15
  import { getPriceImpact } from '../utils/getPriceImpact.js'
17
16
  import { Card } from './Card/Card.js'
18
17
  import { CardIconButton } from './Card/CardIconButton.js'
@@ -56,14 +55,11 @@ export const TransactionDetails: React.FC<TransactionDetailsProps> = ({
56
55
  BigInt(feeCollectionStep.estimate.fromAmount) -
57
56
  BigInt(feeCollectionStep.estimate.toAmount)
58
57
 
59
- const feeAmount = formatUnits(
58
+ feeAmountUSD = formatTokenPrice(
60
59
  estimatedFromAmount,
60
+ feeCollectionStep.action.fromToken.priceUSD,
61
61
  feeCollectionStep.action.fromToken.decimals
62
62
  )
63
-
64
- feeAmountUSD =
65
- Number.parseFloat(feeAmount) *
66
- Number.parseFloat(feeCollectionStep.action.fromToken.priceUSD)
67
63
  }
68
64
 
69
65
  const showIntegratorFeeCollectionDetails =
@@ -245,7 +241,7 @@ export const TransactionDetails: React.FC<TransactionDetailsProps> = ({
245
241
  sx={{ cursor: 'help' }}
246
242
  >
247
243
  <Typography variant="body2">
248
- {t('format.number', {
244
+ {t('format.tokenAmount', {
249
245
  value: formatTokenAmount(
250
246
  BigInt(route.toAmountMin),
251
247
  route.toToken.decimals
@@ -1,2 +1,2 @@
1
1
  export const name = '@lifi/widget'
2
- export const version = '3.13.2'
2
+ export const version = '3.14.0'
package/src/i18n/en.json CHANGED
@@ -6,7 +6,8 @@
6
6
  "format": {
7
7
  "currency": "{{value, currencyExt(currency: USD)}}",
8
8
  "number": "{{value, number(maximumFractionDigits: 9)}}",
9
- "percent": "{{value, percent(maximumFractionDigits: 2)}}"
9
+ "percent": "{{value, percent(maximumFractionDigits: 2)}}",
10
+ "tokenAmount": "{{value, numberExt}}"
10
11
  },
11
12
  "button": {
12
13
  "auto": "Auto",
@@ -88,7 +89,8 @@
88
89
  "emptyBridgesList": "We couldn't find any bridges that match your search",
89
90
  "emptyExchangesList": "We couldn't find any exchanges that match your search",
90
91
  "emptyTransactionHistory": "Transaction history is only stored locally and will be deleted if you clear your browser data.",
91
- "fundsToExchange": "Funds sent to an exchange may be lost",
92
+ "smartContractAccount": "Always ensure your smart contract account is set up on the destination chain to avoid potential fund loss.",
93
+ "fundsToExchange": "Funds sent directly to exchanges might not be recoverable.",
92
94
  "toAddressIsRequired": "Please provide the destination wallet address to which the funds will be transferred.",
93
95
  "routeNotFound": "Reasons for that could be: low liquidity, amount selected is too low, gas costs are too high or there are no routes for the selected combination."
94
96
  },
@@ -1,5 +1,4 @@
1
1
  import { Error as ErrorIcon, Info, TurnedIn } from '@mui/icons-material'
2
- import { LoadingButton } from '@mui/lab'
3
2
  import { Button, Typography } from '@mui/material'
4
3
  import type { ChangeEvent, MutableRefObject } from 'react'
5
4
  import { forwardRef, useState } from 'react'
@@ -217,7 +216,7 @@ export const BookmarkAddressSheet = forwardRef<
217
216
  <Button variant="text" onClick={handleCancel} fullWidth>
218
217
  {t('button.cancel')}
219
218
  </Button>
220
- <LoadingButton
219
+ <Button
221
220
  variant="contained"
222
221
  onClick={handleBookmark}
223
222
  loading={isValidating}
@@ -226,7 +225,7 @@ export const BookmarkAddressSheet = forwardRef<
226
225
  focusRipple
227
226
  >
228
227
  {t('button.bookmark')}
229
- </LoadingButton>
228
+ </Button>
230
229
  </SendToWalletButtonRow>
231
230
  </SendToWalletSheetContainer>
232
231
  </BottomSheet>
@@ -1,11 +1,12 @@
1
- import { Info, Wallet } from '@mui/icons-material'
2
- import { Button, Typography } from '@mui/material'
1
+ import { Info, Wallet, Warning } from '@mui/icons-material'
2
+ import { Box, Button, Typography } from '@mui/material'
3
3
  import type { MutableRefObject } from 'react'
4
4
  import { forwardRef } from 'react'
5
5
  import { useTranslation } from 'react-i18next'
6
6
  import { AlertMessage } from '../../components/AlertMessage/AlertMessage.js'
7
7
  import { BottomSheet } from '../../components/BottomSheet/BottomSheet.js'
8
8
  import type { BottomSheetBase } from '../../components/BottomSheet/types.js'
9
+ import { useIsContractAddress } from '../../hooks/useIsContractAddress.js'
9
10
  import { useNavigateBack } from '../../hooks/useNavigateBack.js'
10
11
  import type { Bookmark } from '../../stores/bookmarks/types.js'
11
12
  import { useFieldActions } from '../../stores/form/useFieldActions.js'
@@ -21,16 +22,22 @@ import {
21
22
  interface ConfirmAddressSheetProps {
22
23
  onConfirm: (wallet: Bookmark) => void
23
24
  validatedBookmark?: Bookmark
25
+ chainId?: number
24
26
  }
25
27
 
26
28
  export const ConfirmAddressSheet = forwardRef<
27
29
  BottomSheetBase,
28
30
  ConfirmAddressSheetProps
29
- >(({ validatedBookmark, onConfirm }, ref) => {
31
+ >(({ validatedBookmark, onConfirm, chainId }, ref) => {
30
32
  const { t } = useTranslation()
31
33
  const { navigateBack } = useNavigateBack()
32
34
  const { setFieldValue } = useFieldActions()
33
35
  const { setSendToWallet } = useSendToWalletActions()
36
+ const isContractAddress = useIsContractAddress(
37
+ validatedBookmark?.address,
38
+ chainId,
39
+ validatedBookmark?.chainType
40
+ )
34
41
 
35
42
  const handleClose = () => {
36
43
  ;(ref as MutableRefObject<BottomSheetBase>).current?.close()
@@ -71,12 +78,27 @@ export const ConfirmAddressSheet = forwardRef<
71
78
  </SheetAddressContainer>
72
79
  <AlertMessage
73
80
  title={
74
- <Typography variant="body2">
75
- {t('info.message.fundsToExchange')}
76
- </Typography>
81
+ <Box sx={{ display: 'grid', gap: 1 }}>
82
+ <Typography variant="body2" fontWeight={500}>
83
+ {t('info.message.fundsToExchange')}
84
+ </Typography>
85
+ </Box>
77
86
  }
78
87
  icon={<Info />}
88
+ multiline
79
89
  />
90
+ {isContractAddress ? (
91
+ <AlertMessage
92
+ title={
93
+ <Typography variant="body2" fontWeight={500}>
94
+ {t('info.message.smartContractAccount')}
95
+ </Typography>
96
+ }
97
+ icon={<Warning />}
98
+ severity="warning"
99
+ multiline
100
+ />
101
+ ) : null}
80
102
  <SendToWalletButtonRow>
81
103
  <Button variant="text" onClick={handleClose} fullWidth>
82
104
  {t('button.cancel')}
@@ -1,3 +1,4 @@
1
+ import { ChainType } from '@lifi/sdk'
1
2
  import { useAccount } from '@lifi/wallet-management'
2
3
  import {
3
4
  Error as ErrorIcon,
@@ -58,8 +59,6 @@ export const SendToWalletPage = () => {
58
59
  const [validatedWallet, setValidatedWallet] = useState<Bookmark>()
59
60
  const [errorMessage, setErrorMessage] = useState('')
60
61
  const { validateAddress, isValidating } = useAddressValidation()
61
- const { accounts } = useAccount()
62
- const connectedWallets = accounts.filter((account) => account.isConnected)
63
62
  const { requiredToChainType } = useToAddressRequirements()
64
63
  const [toChainId] = useFieldValues('toChain')
65
64
  const { chain: toChain } = useChain(toChainId)
@@ -67,6 +66,12 @@ export const SendToWalletPage = () => {
67
66
  const [isBookmarkButtonLoading, setIsBookmarkButtonLoading] = useState(false)
68
67
  const { variant } = useWidgetConfig()
69
68
 
69
+ const { accounts } = useAccount()
70
+ const connectedWallets = accounts.filter((account) => account.isConnected)
71
+ const connectedEVMChainId = connectedWallets.find(
72
+ (account) => account.chainType === ChainType.EVM
73
+ )?.chainId
74
+
70
75
  useHeader(t('header.sendToWallet'))
71
76
 
72
77
  const handleInputChange = (e: ChangeEvent) => {
@@ -241,6 +246,7 @@ export const SendToWalletPage = () => {
241
246
  ref={confirmAddressSheetRef}
242
247
  validatedBookmark={validatedWallet}
243
248
  onConfirm={handleOnConfirm}
249
+ chainId={connectedEVMChainId || toChainId}
244
250
  />
245
251
  <BookmarkAddressSheet
246
252
  ref={bookmarkAddressSheetRef}
@@ -146,7 +146,7 @@ const ExchangeRateBottomSheetContent: React.FC<
146
146
  fontWeight: 600,
147
147
  }}
148
148
  >
149
- {t('format.number', {
149
+ {t('format.tokenAmount', {
150
150
  value: formatTokenAmount(
151
151
  BigInt(data.oldToAmount),
152
152
  data.toToken.decimals
@@ -168,7 +168,7 @@ const ExchangeRateBottomSheetContent: React.FC<
168
168
  fontWeight: 600,
169
169
  }}
170
170
  >
171
- {t('format.number', {
171
+ {t('format.tokenAmount', {
172
172
  value: formatTokenAmount(
173
173
  BigInt(data?.newToAmount),
174
174
  data?.toToken.decimals
@@ -4,11 +4,12 @@ import { useMemo } from 'react'
4
4
  import { I18nextProvider } from 'react-i18next'
5
5
  import * as supportedLanguages from '../../i18n/index.js'
6
6
  import { useSettings } from '../../stores/settings/useSettings.js'
7
+ import { compactNumberFormatter } from '../../utils/compactNumberFormatter.js'
7
8
  import { deepMerge } from '../../utils/deepMerge.js'
8
9
  import { isItemAllowed } from '../../utils/item.js'
10
+ import { percentFormatter } from '../../utils/percentFormatter.js'
9
11
  import { useWidgetConfig } from '../WidgetProvider/WidgetProvider.js'
10
12
  import { currencyExtendedFormatter } from './currencyExtendedFormatter.js'
11
- import { percentFormatter } from './percentFormatter.js'
12
13
  import type { LanguageKey, LanguageTranslationResources } from './types.js'
13
14
 
14
15
  export const I18nProvider: React.FC<React.PropsWithChildren> = ({
@@ -63,6 +64,7 @@ export const I18nProvider: React.FC<React.PropsWithChildren> = ({
63
64
 
64
65
  i18n.init()
65
66
 
67
+ i18n.services.formatter?.addCached('numberExt', compactNumberFormatter)
66
68
  i18n.services.formatter?.addCached('currencyExt', currencyExtendedFormatter)
67
69
  i18n.services.formatter?.addCached('percent', percentFormatter)
68
70
 
@@ -18,8 +18,8 @@ export const useExecutingRoutesIds = () => {
18
18
  )
19
19
  .sort(
20
20
  (a, b) =>
21
- (b?.route.steps[0].execution?.process[0].startedAt ?? 0) -
22
- (a?.route.steps[0].execution?.process[0].startedAt ?? 0)
21
+ (b?.route.steps[0].execution?.process[0]?.startedAt ?? 0) -
22
+ (a?.route.steps[0].execution?.process[0]?.startedAt ?? 0)
23
23
  )
24
24
  .map(({ route }) => route.id),
25
25
  shallow
@@ -1,5 +1,3 @@
1
- import { loadingButtonClasses } from '@mui/lab'
2
- import type {} from '@mui/lab/themeAugmentation'
3
1
  import type {
4
2
  CSSObject,
5
3
  PaletteMode,
@@ -8,6 +6,7 @@ import type {
8
6
  } from '@mui/material'
9
7
  import {
10
8
  alpha,
9
+ buttonClasses,
11
10
  createTheme as createMuiTheme,
12
11
  css,
13
12
  darken,
@@ -247,19 +246,18 @@ export const createTheme = (
247
246
  cursor: 'not-allowed',
248
247
  pointerEvents: 'auto',
249
248
  },
250
- [`&.${loadingButtonClasses.loading}.Mui-disabled`]: {
249
+ [`&.${buttonClasses.loading}.Mui-disabled`]: {
251
250
  backgroundColor: primaryMainColor,
252
251
  color: contrastButtonColor,
253
252
  cursor: 'auto',
254
253
  pointerEvents: 'auto',
255
254
  },
256
- [`.${loadingButtonClasses.loadingIndicator}`]: {
255
+ [`.${buttonClasses.loadingIndicator}`]: {
257
256
  color: contrastButtonColor,
258
257
  },
259
- [`&.${loadingButtonClasses.root}.${loadingButtonClasses.loading}`]:
260
- {
261
- color: 'transparent',
262
- },
258
+ [`&.${buttonClasses.root}.${buttonClasses.loading}`]: {
259
+ color: 'transparent',
260
+ },
263
261
  ...getStyleOverrides('MuiButton', 'root', widgetTheme, ownerState),
264
262
  }),
265
263
  text: ({ ownerState }) => ({
@@ -1,4 +1,3 @@
1
- import type {} from '@mui/lab/themeAugmentation'
2
1
  import type {
3
2
  CardProps,
4
3
  ComponentsOverrides,
@@ -13,6 +13,7 @@ import type {
13
13
  PaletteMode,
14
14
  PaletteOptions,
15
15
  Shape,
16
+ SxProps,
16
17
  Theme,
17
18
  } from '@mui/material'
18
19
  import type { TypographyOptions } from '@mui/material/styles/createTypography.js'
@@ -191,6 +192,22 @@ export type WidgetLanguages = {
191
192
 
192
193
  export type PoweredByType = 'default' | 'jumper'
193
194
 
195
+ export interface RouteLabel {
196
+ text: string
197
+ sx?: SxProps<Theme>
198
+ }
199
+
200
+ export interface RouteLabelRule {
201
+ label: RouteLabel
202
+ // Matching criteria
203
+ bridges?: AllowDeny<string>
204
+ exchanges?: AllowDeny<string>
205
+ fromChainId?: number[]
206
+ toChainId?: number[]
207
+ fromTokenAddress?: string[]
208
+ toTokenAddress?: string[]
209
+ }
210
+
194
211
  export interface WidgetConfig {
195
212
  fromChain?: number
196
213
  toChain?: number
@@ -243,6 +260,11 @@ export interface WidgetConfig {
243
260
  explorerUrls?: Record<number, string[]> &
244
261
  Partial<Record<'internal', string[]>>
245
262
  poweredBy?: PoweredByType
263
+
264
+ /**
265
+ * Custom labels/badges to show on routes based on specified rules
266
+ */
267
+ routeLabels?: RouteLabelRule[]
246
268
  }
247
269
 
248
270
  export interface FormFieldOptions {
@@ -0,0 +1,67 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { compactNumberFormatter } from './compactNumberFormatter.js'
3
+
4
+ describe('Compact Number Formatter', () => {
5
+ describe('basic formatting', () => {
6
+ const testCases = [
7
+ { input: '0.123456789', expected: '0.123456' },
8
+ { input: '0.0123456', expected: '0.012345' },
9
+ { input: '0.00123456', expected: '0.001234' },
10
+ { input: '0.000123456', expected: '0.000123' },
11
+ { input: '0.06942', expected: '0.06942' },
12
+ { input: '0.006942', expected: '0.006942' },
13
+ { input: '0.0006942', expected: '0.000694' },
14
+ { input: '0.00006942', expected: '0.0₄6942' },
15
+ { input: '0.00000123456', expected: '0.0₅1234' },
16
+ { input: '0.000006942', expected: '0.0₅6942' },
17
+ { input: '0.00000000001', expected: '0.0₁₀1' },
18
+ { input: '0.00000006942', expected: '0.0₇6942' },
19
+ { input: '0.0000000694269', expected: '0.0₇6942' },
20
+ { input: '0.0000000694069', expected: '0.0₇694' },
21
+ { input: '0.000000006942', expected: '0.0₈6942' },
22
+ {
23
+ input: '0.000000000000006942',
24
+ expected: '0.0₁₄6942',
25
+ },
26
+ { input: '-0.0000042', expected: '-0.0₅42' },
27
+ { input: '-0.000000042', expected: '-0.0₇42' },
28
+ { input: '1.0000000042', expected: '1.0₈42' },
29
+ { input: '123456789.00042', expected: '123456789.00042' },
30
+ {
31
+ input: '123456789.000042',
32
+ expected: '123456789.0₄42',
33
+ },
34
+ {
35
+ input: '123456789.0000042',
36
+ expected: '123456789.0₅42',
37
+ },
38
+ { input: '1234567.000000042', expected: '1234567.0₇42' },
39
+ {
40
+ input: '1234567.000000042',
41
+ expected: '1,234,567.0₇42',
42
+ lng: 'en',
43
+ useGrouping: true,
44
+ },
45
+ {
46
+ input: '1234567.987654321',
47
+ expected: '1,234,567.987654',
48
+ lng: 'en',
49
+ useGrouping: true,
50
+ },
51
+ {
52
+ input: '1234567.000000042',
53
+ expected: '1.234.567,0₇42',
54
+ lng: 'de',
55
+ useGrouping: true,
56
+ },
57
+ ]
58
+
59
+ testCases.forEach(({ input, expected, lng, useGrouping = false }) => {
60
+ it(`should format ${input} correctly`, () => {
61
+ expect(compactNumberFormatter(lng, { useGrouping })(input)).toBe(
62
+ expected
63
+ )
64
+ })
65
+ })
66
+ })
67
+ })
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Converts a non-negative integer to its subscript representation using Unicode subscript characters.
3
+ * @param n - The number to convert to subscript
4
+ * @returns A string where each digit is converted to its subscript equivalent
5
+ * @example
6
+ * toSubscript(5) => '₅'
7
+ * toSubscript(10) => '₁₀'
8
+ */
9
+ function toSubscript(n: number): string {
10
+ return n
11
+ .toString()
12
+ .split('')
13
+ .map((digit) => String.fromCharCode(8320 + Number(digit)))
14
+ .join('')
15
+ }
16
+
17
+ /**
18
+ * Formats numbers with special handling for small decimal values, converting runs of leading zeros
19
+ * into a more compact subscript notation.
20
+ *
21
+ * @param value - The numeric value to format (expected to be a string representing a BigInt)
22
+ * @param lng - The locale string for number formatting
23
+ * @param options - Formatting options including decimals and Intl.NumberFormat options
24
+ * @returns A formatted string with subscript notation for leading zeros
25
+ */
26
+ export const compactNumberFormatter = (
27
+ lng: string | undefined,
28
+ options: any
29
+ ) => {
30
+ const formatter = new Intl.NumberFormat(lng, {
31
+ notation: 'standard',
32
+ roundingPriority: 'morePrecision',
33
+ maximumSignificantDigits: 21,
34
+ maximumFractionDigits: 21,
35
+ ...options,
36
+ })
37
+ return (value: any) => {
38
+ if (!Number.parseFloat(value) || Number.isNaN(Number(value))) {
39
+ return '0'
40
+ }
41
+
42
+ const parts = formatter.formatToParts(value as Intl.StringNumericLiteral)
43
+ let integerPart = ''
44
+ let fractionPart = ''
45
+ let decimalSeparator = ''
46
+ let minusSign = ''
47
+
48
+ for (const p of parts) {
49
+ switch (p.type) {
50
+ case 'integer':
51
+ case 'group':
52
+ integerPart += p.value
53
+ break
54
+ case 'fraction':
55
+ fractionPart += p.value
56
+ break
57
+ case 'decimal':
58
+ decimalSeparator = p.value
59
+ break
60
+ case 'minusSign':
61
+ minusSign = p.value
62
+ break
63
+ }
64
+ }
65
+
66
+ // For numbers with no decimal part, return early
67
+ if (!fractionPart) {
68
+ return `${minusSign}${integerPart}`
69
+ }
70
+
71
+ // Count consecutive leading zeros in the fraction part
72
+ const match = fractionPart.match(/^0+/)
73
+ const leadingZerosCount = match ? match[0].length : 0
74
+
75
+ // For numbers with few leading zeros (≤ 3), format normally
76
+ // but limit the total length and trim trailing zeros
77
+ if (leadingZerosCount <= 3) {
78
+ fractionPart = fractionPart.slice(0, 6).replace(/0+$/, '')
79
+ return `${minusSign}${integerPart}${decimalSeparator}${fractionPart}`
80
+ }
81
+
82
+ // For numbers with many leading zeros (> 3), use subscript notation
83
+ // Format: "0.0₍number_of_zeros₎significant_digits"
84
+ const zerosSubscript = toSubscript(leadingZerosCount)
85
+ const remainder = fractionPart
86
+ .slice(leadingZerosCount, leadingZerosCount + 4)
87
+ .replace(/0+$/, '')
88
+
89
+ return `${minusSign}${integerPart}${decimalSeparator}0${zerosSubscript}${remainder}`
90
+ }
91
+ }
@@ -8,8 +8,8 @@ import type {
8
8
  TokenAmount,
9
9
  ToolsResponse,
10
10
  } from '@lifi/sdk'
11
- import { formatUnits } from 'viem'
12
11
  import type { RouteExecution } from '../stores/routes/types.js'
12
+ import { formatTokenPrice } from './format.js'
13
13
 
14
14
  const buildProcessFromTxHistory = (tx: FullStatusData): Process[] => {
15
15
  const sending = tx.sending as ExtendedTransactionInfo
@@ -105,9 +105,11 @@ export const buildRouteFromTxHistory = (
105
105
  : sendingValue
106
106
  const sendingFeeAmountUsd =
107
107
  sending.gasToken.priceUSD && sendingFeeAmount
108
- ? Number.parseFloat(
109
- formatUnits(sendingFeeAmount, sending.gasToken.decimals)
110
- ) * Number.parseFloat(sending.gasToken.priceUSD)
108
+ ? formatTokenPrice(
109
+ sendingFeeAmount,
110
+ sending.gasToken.priceUSD,
111
+ sending.gasToken.decimals
112
+ )
111
113
  : 0
112
114
 
113
115
  const feeCosts: FeeCost[] | undefined = sendingFeeAmount
package/src/utils/fees.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { FeeCost, GasCost, RouteExtended, Token } from '@lifi/sdk'
2
- import { formatUnits } from 'viem'
2
+ import { formatTokenPrice } from './format.js'
3
3
 
4
4
  export interface FeesBreakdown {
5
5
  amount: bigint
@@ -109,9 +109,11 @@ export const getStepFeeCostsBreakdown = (
109
109
  const { amount, amountUSD } = feeCosts.reduce(
110
110
  (acc, feeCost) => {
111
111
  const feeAmount = BigInt(Number(feeCost.amount).toFixed(0) || 0)
112
- const amountUSD =
113
- Number.parseFloat(feeCost.token.priceUSD || '0') *
114
- Number.parseFloat(formatUnits(feeAmount, feeCost.token.decimals))
112
+ const amountUSD = formatTokenPrice(
113
+ feeAmount,
114
+ feeCost.token.priceUSD,
115
+ feeCost.token.decimals
116
+ )
115
117
 
116
118
  acc.amount += feeAmount
117
119
  acc.amountUSD += amountUSD