@imtbl/sdk 1.78.1 → 1.78.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 (55) hide show
  1. package/dist/{blockchain_data-dtlp9hdb.js → blockchain_data-DXU4U01i.js} +2 -2
  2. package/dist/blockchain_data.js +3 -3
  3. package/dist/browser/checkout/{AddTokensWidget-phRi8isq.js → AddTokensWidget-hN4Oz8Ns.js} +3 -3
  4. package/dist/browser/checkout/{BridgeWidget-DqBqp6iY.js → BridgeWidget-A_pLUANI.js} +6 -6
  5. package/dist/browser/checkout/{CommerceWidget-BakoRfZj.js → CommerceWidget-BoKaAY5y.js} +13 -13
  6. package/dist/browser/checkout/{FeesBreakdown-BeOGIvgY.js → FeesBreakdown-Cnmi6JuS.js} +1 -1
  7. package/dist/browser/checkout/{OnRampWidget-BID9kc9s.js → OnRampWidget-CYqTRHSC.js} +3 -3
  8. package/dist/browser/checkout/{SaleWidget-B6lZ8hGg.js → SaleWidget-DIW0k6AY.js} +10 -10
  9. package/dist/browser/checkout/{SpendingCapHero-DW6X8cGr.js → SpendingCapHero-Dp3KVsiY.js} +1 -1
  10. package/dist/browser/checkout/SwapWidget-CO4Ks4Im.js +1850 -0
  11. package/dist/browser/checkout/{TokenImage-DtkH2h2e.js → TokenImage-BNq0SnBY.js} +1 -1
  12. package/dist/browser/checkout/{TopUpView-BoCJELBq.js → TopUpView-CHsQmxQQ.js} +1 -1
  13. package/dist/browser/checkout/{WalletApproveHero-BxkUQIWN.js → WalletApproveHero-Bj4aKxP9.js} +3 -3
  14. package/dist/browser/checkout/{WalletWidget-C8iWV1OY.js → WalletWidget-BsKIkdBf.js} +3 -3
  15. package/dist/browser/checkout/{auto-track-Densi2-k.js → auto-track-BM5HqFI3.js} +1 -1
  16. package/dist/browser/checkout/{index-C-4DEd8a.js → index-BDZfw_Cp.js} +27 -135
  17. package/dist/browser/checkout/{index-CGZ3kTp1.js → index-CNJ98XFR.js} +2 -2
  18. package/dist/browser/checkout/{index-DSuIrOmV.js → index-Cp7l-bwg.js} +1 -1
  19. package/dist/browser/checkout/{index-CJ-HyQqV.js → index-D-MV_CPF.js} +1 -1
  20. package/dist/browser/checkout/{index-Djor6tHu.js → index-D0Nv1xcY.js} +1 -1
  21. package/dist/browser/checkout/{index-fbNLtQZy.js → index-D9AyMqDS.js} +1 -1
  22. package/dist/browser/checkout/{index-Bz7QkIrz.js → index-DatkoxOH.js} +1 -1
  23. package/dist/browser/checkout/{index-BWF1wMhZ.js → index-DubSxoZp.js} +1 -1
  24. package/dist/browser/checkout/{index.umd-BI-_-mXw.js → index.umd-Dyg-Hme-.js} +1 -1
  25. package/dist/browser/checkout/sdk.js +4 -4
  26. package/dist/browser/checkout/{useInterval-CfVGG_W7.js → useInterval-WSD1wTJp.js} +1 -1
  27. package/dist/browser/checkout/widgets-esm.js +1 -1
  28. package/dist/browser/checkout/widgets.js +181 -177
  29. package/dist/{checkout-B-BLfuyF.js → checkout-BcLZx2nl.js} +5 -11
  30. package/dist/checkout.js +5 -5
  31. package/dist/{config-DK_IUjmj.js → config-AmjUeqX0.js} +1 -1
  32. package/dist/config.js +1 -1
  33. package/dist/{index-2-Yhh8Lo.js → index-B7K98OVK.js} +1 -1
  34. package/dist/{index-wc_sNfXQ.js → index-Bgucm0s0.js} +3 -3
  35. package/dist/{index-B0oHQE2a.js → index-CT3wTyhk.js} +1 -1
  36. package/dist/{index-DmDCdEAA.js → index-DONwAU5q.js} +8 -5
  37. package/dist/{index-Bi3YfX-o.js → index-DW7yzuKO.js} +1 -1
  38. package/dist/{index-8TTFjDjB.js → index-UkqN57JB.js} +4 -4
  39. package/dist/index.browser.js +5 -5
  40. package/dist/index.browser.js.map +1 -1
  41. package/dist/index.cjs +64 -53
  42. package/dist/index.js +14 -14
  43. package/dist/{minting_backend-Blarurl3.js → minting_backend-Bfjb4y_D.js} +3 -3
  44. package/dist/minting_backend.js +5 -5
  45. package/dist/{orderbook-C8nW30oG.js → orderbook-730LCA9X.js} +1 -1
  46. package/dist/orderbook.js +2 -2
  47. package/dist/{passport-Dn-dJ0as.js → passport-CxcuwLFF.js} +53 -39
  48. package/dist/passport.js +4 -4
  49. package/dist/version.json +1 -1
  50. package/dist/{webhook-Bk8qJg4f.js → webhook-DGMGtPc_.js} +1 -1
  51. package/dist/webhook.js +2 -2
  52. package/dist/{x-BTBIq3r-.js → x-Ca_Tzy97.js} +3 -3
  53. package/dist/x.js +4 -4
  54. package/package.json +1 -1
  55. package/dist/browser/checkout/SwapWidget-UFkXHv2L.js +0 -1738
@@ -1,1738 +0,0 @@
1
- import { u as useTranslation, cX as WidgetTheme, a6 as getRemoteImage, q as jsxs, cY as quickswapFooterStyles, D as Box, cZ as quickswapFooterLogoStyles, p as jsx, at as Body, c_ as quickswapFooterDisclaimerTextStyles, r as reactExports, J as Button, c$ as getImxTokenImage, F as Drawer, bm as CloudImage, H as Heading, cf as Logo, aD as Icon, bp as BigNumber, bN as formatUnits, bu as calculateCryptoToFiat, bJ as tokenValueFormat, bU as isGasFree, cv as parseUnits, cs as formatZeroAmount, bW as ConnectLoaderContext, ai as getDefaultTokenImage, ak as isNativeToken, ct as NATIVE, bV as CryptoFiatContext, a9 as ViewContext, M as useAnalytics, c0 as CryptoFiatActions, cy as DEFAULT_QUOTE_REFRESH_INTERVAL, _ as isPassportProvider, d0 as parseEther, bj as amountInputValidation, av as Tooltip, T as UserJourney, bh as Environment, V as ViewActions, n as SharedViews, i as getL2ChainId, Q as Fragment, d1 as ESTIMATE_DEBOUNCE, d2 as DEFAULT_TOKEN_VALIDATION_DECIMALS, aN as isAddressSanctioned, cx as DEFAULT_TOKEN_DECIMALS, ac as EventTargetContext, d3 as sendSwapWidgetCloseEvent, aV as orchestrationEvents, I as IMTBLWidgetEvents, bD as HeaderNavigation, L as LoadingView, aT as SimpleLayout, cB as IMX_TOKEN_SYMBOL, c5 as CheckoutErrorType, cG as SimpleTextBody, cH as FooterButton, bc as viewReducer, bd as initialViewState, aj as TokenFilterTypes, d4 as DEFAULT_BALANCE_RETRY_POLICY, aM as fetchRiskAssessment, d5 as SwapDirection$1, cN as StatusView, d6 as sendSwapSuccessEvent, bH as StatusType, d7 as sendSwapFailedEvent, d8 as sendSwapRejectedEvent, E as ErrorView, bn as ServiceUnavailableErrorView, c3 as CryptoFiatProvider } from './index-C-4DEd8a.js';
2
- import { S as SelectForm, T as TextInputForm, F as Fees, a as TransactionRejected, N as NetworkSwitchDrawer, W as WalletApproveHero, g as getAllowedBalances } from './WalletApproveHero-BxkUQIWN.js';
3
- import { u as useInterval } from './useInterval-CfVGG_W7.js';
4
- import { S as SwapWidgetViews, T as TopUpView } from './TopUpView-BoCJELBq.js';
5
- import { S as SpendingCapHero } from './SpendingCapHero-DW6X8cGr.js';
6
- import './TokenImage-DtkH2h2e.js';
7
- import './FeesBreakdown-BeOGIvgY.js';
8
-
9
- function QuickswapFooter({ theme, environment }) {
10
- const { t } = useTranslation();
11
- const logo = theme === WidgetTheme.DARK
12
- ? getRemoteImage(environment, '/quickswapdark.webp')
13
- : getRemoteImage(environment, '/quickswaplight.webp');
14
- return (jsxs(Box, { testId: "quickswap-footer-container", sx: quickswapFooterStyles, children: [jsxs(Box, { testId: "quickswap-logo", sx: quickswapFooterLogoStyles, children: [jsx(Body, { size: "xSmall", sx: { paddingRight: 'base.spacing.x1' }, children: "By" }), jsx("img", { style: { height: '26px' }, alt: "Quickswap logo", src: logo })] }), jsx(Body, { testId: "quickswap-footer-disclaimer-text", size: "xSmall", sx: quickswapFooterDisclaimerTextStyles, children: t('footers.quickswapFooter.disclaimerText') })] }));
15
- }
16
-
17
- const initialSwapState = {
18
- exchange: null,
19
- walletProviderName: null,
20
- network: null,
21
- tokenBalances: [],
22
- supportedTopUps: null,
23
- allowedTokens: [],
24
- autoProceed: false,
25
- riskAssessment: undefined,
26
- };
27
- var SwapActions;
28
- (function (SwapActions) {
29
- SwapActions["SET_EXCHANGE"] = "SET_EXCHANGE";
30
- SwapActions["SET_WALLET_PROVIDER_NAME"] = "SET_WALLET_PROVIDER_NAME";
31
- SwapActions["SET_NETWORK"] = "SET_NETWORK";
32
- SwapActions["SET_SUPPORTED_TOP_UPS"] = "SET_SUPPORTED_TOP_UPS";
33
- SwapActions["SET_TOKEN_BALANCES"] = "SET_TOKEN_BALANCES";
34
- SwapActions["SET_ALLOWED_TOKENS"] = "SET_ALLOWED_TOKENS";
35
- SwapActions["SET_AUTO_PROCEED"] = "SET_AUTO_PROCEED";
36
- SwapActions["SET_RISK_ASSESSMENT"] = "SET_RISK_ASSESSMENT";
37
- })(SwapActions || (SwapActions = {}));
38
- // eslint-disable-next-line @typescript-eslint/naming-convention
39
- const SwapContext = reactExports.createContext({
40
- swapState: initialSwapState,
41
- swapDispatch: () => { },
42
- });
43
- SwapContext.displayName = 'SwapContext'; // help with debugging Context in browser
44
- const swapReducer = (state, action) => {
45
- switch (action.payload.type) {
46
- case SwapActions.SET_EXCHANGE:
47
- return {
48
- ...state,
49
- exchange: action.payload.exchange,
50
- };
51
- case SwapActions.SET_WALLET_PROVIDER_NAME:
52
- return {
53
- ...state,
54
- walletProviderName: action.payload.walletProviderName,
55
- };
56
- case SwapActions.SET_NETWORK:
57
- return {
58
- ...state,
59
- network: action.payload.network,
60
- };
61
- case SwapActions.SET_SUPPORTED_TOP_UPS:
62
- return {
63
- ...state,
64
- supportedTopUps: {
65
- isSwapEnabled: action.payload.supportedTopUps.isSwapEnabled ?? true,
66
- isOnRampEnabled: action.payload.supportedTopUps.isOnRampEnabled ?? true,
67
- isBridgeEnabled: action.payload.supportedTopUps.isBridgeEnabled ?? true,
68
- },
69
- };
70
- case SwapActions.SET_TOKEN_BALANCES:
71
- return {
72
- ...state,
73
- tokenBalances: action.payload.tokenBalances,
74
- };
75
- case SwapActions.SET_ALLOWED_TOKENS:
76
- return {
77
- ...state,
78
- allowedTokens: action.payload.allowedTokens,
79
- };
80
- case SwapActions.SET_AUTO_PROCEED:
81
- return {
82
- ...state,
83
- autoProceed: action.payload.autoProceed,
84
- direction: action.payload.direction,
85
- };
86
- case SwapActions.SET_RISK_ASSESSMENT:
87
- return {
88
- ...state,
89
- riskAssessment: action.payload.riskAssessment,
90
- };
91
- default:
92
- return state;
93
- }
94
- };
95
-
96
- const selectInputBoxStyle = {
97
- display: 'flex',
98
- flexDirection: 'row',
99
- justifyContent: 'space-between',
100
- columnGap: 'base.spacing.x1',
101
- };
102
- const selectStyle = {
103
- flex: 1,
104
- };
105
- const inputStyle = {
106
- flex: 2,
107
- };
108
-
109
- function SelectInput({ testId, options, textInputValue, textInputPlaceholder, textInputValidator, textInputType, onTextInputChange, onTextInputBlur, onTextInputFocus, textInputTextAlign, textInputSubtext, textInputErrorMessage, testInputMode, selectTextAlign, selectSubtext, selectErrorMessage, textInputMaxButtonClick, onSelectChange, textInputDisabled, selectInputDisabled, selectedOption, coinSelectorHeading, defaultTokenImage, environment, theme, }) {
110
- return (jsxs(Box, { sx: selectInputBoxStyle, children: [jsx(Box, { sx: selectStyle, children: jsx(SelectForm, { testId: `${testId}-select-form`, options: options, subtext: selectSubtext, textAlign: selectTextAlign, errorMessage: selectErrorMessage, onSelectChange: onSelectChange, disabled: selectInputDisabled, selectedOption: selectedOption, coinSelectorHeading: coinSelectorHeading, defaultTokenImage: defaultTokenImage, environment: environment, theme: theme }) }), jsx(Box, { sx: inputStyle, children: jsx(TextInputForm, { type: textInputType, testId: `${testId}-text-form`, value: textInputValue, placeholder: textInputPlaceholder, subtext: textInputSubtext, textAlign: textInputTextAlign, errorMessage: textInputErrorMessage, validator: textInputValidator, onTextInputChange: onTextInputChange, onTextInputBlur: onTextInputBlur, onTextInputFocus: onTextInputFocus, maxButtonClick: textInputMaxButtonClick, disabled: textInputDisabled, inputMode: testInputMode }) })] }));
111
- }
112
-
113
- function validateFromToken(fromToken) {
114
- if (!fromToken)
115
- return 'views.SWAP.validation.noFromTokenSelected';
116
- return '';
117
- }
118
- function validateFromAmount(amount, balance) {
119
- if (!amount || parseFloat(amount) === 0)
120
- return 'views.SWAP.validation.noAmountInputted';
121
- if (balance && Number(amount) > Number(balance))
122
- return 'views.SWAP.validation.insufficientBalance';
123
- return '';
124
- }
125
- function validateToToken(toToken) {
126
- if (!toToken)
127
- return 'views.SWAP.validation.noToTokenSelected';
128
- return '';
129
- }
130
- function validateToAmount(amount) {
131
- if (!amount || parseFloat(amount) === 0)
132
- return 'views.SWAP.validation.noAmountInputted';
133
- return '';
134
- }
135
-
136
- const swapButtonBoxStyle = {
137
- display: 'flex',
138
- flexDirection: 'column',
139
- paddingY: 'base.spacing.x6',
140
- paddingX: 'base.spacing.x4',
141
- };
142
- const swapButtonIconLoadingStyle = {
143
- width: 'base.icon.size.400',
144
- };
145
-
146
- function SwapButton({ loading, validator, sendTransaction, }) {
147
- const { t } = useTranslation();
148
- const handleClick = async () => {
149
- const canSwap = validator();
150
- if (canSwap) {
151
- await sendTransaction();
152
- }
153
- };
154
- return (jsx(Box, { sx: swapButtonBoxStyle, children: jsx(Button, { testId: "swap-button", disabled: loading, variant: "primary", onClick: handleClick, size: "large", children: loading ? (jsx(Button.Icon, { icon: "Loading", sx: swapButtonIconLoadingStyle })) : t('views.SWAP.swapForm.buttonText') }) }));
155
- }
156
-
157
- const containerStyles$1 = {
158
- display: 'flex',
159
- flexDirection: 'column',
160
- alignItems: 'center',
161
- paddingTop: 'base.spacing.x6',
162
- paddingBottom: 'base.spacing.x1',
163
- height: '100%',
164
- };
165
- const contentTextStyles$1 = {
166
- color: 'base.color.text.body.secondary',
167
- fontFamily: 'base.font.family.heading.secondary',
168
- textAlign: 'center',
169
- marginTop: 'base.spacing.x4',
170
- };
171
- const actionButtonContainerStyles$1 = {
172
- display: 'flex',
173
- flexDirection: 'column',
174
- justifyContent: 'center',
175
- gap: 'base.spacing.x2',
176
- height: '100%',
177
- width: '100%',
178
- };
179
- const actionButtonStyles$1 = {
180
- width: '100%',
181
- height: 'base.spacing.x16',
182
- };
183
- const logoContainerStyles$1 = {
184
- display: 'flex',
185
- flexDirection: 'column',
186
- justifyContent: 'center',
187
- alignItems: 'center',
188
- paddingTop: 'base.spacing.x6',
189
- };
190
-
191
- function NotEnoughImx({ environment, visible, showAdjustAmount, hasZeroImx, onCloseDrawer, onAddCoinsClick, }) {
192
- const { t } = useTranslation();
193
- const imxLogo = getImxTokenImage(environment);
194
- return (jsx(Drawer, { size: "full", onCloseDrawer: onCloseDrawer, visible: visible, showHeaderBar: false, children: jsx(Drawer.Content, { children: jsxs(Box, { testId: "not-enough-gas-bottom-sheet", sx: containerStyles$1, children: [jsx(CloudImage, { sx: { w: 'base.icon.size.600', h: 'base.icon.size.600' }, use: (jsx("img", { src: imxLogo, alt: t(`drawers.notEnoughImx.content.${hasZeroImx ? 'noImx' : 'insufficientImx'}.heading`) })) }), jsx(Heading, { size: "small", sx: contentTextStyles$1, testId: "not-enough-gas-heading", children: t(`drawers.notEnoughImx.content.${hasZeroImx ? 'noImx' : 'insufficientImx'}.heading`) }), jsx(Body, { sx: contentTextStyles$1, children: t(`drawers.notEnoughImx.content.${hasZeroImx ? 'noImx' : 'insufficientImx'}.body`) }), jsxs(Box, { sx: actionButtonContainerStyles$1, children: [showAdjustAmount && (jsx(Button, { testId: "not-enough-gas-adjust-amount-button", sx: actionButtonStyles$1, variant: "tertiary", onClick: onCloseDrawer, children: t('drawers.notEnoughImx.buttons.adjustAmount') })), jsx(Button, { testId: "not-enough-gas-add-imx-button", sx: actionButtonStyles$1, variant: "tertiary", onClick: onAddCoinsClick, children: t('drawers.notEnoughImx.buttons.addMoreImx') }), jsx(Button, { sx: actionButtonStyles$1, variant: "tertiary", onClick: onCloseDrawer, testId: "not-enough-gas-cancel-button", children: t('drawers.notEnoughImx.buttons.cancel') })] }), jsx(Box, { sx: logoContainerStyles$1, children: jsx(Logo, { testId: "footer-logo-image", logo: "ImmutableHorizontalLockup", sx: { width: 'base.spacing.x25' } }) })] }) }) }));
195
- }
196
-
197
- const containerStyles = {
198
- display: 'flex',
199
- flexDirection: 'column',
200
- alignItems: 'center',
201
- paddingTop: 'base.spacing.x6',
202
- paddingBottom: 'base.spacing.x4',
203
- paddingX: 'base.spacing.x4',
204
- height: '100%',
205
- };
206
- const contentTextStyles = {
207
- color: 'base.color.text.body.secondary',
208
- fontFamily: 'base.font.family.heading.secondary',
209
- textAlign: 'center',
210
- marginTop: 'base.spacing.x4',
211
- };
212
- const actionButtonContainerStyles = {
213
- display: 'flex',
214
- flexDirection: 'column',
215
- justifyContent: 'flex-end',
216
- alignItems: 'center',
217
- gap: 'base.spacing.x2',
218
- height: '100%',
219
- width: '100%',
220
- };
221
- const actionButtonStyles = {
222
- width: '100%',
223
- height: 'base.spacing.x16',
224
- marginBottom: 'base.spacing.x16',
225
- };
226
- const logoContainerStyles = {
227
- display: 'flex',
228
- flexDirection: 'column',
229
- justifyContent: 'center',
230
- alignItems: 'center',
231
- paddingTop: 'base.spacing.x6',
232
- };
233
- const statusStyles = {
234
- width: 'base.icon.size.600',
235
- fill: 'base.color.status.fatal.bright',
236
- };
237
-
238
- function UnableToSwap({ visible, onCloseDrawer }) {
239
- const { t } = useTranslation();
240
- return (jsx(Drawer, { size: "full", onCloseDrawer: onCloseDrawer, visible: visible, showHeaderBar: false, children: jsx(Drawer.Content, { children: jsxs(Box, { testId: "unable-to-swap-bottom-sheet", sx: containerStyles, children: [jsx(Icon, { icon: "Alert", testId: "unable-to-swap-icon", variant: "bold", sx: statusStyles }), jsx(Heading, { size: "small", sx: contentTextStyles, testId: "unable-to-swap-heading", children: t('drawers.unableToSwap.heading') }), jsx(Body, { sx: contentTextStyles, children: t('drawers.unableToSwap.body') }), jsx(Box, { sx: actionButtonContainerStyles, children: jsx(Button, { sx: actionButtonStyles, variant: "tertiary", onClick: onCloseDrawer, testId: "unable-to-swap-cancel-button", children: t('drawers.unableToSwap.buttons.cancel') }) }), jsx(Box, { sx: logoContainerStyles, children: jsx(Logo, { testId: "footer-logo-image", logo: "ImmutableHorizontalLockup", sx: { width: 'base.spacing.x25' } }) })] }) }) }));
241
- }
242
-
243
- const useDebounce = (value, delay = 500) => {
244
- const [debouncedValue, setDebouncedValue] = reactExports.useState(value);
245
- reactExports.useEffect(() => {
246
- const timer = setTimeout(() => {
247
- setDebouncedValue(value);
248
- }, delay);
249
- // Cleanup the timer used by debounce
250
- return () => {
251
- clearTimeout(timer);
252
- };
253
- }, [value, delay]);
254
- return debouncedValue;
255
- };
256
-
257
- /**
258
- * CancellablePromise extends a promise by adding the ability to flag it as cancelled.
259
- */
260
- class CancellablePromise {
261
- static id = 0;
262
- promiseId = 0;
263
- promise;
264
- isCancelled = false;
265
- onCancel = null;
266
- rejectPromise = () => { };
267
- constructor(executor) {
268
- CancellablePromise.id += 1;
269
- this.promiseId = CancellablePromise.id;
270
- this.promise = new Promise((resolve, reject) => {
271
- // Save the reject function to use it for cancellation
272
- this.rejectPromise = reject;
273
- executor((value) => {
274
- if (!this.isCancelled) {
275
- resolve(value);
276
- }
277
- else {
278
- reject({ cancelled: true });
279
- }
280
- }, (reason) => {
281
- if (!this.isCancelled) {
282
- reject(reason);
283
- }
284
- else {
285
- reject({ cancelled: true });
286
- }
287
- });
288
- });
289
- }
290
- static all(values) {
291
- return new CancellablePromise((resolve, reject) => {
292
- Promise.all(values.map((value) => {
293
- if (value instanceof CancellablePromise) {
294
- return value.promise;
295
- }
296
- return value;
297
- })).then(resolve, reject);
298
- });
299
- }
300
- then(onfulfilled, onrejected) {
301
- return new CancellablePromise((resolve, reject) => {
302
- this.promise.then((value) => (onfulfilled ? resolve(onfulfilled(value)) : resolve(value)), (reason) => (onrejected ? resolve(onrejected(reason)) : reject(reason)));
303
- });
304
- }
305
- catch(onrejected) {
306
- return this.then(undefined, onrejected);
307
- }
308
- finally(onfinally) {
309
- return new CancellablePromise((resolve, reject) => {
310
- this.promise
311
- .then(resolve, reject)
312
- .finally(() => {
313
- if (onfinally) {
314
- onfinally();
315
- }
316
- });
317
- });
318
- }
319
- cancel() {
320
- if (!this.isCancelled) {
321
- this.isCancelled = true;
322
- if (this.onCancel) {
323
- this.onCancel();
324
- }
325
- this.rejectPromise({ cancelled: true });
326
- }
327
- }
328
- onCancelled(callback) {
329
- this.onCancel = callback;
330
- }
331
- get cancelled() {
332
- return this.isCancelled;
333
- }
334
- }
335
-
336
- /**
337
- * Formats a quote into a list of fees for the fee drawer
338
- * @param swapQuote
339
- * @param cryptoFiatState
340
- * @param t
341
- */
342
- const formatSwapFees = (swapQuote, cryptoFiatState, t) => {
343
- const fees = [];
344
- if (!swapQuote.swap)
345
- return fees;
346
- const addFee = (estimate, label, prefix = '≈ ') => {
347
- const value = BigNumber.from(estimate?.value ?? 0);
348
- if (estimate && value.gt(0)) {
349
- const formattedFee = formatUnits(value, estimate.token.decimals);
350
- fees.push({
351
- label,
352
- fiatAmount: `≈ ${t('drawers.feesBreakdown.fees.fiatPricePrefix')}${calculateCryptoToFiat(formattedFee, estimate.token.symbol || '', cryptoFiatState.conversions)}`,
353
- amount: `${tokenValueFormat(formattedFee)}`,
354
- prefix,
355
- token: estimate.token,
356
- });
357
- }
358
- };
359
- // Format gas fee
360
- if (swapQuote.swap && swapQuote.swap.gasFeeEstimate) {
361
- addFee(swapQuote.swap.gasFeeEstimate, t('drawers.feesBreakdown.fees.swapGasFee.label'));
362
- }
363
- // Format gas fee approval
364
- if (swapQuote.approval && swapQuote.approval.gasFeeEstimate) {
365
- addFee(swapQuote.approval.gasFeeEstimate, t('drawers.feesBreakdown.fees.approvalFee.label'));
366
- }
367
- // Format the secondary fees
368
- swapQuote.quote?.fees?.forEach((fee) => {
369
- addFee(fee.amount, t('drawers.feesBreakdown.fees.swapSecondaryFee.label', { amount: `${(fee.basisPoints / 100)}%` }), '');
370
- });
371
- return fees;
372
- };
373
-
374
- /**
375
- * Adjusts the quote for gas free txn so we don't have to adjust it everywhere
376
- * @param checkProvider
377
- * @param currentQuote
378
- */
379
- const processGasFree = (checkProvider, currentQuote) => {
380
- if (!isGasFree(checkProvider)) {
381
- return currentQuote;
382
- }
383
- // Remove the quote gas fees as they are being covered by Relayer
384
- const adjustedQuote = { ...currentQuote };
385
- if (adjustedQuote.swap?.gasFeeEstimate) {
386
- adjustedQuote.swap.gasFeeEstimate.value = BigNumber.from(0);
387
- }
388
- if (adjustedQuote.approval?.gasFeeEstimate) {
389
- adjustedQuote.approval.gasFeeEstimate.value = BigNumber.from(0);
390
- }
391
- return adjustedQuote;
392
- };
393
-
394
- /**
395
- * Ensures that the fees token has the correct symbol. At the moment the dex quote doesn't return it.
396
- * Assumes the fee token is the from token. If it's not, it will be incorrect.
397
- * TODO: Fix this when the canonical tokens list comes into play so we can look up the symbol based on address
398
- * @param fromToken Assumption is fees are delineated in this from token
399
- * @param currentQuote
400
- */
401
- const processSecondaryFees = (fromToken, currentQuote) => {
402
- if (!currentQuote.quote.fees)
403
- return currentQuote;
404
- const adjustedFees = currentQuote.quote.fees.map((fee) => {
405
- if (fee.amount.token.symbol)
406
- return fee;
407
- return {
408
- ...fee,
409
- amount: {
410
- ...fee.amount,
411
- token: {
412
- ...fee.amount.token,
413
- symbol: (fromToken.address === fee.amount.token.address) ? fromToken.symbol : fee.amount.token.symbol,
414
- },
415
- },
416
- };
417
- });
418
- return { ...currentQuote, quote: { ...currentQuote.quote, fees: adjustedFees } };
419
- };
420
-
421
- /**
422
- * Ensures that the quote token has the correct symbol. At the moment the dex quote doesn't return it.
423
- * @param toToken
424
- * @param currentQuote
425
- */
426
- const processQuoteToken = (toToken, currentQuote) => {
427
- if (!currentQuote.quote.amount && !currentQuote.quote.amountWithMaxSlippage)
428
- return currentQuote;
429
- const adjustedAmount = {
430
- ...currentQuote.quote.amount,
431
- token: {
432
- ...currentQuote.quote.amount.token,
433
- symbol: (toToken.address === currentQuote.quote.amount.token.address)
434
- ? toToken.symbol : currentQuote.quote.amount.token.symbol,
435
- },
436
- };
437
- const adjustedAmountWithMaxSlippage = {
438
- ...currentQuote.quote.amountWithMaxSlippage,
439
- token: {
440
- ...currentQuote.quote.amountWithMaxSlippage.token,
441
- symbol: (toToken.address === currentQuote.quote.amountWithMaxSlippage.token.address)
442
- ? toToken.symbol : currentQuote.quote.amountWithMaxSlippage.token.symbol,
443
- },
444
- };
445
- return {
446
- ...currentQuote,
447
- quote: {
448
- ...currentQuote.quote,
449
- amount: adjustedAmount,
450
- amountWithMaxSlippage: adjustedAmountWithMaxSlippage,
451
- },
452
- };
453
- };
454
-
455
- const formatQuoteConversionRate = (amount, token, quote, labelKey, t) => {
456
- // Grab the token from the quote secondary fees
457
- // NOTE: This has a dependency on the secondary fee and needs to change if we change that fee
458
- const secondaryFee = quote.quote.fees[0];
459
- const fromToken = token;
460
- const toToken = quote.quote.amount.token;
461
- // Parse the fromAmount input, multiply by 10^decimals to convert to integer units
462
- const parsedFromAmount = parseFloat(amount);
463
- const relativeFromAmount = parseUnits(parsedFromAmount.toString(), fromToken.decimals);
464
- const relativeToAmount = BigNumber.from(quote.quote.amount.value);
465
- // Determine the maximum decimal places to equalize to
466
- const fromDecimals = fromToken.decimals;
467
- const toDecimals = quote.quote.amount.token.decimals;
468
- const maxDecimals = Math.max(fromDecimals, toDecimals);
469
- // Calculate scale factors based on maximum decimals
470
- const fromScaleFactor = BigNumber.from('10').pow(maxDecimals - fromDecimals);
471
- const toScaleFactor = BigNumber.from('10').pow(maxDecimals - toDecimals);
472
- // Adjust amounts to the same decimal scale
473
- const adjustedFromAmount = relativeFromAmount.mul(fromScaleFactor);
474
- const adjustedToAmount = relativeToAmount.mul(toScaleFactor);
475
- // Calculate conversion rate
476
- const initialRate = adjustedToAmount.div(adjustedFromAmount);
477
- // Calculate the remainder and adjust it correctly
478
- const conversionRemainder = adjustedToAmount.mod(adjustedFromAmount);
479
- const remainderAdjustmentFactor = BigNumber.from('10').pow(maxDecimals);
480
- const adjustedRemainder = conversionRemainder.mul(remainderAdjustmentFactor).div(adjustedFromAmount);
481
- // Compose the total conversion rate by adding the adjusted remainder
482
- const accurateRate = initialRate.mul(remainderAdjustmentFactor).add(adjustedRemainder);
483
- const formattedConversion = formatZeroAmount(tokenValueFormat(formatUnits(accurateRate, maxDecimals)), true);
484
- return t(labelKey, {
485
- fromSymbol: fromToken.symbol,
486
- toSymbol: toToken.symbol,
487
- rate: formattedConversion,
488
- fee: (secondaryFee?.basisPoints ?? 0) / 100,
489
- });
490
- };
491
-
492
- var SwapDirection;
493
- (function (SwapDirection) {
494
- SwapDirection["FROM"] = "FROM";
495
- SwapDirection["TO"] = "TO";
496
- })(SwapDirection || (SwapDirection = {}));
497
- // Ensures that the to token address does not match the from token address
498
- const shouldSetToAddress = (toAddress, fromAddress) => {
499
- if (toAddress === undefined)
500
- return false;
501
- if (toAddress === '')
502
- return false;
503
- if (fromAddress === toAddress)
504
- return false;
505
- return true;
506
- };
507
- let quoteRequest;
508
- function SwapForm({ data, theme, cancelAutoProceed }) {
509
- const { t } = useTranslation();
510
- const { swapState: { allowedTokens, tokenBalances, network, autoProceed, riskAssessment, }, } = reactExports.useContext(SwapContext);
511
- const { connectLoaderState } = reactExports.useContext(ConnectLoaderContext);
512
- const { checkout, provider } = connectLoaderState;
513
- const defaultTokenImage = getDefaultTokenImage(checkout?.config.environment, theme);
514
- const formatTokenOptionsId = reactExports.useCallback((symbol, address) => (isNativeToken(address)
515
- ? NATIVE
516
- : `${symbol.toLowerCase()}-${address.toLowerCase()}`), []);
517
- const { cryptoFiatState, cryptoFiatDispatch } = reactExports.useContext(CryptoFiatContext);
518
- const { viewDispatch } = reactExports.useContext(ViewContext);
519
- const [direction, setDirection] = reactExports.useState(SwapDirection.FROM);
520
- const [loading, setLoading] = reactExports.useState(false);
521
- const { track } = useAnalytics();
522
- // Form State
523
- const [fromAmount, setFromAmount] = reactExports.useState(data?.fromAmount || '');
524
- const [fromAmountError, setFromAmountError] = reactExports.useState('');
525
- const debouncedFromAmount = useDebounce(fromAmount, ESTIMATE_DEBOUNCE);
526
- const [fromToken, setFromToken] = reactExports.useState();
527
- const [fromBalance, setFromBalance] = reactExports.useState('');
528
- const [fromTokenError, setFromTokenError] = reactExports.useState('');
529
- const [fromMaxTrigger, setFromMaxTrigger] = reactExports.useState(0);
530
- const [toAmount, setToAmount] = reactExports.useState(data?.toAmount || '');
531
- const [toAmountError, setToAmountError] = reactExports.useState('');
532
- const debouncedToAmount = useDebounce(toAmount, ESTIMATE_DEBOUNCE);
533
- const [toToken, setToToken] = reactExports.useState();
534
- const [toTokenError, setToTokenError] = reactExports.useState('');
535
- const [fromFiatValue, setFromFiatValue] = reactExports.useState('');
536
- const [loadedToAndFromTokens, setLoadedToAndFromTokens] = reactExports.useState(false);
537
- // Quote
538
- const [quote, setQuote] = reactExports.useState(null);
539
- const [gasFeeValue, setGasFeeValue] = reactExports.useState('');
540
- const [gasFeeToken, setGasFeeToken] = reactExports.useState(undefined);
541
- const [gasFeeFiatValue, setGasFeeFiatValue] = reactExports.useState('');
542
- const [tokensOptionsFrom, setTokensOptionsForm] = reactExports.useState([]);
543
- const formattedFees = reactExports.useMemo(() => (quote ? formatSwapFees(quote, cryptoFiatState, t) : []), [quote, cryptoFiatState, t]);
544
- const [conversionToken, setConversionToken] = reactExports.useState(null);
545
- const [conversionAmount, setConversionAmount] = reactExports.useState('');
546
- const swapConversionRateTooltip = reactExports.useMemo(() => {
547
- if (!quote || !conversionAmount || !conversionToken)
548
- return '';
549
- return formatQuoteConversionRate(conversionAmount, conversionToken, quote, 'views.SWAP.swapForm.conversionRate', t);
550
- }, [conversionAmount, conversionToken, quote, t]);
551
- // Drawers
552
- const [showNotEnoughImxDrawer, setShowNotEnoughImxDrawer] = reactExports.useState(false);
553
- const [showUnableToSwapDrawer, setShowUnableToSwapDrawer] = reactExports.useState(false);
554
- const [showNetworkSwitchDrawer, setShowNetworkSwitchDrawer] = reactExports.useState(false);
555
- const [showTxnRejectedState, setShowTxnRejectedState] = reactExports.useState(false);
556
- reactExports.useEffect(() => {
557
- if (tokenBalances.length === 0)
558
- return;
559
- if (!network)
560
- return;
561
- const fromOptions = tokenBalances
562
- .filter((b) => b.balance.gt(0))
563
- .map((tokenBalance) => ({
564
- id: formatTokenOptionsId(tokenBalance.token.symbol, tokenBalance.token.address),
565
- name: tokenBalance.token.name,
566
- symbol: tokenBalance.token.symbol,
567
- icon: tokenBalance.token.icon,
568
- balance: {
569
- formattedAmount: tokenValueFormat(tokenBalance.formattedBalance),
570
- formattedFiatAmount: cryptoFiatState.conversions.size === 0 ? formatZeroAmount('') : calculateCryptoToFiat(tokenBalance.formattedBalance, tokenBalance.token.symbol || '', cryptoFiatState.conversions),
571
- },
572
- }));
573
- setTokensOptionsForm(fromOptions);
574
- // Set initial token options if provided
575
- if (data?.fromTokenAddress && !fromToken) {
576
- setFromToken(allowedTokens.find((token) => (isNativeToken(token.address)
577
- && data?.fromTokenAddress?.toLowerCase() === NATIVE)
578
- || token.address?.toLowerCase()
579
- === data?.fromTokenAddress?.toLowerCase()));
580
- setFromBalance(tokenBalances.find((tokenBalance) => (isNativeToken(tokenBalance.token.address)
581
- && data?.fromTokenAddress?.toLowerCase() === NATIVE)
582
- || (tokenBalance.token.address?.toLowerCase() === data?.fromTokenAddress?.toLowerCase()))?.formattedBalance ?? '');
583
- }
584
- if (shouldSetToAddress(data?.toTokenAddress, data?.fromTokenAddress) && !toToken) {
585
- setToToken(allowedTokens.find((token) => (isNativeToken(token.address) && data?.toTokenAddress?.toLowerCase() === NATIVE) || (token.address?.toLowerCase() === data?.toTokenAddress?.toLowerCase())));
586
- }
587
- setLoadedToAndFromTokens(true);
588
- }, [
589
- tokenBalances,
590
- allowedTokens,
591
- cryptoFiatState.conversions,
592
- data?.fromTokenAddress,
593
- data?.toTokenAddress,
594
- setFromToken,
595
- setFromBalance,
596
- setToToken,
597
- setTokensOptionsForm,
598
- formatTokenOptionsId,
599
- formatZeroAmount,
600
- network,
601
- ]);
602
- const tokensOptionsTo = reactExports.useMemo(() => allowedTokens
603
- .map((token) => ({
604
- id: formatTokenOptionsId(token.symbol, token.address),
605
- name: token.name,
606
- symbol: token.symbol,
607
- icon: token.icon,
608
- })), [allowedTokens, fromToken]);
609
- reactExports.useEffect(() => {
610
- cryptoFiatDispatch({
611
- payload: {
612
- type: CryptoFiatActions.SET_TOKEN_SYMBOLS,
613
- tokenSymbols: allowedTokens.map((token) => token.symbol),
614
- },
615
- });
616
- }, [cryptoFiatDispatch, allowedTokens]);
617
- // ------------------//
618
- // FETCH QUOTES //
619
- // ------------------//
620
- const resetFormErrors = () => {
621
- setFromAmountError('');
622
- setFromTokenError('');
623
- setToAmountError('');
624
- setToTokenError('');
625
- };
626
- const resetQuote = () => {
627
- if (quoteRequest) {
628
- quoteRequest.cancel();
629
- }
630
- setConversionAmount('');
631
- setConversionToken(null);
632
- setGasFeeFiatValue('');
633
- setQuote(null);
634
- };
635
- const processFetchQuoteFrom = async (silently = false) => {
636
- if (!provider)
637
- return;
638
- if (!checkout)
639
- return;
640
- if (!fromToken)
641
- return;
642
- if (!toToken)
643
- return;
644
- try {
645
- const quoteResultPromise = checkout.swapQuote({
646
- provider,
647
- fromToken,
648
- toToken,
649
- fromAmount,
650
- });
651
- const currentQuoteRequest = CancellablePromise.all([
652
- quoteResultPromise,
653
- ]);
654
- quoteRequest = currentQuoteRequest;
655
- const resolved = await currentQuoteRequest;
656
- let quoteResult = processGasFree(provider, resolved[0]);
657
- quoteResult = processSecondaryFees(fromToken, quoteResult);
658
- quoteResult = processQuoteToken(toToken, quoteResult);
659
- const estimate = quoteResult.swap.gasFeeEstimate;
660
- let gasFeeTotal = BigNumber.from(estimate?.value || 0);
661
- if (quoteResult.approval?.gasFeeEstimate) {
662
- gasFeeTotal = gasFeeTotal.add(quoteResult.approval.gasFeeEstimate.value);
663
- }
664
- const gasFee = formatUnits(gasFeeTotal, DEFAULT_TOKEN_DECIMALS);
665
- const estimateToken = estimate?.token;
666
- const gasToken = allowedTokens.find((token) => token.address?.toLocaleLowerCase() === estimateToken?.address?.toLocaleLowerCase());
667
- setConversionToken(fromToken);
668
- setConversionAmount(fromAmount);
669
- setQuote(quoteResult);
670
- setGasFeeValue(gasFee);
671
- setGasFeeToken({
672
- name: gasToken?.name || '',
673
- symbol: gasToken?.symbol || '',
674
- decimals: gasToken?.decimals || 0,
675
- address: gasToken?.address,
676
- icon: gasToken?.icon,
677
- });
678
- setGasFeeFiatValue(calculateCryptoToFiat(gasFee, gasToken?.symbol || '', cryptoFiatState.conversions));
679
- setToAmount(formatZeroAmount(tokenValueFormat(formatUnits(quoteResult.quote.amount.value, quoteResult.quote.amount.token.decimals), quoteResult.quote.amount.token.decimals)));
680
- resetFormErrors();
681
- }
682
- catch (error) {
683
- if (!error.cancelled) {
684
- // eslint-disable-next-line no-console
685
- console.error('Error fetching quote.', error);
686
- resetQuote();
687
- setShowNotEnoughImxDrawer(false);
688
- setShowUnableToSwapDrawer(true);
689
- }
690
- }
691
- if (!silently) {
692
- setLoading(false);
693
- }
694
- };
695
- const processFetchQuoteTo = async (silently = false) => {
696
- if (!provider)
697
- return;
698
- if (!checkout)
699
- return;
700
- if (!fromToken)
701
- return;
702
- if (!toToken)
703
- return;
704
- try {
705
- const quoteResultPromise = checkout.swapQuote({
706
- provider,
707
- fromToken,
708
- toToken,
709
- fromAmount: undefined,
710
- toAmount,
711
- });
712
- const currentQuoteRequest = CancellablePromise.all([
713
- quoteResultPromise,
714
- ]);
715
- quoteRequest = currentQuoteRequest;
716
- const resolved = await currentQuoteRequest;
717
- let quoteResult = processGasFree(provider, resolved[0]);
718
- quoteResult = processSecondaryFees(fromToken, quoteResult);
719
- const estimate = quoteResult.swap.gasFeeEstimate;
720
- let gasFeeTotal = BigNumber.from(estimate?.value || 0);
721
- if (quoteResult.approval?.gasFeeEstimate) {
722
- gasFeeTotal = gasFeeTotal.add(quoteResult.approval.gasFeeEstimate.value);
723
- }
724
- const gasFee = formatUnits(gasFeeTotal, DEFAULT_TOKEN_DECIMALS);
725
- const estimateToken = estimate?.token;
726
- const gasToken = allowedTokens.find((token) => token.symbol === estimateToken?.symbol);
727
- setConversionToken(toToken);
728
- setConversionAmount(toAmount);
729
- setQuote(quoteResult);
730
- setGasFeeValue(gasFee);
731
- setGasFeeToken({
732
- name: gasToken?.name || '',
733
- symbol: gasToken?.symbol || '',
734
- decimals: gasToken?.decimals || 0,
735
- address: gasToken?.address,
736
- icon: gasToken?.icon,
737
- });
738
- setGasFeeFiatValue(calculateCryptoToFiat(gasFee, gasToken?.symbol || '', cryptoFiatState.conversions));
739
- setFromAmount(formatZeroAmount(tokenValueFormat(formatUnits(quoteResult.quote.amount.value, quoteResult.quote.amount.token.decimals))));
740
- resetFormErrors();
741
- }
742
- catch (error) {
743
- if (!error.cancelled) {
744
- resetQuote();
745
- setShowNotEnoughImxDrawer(false);
746
- setShowUnableToSwapDrawer(true);
747
- }
748
- }
749
- if (!silently) {
750
- setLoading(false);
751
- }
752
- };
753
- const canRunFromQuote = (amount, silently) => {
754
- if (Number.isNaN(parseFloat(amount)))
755
- return false;
756
- if (parseFloat(amount) <= 0)
757
- return false;
758
- if (!fromToken)
759
- return false;
760
- if (!toToken)
761
- return false;
762
- if (silently && loading)
763
- return false;
764
- return true;
765
- };
766
- const fetchQuoteFrom = async (silently = false) => {
767
- if (!canRunFromQuote(fromAmount, silently))
768
- return;
769
- // Cancel any existing quote
770
- if (quoteRequest) {
771
- quoteRequest.cancel();
772
- }
773
- if (!silently) {
774
- setLoading(true);
775
- }
776
- await processFetchQuoteFrom(silently);
777
- };
778
- const canRunToQuote = (amount, silently) => {
779
- if (Number.isNaN(parseFloat(amount)))
780
- return false;
781
- if (parseFloat(amount) <= 0)
782
- return false;
783
- if (!fromToken)
784
- return false;
785
- if (!toToken)
786
- return false;
787
- if (silently && loading)
788
- return false;
789
- return true;
790
- };
791
- const fetchQuoteTo = async (silently = false) => {
792
- if (!canRunToQuote(toAmount, silently))
793
- return;
794
- // Cancel any existing quote
795
- if (quoteRequest) {
796
- quoteRequest.cancel();
797
- }
798
- if (!silently) {
799
- setLoading(true);
800
- }
801
- await processFetchQuoteTo(silently);
802
- };
803
- const fetchQuote = async (silently = false) => {
804
- if (direction === SwapDirection.FROM)
805
- await fetchQuoteFrom(silently);
806
- else
807
- await fetchQuoteTo(silently);
808
- };
809
- // Silently refresh the quote
810
- useInterval(() => {
811
- fetchQuote(true);
812
- }, DEFAULT_QUOTE_REFRESH_INTERVAL);
813
- // Fetch quote triggers
814
- reactExports.useEffect(() => {
815
- if (direction === SwapDirection.FROM) {
816
- if (debouncedFromAmount <= 0) {
817
- setLoading(false);
818
- resetQuote();
819
- return;
820
- }
821
- (async () => await fetchQuote())();
822
- }
823
- }, [debouncedFromAmount, fromToken, toToken, fromMaxTrigger]);
824
- reactExports.useEffect(() => {
825
- if (direction === SwapDirection.TO) {
826
- if (debouncedToAmount <= 0) {
827
- setLoading(false);
828
- resetQuote();
829
- return;
830
- }
831
- (async () => await fetchQuote())();
832
- }
833
- }, [debouncedToAmount, toToken, fromToken]);
834
- // during swaps, having enough IMX to cover the gas fee means (only relevant for non-Passport wallets)
835
- // 1. swapping from any token to any token costs IMX - so do a check
836
- // 2. If the swap from token is also IMX, include the additional amount into the calc
837
- // as user will need enough imx for the swap amount and the gas
838
- const insufficientFundsForGas = reactExports.useMemo(() => {
839
- if (!provider)
840
- return true;
841
- if (isPassportProvider(provider))
842
- return false;
843
- const imxBalance = tokenBalances.find((b) => b.token.address?.toLowerCase() === NATIVE);
844
- if (!imxBalance)
845
- return true;
846
- const fromTokenIsImx = fromToken?.address?.toLowerCase() === NATIVE;
847
- const gasAmount = parseEther(gasFeeValue.length !== 0 ? gasFeeValue : '0');
848
- const additionalAmount = fromTokenIsImx && !Number.isNaN(parseFloat(fromAmount))
849
- ? parseUnits(fromAmount, fromToken?.decimals || 18)
850
- : BigNumber.from('0');
851
- return gasAmount.add(additionalAmount).gt(imxBalance.balance);
852
- }, [gasFeeValue, tokenBalances, fromToken, fromAmount, provider]);
853
- // -------------//
854
- // FROM //
855
- // -------------//
856
- reactExports.useEffect(() => {
857
- if (!fromAmount)
858
- return;
859
- if (!fromToken)
860
- return;
861
- setFromFiatValue(calculateCryptoToFiat(fromAmount, fromToken.symbol, cryptoFiatState.conversions));
862
- }, [fromAmount, fromToken, cryptoFiatState.conversions]);
863
- const onFromSelectChange = reactExports.useCallback((value) => {
864
- const selected = tokenBalances
865
- .find((tokenBalance) => value === formatTokenOptionsId(tokenBalance.token.symbol, tokenBalance.token.address));
866
- if (!selected)
867
- return;
868
- if (toToken && value === formatTokenOptionsId(toToken.symbol, toToken?.address)) {
869
- setToToken(undefined);
870
- }
871
- setFromToken(selected.token);
872
- setFromBalance(selected.formattedBalance);
873
- setFromTokenError('');
874
- }, [toToken]);
875
- const onFromTextInputFocus = () => {
876
- setDirection(SwapDirection.FROM);
877
- };
878
- const onFromTextInputChange = (value) => {
879
- if (value === fromAmount) {
880
- return;
881
- }
882
- resetFormErrors();
883
- resetQuote();
884
- setToAmount('');
885
- if (canRunFromQuote(value, false)) {
886
- setLoading(true);
887
- }
888
- setFromAmount(value);
889
- };
890
- const textInputMaxButtonClick = () => {
891
- if (!fromBalance)
892
- return;
893
- const fromBalanceTruncated = fromBalance.slice(0, fromBalance.indexOf('.') + DEFAULT_TOKEN_VALIDATION_DECIMALS + 1);
894
- resetFormErrors();
895
- resetQuote();
896
- setDirection(SwapDirection.FROM);
897
- setToAmount('');
898
- if (canRunFromQuote(fromBalanceTruncated, false)) {
899
- setLoading(true);
900
- }
901
- if (fromAmount === fromBalanceTruncated) {
902
- setFromMaxTrigger(fromMaxTrigger + 1);
903
- }
904
- else {
905
- setFromAmount(fromBalanceTruncated);
906
- }
907
- track({
908
- userJourney: UserJourney.SWAP,
909
- screen: 'SwapCoins',
910
- control: 'MaxFrom',
911
- controlType: 'Button',
912
- extras: {
913
- fromBalance,
914
- fromBalanceTruncated,
915
- },
916
- });
917
- };
918
- // ------------//
919
- // TO //
920
- // ------------//
921
- const onToSelectChange = reactExports.useCallback((value) => {
922
- const selected = allowedTokens.find((token) => value === formatTokenOptionsId(token.symbol, token.address));
923
- if (!selected)
924
- return;
925
- if (fromToken && value === formatTokenOptionsId(fromToken.symbol, fromToken?.address)) {
926
- setFromToken(undefined);
927
- }
928
- setToToken(selected);
929
- setToTokenError('');
930
- }, [fromToken]);
931
- const onToTextInputFocus = () => {
932
- setDirection(SwapDirection.TO);
933
- };
934
- const onToTextInputChange = (value) => {
935
- if (value === toAmount) {
936
- return;
937
- }
938
- resetFormErrors();
939
- resetQuote();
940
- setFromFiatValue('');
941
- setFromAmount('');
942
- if (canRunToQuote(value, false)) {
943
- setLoading(true);
944
- }
945
- setToAmount(value);
946
- };
947
- const openNotEnoughImxDrawer = () => {
948
- setShowUnableToSwapDrawer(false);
949
- setShowNotEnoughImxDrawer(true);
950
- };
951
- const SwapFormValidator = () => {
952
- const validateFromTokenError = validateFromToken(fromToken);
953
- const validateFromAmountError = validateFromAmount(fromAmount, fromBalance);
954
- const validateToTokenError = validateToToken(toToken);
955
- const validateToAmountError = validateToAmount(toAmount);
956
- if (direction === SwapDirection.FROM) {
957
- setToAmountError('');
958
- if (validateFromAmountError) {
959
- setFromAmountError(validateFromAmountError);
960
- }
961
- }
962
- else if (direction === SwapDirection.TO) {
963
- setFromAmountError('');
964
- if (validateToAmountError) {
965
- setToAmountError(validateToAmountError);
966
- }
967
- }
968
- if (validateFromTokenError)
969
- setFromTokenError(validateFromTokenError);
970
- if (validateToTokenError)
971
- setToTokenError(validateToTokenError);
972
- let isSwapFormValid = true;
973
- if (validateFromTokenError
974
- || validateToTokenError
975
- || (validateFromAmountError && direction === SwapDirection.FROM)
976
- || (validateToAmountError && direction === SwapDirection.TO))
977
- isSwapFormValid = false;
978
- track({
979
- userJourney: UserJourney.SWAP,
980
- screen: 'SwapCoins',
981
- control: 'FormValid',
982
- controlType: 'Button',
983
- extras: {
984
- isSwapFormValid,
985
- swapFromAddress: fromToken?.address,
986
- swapFromAmount: fromAmount,
987
- swapFromTokenSymbol: fromToken?.symbol,
988
- swapToAddress: toToken?.address,
989
- swapToAmount: toAmount,
990
- swapToTokenSymbol: toToken?.symbol,
991
- autoProceed,
992
- },
993
- });
994
- return isSwapFormValid;
995
- };
996
- const isFormValidForAutoProceed = reactExports.useMemo(() => {
997
- if (!autoProceed)
998
- return false;
999
- if (loadedToAndFromTokens === false)
1000
- return false;
1001
- return !loading;
1002
- }, [autoProceed, loading, loadedToAndFromTokens]);
1003
- const canAutoSwap = reactExports.useMemo(() => {
1004
- if (!autoProceed)
1005
- return false;
1006
- if (!isFormValidForAutoProceed)
1007
- return false;
1008
- const isFormValid = SwapFormValidator();
1009
- if (!isFormValid) {
1010
- cancelAutoProceed();
1011
- return false;
1012
- }
1013
- return true;
1014
- }, [isFormValidForAutoProceed]);
1015
- const sendTransaction = async () => {
1016
- if (!quote)
1017
- return;
1018
- if (riskAssessment && isAddressSanctioned(riskAssessment)) {
1019
- viewDispatch({
1020
- payload: {
1021
- type: ViewActions.UPDATE_VIEW,
1022
- view: {
1023
- type: SwapWidgetViews.SERVICE_UNAVAILABLE,
1024
- },
1025
- },
1026
- });
1027
- return;
1028
- }
1029
- const transaction = quote;
1030
- const isValid = SwapFormValidator();
1031
- // Tracking swap from data here and is valid or not to understand behaviour
1032
- track({
1033
- userJourney: UserJourney.SWAP,
1034
- screen: 'SwapCoins',
1035
- control: 'Swap',
1036
- controlType: 'Button',
1037
- extras: {
1038
- swapFromAddress: data?.fromTokenAddress,
1039
- swapFromAmount: data?.fromAmount,
1040
- swapFromTokenSymbol: data?.fromTokenSymbol,
1041
- swapToAddress: data?.toTokenAddress,
1042
- swapToAmount: data?.toAmount,
1043
- swapToTokenSymbol: data?.toTokenSymbol,
1044
- isSwapFormValid: isValid,
1045
- hasFundsForGas: !insufficientFundsForGas,
1046
- autoProceed,
1047
- },
1048
- });
1049
- if (!isValid)
1050
- return;
1051
- if (!checkout || !provider || !transaction)
1052
- return;
1053
- if (insufficientFundsForGas) {
1054
- cancelAutoProceed();
1055
- openNotEnoughImxDrawer();
1056
- return;
1057
- }
1058
- try {
1059
- // check for switch network here
1060
- const currentChainId = await provider.provider.request({ method: 'eth_chainId', params: [] });
1061
- // eslint-disable-next-line radix
1062
- const parsedChainId = parseInt(currentChainId.toString());
1063
- if (parsedChainId !== getL2ChainId(checkout.config)) {
1064
- setShowNetworkSwitchDrawer(true);
1065
- return;
1066
- }
1067
- }
1068
- catch (err) {
1069
- // eslint-disable-next-line no-console
1070
- console.error('Current network check failed', err);
1071
- }
1072
- if (!transaction)
1073
- return;
1074
- setLoading(true);
1075
- const prefilledSwapData = {
1076
- fromAmount: data?.fromAmount || '',
1077
- fromTokenAddress: data?.fromTokenAddress || '',
1078
- toTokenAddress: data?.toTokenAddress || '',
1079
- toAmount: data?.toAmount || '',
1080
- };
1081
- viewDispatch({
1082
- payload: {
1083
- type: ViewActions.UPDATE_VIEW,
1084
- view: {
1085
- type: SwapWidgetViews.APPROVE_ERC20,
1086
- data: {
1087
- approveTransaction: transaction.approval?.transaction,
1088
- transaction: transaction.swap.transaction,
1089
- info: transaction.quote,
1090
- swapFormInfo: prefilledSwapData,
1091
- autoProceed,
1092
- },
1093
- },
1094
- },
1095
- });
1096
- };
1097
- const shouldSendTransaction = reactExports.useMemo(() => {
1098
- if (canAutoSwap === true && autoProceed === true) {
1099
- return true;
1100
- }
1101
- return undefined;
1102
- }, [canAutoSwap, autoProceed]);
1103
- reactExports.useEffect(() => {
1104
- if (shouldSendTransaction === undefined)
1105
- return;
1106
- sendTransaction();
1107
- }, [shouldSendTransaction]);
1108
- return (jsxs(Fragment, { children: [jsxs(Box, { sx: {
1109
- visibility: autoProceed ? 'hidden' : 'visible',
1110
- paddingX: 'base.spacing.x4',
1111
- marginBottom: 'base.spacing.x2',
1112
- }, children: [jsx(Heading, { size: "small", weight: "regular", sx: { paddingBottom: 'base.spacing.x4' }, children: t('views.SWAP.content.title') }), jsxs(Box, { sx: {
1113
- display: 'flex',
1114
- flexDirection: 'column',
1115
- rowGap: 'base.spacing.x6',
1116
- paddingBottom: 'base.spacing.x2',
1117
- }, children: [jsxs(Box, { children: [jsx(Heading, { size: "xSmall", sx: {
1118
- display: 'flex',
1119
- justifyContent: 'space-between',
1120
- paddingBottom: 'base.spacing.x1',
1121
- }, children: t('views.SWAP.swapForm.from.label') }), jsx(SelectInput, { testId: "fromTokenInputs", options: tokensOptionsFrom, selectSubtext: fromToken
1122
- ? `${t('views.SWAP.content.availableBalancePrefix')} ${tokenValueFormat(fromBalance)}`
1123
- : '', selectTextAlign: "left", textInputType: "number", testInputMode: "decimal", textInputValue: fromAmount, textInputPlaceholder: t('views.SWAP.swapForm.from.inputPlaceholder'), textInputSubtext: `${t('views.SWAP.content.fiatPricePrefix')}
1124
- $${formatZeroAmount(fromFiatValue, true)}`, textInputTextAlign: "right", textInputValidator: amountInputValidation, onTextInputChange: (v) => onFromTextInputChange(v), onTextInputFocus: onFromTextInputFocus, textInputMaxButtonClick: textInputMaxButtonClick, onSelectChange: onFromSelectChange, textInputErrorMessage: t(fromAmountError), selectErrorMessage: t(fromTokenError), selectedOption: fromToken
1125
- ? formatTokenOptionsId(fromToken.symbol, fromToken.address)
1126
- : undefined, coinSelectorHeading: t('views.SWAP.swapForm.from.selectorTitle'), defaultTokenImage: defaultTokenImage, environment: checkout?.config.environment, theme: theme })] }), jsxs(Box, { children: [jsxs(Box, { sx: {
1127
- display: 'flex',
1128
- justifyContent: 'space-between',
1129
- paddingBottom: 'base.spacing.x1',
1130
- }, children: [jsx(Heading, { size: "xSmall", children: t('views.SWAP.swapForm.to.label') }), swapConversionRateTooltip?.length > 0 && (jsxs(Tooltip, { children: [jsx(Tooltip.Target, { children: jsx(Icon, { icon: "InformationCircle", sx: {
1131
- w: 'base.icon.size.300',
1132
- } }) }), jsx(Tooltip.Content, { children: swapConversionRateTooltip })] }))] }), jsx(SelectInput, { testId: "toTokenInputs", options: tokensOptionsTo, selectTextAlign: "left", textInputType: "number", testInputMode: "decimal", textInputValue: toAmount, textInputPlaceholder: t('views.SWAP.swapForm.to.inputPlaceholder'), textInputTextAlign: "right", textInputValidator: amountInputValidation, onTextInputChange: (v) => onToTextInputChange(v), onTextInputFocus: onToTextInputFocus, onSelectChange: onToSelectChange, textInputErrorMessage: t(toAmountError), selectErrorMessage: t(toTokenError), selectedOption: toToken
1133
- ? formatTokenOptionsId(toToken.symbol, toToken.address)
1134
- : undefined, coinSelectorHeading: t('views.SWAP.swapForm.to.selectorTitle'), defaultTokenImage: defaultTokenImage, environment: checkout?.config.environment, theme: theme })] })] }), !isPassportProvider(provider) && (jsx(Fees, { gasFeeFiatValue: gasFeeFiatValue, gasFeeToken: gasFeeToken, gasFeeValue: gasFeeValue, fees: formattedFees, onFeesClick: () => {
1135
- track({
1136
- userJourney: UserJourney.SWAP,
1137
- screen: 'SwapCoins',
1138
- control: 'ViewFees',
1139
- controlType: 'Button',
1140
- });
1141
- }, sx: {
1142
- paddingBottom: '0',
1143
- }, loading: loading }))] }), !autoProceed && (jsx(SwapButton, { validator: SwapFormValidator, loading: loading, sendTransaction: sendTransaction })), jsx(TransactionRejected, { visible: showTxnRejectedState, showHeaderBar: false, onCloseDrawer: () => setShowTxnRejectedState(false), onRetry: () => {
1144
- sendTransaction();
1145
- setShowTxnRejectedState(false);
1146
- } }), jsx(NotEnoughImx, { environment: checkout?.config.environment ?? Environment.PRODUCTION, visible: showNotEnoughImxDrawer, showAdjustAmount: fromToken?.address === NATIVE, hasZeroImx: false, onAddCoinsClick: () => {
1147
- viewDispatch({
1148
- payload: {
1149
- type: ViewActions.UPDATE_VIEW,
1150
- view: {
1151
- type: SharedViews.TOP_UP_VIEW,
1152
- },
1153
- currentViewData: {
1154
- fromTokenAddress: fromToken?.address ?? '',
1155
- fromAmount,
1156
- toTokenAddress: toToken?.address ?? '',
1157
- },
1158
- },
1159
- });
1160
- }, onCloseDrawer: () => setShowNotEnoughImxDrawer(false) }), jsx(UnableToSwap, { visible: showUnableToSwapDrawer, onCloseDrawer: () => {
1161
- setShowUnableToSwapDrawer(false);
1162
- setFromToken(undefined);
1163
- setFromAmount('');
1164
- setToToken(undefined);
1165
- setToAmount('');
1166
- } }), jsx(NetworkSwitchDrawer, { visible: showNetworkSwitchDrawer, targetChainId: getL2ChainId(checkout?.config), provider: provider, checkout: checkout, onCloseDrawer: () => setShowNetworkSwitchDrawer(false) })] }));
1167
- }
1168
-
1169
- const hasZeroBalance = (tokenBalances, symbol) => {
1170
- if (tokenBalances.length === 0)
1171
- return true;
1172
- let zeroBalance = false;
1173
- tokenBalances
1174
- .forEach((t) => {
1175
- if (t.token.symbol === symbol && t.balance.eq(0)) {
1176
- zeroBalance = true;
1177
- }
1178
- });
1179
- return zeroBalance;
1180
- };
1181
-
1182
- function SwapCoins({ theme, cancelAutoProceed, fromAmount, toAmount, fromTokenAddress, toTokenAddress, showBackButton, }) {
1183
- const { t } = useTranslation();
1184
- const { viewDispatch } = reactExports.useContext(ViewContext);
1185
- const { eventTargetState: { eventTarget } } = reactExports.useContext(EventTargetContext);
1186
- const { swapState: { tokenBalances, autoProceed, }, } = reactExports.useContext(SwapContext);
1187
- const { connectLoaderState: { checkout, provider, }, } = reactExports.useContext(ConnectLoaderContext);
1188
- const [showNotEnoughImxDrawer, setShowNotEnoughImxDrawer] = reactExports.useState(false);
1189
- const { page } = useAnalytics();
1190
- reactExports.useEffect(() => {
1191
- page({
1192
- userJourney: UserJourney.SWAP,
1193
- screen: 'SwapCoins',
1194
- extras: {
1195
- fromAmount,
1196
- toAmount,
1197
- fromTokenAddress,
1198
- toTokenAddress,
1199
- },
1200
- });
1201
- }, []);
1202
- reactExports.useEffect(() => {
1203
- if (hasZeroBalance(tokenBalances, IMX_TOKEN_SYMBOL) && !isPassportProvider(provider)) {
1204
- setShowNotEnoughImxDrawer(true);
1205
- }
1206
- }, [tokenBalances]);
1207
- return (jsxs(SimpleLayout, { header: !autoProceed ? (jsx(HeaderNavigation, { title: t('views.SWAP.header.title'), onCloseButtonClick: () => sendSwapWidgetCloseEvent(eventTarget), showBack: showBackButton, onBackButtonClick: () => {
1208
- orchestrationEvents.sendRequestGoBackEvent(eventTarget, IMTBLWidgetEvents.IMTBL_SWAP_WIDGET_EVENT, {});
1209
- } })) : '', footer: jsx(QuickswapFooter, { environment: checkout?.config.environment, theme: theme }), children: [jsxs(Box, { sx: {
1210
- height: '100%',
1211
- display: 'flex',
1212
- flexDirection: 'column',
1213
- justifyContent: 'space-between',
1214
- }, children: [jsx(SwapForm, { cancelAutoProceed: cancelAutoProceed, data: {
1215
- fromAmount,
1216
- toAmount,
1217
- fromTokenAddress,
1218
- toTokenAddress,
1219
- }, theme: theme }), jsx(NotEnoughImx, { environment: checkout?.config.environment ?? Environment.PRODUCTION, visible: showNotEnoughImxDrawer, showAdjustAmount: false, hasZeroImx: true, onAddCoinsClick: () => {
1220
- viewDispatch({
1221
- payload: {
1222
- type: ViewActions.UPDATE_VIEW,
1223
- view: {
1224
- type: SharedViews.TOP_UP_VIEW,
1225
- },
1226
- },
1227
- });
1228
- }, onCloseDrawer: () => {
1229
- setShowNotEnoughImxDrawer(false);
1230
- } })] }), autoProceed && jsx(LoadingView, { loadingText: t('views.SWAP.PREPARE_SWAP.loading.text') })] }));
1231
- }
1232
-
1233
- function SwapInProgress({ transactionResponse, swapForm, }) {
1234
- const { t } = useTranslation();
1235
- const { viewDispatch } = reactExports.useContext(ViewContext);
1236
- const { page } = useAnalytics();
1237
- reactExports.useEffect(() => {
1238
- page({
1239
- userJourney: UserJourney.SWAP,
1240
- screen: 'SwapInProgress',
1241
- extras: {
1242
- swapFormInfo: swapForm,
1243
- },
1244
- });
1245
- }, []);
1246
- reactExports.useEffect(() => {
1247
- (async () => {
1248
- try {
1249
- const receipt = await transactionResponse.wait();
1250
- if (receipt.status === 1) {
1251
- viewDispatch({
1252
- payload: {
1253
- type: ViewActions.UPDATE_VIEW,
1254
- view: {
1255
- type: SwapWidgetViews.SUCCESS,
1256
- data: {
1257
- fromTokenAddress: swapForm.fromTokenAddress,
1258
- fromAmount: swapForm.fromAmount,
1259
- toTokenAddress: swapForm.toTokenAddress,
1260
- toAmount: swapForm.toAmount || '',
1261
- transactionHash: receipt.transactionHash,
1262
- },
1263
- },
1264
- },
1265
- });
1266
- return;
1267
- }
1268
- viewDispatch({
1269
- payload: {
1270
- type: ViewActions.UPDATE_VIEW,
1271
- view: {
1272
- type: SwapWidgetViews.FAIL,
1273
- data: swapForm,
1274
- reason: 'Transaction failed',
1275
- },
1276
- },
1277
- });
1278
- }
1279
- catch {
1280
- viewDispatch({
1281
- payload: {
1282
- type: ViewActions.UPDATE_VIEW,
1283
- view: {
1284
- type: SwapWidgetViews.FAIL,
1285
- data: swapForm,
1286
- reason: 'Transaction failed',
1287
- },
1288
- },
1289
- });
1290
- }
1291
- })();
1292
- }, [transactionResponse]);
1293
- return (jsx(LoadingView, { loadingText: t('views.SWAP.IN_PROGRESS.loading.text') }));
1294
- }
1295
-
1296
- function ApproveERC20Onboarding({ data }) {
1297
- const { t } = useTranslation();
1298
- const { swapState: { allowedTokens } } = reactExports.useContext(SwapContext);
1299
- const { connectLoaderState } = reactExports.useContext(ConnectLoaderContext);
1300
- const { checkout, provider } = connectLoaderState;
1301
- const { viewDispatch } = reactExports.useContext(ViewContext);
1302
- const { eventTargetState: { eventTarget } } = reactExports.useContext(EventTargetContext);
1303
- const isPassport = isPassportProvider(provider);
1304
- const noApprovalTransaction = data.approveTransaction === undefined;
1305
- // Local state
1306
- const [actionDisabled, setActionDisabled] = reactExports.useState(false);
1307
- const [approvalTxnLoading, setApprovalTxnLoading] = reactExports.useState(false);
1308
- const [showSwapTxnStep, setShowSwapTxnStep] = reactExports.useState(noApprovalTransaction);
1309
- const [loading, setLoading] = reactExports.useState(false);
1310
- // reject transaction flags
1311
- const [rejectedSpending, setRejectedSpending] = reactExports.useState(false);
1312
- const [rejectedSwap, setRejectedSwap] = reactExports.useState(false);
1313
- const { page, track } = useAnalytics();
1314
- reactExports.useEffect(() => {
1315
- page({
1316
- userJourney: UserJourney.SWAP,
1317
- screen: 'ApproveERC20',
1318
- extras: {
1319
- swapFormInfo: data.swapFormInfo,
1320
- },
1321
- });
1322
- }, []);
1323
- // Get symbol from swap info for approve amount text
1324
- const fromToken = reactExports.useMemo(() => allowedTokens.find((token) => token.address === data.swapFormInfo.fromTokenAddress), [allowedTokens, data.swapFormInfo.fromTokenAddress]);
1325
- // Common error view function
1326
- const showErrorView = reactExports.useCallback(() => {
1327
- viewDispatch({
1328
- payload: {
1329
- type: ViewActions.UPDATE_VIEW,
1330
- view: {
1331
- type: SharedViews.ERROR_VIEW,
1332
- error: new Error('No checkout object or no provider object found'),
1333
- },
1334
- },
1335
- });
1336
- }, [viewDispatch]);
1337
- const goBackWithSwapData = reactExports.useCallback(() => {
1338
- viewDispatch({
1339
- payload: {
1340
- type: ViewActions.UPDATE_VIEW,
1341
- view: {
1342
- type: SwapWidgetViews.SWAP,
1343
- data: data.swapFormInfo,
1344
- },
1345
- },
1346
- });
1347
- }, [viewDispatch]);
1348
- const handleExceptions = (err, swapFormData) => {
1349
- if (err.type === CheckoutErrorType.UNPREDICTABLE_GAS_LIMIT) {
1350
- viewDispatch({
1351
- payload: {
1352
- type: ViewActions.UPDATE_VIEW,
1353
- view: {
1354
- type: SwapWidgetViews.PRICE_SURGE,
1355
- data: swapFormData,
1356
- },
1357
- },
1358
- });
1359
- return;
1360
- }
1361
- if (err.type === CheckoutErrorType.TRANSACTION_FAILED
1362
- || err.type === CheckoutErrorType.INSUFFICIENT_FUNDS
1363
- || (err.receipt && err.receipt.status === 0)) {
1364
- viewDispatch({
1365
- payload: {
1366
- type: ViewActions.UPDATE_VIEW,
1367
- view: {
1368
- type: SwapWidgetViews.FAIL,
1369
- reason: 'Transaction failed',
1370
- data: swapFormData,
1371
- },
1372
- },
1373
- });
1374
- return;
1375
- }
1376
- // eslint-disable-next-line no-console
1377
- console.error('Approve ERC20 failed', err);
1378
- viewDispatch({
1379
- payload: {
1380
- type: ViewActions.UPDATE_VIEW,
1381
- view: {
1382
- type: SharedViews.ERROR_VIEW,
1383
- error: err,
1384
- },
1385
- },
1386
- });
1387
- };
1388
- const prepareTransaction = (transaction, isGasFree = false) => ({
1389
- ...transaction,
1390
- gasPrice: (isGasFree ? BigNumber.from(0) : undefined),
1391
- });
1392
- /* --------------------- */
1393
- // Approve spending step //
1394
- /* --------------------- */
1395
- const handleApproveSpendingClick = reactExports.useCallback(async () => {
1396
- if (loading)
1397
- return;
1398
- track({
1399
- userJourney: UserJourney.SWAP,
1400
- screen: 'ApproveERC20',
1401
- control: 'ApproveSpending',
1402
- controlType: 'Button',
1403
- extras: {
1404
- autoProceed: data.autoProceed,
1405
- },
1406
- });
1407
- setLoading(true);
1408
- if (!checkout || !provider) {
1409
- showErrorView();
1410
- return;
1411
- }
1412
- if (actionDisabled)
1413
- return;
1414
- setActionDisabled(true);
1415
- try {
1416
- const txnResult = await checkout.sendTransaction({
1417
- provider,
1418
- transaction: prepareTransaction(data.approveTransaction, isPassport),
1419
- });
1420
- setApprovalTxnLoading(true);
1421
- const approvalReceipt = await txnResult.transactionResponse.wait();
1422
- if (approvalReceipt.status !== 1) {
1423
- viewDispatch({
1424
- payload: {
1425
- type: ViewActions.UPDATE_VIEW,
1426
- view: {
1427
- type: SwapWidgetViews.FAIL,
1428
- data: data.swapFormInfo,
1429
- },
1430
- },
1431
- });
1432
- return;
1433
- }
1434
- setApprovalTxnLoading(false);
1435
- setActionDisabled(false);
1436
- setShowSwapTxnStep(true);
1437
- }
1438
- catch (err) {
1439
- setApprovalTxnLoading(false);
1440
- setActionDisabled(false);
1441
- if (err.type === CheckoutErrorType.USER_REJECTED_REQUEST_ERROR) {
1442
- setRejectedSpending(true);
1443
- return;
1444
- }
1445
- handleExceptions(err, data.swapFormInfo);
1446
- }
1447
- finally {
1448
- setLoading(false);
1449
- }
1450
- }, [
1451
- checkout,
1452
- provider,
1453
- showErrorView,
1454
- viewDispatch,
1455
- setRejectedSwap,
1456
- data.approveTransaction,
1457
- data.swapFormInfo,
1458
- actionDisabled,
1459
- setActionDisabled,
1460
- setApprovalTxnLoading,
1461
- ]);
1462
- const approveSpendingContent = reactExports.useMemo(() => (jsxs(SimpleTextBody, { heading: t(`views.APPROVE_ERC20.approveSpending.content.${isPassport ? 'passport' : 'metamask'}.heading`), children: [isPassport && (jsx(Box, { children: t('views.APPROVE_ERC20.approveSpending.content.passport.body') })), !isPassport
1463
- // eslint-disable-next-line max-len
1464
- && (jsx(Box, { children: t('views.APPROVE_ERC20.approveSpending.content.metamask.body', { amount: `${data.swapFormInfo.fromAmount} ${fromToken?.symbol || ''}` }) }))] })), [data.swapFormInfo, fromToken, isPassport]);
1465
- const approveSpendingFooter = reactExports.useMemo(() => (jsx(FooterButton, { loading: loading, actionText: t(rejectedSpending
1466
- ? 'views.APPROVE_ERC20.approveSpending.footer.retryText'
1467
- : 'views.APPROVE_ERC20.approveSpending.footer.buttonText'), onActionClick: handleApproveSpendingClick })), [rejectedSpending, handleApproveSpendingClick, loading]);
1468
- /* ----------------- */
1469
- // Approve swap step //
1470
- /* ----------------- */
1471
- const handleApproveSwapClick = reactExports.useCallback(async () => {
1472
- if (loading)
1473
- return;
1474
- track({
1475
- userJourney: UserJourney.SWAP,
1476
- screen: 'ApproveERC20',
1477
- control: 'ApproveSwap',
1478
- controlType: 'Button',
1479
- extras: {
1480
- autoProceed: data.autoProceed,
1481
- },
1482
- });
1483
- setLoading(true);
1484
- if (!checkout || !provider) {
1485
- showErrorView();
1486
- return;
1487
- }
1488
- if (actionDisabled)
1489
- return;
1490
- setActionDisabled(true);
1491
- try {
1492
- const txn = await checkout.sendTransaction({
1493
- provider,
1494
- transaction: prepareTransaction(data.transaction, isPassport),
1495
- });
1496
- setActionDisabled(false);
1497
- // user approves swap
1498
- // go to the Swap In Progress View
1499
- viewDispatch({
1500
- payload: {
1501
- type: ViewActions.UPDATE_VIEW,
1502
- view: {
1503
- type: SwapWidgetViews.IN_PROGRESS,
1504
- data: {
1505
- transactionResponse: txn.transactionResponse,
1506
- swapForm: data.swapFormInfo,
1507
- },
1508
- },
1509
- },
1510
- });
1511
- }
1512
- catch (err) {
1513
- setActionDisabled(false);
1514
- if (err.type === CheckoutErrorType.USER_REJECTED_REQUEST_ERROR) {
1515
- setRejectedSwap(true);
1516
- return;
1517
- }
1518
- handleExceptions(err, data.swapFormInfo);
1519
- }
1520
- finally {
1521
- setLoading(false);
1522
- }
1523
- }, [
1524
- checkout,
1525
- provider,
1526
- showErrorView,
1527
- viewDispatch,
1528
- setRejectedSwap,
1529
- data.transaction,
1530
- data.swapFormInfo,
1531
- actionDisabled,
1532
- setActionDisabled,
1533
- ]);
1534
- const approveSwapContent = (jsx(SimpleTextBody, { heading: t('views.APPROVE_ERC20.approveSwap.content.heading'), children: jsx(Box, { children: t('views.APPROVE_ERC20.approveSwap.content.body') }) }));
1535
- const approveSwapFooter = reactExports.useMemo(() => (jsx(FooterButton, { loading: loading, actionText: t(rejectedSwap
1536
- ? 'views.APPROVE_ERC20.approveSwap.footer.retryText'
1537
- : 'views.APPROVE_ERC20.approveSwap.footer.buttonText'), onActionClick: handleApproveSwapClick })), [rejectedSwap, handleApproveSwapClick, loading]);
1538
- return (jsxs(Fragment, { children: [approvalTxnLoading && (jsx(LoadingView, { loadingText: t('views.APPROVE_ERC20.approveSpending.loading.text') })), !approvalTxnLoading && (jsx(SimpleLayout, { header: (jsx(HeaderNavigation, { transparent: true, showBack: true, onCloseButtonClick: () => sendSwapWidgetCloseEvent(eventTarget), onBackButtonClick: goBackWithSwapData })), floatHeader: true, heroContent: showSwapTxnStep ? jsx(WalletApproveHero, {}) : jsx(SpendingCapHero, {}), footer: showSwapTxnStep ? approveSwapFooter : approveSpendingFooter, children: showSwapTxnStep ? approveSwapContent : approveSpendingContent }))] }));
1539
- }
1540
-
1541
- function SwapWidget({ amount, fromTokenAddress, toTokenAddress, config, autoProceed, direction, showBackButton, }) {
1542
- const { t } = useTranslation();
1543
- const { eventTargetState: { eventTarget }, } = reactExports.useContext(EventTargetContext);
1544
- const { environment, theme, isOnRampEnabled, isSwapEnabled, isBridgeEnabled, } = config;
1545
- const { connectLoaderState: { checkout, provider }, } = reactExports.useContext(ConnectLoaderContext);
1546
- const [viewState, viewDispatch] = reactExports.useReducer(viewReducer, {
1547
- ...initialViewState,
1548
- history: [],
1549
- });
1550
- const [swapState, swapDispatch] = reactExports.useReducer(swapReducer, initialSwapState);
1551
- const { page } = useAnalytics();
1552
- const [errorViewLoading, setErrorViewLoading] = reactExports.useState(false);
1553
- const swapReducerValues = reactExports.useMemo(() => ({ swapState, swapDispatch }), [swapState, swapDispatch]);
1554
- const viewReducerValues = reactExports.useMemo(() => ({ viewState, viewDispatch }), [viewState, viewDispatch]);
1555
- const showErrorView = reactExports.useCallback((error, tryAgain) => {
1556
- viewDispatch({
1557
- payload: {
1558
- type: ViewActions.UPDATE_VIEW,
1559
- view: {
1560
- type: SharedViews.ERROR_VIEW,
1561
- tryAgain,
1562
- error,
1563
- },
1564
- },
1565
- });
1566
- }, [viewDispatch]);
1567
- const showSwapView = reactExports.useCallback(() => {
1568
- viewDispatch({
1569
- payload: {
1570
- type: ViewActions.UPDATE_VIEW,
1571
- view: { type: SwapWidgetViews.SWAP },
1572
- },
1573
- });
1574
- }, [viewDispatch]);
1575
- const loadBalances = reactExports.useCallback(async () => {
1576
- if (!checkout)
1577
- throw new Error('loadBalances: missing checkout');
1578
- if (!provider)
1579
- throw new Error('loadBalances: missing provider');
1580
- try {
1581
- const tokensAndBalances = await getAllowedBalances({
1582
- checkout,
1583
- provider,
1584
- allowTokenListType: TokenFilterTypes.SWAP,
1585
- });
1586
- // Why? Check getAllowedBalances
1587
- if (tokensAndBalances === undefined)
1588
- return false;
1589
- swapDispatch({
1590
- payload: {
1591
- type: SwapActions.SET_ALLOWED_TOKENS,
1592
- allowedTokens: tokensAndBalances.allowList.tokens,
1593
- },
1594
- });
1595
- swapDispatch({
1596
- payload: {
1597
- type: SwapActions.SET_TOKEN_BALANCES,
1598
- tokenBalances: tokensAndBalances.allowedBalances,
1599
- },
1600
- });
1601
- }
1602
- catch (err) {
1603
- if (DEFAULT_BALANCE_RETRY_POLICY.nonRetryable(err)) {
1604
- showErrorView(err, loadBalances);
1605
- return false;
1606
- }
1607
- }
1608
- return true;
1609
- }, [checkout, provider]);
1610
- reactExports.useEffect(() => {
1611
- (async () => {
1612
- if (!checkout || !provider)
1613
- return;
1614
- const network = await checkout.getNetworkInfo({ provider });
1615
- // If the provider's network is not the correct network, return out of this and let the
1616
- // connect loader handle the switch network functionality
1617
- if (network.chainId !== getL2ChainId(checkout.config))
1618
- return;
1619
- swapDispatch({
1620
- payload: {
1621
- type: SwapActions.SET_NETWORK,
1622
- network,
1623
- },
1624
- });
1625
- if (!(await loadBalances()))
1626
- return;
1627
- if (viewState.view.type === SharedViews.LOADING_VIEW) {
1628
- showSwapView();
1629
- }
1630
- })();
1631
- }, [checkout, provider]);
1632
- reactExports.useEffect(() => {
1633
- if (!checkout || swapState.riskAssessment) {
1634
- return;
1635
- }
1636
- (async () => {
1637
- const address = await provider?.getSigner()?.getAddress();
1638
- if (!address) {
1639
- return;
1640
- }
1641
- const assessment = await fetchRiskAssessment([address], checkout.config);
1642
- swapDispatch({
1643
- payload: {
1644
- type: SwapActions.SET_RISK_ASSESSMENT,
1645
- riskAssessment: assessment,
1646
- },
1647
- });
1648
- })();
1649
- }, [checkout, provider]);
1650
- reactExports.useEffect(() => {
1651
- swapDispatch({
1652
- payload: {
1653
- type: SwapActions.SET_AUTO_PROCEED,
1654
- autoProceed: autoProceed ?? false,
1655
- direction: direction ?? SwapDirection$1.FROM,
1656
- },
1657
- });
1658
- }, [autoProceed, direction]);
1659
- const cancelAutoProceed = reactExports.useCallback(() => {
1660
- if (autoProceed) {
1661
- swapDispatch({
1662
- payload: {
1663
- type: SwapActions.SET_AUTO_PROCEED,
1664
- autoProceed: false,
1665
- direction: SwapDirection$1.FROM,
1666
- },
1667
- });
1668
- }
1669
- }, [autoProceed, swapDispatch]);
1670
- const fromAmount = direction === SwapDirection$1.FROM || direction == null ? amount : undefined;
1671
- const toAmount = direction === SwapDirection$1.TO ? amount : undefined;
1672
- return (jsx(ViewContext.Provider, { value: viewReducerValues, children: jsx(SwapContext.Provider, { value: swapReducerValues, children: jsxs(CryptoFiatProvider, { environment: environment, children: [viewState.view.type === SharedViews.LOADING_VIEW && (jsx(LoadingView, { loadingText: t('views.LOADING_VIEW.text') })), viewState.view.type === SwapWidgetViews.SWAP && (jsx(SwapCoins, { theme: theme, cancelAutoProceed: cancelAutoProceed, fromAmount: viewState.view.data?.fromAmount ?? fromAmount, toAmount: viewState.view.data?.toAmount ?? toAmount, fromTokenAddress: viewState.view.data?.fromTokenAddress ?? fromTokenAddress, toTokenAddress: viewState.view.data?.toTokenAddress ?? toTokenAddress, showBackButton: showBackButton })), viewState.view.type === SwapWidgetViews.IN_PROGRESS && (jsx(SwapInProgress, { transactionResponse: viewState.view.data.transactionResponse, swapForm: viewState.view.data.swapForm })), viewState.view.type === SwapWidgetViews.APPROVE_ERC20 && (jsx(ApproveERC20Onboarding, { data: viewState.view.data })), viewState.view.type === SwapWidgetViews.SUCCESS && (jsx(StatusView, { statusText: t('views.SWAP.success.text'), actionText: t('views.SWAP.success.actionText'), onRenderEvent: () => {
1673
- page({
1674
- userJourney: UserJourney.SWAP,
1675
- screen: 'SwapSuccess',
1676
- extras: {
1677
- fromTokenAddress: viewState.view.data?.fromTokenAddress,
1678
- fromAmount: viewState.view.data?.fromAmount,
1679
- toTokenAddress: viewState.view.data?.toTokenAddress,
1680
- toAmount: viewState.view.data?.toAmount,
1681
- },
1682
- });
1683
- sendSwapSuccessEvent(eventTarget, viewState.view.data.transactionHash);
1684
- }, onActionClick: () => sendSwapWidgetCloseEvent(eventTarget), statusType: StatusType.SUCCESS, testId: "success-view" })), viewState.view.type === SwapWidgetViews.FAIL && (jsx(StatusView, { statusText: t('views.SWAP.failed.text'), actionText: t('views.SWAP.failed.actionText'), onRenderEvent: () => {
1685
- page({
1686
- userJourney: UserJourney.SWAP,
1687
- screen: 'SwapFailed',
1688
- });
1689
- sendSwapFailedEvent(eventTarget, 'Transaction failed');
1690
- }, onActionClick: () => {
1691
- if (viewState.view.type === SwapWidgetViews.FAIL) {
1692
- viewDispatch({
1693
- payload: {
1694
- type: ViewActions.UPDATE_VIEW,
1695
- view: {
1696
- type: SwapWidgetViews.SWAP,
1697
- data: viewState.view.data,
1698
- },
1699
- },
1700
- });
1701
- }
1702
- }, statusType: StatusType.FAILURE, onCloseClick: () => sendSwapWidgetCloseEvent(eventTarget), testId: "fail-view" })), viewState.view.type === SwapWidgetViews.PRICE_SURGE && (jsx(StatusView, { statusText: t('views.SWAP.rejected.text'), actionText: t('views.SWAP.rejected.actionText'), onRenderEvent: () => {
1703
- page({
1704
- userJourney: UserJourney.SWAP,
1705
- screen: 'PriceSurge',
1706
- });
1707
- sendSwapRejectedEvent(eventTarget, 'Price surge');
1708
- }, onActionClick: () => {
1709
- if (viewState.view.type === SwapWidgetViews.PRICE_SURGE) {
1710
- viewDispatch({
1711
- payload: {
1712
- type: ViewActions.UPDATE_VIEW,
1713
- view: {
1714
- type: SwapWidgetViews.SWAP,
1715
- data: viewState.view.data,
1716
- },
1717
- },
1718
- });
1719
- }
1720
- }, statusType: StatusType.WARNING, onCloseClick: () => sendSwapWidgetCloseEvent(eventTarget), testId: "price-surge-view" })), viewState.view.type === SharedViews.ERROR_VIEW && (jsx(ErrorView, { actionText: t('views.ERROR_VIEW.actionText'), onActionClick: async () => {
1721
- setErrorViewLoading(true);
1722
- const data = viewState.view;
1723
- if (!data.tryAgain) {
1724
- showSwapView();
1725
- setErrorViewLoading(false);
1726
- return;
1727
- }
1728
- if (await data.tryAgain())
1729
- showSwapView();
1730
- setErrorViewLoading(false);
1731
- }, onCloseClick: () => sendSwapWidgetCloseEvent(eventTarget), errorEventActionLoading: errorViewLoading })), viewState.view.type === SwapWidgetViews.SERVICE_UNAVAILABLE && (jsx(ServiceUnavailableErrorView, { onCloseClick: () => sendSwapWidgetCloseEvent(eventTarget), onBackButtonClick: () => {
1732
- viewDispatch({
1733
- payload: { type: ViewActions.UPDATE_VIEW, view: { type: SwapWidgetViews.SWAP } },
1734
- });
1735
- } })), viewState.view.type === SharedViews.TOP_UP_VIEW && (jsx(TopUpView, { analytics: { userJourney: UserJourney.SWAP }, checkout: checkout, provider: provider, widgetEvent: IMTBLWidgetEvents.IMTBL_SWAP_WIDGET_EVENT, showOnrampOption: isOnRampEnabled, showSwapOption: isSwapEnabled, showBridgeOption: isBridgeEnabled, onCloseButtonClick: () => sendSwapWidgetCloseEvent(eventTarget) }))] }) }) }));
1736
- }
1737
-
1738
- export { SwapWidget as default };