@lifi/widget 4.10.0 → 4.10.2

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 (91) hide show
  1. package/README.md +2 -7
  2. package/dist/esm/components/ActionRow/ActionRow.d.ts +1 -0
  3. package/dist/esm/components/ExecutionStatusCard/ExecutionStatusCard.d.ts +1 -0
  4. package/dist/esm/components/ExecutionStatusCard/StatusIcon.d.ts +1 -0
  5. package/dist/esm/components/FeeBreakdownTooltip.d.ts +1 -0
  6. package/dist/esm/components/IconCircle/IconCircle.d.ts +1 -0
  7. package/dist/esm/components/Messages/WarningMessages.d.ts +1 -0
  8. package/dist/esm/components/RouteCard/RouteDetails.d.ts +1 -0
  9. package/dist/esm/components/Search/SearchInput.d.ts +1 -0
  10. package/dist/esm/components/StepActions/SentToWalletRow.d.ts +1 -0
  11. package/dist/esm/components/StepActions/StepActionsList.d.ts +1 -0
  12. package/dist/esm/components/TokenRate/TokenRate.d.ts +1 -0
  13. package/dist/esm/config/version.d.ts +1 -1
  14. package/dist/esm/config/version.js +1 -1
  15. package/dist/esm/config/version.js.map +1 -1
  16. package/dist/esm/hooks/useAddressActivity.d.ts +1 -0
  17. package/dist/esm/hooks/useAddressValidation.d.ts +1 -0
  18. package/dist/esm/hooks/useAvailableChains.d.ts +1 -0
  19. package/dist/esm/hooks/useExplorer.d.ts +1 -0
  20. package/dist/esm/hooks/useListHeight.d.ts +1 -0
  21. package/dist/esm/hooks/usePacedValue.js +1 -1
  22. package/dist/esm/hooks/usePacedValue.js.map +1 -1
  23. package/dist/esm/hooks/useRouteExecution.d.ts +1 -0
  24. package/dist/esm/hooks/useRouteExecutionMessage.d.ts.map +1 -1
  25. package/dist/esm/hooks/useRouteExecutionMessage.js +4 -8
  26. package/dist/esm/hooks/useRouteExecutionMessage.js.map +1 -1
  27. package/dist/esm/hooks/useRoutes.d.ts +1 -0
  28. package/dist/esm/i18n/bn.json +567 -270
  29. package/dist/esm/i18n/de.json +552 -255
  30. package/dist/esm/i18n/en.json +7 -61
  31. package/dist/esm/i18n/es.json +522 -225
  32. package/dist/esm/i18n/fr.json +541 -244
  33. package/dist/esm/i18n/hi.json +592 -295
  34. package/dist/esm/i18n/id.json +485 -188
  35. package/dist/esm/i18n/it.json +546 -249
  36. package/dist/esm/i18n/ja.json +512 -215
  37. package/dist/esm/i18n/ko.json +553 -256
  38. package/dist/esm/i18n/pl.json +598 -301
  39. package/dist/esm/i18n/pt.json +512 -215
  40. package/dist/esm/i18n/th.json +554 -257
  41. package/dist/esm/i18n/tr.json +538 -241
  42. package/dist/esm/i18n/uk.json +514 -217
  43. package/dist/esm/i18n/vi.json +550 -253
  44. package/dist/esm/i18n/zh.json +528 -231
  45. package/dist/esm/pages/MainPage/MainWarningMessages.d.ts +1 -0
  46. package/dist/esm/pages/SelectTokenPage/SearchTokenInput.d.ts +1 -0
  47. package/dist/esm/pages/TransactionDetailsPage/ContactSupportButton.d.ts +1 -0
  48. package/dist/esm/pages/TransactionPage/ConfirmToAddressSheet.d.ts +1 -0
  49. package/dist/esm/pages/TransactionPage/ExchangeRateBottomSheet.d.ts +1 -0
  50. package/dist/esm/pages/TransactionPage/ExecutionDoneCard.d.ts +1 -0
  51. package/dist/esm/pages/TransactionPage/RouteTracker.d.ts +1 -0
  52. package/dist/esm/pages/TransactionPage/TokenValueBottomSheet.d.ts +1 -0
  53. package/dist/esm/pages/TransactionPage/TransactionDoneButtons.d.ts +1 -0
  54. package/dist/esm/pages/TransactionPage/TransactionFailedButtons.js +70 -18
  55. package/dist/esm/pages/TransactionPage/TransactionFailedButtons.js.map +1 -1
  56. package/dist/esm/pages/TransactionPage/utils.d.ts +3 -0
  57. package/dist/esm/pages/TransactionPage/utils.d.ts.map +1 -1
  58. package/dist/esm/pages/TransactionPage/utils.js +27 -1
  59. package/dist/esm/pages/TransactionPage/utils.js.map +1 -1
  60. package/dist/esm/providers/I18nProvider/I18nProvider.js +2 -2
  61. package/dist/esm/providers/I18nProvider/I18nProvider.js.map +1 -1
  62. package/dist/esm/providers/I18nProvider/enResource.d.ts +2 -2
  63. package/dist/esm/providers/I18nProvider/enResource.js +1 -2
  64. package/dist/esm/providers/I18nProvider/types.d.ts +2 -2
  65. package/dist/esm/providers/I18nProvider/types.d.ts.map +1 -1
  66. package/dist/esm/providers/WalletProvider/WalletProvider.d.ts +1 -0
  67. package/dist/esm/shared.d.ts +2 -2
  68. package/dist/esm/shared.js +2 -2
  69. package/dist/esm/stores/chains/types.d.ts +1 -0
  70. package/dist/esm/stores/form/types.d.ts +1 -0
  71. package/dist/esm/stores/header/types.d.ts +1 -0
  72. package/dist/esm/stores/inputMode/useInputModeStore.d.ts +1 -0
  73. package/dist/esm/stores/routes/utils.d.ts +1 -1
  74. package/dist/esm/stores/routes/utils.d.ts.map +1 -1
  75. package/dist/esm/stores/routes/utils.js +13 -1
  76. package/dist/esm/stores/routes/utils.js.map +1 -1
  77. package/dist/esm/utils/getErrorMessage.js +4 -0
  78. package/dist/esm/utils/getErrorMessage.js.map +1 -1
  79. package/dist/esm/utils/getPriceImpact.d.ts +1 -0
  80. package/dist/esm/utils/navigationRoutes.d.ts +1 -0
  81. package/dist/esm/utils/scopeKeys.d.ts +1 -0
  82. package/package.json +11 -11
  83. package/src/config/version.ts +1 -1
  84. package/src/hooks/usePacedValue.ts +1 -1
  85. package/src/hooks/useRouteExecutionMessage.ts +10 -13
  86. package/src/pages/TransactionPage/TransactionFailedButtons.test.tsx +497 -0
  87. package/src/pages/TransactionPage/TransactionFailedButtons.tsx +78 -9
  88. package/src/pages/TransactionPage/utils.ts +81 -0
  89. package/src/shared.ts +1 -0
  90. package/src/stores/routes/utils.ts +24 -1
  91. package/src/utils/getErrorMessage.ts +5 -0
@@ -1,22 +1,30 @@
1
1
  import type { RouteExtended } from '@lifi/sdk'
2
2
  import { Box, Button } from '@mui/material'
3
+ import { useNavigate } from '@tanstack/react-router'
3
4
  import { useRef } from 'react'
4
5
  import { useTranslation } from 'react-i18next'
5
6
  import type { BottomSheetBase } from '../../components/BottomSheet/types.js'
6
7
  import { useAddressActivity } from '../../hooks/useAddressActivity.js'
7
8
  import { useNavigateBack } from '../../hooks/useNavigateBack.js'
9
+ import { useSwapOnly } from '../../hooks/useSwapOnly.js'
8
10
  import { useWidgetEvents } from '../../hooks/useWidgetEvents.js'
9
11
  import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js'
12
+ import { useBookmarkActions } from '../../stores/bookmarks/useBookmarkActions.js'
13
+ import { useFieldActions } from '../../stores/form/useFieldActions.js'
10
14
  import { WidgetEvent } from '../../types/events.js'
11
15
  import { getAccumulatedFeeCostsBreakdown } from '../../utils/fees.js'
16
+ import { navigationRoutes } from '../../utils/navigationRoutes.js'
12
17
  import { ConfirmToAddressSheet } from './ConfirmToAddressSheet.js'
13
18
  import { StartTransactionButton } from './StartTransactionButton.js'
14
19
  import { TokenValueBottomSheet } from './TokenValueBottomSheet.js'
15
20
  import type { RetryGate } from './utils.js'
16
21
  import {
17
22
  calculateValueLossPercentage,
23
+ canStartNewSwap,
24
+ getNewSwapFormValues,
18
25
  getRetryGates,
19
26
  getTokenValueLossThreshold,
27
+ isCallBundleNotFound,
20
28
  openNextGate,
21
29
  } from './utils.js'
22
30
 
@@ -32,7 +40,11 @@ export const TransactionFailedButtons: React.FC<
32
40
  const { t } = useTranslation()
33
41
  const emitter = useWidgetEvents()
34
42
  const navigateBack = useNavigateBack()
35
- const { mode, hiddenUI } = useWidgetConfig()
43
+ const navigate = useNavigate()
44
+ const { mode, hiddenUI, disabledUI, requiredUI } = useWidgetConfig()
45
+ const swapOnly = useSwapOnly()
46
+ const { setFieldValue, getFieldValues } = useFieldActions()
47
+ const { setSelectedBookmark, getSelectedBookmark } = useBookmarkActions()
36
48
 
37
49
  const tokenValueBottomSheetRef = useRef<BottomSheetBase>(null)
38
50
  const confirmToAddressSheetRef = useRef<BottomSheetBase>(null)
@@ -49,6 +61,49 @@ export const TransactionFailedButtons: React.FC<
49
61
  deleteRoute()
50
62
  }
51
63
 
64
+ const callBundleNotFound = isCallBundleNotFound(route)
65
+ const showStartNewSwap =
66
+ callBundleNotFound && canStartNewSwap({ route, mode, swapOnly })
67
+ // Try again would only wait for the same bundle again.
68
+ const showRetry = !callBundleNotFound
69
+
70
+ // Home, not back: after a reload, the route is opened from Activities.
71
+ const handleStartNewSwap = () => {
72
+ // An empty hidden or locked field still needs the route's receiver.
73
+ const [formReceiver] = getFieldValues('toAddress')
74
+ const keepReceiver = Boolean(
75
+ (disabledUI?.toAddress || hiddenUI?.toAddress) && formReceiver
76
+ )
77
+ const values = getNewSwapFormValues(route, {
78
+ keepReceiver,
79
+ receiverRequired: requiredUI?.toAddress,
80
+ })
81
+ const fieldNames = Object.keys(values) as (keyof typeof values)[]
82
+ const receiver = keepReceiver ? formReceiver : values.toAddress
83
+ // Fill after navigate resolves: this page's unmount cleanup has run by then,
84
+ // as TransactionFailedButtons.test.tsx checks.
85
+ navigate({ to: navigationRoutes.home, replace: true })
86
+ .then(() => {
87
+ for (const fieldName of fieldNames) {
88
+ setFieldValue(fieldName, values[fieldName], {
89
+ isDirty: true,
90
+ isTouched: true,
91
+ })
92
+ }
93
+ // A bookmark name from an earlier receiver must not label this one.
94
+ if (
95
+ getSelectedBookmark()?.address.toLowerCase() !==
96
+ receiver?.toLowerCase()
97
+ ) {
98
+ setSelectedBookmark()
99
+ }
100
+ })
101
+ .catch((error) => {
102
+ console.warn('Filling the new swap failed.', error)
103
+ })
104
+ deleteRoute()
105
+ }
106
+
52
107
  const retryGates = (() => {
53
108
  const { gasCostUSD, feeCostUSD } = getAccumulatedFeeCostsBreakdown(route)
54
109
  return getRetryGates({
@@ -112,14 +167,28 @@ export const TransactionFailedButtons: React.FC<
112
167
  {t('button.delete')}
113
168
  </Button>
114
169
  </Box>
115
- <Box sx={{ flex: 1 }}>
116
- <StartTransactionButton
117
- text={t('button.tryAgain')}
118
- onClick={handleRetryClick}
119
- route={route}
120
- loading={isLoadingAddressActivity}
121
- />
122
- </Box>
170
+ {showStartNewSwap ? (
171
+ <Box sx={{ flex: 1 }}>
172
+ <Button
173
+ variant="contained"
174
+ color="primary"
175
+ onClick={handleStartNewSwap}
176
+ fullWidth
177
+ >
178
+ {t('button.startNewSwap')}
179
+ </Button>
180
+ </Box>
181
+ ) : null}
182
+ {showRetry ? (
183
+ <Box sx={{ flex: 1 }}>
184
+ <StartTransactionButton
185
+ text={t('button.tryAgain')}
186
+ onClick={handleRetryClick}
187
+ route={route}
188
+ loading={isLoadingAddressActivity}
189
+ />
190
+ </Box>
191
+ ) : null}
123
192
  </Box>
124
193
  {mode !== 'custom' ? (
125
194
  <TokenValueBottomSheet
@@ -1,3 +1,8 @@
1
+ import { LiFiErrorCode, type RouteExtended } from '@lifi/sdk'
2
+ import { getFailedStepAction } from '../../stores/routes/utils.js'
3
+ import type { WidgetMode } from '../../types/widget.js'
4
+ import { formatTokenAmount } from '../../utils/format.js'
5
+
1
6
  export const calculateValueLossPercentage = (
2
7
  fromAmountUSD: number,
3
8
  toAmountUSD: number,
@@ -101,3 +106,79 @@ export const getStartGates = (
101
106
  ['address', needsAddressConfirmation(input)],
102
107
  ['value', input.valueLossExceeded && !input.isCustomMode],
103
108
  ]
109
+
110
+ // Only these modes show the plain from/to swap form.
111
+ const newSwapModes: readonly (WidgetMode | undefined)[] = [
112
+ undefined,
113
+ 'default',
114
+ 'split',
115
+ ]
116
+
117
+ export const isCallBundleNotFound = (route: RouteExtended): boolean =>
118
+ getFailedStepAction(route)?.action.error?.code ===
119
+ LiFiErrorCode.CallBundleNotFound
120
+
121
+ // A new swap repeats the whole route.
122
+ export const canStartNewSwap = ({
123
+ route,
124
+ mode,
125
+ swapOnly,
126
+ }: {
127
+ route: RouteExtended
128
+ mode: WidgetMode | undefined
129
+ swapOnly: boolean
130
+ }): boolean => {
131
+ const failed = getFailedStepAction(route)
132
+ if (!failed || !isCallBundleNotFound(route)) {
133
+ return false
134
+ }
135
+ return (
136
+ failed.step === route.steps[0] &&
137
+ route.steps.every(
138
+ (step) => step === failed.step || !step.execution?.actions?.length
139
+ ) &&
140
+ newSwapModes.includes(mode) &&
141
+ // A swap-only form requests no bridges.
142
+ !(swapOnly && route.fromChainId !== route.toChainId)
143
+ )
144
+ }
145
+
146
+ interface NewSwapFormValues {
147
+ fromChain: number
148
+ fromToken: string
149
+ fromAmount: string
150
+ toChain: number
151
+ toToken: string
152
+ toAddress?: string
153
+ }
154
+
155
+ export const getNewSwapFormValues = (
156
+ route: RouteExtended,
157
+ {
158
+ keepReceiver = false,
159
+ receiverRequired = false,
160
+ }: { keepReceiver?: boolean; receiverRequired?: boolean } = {}
161
+ ): NewSwapFormValues => {
162
+ const receiver = route.toAddress || route.fromAddress
163
+ const values: NewSwapFormValues = {
164
+ fromChain: route.fromChainId,
165
+ fromToken: route.fromToken.address,
166
+ // The quote's amount, not a step amount trimmed to the balance: the new quote checks it.
167
+ fromAmount: formatTokenAmount(
168
+ BigInt(route.fromAmount),
169
+ route.fromToken.decimals
170
+ ),
171
+ toChain: route.toChainId,
172
+ toToken: route.toToken.address,
173
+ }
174
+ if (!keepReceiver) {
175
+ // A required receiver stays, also when it is the sender.
176
+ values.toAddress =
177
+ receiver &&
178
+ (receiverRequired ||
179
+ receiver.toLowerCase() !== route.fromAddress?.toLowerCase())
180
+ ? receiver
181
+ : ''
182
+ }
183
+ return values
184
+ }
package/src/shared.ts CHANGED
@@ -114,6 +114,7 @@ export { TransactionDoneButtons } from './pages/TransactionPage/TransactionDoneB
114
114
  export {
115
115
  calculateValueLossPercentage,
116
116
  getTokenValueLossThreshold,
117
+ isCallBundleNotFound,
117
118
  } from './pages/TransactionPage/utils.js'
118
119
 
119
120
  // ── providers ────────────────────────────────────────────────────────────────
@@ -1,4 +1,9 @@
1
- import type { ExecutionAction, RouteExtended } from '@lifi/sdk'
1
+ import type {
2
+ ExecutionAction,
3
+ ExecutionActionType,
4
+ LiFiStepExtended,
5
+ RouteExtended,
6
+ } from '@lifi/sdk'
2
7
  import microdiff from 'microdiff'
3
8
 
4
9
  export const isRouteDone = (route: RouteExtended): boolean => {
@@ -21,6 +26,24 @@ export const isRouteFailed = (route: RouteExtended): boolean => {
21
26
  return route.steps.some((step) => step.execution?.status === 'FAILED')
22
27
  }
23
28
 
29
+ // A step can fail before any action, so its own error then stands in.
30
+ export const getFailedStepAction = (
31
+ route: RouteExtended
32
+ ): { step: LiFiStepExtended; action: ExecutionAction } | undefined => {
33
+ const step = route.steps.find((step) => step.execution?.status === 'FAILED')
34
+ if (!step) {
35
+ return undefined
36
+ }
37
+ const action = step.execution?.actions?.find(
38
+ (action) => action.status === 'FAILED'
39
+ ) || {
40
+ status: 'FAILED',
41
+ type: 'EXECUTION' as ExecutionActionType,
42
+ error: step.execution?.error,
43
+ }
44
+ return { step, action }
45
+ }
46
+
24
47
  export const isRouteActive = (route?: RouteExtended): boolean => {
25
48
  if (!route) {
26
49
  return false
@@ -130,6 +130,11 @@ export function getErrorMessage(
130
130
  chainName: getChainById(step.action.fromChainId)?.name ?? '',
131
131
  })
132
132
  break
133
+ // No "remain in your wallet" suffix: the funds may already be gone.
134
+ case LiFiErrorCode.CallBundleNotFound:
135
+ title = t('error.title.callBundleNotFound')
136
+ message = t('error.message.callBundleNotFound')
137
+ break
133
138
  default:
134
139
  title = t('error.title.unknown')
135
140
  if (failedAction?.txHash) {