@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.
- package/README.md +2 -7
- package/dist/esm/components/ActionRow/ActionRow.d.ts +1 -0
- package/dist/esm/components/ExecutionStatusCard/ExecutionStatusCard.d.ts +1 -0
- package/dist/esm/components/ExecutionStatusCard/StatusIcon.d.ts +1 -0
- package/dist/esm/components/FeeBreakdownTooltip.d.ts +1 -0
- package/dist/esm/components/IconCircle/IconCircle.d.ts +1 -0
- package/dist/esm/components/Messages/WarningMessages.d.ts +1 -0
- package/dist/esm/components/RouteCard/RouteDetails.d.ts +1 -0
- package/dist/esm/components/Search/SearchInput.d.ts +1 -0
- package/dist/esm/components/StepActions/SentToWalletRow.d.ts +1 -0
- package/dist/esm/components/StepActions/StepActionsList.d.ts +1 -0
- package/dist/esm/components/TokenRate/TokenRate.d.ts +1 -0
- package/dist/esm/config/version.d.ts +1 -1
- package/dist/esm/config/version.js +1 -1
- package/dist/esm/config/version.js.map +1 -1
- package/dist/esm/hooks/useAddressActivity.d.ts +1 -0
- package/dist/esm/hooks/useAddressValidation.d.ts +1 -0
- package/dist/esm/hooks/useAvailableChains.d.ts +1 -0
- package/dist/esm/hooks/useExplorer.d.ts +1 -0
- package/dist/esm/hooks/useListHeight.d.ts +1 -0
- package/dist/esm/hooks/usePacedValue.js +1 -1
- package/dist/esm/hooks/usePacedValue.js.map +1 -1
- package/dist/esm/hooks/useRouteExecution.d.ts +1 -0
- package/dist/esm/hooks/useRouteExecutionMessage.d.ts.map +1 -1
- package/dist/esm/hooks/useRouteExecutionMessage.js +4 -8
- package/dist/esm/hooks/useRouteExecutionMessage.js.map +1 -1
- package/dist/esm/hooks/useRoutes.d.ts +1 -0
- package/dist/esm/i18n/bn.json +567 -270
- package/dist/esm/i18n/de.json +552 -255
- package/dist/esm/i18n/en.json +7 -61
- package/dist/esm/i18n/es.json +522 -225
- package/dist/esm/i18n/fr.json +541 -244
- package/dist/esm/i18n/hi.json +592 -295
- package/dist/esm/i18n/id.json +485 -188
- package/dist/esm/i18n/it.json +546 -249
- package/dist/esm/i18n/ja.json +512 -215
- package/dist/esm/i18n/ko.json +553 -256
- package/dist/esm/i18n/pl.json +598 -301
- package/dist/esm/i18n/pt.json +512 -215
- package/dist/esm/i18n/th.json +554 -257
- package/dist/esm/i18n/tr.json +538 -241
- package/dist/esm/i18n/uk.json +514 -217
- package/dist/esm/i18n/vi.json +550 -253
- package/dist/esm/i18n/zh.json +528 -231
- package/dist/esm/pages/MainPage/MainWarningMessages.d.ts +1 -0
- package/dist/esm/pages/SelectTokenPage/SearchTokenInput.d.ts +1 -0
- package/dist/esm/pages/TransactionDetailsPage/ContactSupportButton.d.ts +1 -0
- package/dist/esm/pages/TransactionPage/ConfirmToAddressSheet.d.ts +1 -0
- package/dist/esm/pages/TransactionPage/ExchangeRateBottomSheet.d.ts +1 -0
- package/dist/esm/pages/TransactionPage/ExecutionDoneCard.d.ts +1 -0
- package/dist/esm/pages/TransactionPage/RouteTracker.d.ts +1 -0
- package/dist/esm/pages/TransactionPage/TokenValueBottomSheet.d.ts +1 -0
- package/dist/esm/pages/TransactionPage/TransactionDoneButtons.d.ts +1 -0
- package/dist/esm/pages/TransactionPage/TransactionFailedButtons.js +70 -18
- package/dist/esm/pages/TransactionPage/TransactionFailedButtons.js.map +1 -1
- package/dist/esm/pages/TransactionPage/utils.d.ts +3 -0
- package/dist/esm/pages/TransactionPage/utils.d.ts.map +1 -1
- package/dist/esm/pages/TransactionPage/utils.js +27 -1
- package/dist/esm/pages/TransactionPage/utils.js.map +1 -1
- package/dist/esm/providers/I18nProvider/I18nProvider.js +2 -2
- package/dist/esm/providers/I18nProvider/I18nProvider.js.map +1 -1
- package/dist/esm/providers/I18nProvider/enResource.d.ts +2 -2
- package/dist/esm/providers/I18nProvider/enResource.js +1 -2
- package/dist/esm/providers/I18nProvider/types.d.ts +2 -2
- package/dist/esm/providers/I18nProvider/types.d.ts.map +1 -1
- package/dist/esm/providers/WalletProvider/WalletProvider.d.ts +1 -0
- package/dist/esm/shared.d.ts +2 -2
- package/dist/esm/shared.js +2 -2
- package/dist/esm/stores/chains/types.d.ts +1 -0
- package/dist/esm/stores/form/types.d.ts +1 -0
- package/dist/esm/stores/header/types.d.ts +1 -0
- package/dist/esm/stores/inputMode/useInputModeStore.d.ts +1 -0
- package/dist/esm/stores/routes/utils.d.ts +1 -1
- package/dist/esm/stores/routes/utils.d.ts.map +1 -1
- package/dist/esm/stores/routes/utils.js +13 -1
- package/dist/esm/stores/routes/utils.js.map +1 -1
- package/dist/esm/utils/getErrorMessage.js +4 -0
- package/dist/esm/utils/getErrorMessage.js.map +1 -1
- package/dist/esm/utils/getPriceImpact.d.ts +1 -0
- package/dist/esm/utils/navigationRoutes.d.ts +1 -0
- package/dist/esm/utils/scopeKeys.d.ts +1 -0
- package/package.json +11 -11
- package/src/config/version.ts +1 -1
- package/src/hooks/usePacedValue.ts +1 -1
- package/src/hooks/useRouteExecutionMessage.ts +10 -13
- package/src/pages/TransactionPage/TransactionFailedButtons.test.tsx +497 -0
- package/src/pages/TransactionPage/TransactionFailedButtons.tsx +78 -9
- package/src/pages/TransactionPage/utils.ts +81 -0
- package/src/shared.ts +1 -0
- package/src/stores/routes/utils.ts +24 -1
- 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
|
|
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
|
-
|
|
116
|
-
<
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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 {
|
|
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) {
|