@imtbl/sdk 1.2.0 → 1.4.0-alpha.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 (47) hide show
  1. package/dist/blockchain_data.d.ts +4413 -1793
  2. package/dist/blockchain_data.js +398 -78
  3. package/dist/browser/checkout/BridgeWidget-52ba2210.js +2753 -0
  4. package/dist/browser/checkout/BridgeWidget-fffec54f.js +1 -0
  5. package/dist/browser/checkout/OnRampWidget-04677711.js +288 -0
  6. package/dist/browser/checkout/OnRampWidget-b1c1cc52.js +1 -0
  7. package/dist/browser/checkout/SaleWidget-8b73504f.js +1610 -0
  8. package/dist/browser/checkout/SaleWidget-e9cc4cb2.js +2 -0
  9. package/dist/browser/checkout/SpendingCapHero-43c9cf8c.js +9 -0
  10. package/dist/browser/checkout/SpendingCapHero-af71a6f5.js +1 -0
  11. package/dist/browser/checkout/SwapWidget-45ff8dfa.js +1 -0
  12. package/dist/browser/checkout/SwapWidget-e52876ac.js +1461 -0
  13. package/dist/browser/checkout/TopUpView-590c2c4c.js +365 -0
  14. package/dist/browser/checkout/TopUpView-ff09517a.js +1 -0
  15. package/dist/browser/checkout/WalletWidget-9c4c62e4.js +760 -0
  16. package/dist/browser/checkout/WalletWidget-ed0bd774.js +1 -0
  17. package/dist/browser/checkout/auto-track-1bf3a0ee.js +1 -0
  18. package/dist/browser/checkout/balance-e5a26a9d.js +1 -0
  19. package/dist/browser/checkout/balance-e5d785aa.js +182 -0
  20. package/dist/browser/checkout/index-1405c4f4.js +1 -0
  21. package/dist/browser/checkout/index-3272becd.js +1 -0
  22. package/dist/browser/checkout/index-4511315b.js +5638 -0
  23. package/dist/browser/checkout/index-6ee5fac7.js +1 -0
  24. package/dist/browser/checkout/index-8562ab3b.js +1 -0
  25. package/dist/browser/checkout/index-942c822d.js +1 -0
  26. package/dist/browser/checkout/index-9a5fbaa1.js +90 -0
  27. package/dist/browser/checkout/index-d9cfe478.js +773 -0
  28. package/dist/browser/checkout/index.umd-956dbfb2.js +1 -0
  29. package/dist/browser/checkout/is-plan-event-enabled-80f76ce6.js +1 -0
  30. package/dist/browser/checkout/retry-254e3478.js +58 -0
  31. package/dist/browser/checkout/retry-81ddd897.js +1 -0
  32. package/dist/browser/checkout/sdk.js +4 -4
  33. package/dist/browser/checkout/useTransakEvents-54a554e5.js +150 -0
  34. package/dist/browser/checkout/useTransakEvents-8580b8ed.js +2 -0
  35. package/dist/browser/checkout/widgets.js +1 -872
  36. package/dist/checkout.d.ts +28 -2
  37. package/dist/checkout.js +471 -57
  38. package/dist/config.js +1 -1
  39. package/dist/index.browser.js +12 -12
  40. package/dist/index.browser.js.map +1 -1
  41. package/dist/index.cjs +404 -88
  42. package/dist/index.d.ts +4472 -1842
  43. package/dist/index.js +404 -88
  44. package/dist/orderbook.js +1 -1
  45. package/dist/passport.js +415 -3
  46. package/dist/x.js +2 -2
  47. package/package.json +1 -1
@@ -0,0 +1,1610 @@
1
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
+ import { useState, useCallback, createContext, useRef, useContext, useEffect, useMemo, useReducer } from 'react';
3
+ import { RoutingOutcomeType, ItemType, TransactionOrGasType, GasTokenType, FundingStepType, SalePaymentTypes, IMTBLWidgetEvents, ConnectTargetLayer, SwapEventType, BridgeEventType, ConnectEventType, ChainId, BlockExplorerService } from '@imtbl/checkout-sdk';
4
+ import { Environment } from '@imtbl/config';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { M as SaleErrorTypes, k as getL2ChainId, N as NATIVE, x as calculateCryptoToFiat, P as formatFiatString, Q as SmartCheckoutErrorTypes, V as ViewContext, b as ViewActions, R as SaleWidgetViews, T as FundWithSmartCheckoutSubViews, C as ConnectLoaderContext, X as eventTargetReducer, Y as initialEventTargetState, l as getL1ChainId, _ as EventTargetActions, E as EventTargetContext, L as LoadingView, $ as ConnectWidget, a0 as ConnectWidgetViews, a1 as ConnectLoaderActions, t as tokenValueFormat, a as SimpleLayout, H as HeaderNavigation, a2 as sendSaleWidgetCloseEvent, F as FooterLogo, u as useAnalytics, U as UserJourney, a3 as sendSaleSuccessEvent, a4 as sendSaleFailedEvent, a5 as sendSaleTransactionSuccessEvent, a6 as sendSalePaymentMethodEvent, a7 as CenteredBoxContent, a8 as LoadingBox, S as SharedViews, o as orchestrationEvents, f as StatusType, d as StatusView, a9 as widgetTheme, v as viewReducer, i as initialViewState } from './index-4511315b.js';
7
+ import { BigNumber } from 'ethers';
8
+ import { parseUnits } from 'ethers/lib/utils';
9
+ import { MenuItem, Heading, HorizontalMenu, Button, Drawer, Box, Body, Banner, Link } from '@biom3/react';
10
+ import { B as BridgeWidget, g as getChainNameById } from './BridgeWidget-52ba2210.js';
11
+ import SwapWidget from './SwapWidget-e52876ac.js';
12
+ import { C as CryptoFiatContext, a as CryptoFiatActions, b as CryptoFiatProvider } from './TopUpView-590c2c4c.js';
13
+ import { u as useTransakEvents } from './useTransakEvents-54a554e5.js';
14
+ import pako from 'pako';
15
+ import '@biom3/design-tokens';
16
+ import '@imtbl/react-analytics';
17
+ import 'react-dom/client';
18
+ import '@ethersproject/providers';
19
+ import 'i18next';
20
+ import 'i18next-browser-languagedetector';
21
+ import '@imtbl/bridge-sdk';
22
+ import 'axios';
23
+ import './retry-254e3478.js';
24
+ import './balance-e5d785aa.js';
25
+ import '@rive-app/react-canvas';
26
+ import '@imtbl/dex-sdk';
27
+ import './SpendingCapHero-43c9cf8c.js';
28
+ import '@imtbl/cryptofiat';
29
+ import 'url';
30
+
31
+ /* eslint-disable @typescript-eslint/naming-convention */
32
+ const PRIMARY_SALES_API_BASE_URL = {
33
+ [Environment.SANDBOX]: 'https://api.sandbox.immutable.com/v1/primary-sales',
34
+ [Environment.PRODUCTION]: 'https://api.immutable.com/v1/primary-sales',
35
+ };
36
+ var SignCurrencyFilter;
37
+ (function (SignCurrencyFilter) {
38
+ SignCurrencyFilter["CONTRACT_ADDRESS"] = "contract_address";
39
+ SignCurrencyFilter["CURRENCY_SYMBOL"] = "currency_symbol";
40
+ })(SignCurrencyFilter || (SignCurrencyFilter = {}));
41
+ const toSignedProduct = (product, currency, item) => ({
42
+ productId: product.product_id,
43
+ image: item?.image || '',
44
+ qty: item?.qty || 1,
45
+ name: item?.name || '',
46
+ description: item?.description || '',
47
+ currency,
48
+ contractType: product.contract_type,
49
+ collectionAddress: product.collection_address,
50
+ amount: product.detail.map(({ amount }) => amount),
51
+ tokenId: product.detail.map(({ token_id: tokenId }) => tokenId),
52
+ });
53
+ const toSignResponse = (signApiResponse, items) => {
54
+ const { order, transactions } = signApiResponse;
55
+ return {
56
+ order: {
57
+ currency: {
58
+ name: order.currency.name,
59
+ erc20Address: order.currency.erc20_address,
60
+ },
61
+ products: order.products
62
+ .map((product) => toSignedProduct(product, order.currency.name, items.find((item) => item.productId === product.product_id)))
63
+ .reduce((acc, product) => {
64
+ const index = acc.findIndex((n) => n.name === product.name);
65
+ if (index === -1) {
66
+ acc.push({ ...product });
67
+ }
68
+ if (index > -1) {
69
+ acc[index].amount = [...acc[index].amount, ...product.amount];
70
+ acc[index].tokenId = [...acc[index].tokenId, ...product.tokenId];
71
+ }
72
+ return acc;
73
+ }, []),
74
+ totalAmount: Number(order.total_amount),
75
+ },
76
+ transactions: transactions.map((transaction) => ({
77
+ tokenAddress: transaction.contract_address,
78
+ gasEstimate: transaction.gas_estimate,
79
+ methodCall: transaction.method_call,
80
+ params: {
81
+ reference: transaction.params.reference || '',
82
+ amount: transaction.params.amount || 0,
83
+ spender: transaction.params.spender || '',
84
+ },
85
+ rawData: transaction.raw_data,
86
+ })),
87
+ };
88
+ };
89
+ const useSignOrder = (input) => {
90
+ const { provider, items, fromTokenAddress, recipientAddress, env, environmentId, } = input;
91
+ const [signError, setSignError] = useState(undefined);
92
+ const [signResponse, setSignResponse] = useState(undefined);
93
+ const [executeResponse, setExecuteResponse] = useState({
94
+ done: false,
95
+ transactions: [],
96
+ });
97
+ const setExecuteTransactions = (transaction) => {
98
+ setExecuteResponse((prev) => ({
99
+ ...prev,
100
+ transactions: [...prev.transactions, transaction],
101
+ }));
102
+ };
103
+ const setExecuteDone = () => setExecuteResponse((prev) => ({ ...prev, done: true }));
104
+ const setExecuteFailed = () => setExecuteResponse({
105
+ done: false,
106
+ transactions: [],
107
+ });
108
+ const sendTransaction = useCallback(async (to, data, gasLimit, method) => {
109
+ let transactionHash;
110
+ try {
111
+ const signer = provider?.getSigner();
112
+ const gasPrice = await provider?.getGasPrice();
113
+ const txnResponse = await signer?.sendTransaction({
114
+ to,
115
+ data,
116
+ gasPrice,
117
+ gasLimit,
118
+ });
119
+ setExecuteTransactions({ method, hash: txnResponse?.hash });
120
+ await txnResponse?.wait(1);
121
+ transactionHash = txnResponse?.hash || '';
122
+ return [transactionHash, undefined];
123
+ }
124
+ catch (err) {
125
+ const reason = `${err?.reason || err?.message || ''}`.toLowerCase();
126
+ transactionHash = err?.transactionHash;
127
+ let errorType = SaleErrorTypes.WALLET_FAILED;
128
+ if (reason.includes('rejected') && reason.includes('user')) {
129
+ errorType = SaleErrorTypes.WALLET_REJECTED;
130
+ }
131
+ if (reason.includes('failed to submit')
132
+ && reason.includes('highest gas limit')) {
133
+ errorType = SaleErrorTypes.WALLET_REJECTED_NO_FUNDS;
134
+ }
135
+ if (reason.includes('status failed')
136
+ || reason.includes('transaction failed')) {
137
+ errorType = SaleErrorTypes.TRANSACTION_FAILED;
138
+ }
139
+ setSignError({
140
+ type: errorType,
141
+ data: { error: err },
142
+ });
143
+ return [undefined, err];
144
+ }
145
+ }, [provider]);
146
+ const sign = useCallback(async (paymentType) => {
147
+ try {
148
+ const data = {
149
+ recipient_address: recipientAddress,
150
+ payment_type: paymentType,
151
+ currency_filter: SignCurrencyFilter.CONTRACT_ADDRESS,
152
+ currency_value: fromTokenAddress,
153
+ products: items.map((item) => ({
154
+ product_id: item.productId,
155
+ quantity: item.qty,
156
+ })),
157
+ };
158
+ const baseUrl = `${PRIMARY_SALES_API_BASE_URL[env]}/${environmentId}/order/sign`;
159
+ const response = await fetch(baseUrl, {
160
+ method: 'POST',
161
+ headers: {
162
+ 'Content-Type': 'application/json',
163
+ },
164
+ body: JSON.stringify(data),
165
+ });
166
+ const { ok, status } = response;
167
+ if (!ok) {
168
+ const { code } = (await response.json());
169
+ let errorType;
170
+ switch (status) {
171
+ case 400:
172
+ errorType = SaleErrorTypes.SERVICE_BREAKDOWN;
173
+ break;
174
+ case 404:
175
+ if (code === 'insufficient_stock') {
176
+ errorType = SaleErrorTypes.INSUFFICIENT_STOCK;
177
+ }
178
+ else {
179
+ errorType = SaleErrorTypes.PRODUCT_NOT_FOUND;
180
+ }
181
+ break;
182
+ case 429:
183
+ case 500:
184
+ errorType = SaleErrorTypes.DEFAULT;
185
+ break;
186
+ default:
187
+ throw new Error('Unknown error');
188
+ }
189
+ setSignError({ type: errorType });
190
+ return undefined;
191
+ }
192
+ const responseData = toSignResponse(await response.json(), items);
193
+ setSignResponse(responseData);
194
+ return responseData;
195
+ }
196
+ catch (e) {
197
+ setSignError({ type: SaleErrorTypes.DEFAULT, data: { error: e } });
198
+ }
199
+ return undefined;
200
+ }, [items, fromTokenAddress, recipientAddress, environmentId, env, provider]);
201
+ const execute = async (signData, onTxnSuccess, onTxnError) => {
202
+ if (!signData) {
203
+ setSignError({
204
+ type: SaleErrorTypes.DEFAULT,
205
+ data: { reason: 'No sign data' },
206
+ });
207
+ return [];
208
+ }
209
+ let successful = true;
210
+ const execTransactions = [];
211
+ for (const transaction of signData.transactions) {
212
+ const { tokenAddress: to, rawData: data, methodCall: method, gasEstimate, } = transaction;
213
+ // eslint-disable-next-line no-await-in-loop
214
+ const [hash, txnError] = await sendTransaction(to, data, gasEstimate, method);
215
+ if (txnError || !hash) {
216
+ successful = false;
217
+ onTxnError(txnError, execTransactions);
218
+ break;
219
+ }
220
+ execTransactions.push({ method, hash });
221
+ onTxnSuccess({ method, hash });
222
+ }
223
+ (successful ? setExecuteDone : setExecuteFailed)();
224
+ return execTransactions;
225
+ };
226
+ return {
227
+ sign,
228
+ signResponse,
229
+ signError,
230
+ execute,
231
+ executeResponse,
232
+ };
233
+ };
234
+
235
+ const MAX_GAS_LIMIT$1 = '30000000';
236
+ const getItemRequirements = (amount, spenderAddress, tokenAddress) => [
237
+ {
238
+ type: ItemType.ERC20,
239
+ tokenAddress,
240
+ spenderAddress,
241
+ amount,
242
+ },
243
+ ];
244
+ const getGasEstimate = () => ({
245
+ type: TransactionOrGasType.GAS,
246
+ gasToken: {
247
+ type: GasTokenType.NATIVE,
248
+ limit: BigNumber.from(MAX_GAS_LIMIT$1),
249
+ },
250
+ });
251
+ const isUserFractionalBalanceBlocked = async (walletAddress, tokenAddress, amount, checkout, provider) => {
252
+ const chainId = getL2ChainId(checkout.config);
253
+ const balanceResponse = await checkout.getAllBalances({ provider: provider, walletAddress, chainId });
254
+ const zero = BigNumber.from('0');
255
+ const purchaseBalance = balanceResponse.balances.find((balance) => balance.token.address === tokenAddress);
256
+ if (!purchaseBalance) {
257
+ return false;
258
+ }
259
+ const formattedAmount = parseUnits(amount, purchaseBalance.token.decimals);
260
+ if (purchaseBalance.balance.gt(zero) && purchaseBalance.balance.lt(formattedAmount)) {
261
+ return true;
262
+ }
263
+ const isPassport = !!provider?.provider?.isPassport;
264
+ if (isPassport) {
265
+ return false;
266
+ }
267
+ const imxBalance = balanceResponse.balances.find((balance) => balance.token.address === NATIVE);
268
+ const imxBalanceAmount = imxBalance ? imxBalance.balance : BigNumber.from('0');
269
+ if (imxBalanceAmount.gte(zero) && imxBalanceAmount.lt(BigNumber.from(MAX_GAS_LIMIT$1))) {
270
+ return true;
271
+ }
272
+ return false;
273
+ };
274
+ const fundingRouteFees = (fundingRoute, conversions) => {
275
+ const fees = [];
276
+ for (const step of fundingRoute.steps) {
277
+ switch (step.type) {
278
+ case FundingStepType.BRIDGE:
279
+ fees.push(step.fees.approvalGasFee);
280
+ fees.push(...step.fees.bridgeFees);
281
+ fees.push(step.fees.bridgeGasFee);
282
+ break;
283
+ case FundingStepType.SWAP:
284
+ fees.push(step.fees.approvalGasFee);
285
+ fees.push(...step.fees.swapFees);
286
+ fees.push(step.fees.swapGasFee);
287
+ break;
288
+ }
289
+ }
290
+ let totalUsd = 0;
291
+ for (const fee of fees) {
292
+ if (fee.token) {
293
+ const feeUsd = calculateCryptoToFiat(fee.formattedAmount, fee.token.symbol, conversions);
294
+ totalUsd += parseFloat(feeUsd);
295
+ }
296
+ }
297
+ return formatFiatString(totalUsd);
298
+ };
299
+ const smartCheckoutTokensList = (smartCheckoutResult) => {
300
+ if (smartCheckoutResult.sufficient
301
+ || smartCheckoutResult.router.routingOutcome.type !== RoutingOutcomeType.ROUTES_FOUND) {
302
+ return [];
303
+ }
304
+ const tokenSymbols = [];
305
+ for (const requirement of smartCheckoutResult.transactionRequirements) {
306
+ const { token } = requirement.current;
307
+ if (!tokenSymbols.includes(token.symbol)) {
308
+ tokenSymbols.push(token.symbol);
309
+ }
310
+ }
311
+ for (const fundingRoute of smartCheckoutResult.router.routingOutcome.fundingRoutes) {
312
+ for (const step of fundingRoute.steps) {
313
+ if (!tokenSymbols.includes(step.fundingItem.token.symbol)) {
314
+ tokenSymbols.push(step.fundingItem.token.symbol);
315
+ }
316
+ }
317
+ }
318
+ return tokenSymbols;
319
+ };
320
+ const filterSmartCheckoutResult = (smartCheckoutResult) => {
321
+ if (smartCheckoutResult.sufficient
322
+ || smartCheckoutResult.router.routingOutcome.type !== RoutingOutcomeType.ROUTES_FOUND) {
323
+ return smartCheckoutResult;
324
+ }
325
+ const filteredFundingRoutes = smartCheckoutResult.router.routingOutcome.fundingRoutes
326
+ .filter((route) => !route.steps.some((step) => step.type !== FundingStepType.SWAP));
327
+ let routingOutcome;
328
+ if (filteredFundingRoutes.length === 0) {
329
+ routingOutcome = {
330
+ type: RoutingOutcomeType.NO_ROUTES_FOUND,
331
+ message: 'Smart Checkout did not find any Swap routes to fulfill the transaction',
332
+ };
333
+ }
334
+ else {
335
+ routingOutcome = {
336
+ type: RoutingOutcomeType.ROUTES_FOUND,
337
+ fundingRoutes: filteredFundingRoutes,
338
+ };
339
+ }
340
+ return {
341
+ ...smartCheckoutResult,
342
+ router: {
343
+ ...smartCheckoutResult.router,
344
+ routingOutcome,
345
+ },
346
+ };
347
+ };
348
+
349
+ const useSmartCheckout = ({ checkout, provider, items, amount, tokenAddress, }) => {
350
+ const [smartCheckoutResult, setSmartCheckoutResult] = useState(undefined);
351
+ const [smartCheckoutError, setSmartCheckoutError] = useState(undefined);
352
+ const smartCheckout = useCallback(async () => {
353
+ try {
354
+ const signer = provider?.getSigner();
355
+ const spenderAddress = await signer?.getAddress() || '';
356
+ const userFractionalBalanceBlocked = await isUserFractionalBalanceBlocked(spenderAddress, tokenAddress, amount, checkout, provider);
357
+ if (userFractionalBalanceBlocked) {
358
+ throw new Error(SmartCheckoutErrorTypes.FRACTIONAL_BALANCE_BLOCKED);
359
+ }
360
+ const itemRequirements = getItemRequirements(amount, spenderAddress, tokenAddress);
361
+ const gasEstimate = getGasEstimate();
362
+ const res = await checkout?.smartCheckout({
363
+ provider: provider,
364
+ itemRequirements,
365
+ transactionOrGasAmount: gasEstimate,
366
+ });
367
+ if (!res) {
368
+ throw new Error();
369
+ }
370
+ const result = { ...res };
371
+ const filteredSmartCheckoutResult = filterSmartCheckoutResult(result);
372
+ setSmartCheckoutResult(filteredSmartCheckoutResult);
373
+ return result;
374
+ }
375
+ catch (err) {
376
+ setSmartCheckoutError({
377
+ type: SaleErrorTypes.SMART_CHECKOUT_ERROR,
378
+ data: { error: err },
379
+ });
380
+ }
381
+ return undefined;
382
+ }, [checkout, provider, items, amount, tokenAddress]);
383
+ return {
384
+ smartCheckout, smartCheckoutResult, smartCheckoutError,
385
+ };
386
+ };
387
+
388
+ // eslint-disable-next-line @typescript-eslint/naming-convention
389
+ const SaleContext = createContext({
390
+ items: [],
391
+ amount: '',
392
+ fromTokenAddress: '',
393
+ collectionName: '',
394
+ provider: undefined,
395
+ checkout: undefined,
396
+ environmentId: '',
397
+ env: '',
398
+ recipientAddress: '',
399
+ recipientEmail: '',
400
+ sign: () => Promise.resolve(undefined),
401
+ execute: () => Promise.resolve([]),
402
+ signResponse: undefined,
403
+ signError: undefined,
404
+ executeResponse: undefined,
405
+ passport: undefined,
406
+ isPassportWallet: false,
407
+ paymentMethod: undefined,
408
+ setPaymentMethod: () => { },
409
+ goBackToPaymentMethods: () => { },
410
+ goToErrorView: () => { },
411
+ goToSuccessView: () => { },
412
+ config: {},
413
+ querySmartCheckout: () => Promise.resolve(undefined),
414
+ smartCheckoutResult: undefined,
415
+ fundingRoutes: [],
416
+ disabledPaymentTypes: [],
417
+ invalidParameters: false,
418
+ });
419
+ SaleContext.displayName = 'SaleSaleContext';
420
+ /** Max attemps to retry with same payment method */
421
+ const MAX_ERROR_RETRIES = 1;
422
+ function SaleContextProvider(props) {
423
+ const { children, value: { config, env, environmentId, items, amount, fromTokenAddress, provider, checkout, passport, collectionName, }, } = props;
424
+ const errorRetries = useRef(0);
425
+ const { viewDispatch } = useContext(ViewContext);
426
+ const [{ recipientEmail, recipientAddress }, setUserInfo] = useState({
427
+ recipientEmail: '',
428
+ recipientAddress: '',
429
+ });
430
+ const [paymentMethod, setPaymentMethod] = useState(undefined);
431
+ const [fundingRoutes, setFundingRoutes] = useState([]);
432
+ const [disabledPaymentTypes, setDisabledPaymentTypes] = useState([]);
433
+ const [invalidParameters, setInvalidParameters] = useState(false);
434
+ const goBackToPaymentMethods = useCallback((type, showInsufficientCoinsBanner) => {
435
+ setPaymentMethod(type);
436
+ viewDispatch({
437
+ payload: {
438
+ type: ViewActions.UPDATE_VIEW,
439
+ view: {
440
+ type: SaleWidgetViews.PAYMENT_METHODS,
441
+ data: {
442
+ showInsufficientCoinsBanner,
443
+ },
444
+ },
445
+ },
446
+ });
447
+ }, []);
448
+ useEffect(() => {
449
+ const getUserInfo = async () => {
450
+ const signer = provider?.getSigner();
451
+ const address = (await signer?.getAddress()) || '';
452
+ const email = (await passport?.getUserInfo())?.email || '';
453
+ setUserInfo({ recipientEmail: email, recipientAddress: address });
454
+ };
455
+ getUserInfo();
456
+ }, [provider]);
457
+ const { sign: signOrder, execute, signResponse, signError, executeResponse, } = useSignOrder({
458
+ items,
459
+ provider,
460
+ fromTokenAddress,
461
+ recipientAddress,
462
+ environmentId,
463
+ env,
464
+ });
465
+ const sign = useCallback(async (type, callback) => {
466
+ const response = await signOrder(type);
467
+ if (!response)
468
+ return undefined;
469
+ callback?.(response);
470
+ return response;
471
+ }, [signOrder]);
472
+ const goToErrorView = useCallback((errorType, data = {}) => {
473
+ errorRetries.current += 1;
474
+ if (errorRetries.current > MAX_ERROR_RETRIES) {
475
+ errorRetries.current = 0;
476
+ setPaymentMethod(undefined);
477
+ }
478
+ viewDispatch({
479
+ payload: {
480
+ type: ViewActions.UPDATE_VIEW,
481
+ view: {
482
+ type: SaleWidgetViews.SALE_FAIL,
483
+ data: {
484
+ ...data,
485
+ errorType,
486
+ paymentMethod,
487
+ transactions: executeResponse.transactions,
488
+ },
489
+ },
490
+ },
491
+ });
492
+ }, [paymentMethod, setPaymentMethod, executeResponse]);
493
+ const goToSuccessView = useCallback((data) => {
494
+ viewDispatch({
495
+ payload: {
496
+ type: ViewActions.UPDATE_VIEW,
497
+ view: {
498
+ type: SaleWidgetViews.SALE_SUCCESS,
499
+ data: {
500
+ paymentMethod,
501
+ transactions: executeResponse.transactions,
502
+ ...data,
503
+ },
504
+ },
505
+ },
506
+ });
507
+ }, [[paymentMethod, executeResponse]]);
508
+ useEffect(() => {
509
+ if (!signError)
510
+ return;
511
+ goToErrorView(signError.type, signError.data);
512
+ }, [signError]);
513
+ const { smartCheckout, smartCheckoutResult, smartCheckoutError } = useSmartCheckout({
514
+ provider,
515
+ checkout,
516
+ items,
517
+ amount,
518
+ tokenAddress: fromTokenAddress,
519
+ });
520
+ useEffect(() => {
521
+ if (!smartCheckoutError)
522
+ return;
523
+ if (smartCheckoutError.data?.error?.message === SmartCheckoutErrorTypes.FRACTIONAL_BALANCE_BLOCKED) {
524
+ setDisabledPaymentTypes([SalePaymentTypes.CRYPTO]);
525
+ goBackToPaymentMethods(undefined, true);
526
+ return;
527
+ }
528
+ goToErrorView(smartCheckoutError.type, smartCheckoutError.data);
529
+ }, [smartCheckoutError]);
530
+ const querySmartCheckout = useCallback(async (callback) => {
531
+ const result = await smartCheckout();
532
+ callback?.(result);
533
+ return result;
534
+ }, [smartCheckout]);
535
+ useEffect(() => {
536
+ if (!smartCheckoutResult) {
537
+ return;
538
+ }
539
+ if (smartCheckoutResult.sufficient) {
540
+ sign(SalePaymentTypes.CRYPTO);
541
+ viewDispatch({
542
+ payload: {
543
+ type: ViewActions.UPDATE_VIEW,
544
+ view: {
545
+ type: SaleWidgetViews.PAY_WITH_COINS,
546
+ },
547
+ },
548
+ });
549
+ }
550
+ if (!smartCheckoutResult.sufficient) {
551
+ switch (smartCheckoutResult.router.routingOutcome.type) {
552
+ case RoutingOutcomeType.ROUTES_FOUND:
553
+ setFundingRoutes(smartCheckoutResult.router.routingOutcome.fundingRoutes);
554
+ viewDispatch({
555
+ payload: {
556
+ type: ViewActions.UPDATE_VIEW,
557
+ view: {
558
+ type: SaleWidgetViews.FUND_WITH_SMART_CHECKOUT,
559
+ subView: FundWithSmartCheckoutSubViews.FUNDING_ROUTE_SELECT,
560
+ },
561
+ },
562
+ });
563
+ break;
564
+ case RoutingOutcomeType.NO_ROUTES_FOUND:
565
+ case RoutingOutcomeType.NO_ROUTE_OPTIONS:
566
+ default:
567
+ setFundingRoutes([]);
568
+ setPaymentMethod(undefined);
569
+ setDisabledPaymentTypes([SalePaymentTypes.CRYPTO]);
570
+ goBackToPaymentMethods(undefined, true);
571
+ break;
572
+ }
573
+ }
574
+ }, [smartCheckoutResult]);
575
+ useEffect(() => {
576
+ const invalidItems = !items || items.length === 0;
577
+ const invalidAmount = !amount || amount === '0';
578
+ const invalidFromTokenAddress = !fromTokenAddress || !fromTokenAddress.startsWith('0x');
579
+ if (invalidItems || invalidAmount || invalidFromTokenAddress || !collectionName || !environmentId) {
580
+ setInvalidParameters(true);
581
+ }
582
+ }, [items, amount, fromTokenAddress, collectionName, environmentId]);
583
+ const values = useMemo(() => ({
584
+ config,
585
+ items,
586
+ amount,
587
+ fromTokenAddress,
588
+ sign,
589
+ signResponse,
590
+ signError,
591
+ execute,
592
+ executeResponse,
593
+ environmentId,
594
+ collectionName,
595
+ env,
596
+ provider,
597
+ checkout,
598
+ recipientAddress,
599
+ recipientEmail,
600
+ paymentMethod,
601
+ setPaymentMethod,
602
+ goBackToPaymentMethods,
603
+ goToErrorView,
604
+ goToSuccessView,
605
+ isPassportWallet: !!provider?.provider?.isPassport,
606
+ querySmartCheckout,
607
+ smartCheckoutResult,
608
+ fundingRoutes,
609
+ disabledPaymentTypes,
610
+ invalidParameters,
611
+ }), [
612
+ config,
613
+ env,
614
+ environmentId,
615
+ items,
616
+ amount,
617
+ fromTokenAddress,
618
+ collectionName,
619
+ provider,
620
+ checkout,
621
+ recipientAddress,
622
+ recipientEmail,
623
+ signResponse,
624
+ signError,
625
+ executeResponse,
626
+ paymentMethod,
627
+ goBackToPaymentMethods,
628
+ goToErrorView,
629
+ goToSuccessView,
630
+ sign,
631
+ querySmartCheckout,
632
+ smartCheckoutResult,
633
+ fundingRoutes,
634
+ disabledPaymentTypes,
635
+ invalidParameters,
636
+ ]);
637
+ return jsx(SaleContext.Provider, { value: values, children: children });
638
+ }
639
+ function useSaleContext() {
640
+ return useContext(SaleContext);
641
+ }
642
+
643
+ var FundingRouteExecuteViews;
644
+ (function (FundingRouteExecuteViews) {
645
+ FundingRouteExecuteViews["LOADING"] = "LOADING";
646
+ FundingRouteExecuteViews["EXECUTE_BRIDGE"] = "EXECUTE_BRIDGE";
647
+ FundingRouteExecuteViews["EXECUTE_SWAP"] = "EXECUTE_SWAP";
648
+ FundingRouteExecuteViews["SWITCH_NETWORK_ETH"] = "SWITCH_NETWORK_ETH";
649
+ FundingRouteExecuteViews["SWITCH_NETWORK_ZKEVM"] = "SWITCH_NETWORK_ZKEVM";
650
+ })(FundingRouteExecuteViews || (FundingRouteExecuteViews = {}));
651
+ function FundingRouteExecute({ fundingRouteStep, onFundingRouteExecuted }) {
652
+ const { t } = useTranslation();
653
+ const { config, provider, checkout, fromTokenAddress: requiredTokenAddress, } = useSaleContext();
654
+ const { viewDispatch } = useContext(ViewContext);
655
+ const { connectLoaderDispatch } = useContext(ConnectLoaderContext);
656
+ const [swapParams, setSwapParams] = useState(undefined);
657
+ const [bridgeParams, setBridgeParams] = useState(undefined);
658
+ const [view, setView] = useState(FundingRouteExecuteViews.LOADING);
659
+ const nextView = useRef(false);
660
+ const stepSuccess = useRef(undefined);
661
+ const stepFailed = useRef(undefined);
662
+ const [eventTargetState, eventTargetDispatch] = useReducer(eventTargetReducer, initialEventTargetState);
663
+ const eventTargetReducerValues = useMemo(() => ({ eventTargetState, eventTargetDispatch }), [eventTargetState, eventTargetDispatch]);
664
+ const eventTarget = new EventTarget();
665
+ const sendFailEvent = (errorData) => {
666
+ viewDispatch({
667
+ payload: {
668
+ type: ViewActions.UPDATE_VIEW,
669
+ view: {
670
+ type: SaleWidgetViews.SALE_FAIL,
671
+ data: {
672
+ errorType: SaleErrorTypes.SMART_CHECKOUT_EXECUTE_ERROR,
673
+ errorData,
674
+ },
675
+ },
676
+ },
677
+ });
678
+ };
679
+ const handleStep = useCallback(async (step) => {
680
+ if (!checkout || !provider) {
681
+ throw new Error('checkout or provider not available.');
682
+ }
683
+ const network = await checkout.getNetworkInfo({
684
+ provider,
685
+ });
686
+ if (step.type === FundingStepType.BRIDGE) {
687
+ setBridgeParams({
688
+ tokenAddress: step.fundingItem.token.address,
689
+ amount: step.fundingItem.fundsRequired.formattedAmount,
690
+ });
691
+ if (network.chainId === getL1ChainId(checkout.config)) {
692
+ setView(FundingRouteExecuteViews.EXECUTE_BRIDGE);
693
+ return;
694
+ }
695
+ nextView.current = FundingRouteExecuteViews.EXECUTE_BRIDGE;
696
+ setView(FundingRouteExecuteViews.SWITCH_NETWORK_ETH);
697
+ }
698
+ if (step.type === FundingStepType.SWAP) {
699
+ setSwapParams({
700
+ amount: step.fundingItem.fundsRequired.formattedAmount,
701
+ fromTokenAddress: step.fundingItem.token.address,
702
+ toTokenAddress: requiredTokenAddress,
703
+ });
704
+ if (network.chainId === getL2ChainId(checkout.config)) {
705
+ setView(FundingRouteExecuteViews.EXECUTE_SWAP);
706
+ return;
707
+ }
708
+ nextView.current = FundingRouteExecuteViews.EXECUTE_SWAP;
709
+ setView(FundingRouteExecuteViews.SWITCH_NETWORK_ZKEVM);
710
+ }
711
+ }, [provider, checkout]);
712
+ useEffect(() => {
713
+ if (!fundingRouteStep) {
714
+ return;
715
+ }
716
+ try {
717
+ handleStep(fundingRouteStep);
718
+ }
719
+ catch (err) {
720
+ sendFailEvent(err);
721
+ }
722
+ }, [fundingRouteStep]);
723
+ const onCloseWidget = () => {
724
+ // Need to check SUCCESS first, as it's possible for widget to emit both FAILED and SUCCESS.
725
+ if (stepSuccess.current) {
726
+ stepSuccess.current = undefined;
727
+ stepFailed.current = undefined;
728
+ onFundingRouteExecuted();
729
+ }
730
+ else {
731
+ sendFailEvent(stepFailed.current);
732
+ }
733
+ };
734
+ const handleCustomEvent = (event) => {
735
+ switch (event.detail.type) {
736
+ case BridgeEventType.TRANSACTION_SENT:
737
+ case SwapEventType.SUCCESS: {
738
+ const successEvent = event.detail.data;
739
+ stepSuccess.current = successEvent;
740
+ break;
741
+ }
742
+ case BridgeEventType.FAILURE:
743
+ case SwapEventType.FAILURE: {
744
+ // On FAILURE, widget will prompt user to try again.
745
+ // We need to know if it failed though when they close the widget
746
+ const failureEvent = event.detail.data;
747
+ stepFailed.current = failureEvent;
748
+ break;
749
+ }
750
+ case BridgeEventType.CLOSE_WIDGET:
751
+ case SwapEventType.CLOSE_WIDGET: {
752
+ onCloseWidget();
753
+ break;
754
+ }
755
+ case SwapEventType.REJECTED:
756
+ }
757
+ };
758
+ const handleConnectEvent = (event) => {
759
+ switch (event.detail.type) {
760
+ case ConnectEventType.SUCCESS: {
761
+ const eventData = event.detail.data;
762
+ connectLoaderDispatch({
763
+ payload: {
764
+ type: ConnectLoaderActions.SET_PROVIDER,
765
+ provider: eventData.provider,
766
+ },
767
+ });
768
+ if (nextView.current !== false) {
769
+ setView(nextView.current);
770
+ nextView.current = false;
771
+ }
772
+ break;
773
+ }
774
+ case ConnectEventType.FAILURE:
775
+ case ConnectEventType.CLOSE_WIDGET:
776
+ default:
777
+ onCloseWidget();
778
+ break;
779
+ }
780
+ };
781
+ useEffect(() => {
782
+ // Handle the connect event when switching networks
783
+ eventTarget.addEventListener(IMTBLWidgetEvents.IMTBL_CONNECT_WIDGET_EVENT, handleConnectEvent);
784
+ // Handle the other widget events
785
+ eventTarget.addEventListener(IMTBLWidgetEvents.IMTBL_BRIDGE_WIDGET_EVENT, handleCustomEvent);
786
+ eventTarget.addEventListener(IMTBLWidgetEvents.IMTBL_SWAP_WIDGET_EVENT, handleCustomEvent);
787
+ // Remove the custom event listener when the component unmounts
788
+ return () => {
789
+ eventTarget.removeEventListener(IMTBLWidgetEvents.IMTBL_CONNECT_WIDGET_EVENT, handleConnectEvent);
790
+ eventTarget.removeEventListener(IMTBLWidgetEvents.IMTBL_BRIDGE_WIDGET_EVENT, handleCustomEvent);
791
+ eventTarget.removeEventListener(IMTBLWidgetEvents.IMTBL_SWAP_WIDGET_EVENT, handleCustomEvent);
792
+ };
793
+ }, []);
794
+ useEffect(() => {
795
+ eventTargetDispatch({
796
+ payload: {
797
+ type: EventTargetActions.SET_EVENT_TARGET,
798
+ eventTarget,
799
+ },
800
+ });
801
+ }, [checkout]);
802
+ return (jsxs(EventTargetContext.Provider, { value: eventTargetReducerValues, children: [view === FundingRouteExecuteViews.LOADING && (jsx(LoadingView, { loadingText: t('views.FUND_WITH_SMART_CHECKOUT.loading.checkingBalances') })), view === FundingRouteExecuteViews.EXECUTE_BRIDGE && (jsx(BridgeWidget, { ...bridgeParams, config: config, checkout: checkout })), view === FundingRouteExecuteViews.EXECUTE_SWAP && (jsx(SwapWidget, { ...swapParams, config: config })), view === FundingRouteExecuteViews.SWITCH_NETWORK_ETH && (jsx(ConnectWidget, { config: config, targetLayer: ConnectTargetLayer.LAYER1, web3Provider: provider, checkout: checkout, deepLink: ConnectWidgetViews.SWITCH_NETWORK })), view === FundingRouteExecuteViews.SWITCH_NETWORK_ZKEVM && (jsx(ConnectWidget, { config: config, targetLayer: ConnectTargetLayer.LAYER2, web3Provider: provider, checkout: checkout, deepLink: ConnectWidgetViews.SWITCH_NETWORK }))] }));
803
+ }
804
+
805
+ // Taken from packages/checkout/widgets-lib/src/widgets/wallet/components/NetworkMenu/NetworkMenu.tsx
806
+ const networkIcon = {
807
+ [ChainId.IMTBL_ZKEVM_DEVNET]: 'Immutable',
808
+ [ChainId.IMTBL_ZKEVM_MAINNET]: 'Immutable',
809
+ [ChainId.IMTBL_ZKEVM_TESTNET]: 'Immutable',
810
+ [ChainId.ETHEREUM]: 'EthToken',
811
+ [ChainId.SEPOLIA]: 'EthToken',
812
+ };
813
+ const logoColour = {
814
+ [ChainId.IMTBL_ZKEVM_DEVNET]: 'base.color.text.link.primary',
815
+ [ChainId.IMTBL_ZKEVM_TESTNET]: 'base.color.text.link.primary',
816
+ [ChainId.IMTBL_ZKEVM_MAINNET]: 'base.color.text.link.primary',
817
+ [ChainId.ETHEREUM]: 'base.color.accent.5',
818
+ [ChainId.SEPOLIA]: 'base.color.accent.5',
819
+ };
820
+ function FundingRouteMenuItem({ onClick, fundingRoute, toggleVisible, selected, size = 'small', }) {
821
+ const { t } = useTranslation();
822
+ const firstFundingStep = fundingRoute.steps[0];
823
+ const { cryptoFiatState } = useContext(CryptoFiatContext);
824
+ const { isPassportWallet } = useSaleContext();
825
+ const [feesUsd, setFeesUsd] = useState(undefined);
826
+ const [usdBalance, setUsdBalance] = useState(undefined);
827
+ useEffect(() => {
828
+ if (!cryptoFiatState.conversions) {
829
+ return;
830
+ }
831
+ try {
832
+ setFeesUsd(fundingRouteFees(fundingRoute, cryptoFiatState.conversions));
833
+ setUsdBalance(calculateCryptoToFiat(firstFundingStep.fundingItem.userBalance.formattedBalance, firstFundingStep.fundingItem.token.symbol, cryptoFiatState.conversions));
834
+ }
835
+ catch {
836
+ setFeesUsd(undefined);
837
+ setUsdBalance(undefined);
838
+ }
839
+ }, [cryptoFiatState, fundingRoute]);
840
+ const networkLabel = () => (jsxs(HorizontalMenu.Button, { rc: jsx("a", {}), sx: {
841
+ pointerEvents: 'none',
842
+ cursor: 'default',
843
+ height: '100%',
844
+ marginLeft: 'base.spacing.x2',
845
+ fontSize: 'base.text.body.xxSmall.regular.fontSize',
846
+ fontWeight: 'base.text.body.xxSmall.regular.fontWeight',
847
+ color: 'base.color.brand.4',
848
+ paddingLeft: 'base.spacing.x2',
849
+ paddingRight: 'base.spacing.x2',
850
+ }, size: "small", children: [jsx(Button.Icon, { icon: networkIcon[firstFundingStep.chainId], sx: {
851
+ width: '14px',
852
+ fill: logoColour[firstFundingStep.chainId],
853
+ } }), getChainNameById(firstFundingStep.chainId)] }));
854
+ return (jsxs(MenuItem, { emphasized: true, testId: "funding-route-menu-item", onClick: onClick, selected: selected, size: size, children: [toggleVisible && jsx(MenuItem.IntentIcon, { icon: "ChevronExpand" }), jsx(MenuItem.FramedIcon, { icon: "Coins", circularFrame: true }), jsx(MenuItem.PriceDisplay, { use: jsx(Heading, { size: "xSmall" }), fiatAmount: `${t('views.FUND_WITH_SMART_CHECKOUT.currency.usdEstimate')}${usdBalance}`, price: tokenValueFormat(firstFundingStep.fundingItem.userBalance.formattedBalance) }), jsxs(MenuItem.Label, { sx: { display: 'flex', wordBreak: 'default' }, children: [firstFundingStep.fundingItem.token.symbol, isPassportWallet ? null : networkLabel()] }), jsxs(MenuItem.Caption, { children: ["Fees \u2248 USD $", feesUsd] })] }));
855
+ }
856
+
857
+ function FundingRouteDrawer({ visible, onCloseDrawer, fundingRoutes, activeFundingRouteIndex, }) {
858
+ const { t } = useTranslation();
859
+ const onClickMenuItem = (selectedFundingRouteIndex) => {
860
+ onCloseDrawer(selectedFundingRouteIndex);
861
+ };
862
+ return (jsx(Drawer, { size: "full", onCloseDrawer: () => onCloseDrawer(activeFundingRouteIndex), visible: visible, showHeaderBar: true, headerBarTitle: t('views.FUND_WITH_SMART_CHECKOUT.fundingRouteDrawer.header'), children: jsx(Drawer.Content, { children: fundingRoutes.map((fundingRoute, i) => (jsx(FundingRouteMenuItem, { onClick: () => onClickMenuItem(i), fundingRoute: fundingRoute, selected: activeFundingRouteIndex === i, size: "medium" }, fundingRoute.steps[0].fundingItem.type + fundingRoute.steps[0].fundingItem.token))) }) }));
863
+ }
864
+
865
+ function PurchaseMenuItem({ fundingRoute, collectionName }) {
866
+ const { t } = useTranslation();
867
+ const { items } = useSaleContext();
868
+ const firstItem = items[0];
869
+ const firstFundingStep = fundingRoute.steps[0];
870
+ const { cryptoFiatState } = useContext(CryptoFiatContext);
871
+ const [usdPurchaseAmount, setUsdPurchaseAmount] = useState(undefined);
872
+ useEffect(() => {
873
+ if (!cryptoFiatState.conversions) {
874
+ return;
875
+ }
876
+ try {
877
+ setUsdPurchaseAmount(calculateCryptoToFiat(firstFundingStep.fundingItem.fundsRequired.formattedAmount, firstFundingStep.fundingItem.token.symbol, cryptoFiatState.conversions));
878
+ }
879
+ catch {
880
+ setUsdPurchaseAmount(undefined);
881
+ }
882
+ }, [cryptoFiatState, fundingRoute]);
883
+ return (jsxs(MenuItem, { emphasized: true, testId: "funding-route-purchase-item", size: "medium", children: [jsx(MenuItem.FramedImage, { imageUrl: firstItem?.image }), jsx(MenuItem.PriceDisplay, { use: jsx(Heading, { size: "xSmall" }), price: `${firstFundingStep?.fundingItem.token.symbol}
884
+ ${tokenValueFormat(firstFundingStep.fundingItem.fundsRequired.formattedAmount)}`, fiatAmount: `${t('views.FUND_WITH_SMART_CHECKOUT.currency.usdEstimate')}${usdPurchaseAmount}` }), jsxs(MenuItem.Label, { children: [firstItem?.name, firstItem?.qty > 1 ? ` x${firstItem.qty}` : null] }), jsx(MenuItem.Caption, { children: collectionName })] }, firstItem?.name));
885
+ }
886
+
887
+ function FundingRouteSelect({ fundingRoutes, collectionName, onFundingRouteSelected, }) {
888
+ const { t } = useTranslation();
889
+ const [smartCheckoutDrawerVisible, setSmartCheckoutDrawerVisible] = useState(false);
890
+ const [activeFundingRouteIndex, setActiveFundingRouteIndex] = useState(0);
891
+ const { viewDispatch } = useContext(ViewContext);
892
+ const { eventTargetState: { eventTarget }, } = useContext(EventTargetContext);
893
+ const { goBackToPaymentMethods } = useSaleContext();
894
+ const onClickContinue = () => {
895
+ onFundingRouteSelected(fundingRoutes[activeFundingRouteIndex]);
896
+ viewDispatch({
897
+ payload: {
898
+ type: ViewActions.UPDATE_VIEW,
899
+ view: {
900
+ type: SaleWidgetViews.FUND_WITH_SMART_CHECKOUT,
901
+ subView: FundWithSmartCheckoutSubViews.FUNDING_ROUTE_EXECUTE,
902
+ },
903
+ },
904
+ });
905
+ };
906
+ const closeDrawer = (selectedFundingRouteIndex) => {
907
+ setActiveFundingRouteIndex(selectedFundingRouteIndex);
908
+ setSmartCheckoutDrawerVisible(false);
909
+ };
910
+ const onSmartCheckoutDropdownClick = () => {
911
+ setSmartCheckoutDrawerVisible(true);
912
+ };
913
+ return (jsxs(SimpleLayout, { testId: "funding-route-select", header: (jsx(HeaderNavigation, { onCloseButtonClick: () => sendSaleWidgetCloseEvent(eventTarget) })), footer: jsx(FooterLogo, {}), children: [jsxs(Box, { sx: {
914
+ display: 'flex',
915
+ flexDirection: 'column',
916
+ paddingX: 'base.spacing.x2',
917
+ paddingY: 'base.spacing.x8',
918
+ rowGap: 'base.spacing.x4',
919
+ height: '100%',
920
+ }, children: [jsx(Heading, { size: "small", children: t('views.FUND_WITH_SMART_CHECKOUT.fundingRouteSelect.heading') }), fundingRoutes.length === 0
921
+ ? [
922
+ jsx(Body, { size: "small", children: t('views.FUND_WITH_SMART_CHECKOUT.fundingRouteSelect.noRoutesAvailable') }, "noRoutesAvailableText"),
923
+ jsx(Button, { variant: "tertiary", children: t('views.FUND_WITH_SMART_CHECKOUT.fundingRouteSelect.payWithCard') }, "payWithCardButton"),
924
+ ]
925
+ : [
926
+ jsx(FundingRouteMenuItem, { "data-testid": "funding-route-select-selected-route", onClick: fundingRoutes.length > 1
927
+ ? onSmartCheckoutDropdownClick
928
+ : () => { }, fundingRoute: fundingRoutes[activeFundingRouteIndex], selected: true, toggleVisible: fundingRoutes.length > 1 }, "selectedFundingRouteMenuItem"),
929
+ jsx(PurchaseMenuItem, { fundingRoute: fundingRoutes[activeFundingRouteIndex], collectionName: collectionName }, "purchaseMenuItem"),
930
+ jsx(Button, { sx: { mt: 'auto' }, variant: "primary", onClick: onClickContinue, children: t('views.FUND_WITH_SMART_CHECKOUT.fundingRouteSelect.continue') }, "continueButton"),
931
+ jsx(Button, { variant: "tertiary", onClick: () => goBackToPaymentMethods(), children: t('views.FUND_WITH_SMART_CHECKOUT.fundingRouteSelect.payWithCardInstead') }, "payWithCardButton"),
932
+ ]] }), jsx(FundingRouteDrawer, { visible: smartCheckoutDrawerVisible, onCloseDrawer: closeDrawer, fundingRoutes: fundingRoutes, activeFundingRouteIndex: activeFundingRouteIndex })] }));
933
+ }
934
+
935
+ const toStringifyTransactions = (transactions) => transactions
936
+ .map(({ method, hash }) => `${method}: ${hash}`).join(' | ');
937
+ const toPascalCase = (str) => str
938
+ .split('_')
939
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
940
+ .join('');
941
+
942
+ const useSaleEvent = () => {
943
+ const { track, page } = useAnalytics();
944
+ const { recipientAddress: userId, recipientEmail: email, signResponse, paymentMethod, } = useSaleContext();
945
+ const { eventTargetState: { eventTarget }, } = useContext(EventTargetContext);
946
+ const defaultView = SaleWidgetViews.PAYMENT_METHODS;
947
+ const commonProps = {
948
+ location: 'web',
949
+ userJourney: UserJourney.SALE,
950
+ };
951
+ const userProps = {
952
+ email,
953
+ userId,
954
+ paymentMethod,
955
+ };
956
+ const orderProps = {
957
+ amount: signResponse?.order.totalAmount,
958
+ currency: signResponse?.order.currency.name,
959
+ };
960
+ const sendCloseEvent = (screen = defaultView, controlType = 'Button', action = 'Pressed') => {
961
+ track({
962
+ ...commonProps,
963
+ screen: toPascalCase(screen),
964
+ control: 'Close',
965
+ controlType,
966
+ action,
967
+ extras: {
968
+ ...userProps,
969
+ },
970
+ });
971
+ sendSaleWidgetCloseEvent(eventTarget);
972
+ };
973
+ const sendSuccessEvent = (screen = defaultView, transactions = [], details) => {
974
+ track({
975
+ ...commonProps,
976
+ screen: toPascalCase(screen),
977
+ control: 'Success',
978
+ controlType: 'Event',
979
+ action: 'Succeeded',
980
+ extras: {
981
+ ...details,
982
+ ...userProps,
983
+ transactions: toStringifyTransactions(transactions),
984
+ ...orderProps,
985
+ paymentMethod,
986
+ },
987
+ });
988
+ sendSaleSuccessEvent(eventTarget, paymentMethod, transactions);
989
+ };
990
+ const sendFailedEvent = (reason, error, transactions = [], screen = defaultView) => {
991
+ track({
992
+ ...commonProps,
993
+ screen: toPascalCase(screen),
994
+ control: 'Fail',
995
+ controlType: 'Event',
996
+ action: 'Failed',
997
+ extras: {
998
+ transactions: toStringifyTransactions(transactions),
999
+ ...error,
1000
+ ...orderProps,
1001
+ ...userProps,
1002
+ paymentMethod,
1003
+ reason,
1004
+ },
1005
+ });
1006
+ sendSaleFailedEvent(eventTarget, reason, error, paymentMethod, transactions);
1007
+ };
1008
+ const sendTransactionSuccessEvent = (transaction) => {
1009
+ sendSaleTransactionSuccessEvent(eventTarget, paymentMethod, [transaction]);
1010
+ };
1011
+ const sendSelectedPaymentMethod = (type, screen) => {
1012
+ track({
1013
+ ...commonProps,
1014
+ screen: toPascalCase(screen),
1015
+ control: 'Select',
1016
+ controlType: 'MenuItem',
1017
+ extras: {
1018
+ paymentMethod: type,
1019
+ },
1020
+ });
1021
+ sendSalePaymentMethodEvent(eventTarget, type);
1022
+ };
1023
+ const sendPageView = (screen, data) => {
1024
+ page({
1025
+ ...commonProps,
1026
+ screen: toPascalCase(screen),
1027
+ action: 'Viewed',
1028
+ ...data,
1029
+ });
1030
+ };
1031
+ const sendOrderCreated = (screen, details) => {
1032
+ track({
1033
+ ...commonProps,
1034
+ screen: toPascalCase(screen),
1035
+ control: 'OrderCreated',
1036
+ controlType: 'Event',
1037
+ extras: {
1038
+ ...details,
1039
+ },
1040
+ });
1041
+ };
1042
+ return {
1043
+ track,
1044
+ page,
1045
+ sendPageView,
1046
+ sendCloseEvent,
1047
+ sendSuccessEvent,
1048
+ sendFailedEvent,
1049
+ sendTransactionSuccessEvent,
1050
+ sendOrderCreated,
1051
+ sendSelectedPaymentMethod,
1052
+ };
1053
+ };
1054
+
1055
+ function FundWithSmartCheckout({ subView }) {
1056
+ const { t } = useTranslation();
1057
+ const { sendPageView } = useSaleEvent();
1058
+ const { viewDispatch } = useContext(ViewContext);
1059
+ const [selectedFundingRoute, setSelectedFundingRoute] = useState(undefined);
1060
+ const [fundingRouteStepIndex, setFundingRouteStepIndex] = useState(0);
1061
+ const { querySmartCheckout, fundingRoutes, smartCheckoutResult, collectionName, } = useSaleContext();
1062
+ const { cryptoFiatDispatch } = useContext(CryptoFiatContext);
1063
+ const smartCheckoutLoading = useRef(false);
1064
+ const onFundingRouteSelected = (fundingRoute) => {
1065
+ setSelectedFundingRoute(fundingRoute);
1066
+ };
1067
+ useEffect(() => {
1068
+ if (subView === FundWithSmartCheckoutSubViews.INIT && !smartCheckoutLoading.current) {
1069
+ smartCheckoutLoading.current = true;
1070
+ try {
1071
+ querySmartCheckout();
1072
+ }
1073
+ finally {
1074
+ smartCheckoutLoading.current = false;
1075
+ }
1076
+ }
1077
+ }, [subView]);
1078
+ useEffect(() => {
1079
+ if (!cryptoFiatDispatch || !smartCheckoutResult)
1080
+ return;
1081
+ const tokenSymbols = smartCheckoutTokensList(smartCheckoutResult);
1082
+ cryptoFiatDispatch({
1083
+ payload: {
1084
+ type: CryptoFiatActions.SET_TOKEN_SYMBOLS,
1085
+ tokenSymbols,
1086
+ },
1087
+ });
1088
+ }, [cryptoFiatDispatch, smartCheckoutResult]);
1089
+ const fundingRouteStep = useMemo(() => {
1090
+ if (!selectedFundingRoute) {
1091
+ return undefined;
1092
+ }
1093
+ return selectedFundingRoute.steps[fundingRouteStepIndex];
1094
+ }, [selectedFundingRoute, fundingRouteStepIndex]);
1095
+ const onFundingRouteExecuted = () => {
1096
+ if (!selectedFundingRoute) {
1097
+ return;
1098
+ }
1099
+ if (fundingRouteStepIndex === selectedFundingRoute.steps.length - 1) {
1100
+ setFundingRouteStepIndex(0);
1101
+ setSelectedFundingRoute(undefined);
1102
+ // ! Recurse with SC to trigger another query and confirm they have the required balance
1103
+ viewDispatch({
1104
+ payload: {
1105
+ type: ViewActions.UPDATE_VIEW,
1106
+ view: {
1107
+ type: SaleWidgetViews.FUND_WITH_SMART_CHECKOUT,
1108
+ subView: FundWithSmartCheckoutSubViews.INIT,
1109
+ },
1110
+ },
1111
+ });
1112
+ }
1113
+ else {
1114
+ setFundingRouteStepIndex(fundingRouteStepIndex + 1);
1115
+ }
1116
+ };
1117
+ useEffect(() => sendPageView(SaleWidgetViews.FUND_WITH_SMART_CHECKOUT, {
1118
+ subView,
1119
+ ...(!!fundingRouteStep && { fundingStep: fundingRouteStep.type }),
1120
+ }), []);
1121
+ return (jsxs(Box, { children: [subView === FundWithSmartCheckoutSubViews.INIT && (jsx(LoadingView, { loadingText: t('views.FUND_WITH_SMART_CHECKOUT.loading.checkingBalances') })), subView === FundWithSmartCheckoutSubViews.FUNDING_ROUTE_SELECT && (jsx(FundingRouteSelect, { fundingRoutes: fundingRoutes, collectionName: collectionName, onFundingRouteSelected: onFundingRouteSelected })), subView === FundWithSmartCheckoutSubViews.FUNDING_ROUTE_EXECUTE && (jsx(FundingRouteExecute, { onFundingRouteExecuted: onFundingRouteExecuted, fundingRouteStep: fundingRouteStep }))] }));
1122
+ }
1123
+
1124
+ const MAX_GAS_LIMIT = '30000000';
1125
+ // TODO: Move to common config file inside Checkout SDK while refactoring onRamp
1126
+ // const { baseUrl, apiKey } = checkout.fiatExchangeConfig('transak')
1127
+ const TRANSAK_API_BASE_URL = {
1128
+ [Environment.SANDBOX]: 'https://global-stg.transak.com',
1129
+ [Environment.PRODUCTION]: 'https://global.transak.com/',
1130
+ };
1131
+ const useTransakIframe = (props) => {
1132
+ const { type, ...transakParams } = props;
1133
+ const [iframeSrc, setIframeSrc] = useState('');
1134
+ // TODO: Get transak config from checkout SDK
1135
+ // const { checkout, provider } = connectLoaderState;
1136
+ // const { config } = checkout.fiatExchangeConfig('transak');
1137
+ // const { environment, apiKey, network } = config
1138
+ const environment = 'sandbox';
1139
+ const apiKey = 'd14b44fb-0f84-4db5-affb-e044040d724b';
1140
+ const network = 'immutablezkevm';
1141
+ const getNFTCheckoutURL = useCallback(() => {
1142
+ const { calldata, nftData: nfts, estimatedGasLimit, ...restTransakParams } = transakParams;
1143
+ // FIXME: defaulting to first nft in the list
1144
+ // as transak currently only supports on nft at a time
1145
+ const nftData = nfts?.slice(0, 1);
1146
+ const gasLimit = estimatedGasLimit > 0 ? estimatedGasLimit : MAX_GAS_LIMIT;
1147
+ const params = {
1148
+ apiKey,
1149
+ network,
1150
+ isNFT: 'true',
1151
+ disableWalletAddressForm: 'true',
1152
+ environment: 'STAGING',
1153
+ calldata: btoa(String.fromCharCode.apply(null, pako.deflate(calldata))),
1154
+ nftData: btoa(JSON.stringify(nftData)),
1155
+ estimatedGasLimit: gasLimit.toString(),
1156
+ ...restTransakParams,
1157
+ themeColor: '0D0D0D',
1158
+ };
1159
+ const baseUrl = `${TRANSAK_API_BASE_URL[environment]}?`;
1160
+ const queryParams = new URLSearchParams(params);
1161
+ const widgetUrl = `${baseUrl}${queryParams.toString()}`;
1162
+ return widgetUrl;
1163
+ }, [props]);
1164
+ useEffect(() => {
1165
+ setIframeSrc(getNFTCheckoutURL());
1166
+ }, [props]);
1167
+ return { iframeSrc };
1168
+ };
1169
+
1170
+ function TransakIframe(props) {
1171
+ const { id, type, email, walletAddress, isPassportWallet, nftData, calldata, cryptoCurrencyCode, estimatedGasLimit, exchangeScreenTitle, smartContractAddress, partnerOrderId, onOpen, onInit, onOrderCreated, onOrderProcessing, onOrderCompleted, onOrderFailed, onFailedToLoad, failedToLoadTimeoutInMs, loadingText, } = props;
1172
+ const iframeRef = useRef(null);
1173
+ const { onLoad, initialised } = useTransakEvents({
1174
+ userJourney: UserJourney.SALE,
1175
+ ref: iframeRef,
1176
+ email,
1177
+ userId: walletAddress,
1178
+ isPassportWallet,
1179
+ onOpen,
1180
+ onOrderCreated,
1181
+ onOrderProcessing,
1182
+ onOrderCompleted,
1183
+ onOrderFailed,
1184
+ onInit,
1185
+ failedToLoadTimeoutInMs,
1186
+ onFailedToLoad,
1187
+ });
1188
+ const { iframeSrc } = useTransakIframe({
1189
+ type,
1190
+ nftData,
1191
+ calldata,
1192
+ cryptoCurrencyCode,
1193
+ estimatedGasLimit,
1194
+ exchangeScreenTitle,
1195
+ smartContractAddress,
1196
+ email,
1197
+ walletAddress,
1198
+ partnerOrderId,
1199
+ });
1200
+ return (jsxs(Fragment, { children: [!initialised && (jsx(CenteredBoxContent, { testId: "loading-view", children: jsx(LoadingBox, { loadingText: loadingText }) })), jsx("iframe", { ref: iframeRef, id: id, src: iframeSrc, title: "Transak-Iframe", allow: "camera;microphone;fullscreen;payment", style: {
1201
+ height: '100%',
1202
+ width: '100%',
1203
+ border: 'none',
1204
+ position: 'absolute',
1205
+ top: 0,
1206
+ left: 0,
1207
+ opacity: initialised ? 1 : 0,
1208
+ transition: 'opacity 0.5s ease-out',
1209
+ }, onLoad: onLoad, onError: () => onFailedToLoad?.() })] }));
1210
+ }
1211
+
1212
+ function WithCard(props) {
1213
+ const { t } = useTranslation();
1214
+ const { onInit, onOpen, onOrderCreated, onOrderProcessing, onOrderCompleted, onOrderFailed, } = props;
1215
+ const { recipientEmail, recipientAddress, isPassportWallet, signResponse, goToErrorView, } = useSaleContext();
1216
+ const executeTxn = signResponse?.transactions.find((txn) => txn.methodCall.startsWith('execute'));
1217
+ if (!signResponse || !executeTxn) {
1218
+ return null;
1219
+ }
1220
+ const nftData = useMemo(() => signResponse.order.products.map((product) => ({
1221
+ collectionAddress: product.collectionAddress,
1222
+ imageURL: product.image,
1223
+ nftName: product.name,
1224
+ price: product.amount,
1225
+ quantity: product.qty,
1226
+ tokenID: product.tokenId,
1227
+ nftType: product.contractType || 'ERC721',
1228
+ })), [signResponse]);
1229
+ const onFailedToLoad = () => {
1230
+ goToErrorView(SaleErrorTypes.TRANSACTION_FAILED);
1231
+ };
1232
+ return (jsx(TransakIframe, { id: "transak-iframe", type: "nft-checkout", email: recipientEmail, walletAddress: recipientAddress, isPassportWallet: isPassportWallet, exchangeScreenTitle: t('views.PAY_WITH_CARD.screenTitle'), loadingText: t('views.PAY_WITH_CARD.loading'), nftData: nftData, calldata: executeTxn.rawData, cryptoCurrencyCode: signResponse.order.currency.name, estimatedGasLimit: executeTxn.gasEstimate, smartContractAddress: executeTxn.tokenAddress, partnerOrderId: executeTxn.params.reference, onInit: onInit, onOpen: onOpen, onOrderCreated: onOrderCreated, onOrderProcessing: onOrderProcessing, onOrderCompleted: onOrderCompleted, onOrderFailed: onOrderFailed, onFailedToLoad: onFailedToLoad }));
1233
+ }
1234
+
1235
+ function PayWithCard() {
1236
+ const { sendPageView } = useSaleEvent();
1237
+ const [initialised, setInitialised] = useState(false);
1238
+ const { goBackToPaymentMethods, goToErrorView, goToSuccessView } = useSaleContext();
1239
+ const { sendOrderCreated } = useSaleEvent();
1240
+ const onInit = () => setInitialised(true);
1241
+ const onOrderFailed = () => {
1242
+ goToErrorView(SaleErrorTypes.TRANSAK_FAILED);
1243
+ };
1244
+ const onOrderProcessing = (data = {}) => {
1245
+ const { id: orderId, status: orderStatus, cryptoAmount, cryptoCurrency, fiatAmount, fiatAmountInUsd, amountPaid, totalFeeInFiat, paymentOptionId: paymentOption, userId, userKycType, walletAddress, nftAssetInfo, } = data;
1246
+ const { nftDataBase64, quantity } = nftAssetInfo || {};
1247
+ goToSuccessView({
1248
+ orderId,
1249
+ orderStatus,
1250
+ cryptoAmount,
1251
+ cryptoCurrency,
1252
+ fiatAmount,
1253
+ fiatAmountInUsd,
1254
+ amountPaid,
1255
+ totalFeeInFiat,
1256
+ paymentOption,
1257
+ userId,
1258
+ userKycType,
1259
+ walletAddress,
1260
+ nftDataBase64,
1261
+ quantity,
1262
+ });
1263
+ };
1264
+ const onOrderCreated = (data = {}) => {
1265
+ const { id: orderId, status: orderStatus, cryptoAmount, cryptoCurrency, fiatAmount, fiatAmountInUsd, amountPaid, totalFeeInFiat, paymentOptionId: paymentOption, userId, userKycType, walletAddress, nftAssetInfo, } = data;
1266
+ const { nftDataBase64, quantity } = nftAssetInfo || {};
1267
+ sendOrderCreated(SaleWidgetViews.PAY_WITH_CARD, {
1268
+ orderId,
1269
+ orderStatus,
1270
+ cryptoAmount,
1271
+ cryptoCurrency,
1272
+ fiatAmount,
1273
+ fiatAmountInUsd,
1274
+ amountPaid,
1275
+ totalFeeInFiat,
1276
+ paymentOption,
1277
+ userId,
1278
+ userKycType,
1279
+ walletAddress,
1280
+ nftDataBase64,
1281
+ quantity,
1282
+ });
1283
+ };
1284
+ useEffect(() => sendPageView(SaleWidgetViews.PAY_WITH_CARD), []);
1285
+ return (jsx(SimpleLayout, { header: initialised && (jsx(HeaderNavigation, { onCloseButtonClick: () => goBackToPaymentMethods() })), footer: jsx(FooterLogo, { hideLogo: initialised }), children: jsx(Box, { style: {
1286
+ display: 'block',
1287
+ position: 'relative',
1288
+ maxWidth: '420px',
1289
+ height: '565px',
1290
+ borderRadius: '1%',
1291
+ overflow: 'hidden',
1292
+ margin: '0 auto',
1293
+ width: '100%',
1294
+ }, children: jsx(WithCard, { onInit: onInit, onOrderFailed: onOrderFailed, onOrderCompleted: onOrderProcessing, onOrderCreated: onOrderCreated, onOrderProcessing: onOrderProcessing }) }) }));
1295
+ }
1296
+
1297
+ function PayWithCoins() {
1298
+ const { t } = useTranslation();
1299
+ const processing = useRef(false);
1300
+ const { sendPageView, sendTransactionSuccessEvent, sendFailedEvent } = useSaleEvent();
1301
+ const { execute, signResponse, executeResponse, goToSuccessView, } = useSaleContext();
1302
+ const expectedTxns = signResponse?.transactions.length || 0;
1303
+ const executedTxns = executeResponse?.transactions.length || 0;
1304
+ let loadingText = t('views.PAYMENT_METHODS.loading.ready');
1305
+ if (signResponse !== undefined) {
1306
+ loadingText = t('views.PAYMENT_METHODS.loading.confirm');
1307
+ }
1308
+ else if (executedTxns > 0 && executedTxns === expectedTxns) {
1309
+ loadingText = t('views.PAYMENT_METHODS.loading.processing');
1310
+ }
1311
+ if (signResponse !== undefined) {
1312
+ loadingText = `${loadingText} ${executedTxns}/${expectedTxns}`;
1313
+ }
1314
+ const sendTransaction = async () => {
1315
+ execute(signResponse, (txn) => {
1316
+ sendTransactionSuccessEvent(txn);
1317
+ }, (error, txns) => {
1318
+ sendFailedEvent(error.toString(), error, txns);
1319
+ });
1320
+ };
1321
+ useEffect(() => {
1322
+ if (signResponse !== undefined && processing.current === false) {
1323
+ processing.current = true;
1324
+ sendTransaction();
1325
+ }
1326
+ }, [signResponse]);
1327
+ useEffect(() => {
1328
+ if (executeResponse?.done === true) {
1329
+ goToSuccessView();
1330
+ }
1331
+ }, [executeResponse]);
1332
+ useEffect(() => sendPageView(SaleWidgetViews.PAY_WITH_COINS), []);
1333
+ return jsx(LoadingView, { loadingText: loadingText, showFooterLogo: true });
1334
+ }
1335
+
1336
+ function PaymentOption(props) {
1337
+ const { t } = useTranslation();
1338
+ const { type, onClick, disabled } = props;
1339
+ // const optionText = options[type];
1340
+ const icon = {
1341
+ [SalePaymentTypes.CRYPTO]: 'Coins',
1342
+ [SalePaymentTypes.FIAT]: 'BankCard',
1343
+ };
1344
+ const handleClick = () => onClick(type);
1345
+ return (jsxs(MenuItem, { size: "medium", emphasized: true, onClick: disabled ? undefined : handleClick, sx: {
1346
+ ...(disabled && { opacity: '0.5', cursor: 'not-allowed' }),
1347
+ marginBottom: 'base.spacing.x1',
1348
+ }, disabled: disabled, children: [jsx(MenuItem.FramedIcon, { icon: icon[type] }), jsx(MenuItem.Label, { size: "medium", children: t(`views.PAYMENT_METHODS.options.${type}.heading`) }), !disabled && jsx(MenuItem.IntentIcon, {}), jsx(MenuItem.Caption, { children: t(`views.PAYMENT_METHODS.options.${type}.${disabled ? 'disabledCaption' : 'caption'}`) })] }));
1349
+ }
1350
+
1351
+ const defaultPaymentOptions = [
1352
+ SalePaymentTypes.FIAT,
1353
+ SalePaymentTypes.CRYPTO,
1354
+ ];
1355
+ function PaymentOptions(props) {
1356
+ const { disabledOptions = [], onClick } = props;
1357
+ return (jsx(Box, { testId: "payment-options-list", sx: {
1358
+ width: '100%',
1359
+ display: 'flex',
1360
+ flexDirection: 'column',
1361
+ justifyContent: 'center',
1362
+ alignItems: 'flex-start',
1363
+ }, children: defaultPaymentOptions.map((type) => (jsx(PaymentOption, { disabled: disabledOptions.includes(type), onClick: onClick, type: type }, type))) }));
1364
+ }
1365
+
1366
+ function PaymentMethods() {
1367
+ const { t } = useTranslation();
1368
+ const { viewState, viewDispatch } = useContext(ViewContext);
1369
+ const { sign, amount, fromTokenAddress: tokenAddress, goToErrorView, paymentMethod, setPaymentMethod, disabledPaymentTypes, invalidParameters, } = useSaleContext();
1370
+ const { sendPageView, sendCloseEvent, sendSelectedPaymentMethod } = useSaleEvent();
1371
+ const { eventTargetState: { eventTarget } } = useContext(EventTargetContext);
1372
+ const handleOptionClick = (type) => setPaymentMethod(type);
1373
+ useEffect(() => {
1374
+ if (paymentMethod) {
1375
+ sendSelectedPaymentMethod(paymentMethod, SaleWidgetViews.PAYMENT_METHODS);
1376
+ }
1377
+ if (paymentMethod === SalePaymentTypes.FIAT) {
1378
+ sign(paymentMethod, () => {
1379
+ viewDispatch({
1380
+ payload: {
1381
+ type: ViewActions.UPDATE_VIEW,
1382
+ view: {
1383
+ type: SaleWidgetViews.PAY_WITH_CARD,
1384
+ },
1385
+ },
1386
+ });
1387
+ });
1388
+ viewDispatch({
1389
+ payload: {
1390
+ type: ViewActions.UPDATE_VIEW,
1391
+ view: {
1392
+ type: SharedViews.LOADING_VIEW,
1393
+ data: { loadingText: t('views.PAYMENT_METHODS.loading.ready') },
1394
+ },
1395
+ },
1396
+ });
1397
+ }
1398
+ if (paymentMethod === SalePaymentTypes.CRYPTO) {
1399
+ viewDispatch({
1400
+ payload: {
1401
+ type: ViewActions.UPDATE_VIEW,
1402
+ view: {
1403
+ type: SaleWidgetViews.FUND_WITH_SMART_CHECKOUT,
1404
+ subView: FundWithSmartCheckoutSubViews.INIT,
1405
+ },
1406
+ },
1407
+ });
1408
+ }
1409
+ }, [paymentMethod]);
1410
+ const insufficientCoinsBanner = (jsx(Box, { sx: { paddingX: 'base.spacing.x2' }, children: jsxs(Banner, { children: [jsx(Banner.Icon, { icon: "InformationCircle" }), jsxs(Banner.Caption, { children: [t('views.PAYMENT_METHODS.insufficientCoinsBanner.caption'), jsx(Link, { sx: { mx: 'base.spacing.x1' }, onClick: () => orchestrationEvents
1411
+ .sendRequestBridgeEvent(eventTarget, IMTBLWidgetEvents.IMTBL_SALE_WIDGET_EVENT, {
1412
+ amount, tokenAddress,
1413
+ }), children: t('views.PAYMENT_METHODS.insufficientCoinsBanner.captionCTA') }), t('views.PAYMENT_METHODS.insufficientCoinsBanner.captionEnd')] })] }) }));
1414
+ useEffect(() => sendPageView(SaleWidgetViews.PAYMENT_METHODS), []);
1415
+ useEffect(() => {
1416
+ if (!invalidParameters)
1417
+ return;
1418
+ goToErrorView(SaleErrorTypes.INVALID_PARAMETERS);
1419
+ }, [invalidParameters]);
1420
+ return (jsx(SimpleLayout, { testId: "payment-methods", header: (jsx(HeaderNavigation, { onCloseButtonClick: () => sendCloseEvent(SaleWidgetViews.PAYMENT_METHODS) })), footer: jsx(FooterLogo, {}), children: jsxs(Box, { sx: {
1421
+ display: 'flex',
1422
+ flexDirection: 'column',
1423
+ paddingX: 'base.spacing.x2',
1424
+ paddingY: 'base.spacing.x8',
1425
+ rowGap: 'base.spacing.x4',
1426
+ }, children: [jsx(Heading, { size: "small", sx: {
1427
+ paddingX: 'base.spacing.x4',
1428
+ }, children: t('views.PAYMENT_METHODS.header.heading') }), jsx(Box, { sx: { paddingX: 'base.spacing.x2' }, children: jsx(PaymentOptions, { disabledOptions: disabledPaymentTypes, onClick: handleOptionClick }) }), viewState.view.data?.showInsufficientCoinsBanner ? insufficientCoinsBanner : null] }) }));
1429
+ }
1430
+
1431
+ function SaleErrorView({ errorType = SaleErrorTypes.DEFAULT, transactionHash, blockExplorerLink, biomeTheme, }) {
1432
+ const { t } = useTranslation();
1433
+ const { goBackToPaymentMethods } = useSaleContext();
1434
+ const { eventTargetState: { eventTarget } } = useContext(EventTargetContext);
1435
+ const closeWidget = () => {
1436
+ sendSaleWidgetCloseEvent(eventTarget);
1437
+ };
1438
+ const errorHandlersConfig = {
1439
+ [SaleErrorTypes.TRANSACTION_FAILED]: {
1440
+ onActionClick: goBackToPaymentMethods,
1441
+ onSecondaryActionClick: transactionHash ? () => {
1442
+ window.open(blockExplorerLink);
1443
+ } : closeWidget,
1444
+ statusType: StatusType.FAILURE,
1445
+ statusIconStyles: {
1446
+ fill: biomeTheme.color.status.destructive.dim,
1447
+ },
1448
+ },
1449
+ [SaleErrorTypes.SERVICE_BREAKDOWN]: {
1450
+ onSecondaryActionClick: closeWidget,
1451
+ statusType: StatusType.INFORMATION,
1452
+ statusIconStyles: {
1453
+ fill: biomeTheme.color.status.fatal.dim,
1454
+ },
1455
+ },
1456
+ [SaleErrorTypes.PRODUCT_NOT_FOUND]: {
1457
+ onSecondaryActionClick: closeWidget,
1458
+ statusType: StatusType.INFORMATION,
1459
+ statusIconStyles: {
1460
+ fill: biomeTheme.color.status.fatal.dim,
1461
+ },
1462
+ },
1463
+ [SaleErrorTypes.INSUFFICIENT_STOCK]: {
1464
+ onSecondaryActionClick: closeWidget,
1465
+ statusType: StatusType.INFORMATION,
1466
+ statusIconStyles: {
1467
+ fill: biomeTheme.color.status.fatal.dim,
1468
+ },
1469
+ },
1470
+ [SaleErrorTypes.TRANSAK_FAILED]: {
1471
+ onActionClick: goBackToPaymentMethods,
1472
+ onSecondaryActionClick: closeWidget,
1473
+ statusType: StatusType.INFORMATION,
1474
+ },
1475
+ [SaleErrorTypes.WALLET_FAILED]: {
1476
+ onActionClick: goBackToPaymentMethods,
1477
+ onSecondaryActionClick: closeWidget,
1478
+ statusType: StatusType.INFORMATION,
1479
+ statusIconStyles: {
1480
+ fill: biomeTheme.color.status.fatal.dim,
1481
+ },
1482
+ },
1483
+ [SaleErrorTypes.WALLET_REJECTED_NO_FUNDS]: {
1484
+ onActionClick: goBackToPaymentMethods,
1485
+ onSecondaryActionClick: closeWidget,
1486
+ statusType: StatusType.INFORMATION,
1487
+ },
1488
+ [SaleErrorTypes.WALLET_REJECTED]: {
1489
+ onActionClick: () => {
1490
+ goBackToPaymentMethods(SalePaymentTypes.CRYPTO);
1491
+ },
1492
+ onSecondaryActionClick: closeWidget,
1493
+ statusType: StatusType.INFORMATION,
1494
+ },
1495
+ [SaleErrorTypes.SMART_CHECKOUT_ERROR]: {
1496
+ onActionClick: goBackToPaymentMethods,
1497
+ onSecondaryActionClick: closeWidget,
1498
+ statusType: StatusType.INFORMATION,
1499
+ },
1500
+ [SaleErrorTypes.SMART_CHECKOUT_EXECUTE_ERROR]: {
1501
+ onActionClick: goBackToPaymentMethods,
1502
+ onSecondaryActionClick: closeWidget,
1503
+ statusType: StatusType.INFORMATION,
1504
+ },
1505
+ [SaleErrorTypes.INVALID_PARAMETERS]: {
1506
+ onSecondaryActionClick: closeWidget,
1507
+ statusType: StatusType.ALERT,
1508
+ statusIconStyles: {
1509
+ fill: biomeTheme.color.status.attention.dim,
1510
+ transform: 'none',
1511
+ },
1512
+ },
1513
+ [SaleErrorTypes.DEFAULT]: {
1514
+ onActionClick: goBackToPaymentMethods,
1515
+ onSecondaryActionClick: closeWidget,
1516
+ statusType: StatusType.INFORMATION,
1517
+ },
1518
+ };
1519
+ const getErrorViewProps = () => {
1520
+ const handlers = errorHandlersConfig[errorType] || {};
1521
+ const secondaryActionText = errorType === SaleErrorTypes.TRANSACTION_FAILED && transactionHash
1522
+ ? t(`views.SALE_FAIL.errors.${SaleErrorTypes.DEFAULT}.secondaryAction`)
1523
+ : t(`views.SALE_FAIL.errors.${errorType}.secondaryAction`);
1524
+ return {
1525
+ testId: 'fail-view',
1526
+ statusText: t(`views.SALE_FAIL.errors.${errorType}.description`),
1527
+ actionText: t(`views.SALE_FAIL.errors.${errorType}.primaryAction`),
1528
+ onActionClick: handlers?.onActionClick,
1529
+ secondaryActionText,
1530
+ onSecondaryActionClick: handlers?.onSecondaryActionClick,
1531
+ onCloseClick: closeWidget,
1532
+ statusType: handlers.statusType,
1533
+ statusIconStyles: {
1534
+ transform: 'rotate(180deg)',
1535
+ fill: biomeTheme.color.status.guidance.dim,
1536
+ ...handlers.statusIconStyles,
1537
+ },
1538
+ };
1539
+ };
1540
+ return (jsx(StatusView, { ...getErrorViewProps() }));
1541
+ }
1542
+
1543
+ function SaleSuccessView({ data }) {
1544
+ const { t } = useTranslation();
1545
+ const { sendCloseEvent, sendSuccessEvent } = useSaleEvent();
1546
+ const closeWidget = () => {
1547
+ sendCloseEvent(SaleWidgetViews.SALE_SUCCESS);
1548
+ };
1549
+ return (jsx(StatusView, { statusText: t('views.SALE_SUCCESS.text'), actionText: t('views.SALE_SUCCESS.actionText'), onRenderEvent: () => {
1550
+ const { transactions, ...rest } = data;
1551
+ sendSuccessEvent(SaleWidgetViews.SALE_SUCCESS, transactions, rest);
1552
+ }, onActionClick: () => closeWidget(), statusType: StatusType.SUCCESS, testId: "success-view" }));
1553
+ }
1554
+
1555
+ function SaleWidget(props) {
1556
+ const { t } = useTranslation();
1557
+ const { config, amount, items, environmentId, fromTokenAddress, collectionName, } = props;
1558
+ const { connectLoaderState } = useContext(ConnectLoaderContext);
1559
+ const { checkout, provider } = connectLoaderState;
1560
+ const chainId = useRef();
1561
+ const { theme } = config;
1562
+ const biomeTheme = useMemo(() => widgetTheme(theme), [theme]);
1563
+ const [viewState, viewDispatch] = useReducer(viewReducer, initialViewState);
1564
+ const viewReducerValues = useMemo(() => ({ viewState, viewDispatch }), [viewState, viewDispatch]);
1565
+ const loadingText = viewState.view.data?.loadingText
1566
+ || t('views.LOADING_VIEW.text');
1567
+ useEffect(() => {
1568
+ if (!checkout || !provider)
1569
+ return;
1570
+ (async () => {
1571
+ const network = await checkout.getNetworkInfo({ provider });
1572
+ chainId.current = network.chainId;
1573
+ })();
1574
+ }, [checkout, provider]);
1575
+ const mounted = useRef(false);
1576
+ const onMount = useCallback(() => {
1577
+ if (!checkout || !provider)
1578
+ return;
1579
+ if (!mounted.current) {
1580
+ mounted.current = true;
1581
+ viewDispatch({
1582
+ payload: {
1583
+ type: ViewActions.UPDATE_VIEW,
1584
+ view: {
1585
+ type: SaleWidgetViews.PAYMENT_METHODS,
1586
+ },
1587
+ },
1588
+ });
1589
+ }
1590
+ }, [checkout, provider]);
1591
+ useEffect(() => {
1592
+ if (!checkout || !provider)
1593
+ return;
1594
+ onMount();
1595
+ }, [checkout, provider]);
1596
+ return (jsx(ViewContext.Provider, { value: viewReducerValues, children: jsx(SaleContextProvider, { value: {
1597
+ config,
1598
+ items,
1599
+ amount,
1600
+ fromTokenAddress,
1601
+ env: checkout.config.environment ?? Environment.SANDBOX,
1602
+ environmentId,
1603
+ provider,
1604
+ checkout,
1605
+ passport: checkout?.passport,
1606
+ collectionName,
1607
+ }, children: jsxs(CryptoFiatProvider, { environment: config.environment, children: [viewState.view.type === SharedViews.LOADING_VIEW && (jsx(LoadingView, { loadingText: loadingText })), viewState.view.type === SaleWidgetViews.PAYMENT_METHODS && (jsx(PaymentMethods, {})), viewState.view.type === SaleWidgetViews.PAY_WITH_CARD && (jsx(PayWithCard, {})), viewState.view.type === SaleWidgetViews.PAY_WITH_COINS && (jsx(PayWithCoins, {})), viewState.view.type === SaleWidgetViews.SALE_FAIL && (jsx(SaleErrorView, { biomeTheme: biomeTheme, errorType: viewState.view.data?.errorType, transactionHash: viewState.view.data?.transactionHash, blockExplorerLink: BlockExplorerService.getTransactionLink(chainId.current, viewState.view.data?.transactionHash) })), viewState.view.type === SaleWidgetViews.SALE_SUCCESS && provider && (jsx(SaleSuccessView, { data: viewState.view.data })), viewState.view.type === SaleWidgetViews.FUND_WITH_SMART_CHECKOUT && (jsx(FundWithSmartCheckout, { subView: viewState.view.subView }))] }) }) }));
1608
+ }
1609
+
1610
+ export { SaleWidget as default };