@lifi/widget 3.14.0-beta.0 → 3.14.1

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 (125) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/esm/components/ActiveTransactions/ActiveTransactionItem.js +0 -1
  3. package/dist/esm/components/ActiveTransactions/ActiveTransactionItem.js.map +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/PriceFormHelperText.js +7 -4
  8. package/dist/esm/components/AmountInput/PriceFormHelperText.js.map +1 -1
  9. package/dist/esm/components/FeeBreakdownTooltip.js +3 -4
  10. package/dist/esm/components/FeeBreakdownTooltip.js.map +1 -1
  11. package/dist/esm/components/GasMessage/FundsSufficiencyMessage.js +1 -1
  12. package/dist/esm/components/GasMessage/FundsSufficiencyMessage.js.map +1 -1
  13. package/dist/esm/components/RouteCard/RouteCard.js +4 -8
  14. package/dist/esm/components/RouteCard/RouteCard.js.map +1 -1
  15. package/dist/esm/components/Step/CircularProgress.js +1 -2
  16. package/dist/esm/components/Step/CircularProgress.js.map +1 -1
  17. package/dist/esm/components/Step/CircularProgress.style.js +2 -13
  18. package/dist/esm/components/Step/CircularProgress.style.js.map +1 -1
  19. package/dist/esm/components/Step/StepProcess.js +1 -2
  20. package/dist/esm/components/Step/StepProcess.js.map +1 -1
  21. package/dist/esm/components/Step/StepTimer.js +1 -2
  22. package/dist/esm/components/Step/StepTimer.js.map +1 -1
  23. package/dist/esm/components/StepActions/StepActions.js +8 -8
  24. package/dist/esm/components/StepActions/StepActions.js.map +1 -1
  25. package/dist/esm/components/ToAddressRequiredMessage.js +1 -1
  26. package/dist/esm/components/ToAddressRequiredMessage.js.map +1 -1
  27. package/dist/esm/components/Token/Token.js +3 -3
  28. package/dist/esm/components/Token/Token.js.map +1 -1
  29. package/dist/esm/components/TokenList/TokenListItem.js +4 -6
  30. package/dist/esm/components/TokenList/TokenListItem.js.map +1 -1
  31. package/dist/esm/components/TokenRate/TokenRate.js +5 -7
  32. package/dist/esm/components/TokenRate/TokenRate.js.map +1 -1
  33. package/dist/esm/components/TransactionDetails.js +3 -7
  34. package/dist/esm/components/TransactionDetails.js.map +1 -1
  35. package/dist/esm/config/version.d.ts +1 -1
  36. package/dist/esm/config/version.js +1 -1
  37. package/dist/esm/config/version.js.map +1 -1
  38. package/dist/esm/hooks/useProcessMessage.js +6 -18
  39. package/dist/esm/hooks/useProcessMessage.js.map +1 -1
  40. package/dist/esm/hooks/useRoutes.d.ts +2 -2
  41. package/dist/esm/hooks/useRoutes.js +58 -99
  42. package/dist/esm/hooks/useRoutes.js.map +1 -1
  43. package/dist/esm/hooks/useSetContentHeight.d.ts +1 -1
  44. package/dist/esm/hooks/useSetContentHeight.js +3 -2
  45. package/dist/esm/hooks/useSetContentHeight.js.map +1 -1
  46. package/dist/esm/i18n/en.json +14 -16
  47. package/dist/esm/index.d.ts +3 -0
  48. package/dist/esm/index.js +3 -0
  49. package/dist/esm/index.js.map +1 -1
  50. package/dist/esm/pages/SendToWallet/ConfirmAddressSheet.d.ts +1 -0
  51. package/dist/esm/pages/SendToWallet/ConfirmAddressSheet.js +22 -14
  52. package/dist/esm/pages/SendToWallet/ConfirmAddressSheet.js.map +1 -1
  53. package/dist/esm/pages/SendToWallet/SendToWalletPage.js +5 -3
  54. package/dist/esm/pages/SendToWallet/SendToWalletPage.js.map +1 -1
  55. package/dist/esm/pages/TransactionPage/ExchangeRateBottomSheet.js +2 -2
  56. package/dist/esm/pages/TransactionPage/ExchangeRateBottomSheet.js.map +1 -1
  57. package/dist/esm/providers/I18nProvider/I18nProvider.js +4 -2
  58. package/dist/esm/providers/I18nProvider/I18nProvider.js.map +1 -1
  59. package/dist/esm/providers/WalletProvider/SDKProviders.js +15 -2
  60. package/dist/esm/providers/WalletProvider/SDKProviders.js.map +1 -1
  61. package/dist/esm/providers/WalletProvider/getSafeMultisigConfig.d.ts +8 -0
  62. package/dist/esm/providers/WalletProvider/getSafeMultisigConfig.js +95 -0
  63. package/dist/esm/providers/WalletProvider/getSafeMultisigConfig.js.map +1 -0
  64. package/dist/esm/types/widget.d.ts +0 -1
  65. package/dist/esm/utils/compactNumberFormatter.d.ts +10 -0
  66. package/dist/esm/utils/compactNumberFormatter.js +81 -0
  67. package/dist/esm/utils/compactNumberFormatter.js.map +1 -0
  68. package/dist/esm/utils/compactNumberFormatter.test.d.ts +1 -0
  69. package/dist/esm/utils/compactNumberFormatter.test.js +64 -0
  70. package/dist/esm/utils/compactNumberFormatter.test.js.map +1 -0
  71. package/dist/esm/utils/converters.js +2 -2
  72. package/dist/esm/utils/converters.js.map +1 -1
  73. package/dist/esm/utils/currencyExtendedFormatter.js.map +1 -0
  74. package/dist/esm/utils/fees.js +2 -3
  75. package/dist/esm/utils/fees.js.map +1 -1
  76. package/dist/esm/utils/format.d.ts +1 -10
  77. package/dist/esm/utils/format.js +8 -47
  78. package/dist/esm/utils/format.js.map +1 -1
  79. package/dist/esm/utils/getPriceImpact.js +7 -6
  80. package/dist/esm/utils/getPriceImpact.js.map +1 -1
  81. package/dist/esm/utils/percentFormatter.js.map +1 -0
  82. package/package.json +8 -8
  83. package/src/components/ActiveTransactions/ActiveTransactionItem.tsx +0 -1
  84. package/src/components/AlertMessage/AlertMessage.tsx +3 -3
  85. package/src/components/AmountInput/PriceFormHelperText.tsx +8 -4
  86. package/src/components/FeeBreakdownTooltip.tsx +3 -4
  87. package/src/components/GasMessage/FundsSufficiencyMessage.tsx +1 -1
  88. package/src/components/RouteCard/RouteCard.tsx +6 -9
  89. package/src/components/Step/CircularProgress.style.tsx +2 -13
  90. package/src/components/Step/CircularProgress.tsx +1 -2
  91. package/src/components/Step/StepProcess.tsx +1 -2
  92. package/src/components/Step/StepTimer.tsx +1 -3
  93. package/src/components/StepActions/StepActions.tsx +16 -13
  94. package/src/components/ToAddressRequiredMessage.tsx +1 -1
  95. package/src/components/Token/Token.tsx +7 -2
  96. package/src/components/TokenList/TokenListItem.tsx +9 -9
  97. package/src/components/TokenRate/TokenRate.tsx +5 -11
  98. package/src/components/TransactionDetails.tsx +4 -8
  99. package/src/config/version.ts +1 -1
  100. package/src/hooks/useProcessMessage.ts +5 -24
  101. package/src/hooks/useRoutes.ts +66 -127
  102. package/src/hooks/useSetContentHeight.ts +4 -2
  103. package/src/i18n/en.json +14 -16
  104. package/src/index.ts +3 -0
  105. package/src/pages/SendToWallet/ConfirmAddressSheet.tsx +81 -40
  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 +4 -2
  109. package/src/providers/WalletProvider/SDKProviders.tsx +16 -2
  110. package/src/providers/WalletProvider/getSafeMultisigConfig.ts +144 -0
  111. package/src/types/widget.ts +0 -1
  112. package/src/utils/compactNumberFormatter.test.ts +67 -0
  113. package/src/utils/compactNumberFormatter.ts +91 -0
  114. package/src/utils/converters.ts +6 -4
  115. package/src/utils/fees.ts +6 -4
  116. package/src/utils/format.ts +14 -60
  117. package/src/utils/getPriceImpact.ts +15 -6
  118. package/dist/esm/providers/I18nProvider/currencyExtendedFormatter.js.map +0 -1
  119. package/dist/esm/providers/I18nProvider/percentFormatter.js.map +0 -1
  120. /package/dist/esm/{providers/I18nProvider → utils}/currencyExtendedFormatter.d.ts +0 -0
  121. /package/dist/esm/{providers/I18nProvider → utils}/currencyExtendedFormatter.js +0 -0
  122. /package/dist/esm/{providers/I18nProvider → utils}/percentFormatter.d.ts +0 -0
  123. /package/dist/esm/{providers/I18nProvider → utils}/percentFormatter.js +0 -0
  124. /package/src/{providers/I18nProvider → utils}/currencyExtendedFormatter.ts +0 -0
  125. /package/src/{providers/I18nProvider → utils}/percentFormatter.ts +0 -0
@@ -1,12 +1,14 @@
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
- import { forwardRef } from 'react'
4
+ import { forwardRef, useRef } 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'
11
+ import { useSetContentHeight } from '../../hooks/useSetContentHeight.js'
10
12
  import type { Bookmark } from '../../stores/bookmarks/types.js'
11
13
  import { useFieldActions } from '../../stores/form/useFieldActions.js'
12
14
  import { useSendToWalletActions } from '../../stores/settings/useSendToWalletStore.js'
@@ -21,20 +23,46 @@ import {
21
23
  interface ConfirmAddressSheetProps {
22
24
  onConfirm: (wallet: Bookmark) => void
23
25
  validatedBookmark?: Bookmark
26
+ chainId?: number
27
+ }
28
+
29
+ interface ConfirmAddressSheetContentProps extends ConfirmAddressSheetProps {
30
+ onClose: () => void
24
31
  }
25
32
 
26
33
  export const ConfirmAddressSheet = forwardRef<
27
34
  BottomSheetBase,
28
35
  ConfirmAddressSheetProps
29
- >(({ validatedBookmark, onConfirm }, ref) => {
36
+ >((props, ref) => {
37
+ const handleClose = () => {
38
+ ;(ref as MutableRefObject<BottomSheetBase>).current?.close()
39
+ }
40
+
41
+ return (
42
+ <BottomSheet ref={ref}>
43
+ <ConfirmAddressSheetContent {...props} onClose={handleClose} />
44
+ </BottomSheet>
45
+ )
46
+ })
47
+
48
+ const ConfirmAddressSheetContent: React.FC<ConfirmAddressSheetContentProps> = ({
49
+ validatedBookmark,
50
+ onConfirm,
51
+ chainId,
52
+ onClose,
53
+ }) => {
30
54
  const { t } = useTranslation()
31
55
  const { navigateBack } = useNavigateBack()
32
56
  const { setFieldValue } = useFieldActions()
33
57
  const { setSendToWallet } = useSendToWalletActions()
58
+ const isContractAddress = useIsContractAddress(
59
+ validatedBookmark?.address,
60
+ chainId,
61
+ validatedBookmark?.chainType
62
+ )
34
63
 
35
- const handleClose = () => {
36
- ;(ref as MutableRefObject<BottomSheetBase>).current?.close()
37
- }
64
+ const containerRef = useRef<HTMLElement>(null)
65
+ useSetContentHeight(containerRef, isContractAddress)
38
66
 
39
67
  const handleConfirm = () => {
40
68
  if (validatedBookmark) {
@@ -44,48 +72,61 @@ export const ConfirmAddressSheet = forwardRef<
44
72
  })
45
73
  onConfirm?.(validatedBookmark)
46
74
  setSendToWallet(true)
47
- handleClose()
75
+ onClose()
48
76
  navigateBack()
49
77
  }
50
78
  }
51
79
 
52
80
  return (
53
- <BottomSheet ref={ref}>
54
- <SendToWalletSheetContainer>
55
- <IconContainer>
56
- <Wallet sx={{ fontSize: 40 }} />
57
- </IconContainer>
58
- <SheetTitle>{t('sendToWallet.confirmWalletAddress')}</SheetTitle>
59
- <SheetAddressContainer>
60
- {validatedBookmark?.name ? (
61
- <Typography
62
- sx={{
63
- fontWeight: 600,
64
- mb: 0.5,
65
- }}
66
- >
67
- {validatedBookmark?.name}
81
+ <SendToWalletSheetContainer ref={containerRef}>
82
+ <IconContainer>
83
+ <Wallet sx={{ fontSize: 40 }} />
84
+ </IconContainer>
85
+ <SheetTitle>{t('sendToWallet.confirmWalletAddress')}</SheetTitle>
86
+ <SheetAddressContainer>
87
+ {validatedBookmark?.name ? (
88
+ <Typography
89
+ sx={{
90
+ fontWeight: 600,
91
+ mb: 0.5,
92
+ }}
93
+ >
94
+ {validatedBookmark?.name}
95
+ </Typography>
96
+ ) : null}
97
+ <Typography>{validatedBookmark?.address}</Typography>
98
+ </SheetAddressContainer>
99
+ <AlertMessage
100
+ title={
101
+ <Box sx={{ display: 'grid', gap: 1 }}>
102
+ <Typography variant="body2" fontWeight={500}>
103
+ {t('info.message.fundsToExchange')}
68
104
  </Typography>
69
- ) : null}
70
- <Typography>{validatedBookmark?.address}</Typography>
71
- </SheetAddressContainer>
105
+ </Box>
106
+ }
107
+ icon={<Info />}
108
+ multiline
109
+ />
110
+ {isContractAddress ? (
72
111
  <AlertMessage
73
112
  title={
74
- <Typography variant="body2">
75
- {t('info.message.fundsToExchange')}
113
+ <Typography variant="body2" fontWeight={500}>
114
+ {t('info.message.smartContractAccount')}
76
115
  </Typography>
77
116
  }
78
- icon={<Info />}
117
+ icon={<Warning />}
118
+ severity="warning"
119
+ multiline
79
120
  />
80
- <SendToWalletButtonRow>
81
- <Button variant="text" onClick={handleClose} fullWidth>
82
- {t('button.cancel')}
83
- </Button>
84
- <Button variant="contained" onClick={handleConfirm} fullWidth>
85
- {t('button.confirm')}
86
- </Button>
87
- </SendToWalletButtonRow>
88
- </SendToWalletSheetContainer>
89
- </BottomSheet>
121
+ ) : null}
122
+ <SendToWalletButtonRow>
123
+ <Button variant="text" onClick={onClose} fullWidth>
124
+ {t('button.cancel')}
125
+ </Button>
126
+ <Button variant="contained" onClick={handleConfirm} fullWidth>
127
+ {t('button.confirm')}
128
+ </Button>
129
+ </SendToWalletButtonRow>
130
+ </SendToWalletSheetContainer>
90
131
  )
91
- })
132
+ }
@@ -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'
8
+ import { currencyExtendedFormatter } from '../../utils/currencyExtendedFormatter.js'
7
9
  import { deepMerge } from '../../utils/deepMerge.js'
8
10
  import { isItemAllowed } from '../../utils/item.js'
11
+ import { percentFormatter } from '../../utils/percentFormatter.js'
9
12
  import { useWidgetConfig } from '../WidgetProvider/WidgetProvider.js'
10
- 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
 
@@ -5,18 +5,20 @@ import { ChainType, EVM, Solana, UTXO, config } from '@lifi/sdk'
5
5
  import type { SignerWalletAdapter } from '@solana/wallet-adapter-base'
6
6
  import { useWallet } from '@solana/wallet-adapter-react'
7
7
  import { useEffect } from 'react'
8
- import { useConfig as useWagmiConfig } from 'wagmi'
8
+ import { useAccount, useConfig as useWagmiConfig } from 'wagmi'
9
9
  import {
10
10
  getConnectorClient as getWagmiConnectorClient,
11
11
  switchChain,
12
12
  } from 'wagmi/actions'
13
13
  import { useWidgetConfig } from '../WidgetProvider/WidgetProvider.js'
14
+ import { getSafeMultisigConfig } from './getSafeMultisigConfig.js'
14
15
 
15
16
  export const SDKProviders = () => {
16
17
  const { sdkConfig } = useWidgetConfig()
17
18
  const { wallet } = useWallet()
18
19
  const wagmiConfig = useWagmiConfig()
19
20
  const bigmiConfig = useBigmiConfig()
21
+ const account = useAccount({ config: wagmiConfig })
20
22
 
21
23
  useEffect(() => {
22
24
  // Configure SDK Providers
@@ -31,6 +33,11 @@ export const SDKProviders = () => {
31
33
  (provider) => provider.type === ChainType.UTXO
32
34
  )
33
35
  if (!hasConfiguredEVMProvider) {
36
+ // TODO: refactor this in favor of EIP-5792: Wallet Call API
37
+ const multisig =
38
+ account.connector?.id === 'safe'
39
+ ? getSafeMultisigConfig(account.connector)
40
+ : undefined
34
41
  providers.push(
35
42
  EVM({
36
43
  getWalletClient: () => getWagmiConnectorClient(wagmiConfig),
@@ -38,6 +45,7 @@ export const SDKProviders = () => {
38
45
  const chain = await switchChain(wagmiConfig, { chainId })
39
46
  return getWagmiConnectorClient(wagmiConfig, { chainId: chain.id })
40
47
  },
48
+ multisig,
41
49
  })
42
50
  )
43
51
  }
@@ -61,7 +69,13 @@ export const SDKProviders = () => {
61
69
  providers.push(...sdkConfig.providers)
62
70
  }
63
71
  config.setProviders(providers)
64
- }, [bigmiConfig, sdkConfig?.providers, wagmiConfig, wallet?.adapter])
72
+ }, [
73
+ account.connector,
74
+ bigmiConfig,
75
+ sdkConfig?.providers,
76
+ wagmiConfig,
77
+ wallet?.adapter,
78
+ ])
65
79
 
66
80
  return null
67
81
  }
@@ -0,0 +1,144 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import type { MultisigTransaction, MultisigTxDetails } from '@lifi/sdk'
3
+ import type { Connector } from 'wagmi'
4
+
5
+ enum TransactionStatus {
6
+ AWAITING_CONFIRMATIONS = 'AWAITING_CONFIRMATIONS',
7
+ AWAITING_EXECUTION = 'AWAITING_EXECUTION',
8
+ CANCELLED = 'CANCELLED',
9
+ FAILED = 'FAILED',
10
+ SUCCESS = 'SUCCESS',
11
+ }
12
+
13
+ type GatewayTransactionDetails = {
14
+ safeAddress: string
15
+ txId: string
16
+ executedAt?: number
17
+ txStatus: TransactionStatus
18
+ txHash?: string
19
+ }
20
+
21
+ export const getSafeMultisigConfig = (connector: Connector) => {
22
+ const getMultisigTransactionDetails = async (
23
+ txHash: string,
24
+ chainId: number,
25
+ updateIntermediateStatus?: () => void
26
+ ): Promise<MultisigTxDetails> => {
27
+ const safeAppProvider = (await connector.getProvider()) as any
28
+ const safeProviderSDK = safeAppProvider.sdk
29
+
30
+ const safeTransactionDetails: GatewayTransactionDetails =
31
+ await safeProviderSDK.txs.getBySafeTxHash(txHash)
32
+
33
+ const safeTxHash = safeTransactionDetails.txId
34
+
35
+ const safeApiTransactionResponse = await fetch(
36
+ `https://safe-client.safe.global/v1/chains/${chainId}/transactions/${safeTxHash}`
37
+ )
38
+
39
+ const safeApiTransactionDetails = await safeApiTransactionResponse.json()
40
+
41
+ const nonTerminalStatus = [
42
+ TransactionStatus.SUCCESS,
43
+ TransactionStatus.CANCELLED,
44
+ TransactionStatus.FAILED,
45
+ ]
46
+
47
+ const isSafeStatusPending =
48
+ !nonTerminalStatus.includes(safeTransactionDetails.txStatus) &&
49
+ !nonTerminalStatus.includes(safeApiTransactionDetails.txStatus)
50
+
51
+ const isAwaitingExecution = [
52
+ safeTransactionDetails.txStatus,
53
+ safeApiTransactionDetails.txStatus,
54
+ ].includes(TransactionStatus.AWAITING_EXECUTION)
55
+
56
+ if (isAwaitingExecution) {
57
+ updateIntermediateStatus?.()
58
+ }
59
+
60
+ if (isSafeStatusPending) {
61
+ await new Promise((resolve) => {
62
+ setTimeout(resolve, 5000)
63
+ })
64
+
65
+ return await getMultisigTransactionDetails(
66
+ txHash,
67
+ chainId,
68
+ updateIntermediateStatus
69
+ )
70
+ }
71
+
72
+ if (
73
+ [
74
+ safeTransactionDetails.txStatus,
75
+ safeApiTransactionDetails.txStatus,
76
+ ].includes(TransactionStatus.SUCCESS)
77
+ ) {
78
+ return {
79
+ status: 'DONE',
80
+ txHash: `0x${safeTransactionDetails.txHash?.slice(2)}`,
81
+ }
82
+ }
83
+
84
+ if (
85
+ [
86
+ safeTransactionDetails.txStatus,
87
+ safeApiTransactionDetails.txStatus,
88
+ ].includes(TransactionStatus.FAILED)
89
+ ) {
90
+ return {
91
+ status: 'FAILED',
92
+ txHash: `0x${safeTransactionDetails.txHash?.slice(2)}`,
93
+ }
94
+ }
95
+
96
+ if (
97
+ [
98
+ safeTransactionDetails.txStatus,
99
+ safeApiTransactionDetails.txStatus,
100
+ ].includes(TransactionStatus.CANCELLED)
101
+ ) {
102
+ return {
103
+ status: 'CANCELLED',
104
+ txHash: `0x${safeTransactionDetails.txHash?.slice(2)}`,
105
+ }
106
+ }
107
+
108
+ if (isSafeStatusPending) {
109
+ return {
110
+ status: 'PENDING',
111
+ txHash: `0x${safeTransactionDetails.txHash?.slice(2)}`,
112
+ }
113
+ }
114
+
115
+ return {
116
+ status: 'PENDING',
117
+ txHash: `0x${safeTransactionDetails.txHash?.slice(2)}`,
118
+ }
119
+ }
120
+
121
+ const sendBatchTransaction = async (
122
+ batchTransactions: MultisigTransaction[]
123
+ ): Promise<`0x${string}`> => {
124
+ const safeAppProvider = (await connector.getProvider()) as any
125
+ const safeProviderSDK = safeAppProvider.sdk
126
+
127
+ try {
128
+ const { safeTxHash } = await safeProviderSDK.txs.send({
129
+ txs: batchTransactions,
130
+ })
131
+
132
+ return `0x${safeTxHash.slice(2)}`
133
+ } catch (error) {
134
+ throw new Error(error as string)
135
+ }
136
+ }
137
+
138
+ return {
139
+ isMultisigWalletClient: connector?.id === 'safe',
140
+ shouldBatchTransactions: connector?.id === 'safe',
141
+ sendBatchTransaction,
142
+ getMultisigTransactionDetails,
143
+ }
144
+ }
@@ -244,7 +244,6 @@ export interface WidgetConfig {
244
244
  hiddenUI?: HiddenUIType[]
245
245
  requiredUI?: RequiredUIType[]
246
246
  useRecommendedRoute?: boolean
247
- useRelayerRoutes?: boolean
248
247
 
249
248
  walletConfig?: WidgetWalletConfig
250
249
  sdkConfig?: WidgetSDKConfig
@@ -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