UNPKG

@dynamic-labs/sdk-react-core

Version:

A React SDK for implementing wallet web3 authentication and authorization to your website.

356 lines (353 loc) 25.9 kB
'use client' import { jsx, jsxs } from 'react/jsx-runtime'; import { useState, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { WalletIcon } from '@dynamic-labs/wallet-book'; import { classNames } from '../../../../utils/functions/classNames/classNames.js'; import '../../../../components/Accordion/components/AccordionItem/AccordionItem.js'; import { ReactComponent as SvgArrowLeft } from '../../../../shared/assets/arrow-left.js'; import { ReactComponent as SvgChevronDown } from '../../../../shared/assets/chevron-down.js'; import { ReactComponent as SvgFooterInfoIcon } from '../../../../shared/assets/footer-info-icon.js'; import { ReactComponent as SvgSwitchToggle } from '../../../../shared/assets/switch-toggle.js'; import '@dynamic-labs/iconic'; import '../../../../context/ViewContext/ViewContext.js'; import '../../../../../../_virtual/_tslib.js'; import '@dynamic-labs/sdk-api-core'; import '../../../../shared/logger.js'; import '@dynamic-labs/wallet-connector-core'; import { shortenWalletAddress } from '../../../../shared/utils/functions/shortenWalletAddress/shortenWalletAddress.js'; import '@dynamic-labs/utils'; import '../../../../utils/constants/colors.js'; import '../../../../utils/constants/values.js'; import '../../../../shared/consts/index.js'; import '../../../../components/Alert/Alert.js'; import '../../../../events/dynamicEvents.js'; import '../../../../context/DynamicContext/DynamicContext.js'; import '../../../../store/state/loadingAndLifecycle/loadingAndLifecycle.js'; import '../../../../store/state/authMode/authMode.js'; import '../../../../context/CaptchaContext/CaptchaContext.js'; import '../../../../context/ErrorContext/ErrorContext.js'; import '@dynamic-labs/multi-wallet'; import 'react-international-phone'; import '../../../../store/state/nonce/nonce.js'; import '../../../../store/state/projectSettings/projectSettings.js'; import '../../../../config/ApiEndpoint.js'; import '../../../../store/state/user/user.js'; import '../../../../locale/locale.js'; import '../../../../store/state/dynamicContextProps/dynamicContextProps.js'; import '../../../../store/state/primaryWalletId/primaryWalletId.js'; import '../../../../store/state/connectedWalletsInfo/connectedWalletsInfo.js'; import '../../../../context/AccessDeniedContext/AccessDeniedContext.js'; import '../../../../context/AccountExistsContext/AccountExistsContext.js'; import '../../../../context/UserWalletsContext/UserWalletsContext.js'; import '../../../../context/VerificationContext/VerificationContext.js'; import 'react-dom'; import '../../../../utils/functions/compareChains/compareChains.js'; import '../../../../views/Passkey/utils/findPrimaryEmbeddedChain/findPrimaryEmbeddedChain.js'; import '../../../../context/ThemeContext/ThemeContext.js'; import '../../../../utils/hooks/useUserUpdateRequest/useUpdateUser/userFieldsSchema.js'; import 'bs58'; import '@dynamic-labs/types'; import '../../../../context/SocialRedirectContext/SocialRedirectContext.js'; import '../../../../context/LoadingContext/LoadingContext.js'; import '../../../../context/WalletContext/WalletContext.js'; import '../../../../utils/hooks/useEmbeddedWallet/useSecureEnclaveEmbeddedWallet/constants.js'; import 'yup'; import '../../../../context/MockContext/MockContext.js'; import '../../../../views/CollectUserDataView/useFields.js'; import '../../../../context/FieldsStateContext/FieldsStateContext.js'; import '../../../../context/UserFieldEditorContext/UserFieldEditorContext.js'; import '@dynamic-labs/rpc-providers'; import '../../../../store/state/walletOptions/walletOptions.js'; import { Typography } from '../../../../components/Typography/Typography.js'; import '../../../../context/FooterAnimationContext/index.js'; import '../../../../components/ShadowDOM/ShadowDOM.js'; import '../../../../components/Transition/ZoomTransition/ZoomTransition.js'; import '../../../../components/Transition/SlideInUpTransition/SlideInUpTransition.js'; import '../../../../components/Transition/OpacityTransition/OpacityTransition.js'; import '../../../../components/OverlayCardBase/OverlayCardTarget/OverlayCardTarget.js'; import { Skeleton } from '../../../../components/Skeleton/Skeleton.js'; import { Tooltip } from '../../../../components/Tooltip/Tooltip.js'; import '../../../../context/WalletGroupContext/WalletGroupContext.js'; import '../../components/DynamicWidgetHeader/DynamicWidgetHeader.js'; import 'react-focus-lock'; import { Icon } from '../../../../components/Icon/Icon.js'; import { useWidgetContext } from '../../context/DynamicWidgetContext.js'; import { IconButton } from '../../../../components/IconButton/IconButton.js'; import '../../../../components/MenuList/Dropdown/Dropdown.js'; import { Image } from '../../../../components/Image/Image.js'; import { TypographyButton } from '../../../../components/TypographyButton/TypographyButton.js'; import 'formik'; import '../../../../utils/hooks/useSubdomainCheck/useSubdomainCheck.js'; import { ModalHeader } from '../../../../components/ModalHeader/ModalHeader.js'; import '../../../../store/state/sendBalances.js'; import '../../../../components/Input/Input.js'; import '../../../../components/OverlayCard/OverlayCard.js'; import '../../../../views/TransactionConfirmationView/TransactionConfirmationView.js'; import '../../../../context/PasskeyContext/PasskeyContext.js'; import '../ManagePasskeysWidgetView/PasskeyCard/PasskeyCard.js'; import '../../../../context/OnrampContext/OnrampContext.js'; import 'qrcode'; import '../../../../../index.js'; import '../../../../context/IpConfigurationContext/IpConfigurationContext.js'; import '../../../../context/ConnectWithOtpContext/ConnectWithOtpContext.js'; import '../../../DynamicBridgeWidget/views/WalletsView/components/SecondaryWallets/SecondaryWallets.js'; import '@hcaptcha/react-hcaptcha'; import '../../../../context/ErrorContext/hooks/useErrorText/useErrorText.js'; import '../../../../components/PasskeyCreatedSuccessBanner/PasskeyCreatedSuccessBanner.js'; import '../../helpers/convertExchangeKeyAndProviderEnum.js'; import '../../../../store/state/connectorsInitializing/connectorsInitializing.js'; import '../../../../store/state/tokenBalances.js'; import '../../../../shared/utils/functions/getInitialUrl/getInitialUrl.js'; import '../../../../components/InlineWidget/InlineWidget.js'; import '../../../../components/IsBrowser/IsBrowser.js'; import '../../../../components/Popper/Popper/Popper.js'; import '../../../../components/Popper/PopperContext/PopperContext.js'; import { FormattedInput } from './FormattedInput/FormattedInput.js'; import { createFormattedInputEmitter } from './FormattedInput/formattedInputEmitter.js'; import { TokenSelectScreen } from './TokenSelectScreen/TokenSelectScreen.js'; import { UNAVAILABLE_VALUE } from './consts.js'; import { convertFromFiat, convertToFiat } from './utils/convert/convert.js'; import { formatValue } from './utils/formatValue/formatValue.js'; import { isFiatOrStablecoin, isFiatToken } from './utils/isFiatToken/isFiatToken.js'; import { isNonZero } from './utils/isNonZero/isNonZero.js'; import { respectsMinimum } from './utils/respectsMinimum/respectsMinimum.js'; import { useExchangeRatesForFunding } from './utils/useExchangeRatesForFunding/useExchangeRatesForFunding.js'; import { useSubmitWalletFunding } from './utils/useSubmitWalletFunding/useSubmitWalletFunding.js'; import { useTokensForFunding } from './utils/useTokensForFunding/useTokensForFunding.js'; import { calculateFiatBalance } from './utils/calculateFiatBalance/calculateFiatBalance.js'; const defaultQuickSuggestions = { token: 'USD', values: [25, 100, 500], }; const rulesThatHideQuickSuggestions = ['exact', 'minimum']; const rulesThatDisableTokenSelect = ['exact', 'exact-with-amount']; const inputEmitter = createFormattedInputEmitter(); const ReceiveWalletFunds = ({ amount: initialAmount, token: initialTokenRaw, wallet, quickSuggestions = defaultQuickSuggestions, }) => { var _a, _b, _c, _d, _e, _f, _g, _h, _j; // For now we hardcode the default value, soon we will fetch this from project settings const initialToken = initialTokenRaw !== null && initialTokenRaw !== void 0 ? initialTokenRaw : { rule: 'recommended', value: 'USDC', }; /** Whether the amount specified in the prop refers to the token amount instead of the fiat amount */ const isInitialAmountValueForToken = (initialToken === null || initialToken === void 0 ? void 0 : initialToken.rule) === 'exact-with-amount'; const minimum = (initialAmount === null || initialAmount === void 0 ? void 0 : initialAmount.rule) === 'minimum' ? { token: isInitialAmountValueForToken ? initialToken.value : 'USD', value: initialAmount.value.toString(), } : undefined; const { t } = useTranslation(); const [hasUserInteracted, setHasUserInteracted] = useState(false); const [conversionErrorIconRef, setConversionErrorIconRef] = useState(null); const { setDynamicWidgetView } = useWidgetContext(); const { isLoading: tokenBalancesLoading, tokens: tokenBalances } = useTokensForFunding({ wallet: wallet, }); const exchangeRates = useExchangeRatesForFunding({ // Initialize amounts after exchange rates are fetched onFetch: (exchangeRates) => { if (tokenAmount === undefined && fiatAmount !== undefined) { setTokenAmount(convertFromFiat(fiatAmount, tokenSymbol, exchangeRates)); } if (fiatAmount === undefined && tokenAmount !== undefined) { setFiatAmount(convertToFiat(tokenAmount, tokenSymbol, exchangeRates)); } }, tokenBalances, }); const [tokenAmount, setTokenAmount] = useState(isInitialAmountValueForToken ? (_a = initialAmount === null || initialAmount === void 0 ? void 0 : initialAmount.value.toString()) !== null && _a !== void 0 ? _a : '' : convertFromFiat((_b = initialAmount === null || initialAmount === void 0 ? void 0 : initialAmount.value.toString()) !== null && _b !== void 0 ? _b : '', initialToken.value, exchangeRates)); const [tokenSymbol, _setTokenSymbol] = useState(initialToken.value); const [fiatAmount, setFiatAmount] = useState(isInitialAmountValueForToken ? convertToFiat(tokenAmount !== null && tokenAmount !== void 0 ? tokenAmount : '', tokenSymbol, exchangeRates) : (_c = initialAmount === null || initialAmount === void 0 ? void 0 : initialAmount.value.toString()) !== null && _c !== void 0 ? _c : ''); const setTokenSymbol = useCallback((symbol) => { setHasUserInteracted(true); _setTokenSymbol(symbol); if (fiatAmount) setTokenAmount(convertFromFiat(fiatAmount, symbol, exchangeRates)); }, [fiatAmount, exchangeRates]); // When we eventually allow changing this, add setter as _setFiatSymbol // And then create a setFiatSymbol function similar to how we have setTokenSymbol const [fiatSymbol] = useState('USD'); const setAmountsByTokenValue = useCallback((amount) => { setTokenAmount(amount); setFiatAmount(convertToFiat(amount, tokenSymbol, exchangeRates)); }, [tokenSymbol, exchangeRates]); const setAmountsByFiatValue = useCallback((amount) => { setFiatAmount(amount); setTokenAmount(convertFromFiat(amount, tokenSymbol, exchangeRates)); }, [tokenSymbol, exchangeRates]); const [showTokenAsPrimary, setShowTokenAsPrimary] = useState(false); const switchPrimaryAndSecondary = useCallback(() => { setHasUserInteracted(true); setShowTokenAsPrimary(!showTokenAsPrimary); if (tokenAmount && isNaN(parseFloat(tokenAmount))) { setTokenAmount(''); } if (fiatAmount && isNaN(parseFloat(fiatAmount))) { setFiatAmount(''); } inputEmitter.emit('focus'); }, [fiatAmount, showTokenAsPrimary, tokenAmount]); const isMinimumRespected = minimum ? respectsMinimum({ token: fiatSymbol, value: fiatAmount !== null && fiatAmount !== void 0 ? fiatAmount : '' }, minimum, exchangeRates) : true; const [primaryData, secondaryData] = showTokenAsPrimary ? [ { amount: tokenAmount, setAmount: setAmountsByTokenValue, symbol: tokenSymbol, }, { amount: fiatAmount, symbol: fiatSymbol }, ] : [ { amount: fiatAmount, setAmount: setAmountsByFiatValue, symbol: fiatSymbol, }, { amount: tokenAmount, symbol: tokenSymbol }, ]; const secondaryDisplay = formatValue({ maxDecimals: isFiatOrStablecoin(secondaryData.symbol) ? 2 : undefined, symbol: secondaryData.symbol, value: secondaryData.amount, withFixedZeros: isFiatOrStablecoin(secondaryData.symbol), }); const quickSuggestionsWithToken = Array.isArray(quickSuggestions) ? { token: 'USD', values: quickSuggestions } : quickSuggestions; const quickSuggestionsParsed = quickSuggestionsWithToken.values .filter((value) => respectsMinimum({ token: quickSuggestionsWithToken.token, value: value.toString() }, minimum, exchangeRates)) .map((value) => ({ display: formatValue({ symbol: quickSuggestionsWithToken.token, value: value, }), value, })) .filter(({ display }) => display !== undefined); const currentToken = tokenBalances === null || tokenBalances === void 0 ? void 0 : tokenBalances.find(({ symbol }) => symbol === tokenSymbol); const fiatBalance = calculateFiatBalance(currentToken, exchangeRates); const [primaryBalance, secondaryBalance] = showTokenAsPrimary ? [ { symbol: tokenSymbol, value: (_d = currentToken === null || currentToken === void 0 ? void 0 : currentToken.balance) !== null && _d !== void 0 ? _d : 0, }, { symbol: fiatSymbol, value: fiatBalance, }, ] : [ { symbol: fiatSymbol, value: fiatBalance, }, { symbol: tokenSymbol, value: (_e = currentToken === null || currentToken === void 0 ? void 0 : currentToken.balance) !== null && _e !== void 0 ? _e : 0, }, ]; const showNotEnoughBalance = Boolean(!tokenBalancesLoading && isNonZero(tokenAmount) && tokenAmount !== undefined && (!currentToken || currentToken.balance < parseFloat(tokenAmount))); const showMinimumRequired = Boolean(!isMinimumRespected && !showNotEnoughBalance); const showQuickSuggestions = quickSuggestionsParsed.length > 0 && !hasUserInteracted && !rulesThatHideQuickSuggestions.includes(initialAmount === null || initialAmount === void 0 ? void 0 : initialAmount.rule) && (tokenSymbol === quickSuggestionsWithToken.token || isFiatToken(quickSuggestionsWithToken.token)); const disableTokenSelect = rulesThatDisableTokenSelect.includes(initialToken.rule); const [showTokenSelect, setShowTokenSelect] = useState(false); const handleSelectToken = useCallback((token) => { setTokenSymbol(token); setShowTokenSelect(false); }, [setTokenSymbol]); const handleQuickSuggestion = useCallback((value) => { setHasUserInteracted(true); if (isFiatToken(quickSuggestionsWithToken.token)) setAmountsByFiatValue(value.toString()); else setAmountsByTokenValue(value.toString()); inputEmitter.emit('focus'); }, [ quickSuggestionsWithToken.token, setAmountsByFiatValue, setAmountsByTokenValue, ]); const handleSubmit = useSubmitWalletFunding(); const disableSubmit = !isMinimumRespected || showNotEnoughBalance || !isNonZero(tokenAmount); const closeTokenSelect = useCallback(() => { setShowTokenSelect(false); inputEmitter.emit('focus'); }, []); const grayOutSecondaryDisplay = !isNonZero(secondaryData.amount) || (isFiatOrStablecoin(secondaryData.symbol) && parseFloat((_f = secondaryData.amount) !== null && _f !== void 0 ? _f : '0') < 0.0099); const inputSymbolProp = primaryData.symbol === 'USD' ? { leading: '$' } : { trailing: primaryData.symbol }; const backButton = (jsx(IconButton, { type: 'button', id: 'back-button', "data-testid": 'back-button', onClick: () => setDynamicWidgetView('choose-wallet-funding-method'), children: jsx(SvgArrowLeft, {}) })); return (jsxs("div", { className: 'fund-from-wallet', children: [!disableTokenSelect && (jsx(TokenSelectScreen, { onClose: closeTokenSelect, onSelectToken: handleSelectToken, tokens: tokenBalances !== null && tokenBalances !== void 0 ? tokenBalances : [], currentToken: tokenSymbol, className: classNames('fund-from-wallet__token-select', { 'fund-from-wallet__token-select--open': showTokenSelect, }), showTokenAsPrimary: showTokenAsPrimary, exchangeRates: exchangeRates })), jsx(ModalHeader, { leading: backButton, children: jsx(Typography, { variant: 'title', children: t('dyn_wallet_funding.from_wallet.amount_input.title') }) }), jsxs("div", { className: 'fund-from-wallet__content', children: [jsxs("div", { className: 'fund-from-wallet__content__amount-container', children: [minimum && (jsx(Typography, { color: 'error-1', className: classNames('fund-from-wallet__content__amount-container__minimum-error', { 'fund-from-wallet__content__amount-container__minimum-error--visible': showMinimumRequired, }), variant: 'body_normal', weight: 'medium', children: t('dyn_wallet_funding.from_wallet.amount_input.minimum_error', { minimum: formatValue({ symbol: minimum.token, value: minimum.value, }), }) })), primaryData.amount !== undefined && (jsx(FormattedInput, Object.assign({ className: 'fund-from-wallet__content__amount-container__amount', value: (initialAmount === null || initialAmount === void 0 ? void 0 : initialAmount.rule) === 'exact' ? (_g = formatValue({ maxDecimals: isFiatOrStablecoin(primaryData.symbol) ? 2 : 8, symbol: undefined, value: primaryData.amount, withFixedZeros: isFiatOrStablecoin(primaryData.symbol), })) !== null && _g !== void 0 ? _g : primaryData.amount : primaryData.amount, onChange: primaryData.setAmount, emitter: inputEmitter, onInteraction: () => setHasUserInteracted(true), locked: (initialAmount === null || initialAmount === void 0 ? void 0 : initialAmount.rule) === 'exact' }, inputSymbolProp))), primaryData.amount === undefined && (jsx(Skeleton, { dataTestId: 'primary-amount-skeleton', container: { className: 'fund-from-wallet__content__amount-container__skeleton', } })), showQuickSuggestions && (jsx("div", { className: 'fund-from-wallet__content__amount-container__quick-suggestions', children: quickSuggestionsParsed.map(({ display, value }) => (jsx(Typography, { variant: 'body_small', color: 'secondary', onClick: () => handleQuickSuggestion(value), weight: 'medium', children: display }, value))) })), !showQuickSuggestions && secondaryDisplay && (jsxs("div", { className: 'fund-from-wallet__content__amount-container__secondary-amount-container', children: [(currentToken === null || currentToken === void 0 ? void 0 : currentToken.logoURI) ? (jsx(Image, { dataTestId: 'token-icon', alt: tokenSymbol, src: currentToken.logoURI, className: 'fund-from-wallet__content__token-icon' })) : null, jsx(Typography, { weight: 'medium', className: classNames('fund-from-wallet__content__amount-container__secondary-amount-container__amount', { 'fund-from-wallet__content__amount-container__secondary-amount-container__amount--grayed-out': grayOutSecondaryDisplay, }), children: secondaryDisplay }), jsx(Icon, { size: 'small', color: 'text-secondary', className: 'fund-from-wallet__content__amount-container__secondary-amount-container__switch', children: jsx(SvgSwitchToggle, { "data-testid": 'switch-primary-and-secondary', onClick: switchPrimaryAndSecondary }) })] })), !showQuickSuggestions && !secondaryDisplay && (jsxs("div", { className: 'fund-from-wallet__content__amount-container__secondary-missing', children: [jsx(Typography, { weight: 'medium', children: formatValue({ symbol: secondaryData.symbol, value: UNAVAILABLE_VALUE, }) }), jsx("div", { ref: setConversionErrorIconRef, children: jsx(Icon, { size: 'medium', color: 'text-tertiary', children: jsx(SvgFooterInfoIcon, {}) }) }), jsx(Tooltip, { content: t('dyn_wallet_funding.from_wallet.amount_input.pricing_unavailable'), targetRef: conversionErrorIconRef, className: 'fund-from-wallet__content__amount-container__secondary-missing__tooltip' })] })), jsx(Typography, { color: 'error-1', className: classNames('fund-from-wallet__content__amount-container__balance-error', { 'fund-from-wallet__content__amount-container__balance-error--visible': showNotEnoughBalance, }), variant: 'body_normal', weight: 'medium', children: t('dyn_wallet_funding.from_wallet.amount_input.balance_error') })] }), jsxs("div", { className: 'fund-from-wallet__content__wallet-card', children: [jsxs("div", { className: 'fund-from-wallet__content__wallet-card__wallet-details', children: [jsx(WalletIcon, { icon: wallet.connector.metadata.icon, walletKey: wallet.connector.key, className: 'fund-from-wallet__content__wallet-card__wallet-details__icon' }), jsxs("div", { className: 'fund-from-wallet__content__wallet-card__wallet-details__rows', children: [jsx(Typography, { variant: 'body_small', weight: 'medium', children: t('dyn_wallet_funding.from_wallet.amount_input.wallet_detail_from') }), jsx(Typography, { variant: 'body_small', color: 'secondary', weight: 'medium', children: shortenWalletAddress(wallet.address) })] })] }), tokenBalancesLoading && (jsx(Skeleton, { dataTestId: 'balance-skeleton', container: { className: 'fund-from-wallet__content__wallet-card__balance-skeleton', } })), !tokenBalancesLoading && (jsxs("div", { className: classNames('fund-from-wallet__content__wallet-card__balance', { 'fund-from-wallet__content__wallet-card__balance--disable-select': disableTokenSelect, }), onClick: () => !disableTokenSelect && setShowTokenSelect(true), children: [jsxs("div", { className: 'fund-from-wallet__content__wallet-card__balance__rows', children: [jsx(Typography, { variant: 'body_small', "data-testid": `primary-balance-amount-${primaryBalance.symbol}`, weight: 'medium', children: formatValue({ maxDecimals: isFiatOrStablecoin(primaryBalance.symbol) ? 2 : 6, symbol: primaryBalance.symbol, value: (_h = primaryBalance.value) !== null && _h !== void 0 ? _h : UNAVAILABLE_VALUE, withFixedZeros: isFiatOrStablecoin(primaryBalance.symbol), }) }), jsx(Typography, { variant: 'body_small', "data-testid": `secondary-balance-amount-${secondaryBalance.symbol}`, color: 'secondary', weight: 'medium', children: formatValue({ maxDecimals: isFiatOrStablecoin(secondaryBalance.symbol) ? 2 : 6, symbol: secondaryBalance.symbol, value: (_j = secondaryBalance.value) !== null && _j !== void 0 ? _j : UNAVAILABLE_VALUE, withFixedZeros: isFiatOrStablecoin(secondaryBalance.symbol), }) })] }), !disableTokenSelect && (jsx(Icon, { color: 'text-tertiary', size: 'small', className: 'fund-from-wallet__content__wallet-card__balance__icon', children: jsx(SvgChevronDown, { "data-testid": 'token-select-dropdown' }) }))] }))] })] }), jsx(TypographyButton, { dataTestId: 'confirm-button', buttonVariant: 'brand-primary', buttonPadding: 'large', typographyProps: { color: 'white' }, className: 'fund-from-wallet__confirm-button', disabled: disableSubmit, onClick: () => handleSubmit({ tokenAmount, tokenBalances, tokenSymbol, wallet, }), children: t('dyn_wallet_funding.from_wallet.amount_input.confirm_button') })] })); }; export { ReceiveWalletFunds };