diff --git a/JPY.svg b/JPY.svg new file mode 100644 index 0000000000000..4e4b9a52acca8 --- /dev/null +++ b/JPY.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/.env b/apps/web/.env index c1798c8507f6e..a21b7aa7d92b5 100644 --- a/apps/web/.env +++ b/apps/web/.env @@ -1,2 +1,4 @@ NEXT_PUBLIC_MM_API_URL=https://linked-pool.pancakeswap.com/quote-service -NEXT_PUBLIC_QUOTING_API=https://swap-quoting.pancakeswap.com/quoting-service \ No newline at end of file +NEXT_PUBLIC_QUOTING_API=https://swap-quoting.pancakeswap.com/quoting-service +MOONPAY_API="https://api.moonpay.com" +MOONPAY_PUBLISHABLE_KEY="pk_live_Ch5fat39X8NvMZwih2k7hK4sDrKanSPz" diff --git a/apps/web/.env.example b/apps/web/.env.example index 56947d54d657d..ff2bdccb92970 100644 --- a/apps/web/.env.example +++ b/apps/web/.env.example @@ -3,4 +3,5 @@ NEXT_PUBLIC_NODE_REAL_API_GOERLI= NEXT_PUBLIC_NODE_PRODUCTION= RISK_APP_SECRET= SERVER_NODE_REAL_API_ETH= -SERVER_NODE_REAL_API_GOERLI= \ No newline at end of file +SERVER_NODE_REAL_API_GOERLI= +NEXT_PUBLIC_MERCURYO_WIDGET_ID= \ No newline at end of file diff --git a/apps/web/public/AUD.svg b/apps/web/public/AUD.svg new file mode 100644 index 0000000000000..d573e264e7ec5 --- /dev/null +++ b/apps/web/public/AUD.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/BRL.svg b/apps/web/public/BRL.svg new file mode 100644 index 0000000000000..80c71379abc33 --- /dev/null +++ b/apps/web/public/BRL.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/CAD.svg b/apps/web/public/CAD.svg new file mode 100644 index 0000000000000..647abcc52bd1f --- /dev/null +++ b/apps/web/public/CAD.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/EUR.svg b/apps/web/public/EUR.svg new file mode 100644 index 0000000000000..663355ae2e70c --- /dev/null +++ b/apps/web/public/EUR.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/GBP.svg b/apps/web/public/GBP.svg new file mode 100644 index 0000000000000..2f329da9eece9 --- /dev/null +++ b/apps/web/public/GBP.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/HKD.svg b/apps/web/public/HKD.svg new file mode 100644 index 0000000000000..e6852d45e287d --- /dev/null +++ b/apps/web/public/HKD.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/IDR.svg b/apps/web/public/IDR.svg new file mode 100644 index 0000000000000..3312306b09944 --- /dev/null +++ b/apps/web/public/IDR.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/JPY.svg b/apps/web/public/JPY.svg new file mode 100644 index 0000000000000..4e4b9a52acca8 --- /dev/null +++ b/apps/web/public/JPY.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/KRW.svg b/apps/web/public/KRW.svg new file mode 100644 index 0000000000000..8df1901b16cc0 --- /dev/null +++ b/apps/web/public/KRW.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/SGD.svg b/apps/web/public/SGD.svg new file mode 100644 index 0000000000000..aa96d6725804d --- /dev/null +++ b/apps/web/public/SGD.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/TWD.svg b/apps/web/public/TWD.svg new file mode 100644 index 0000000000000..812dc3a8dbc19 --- /dev/null +++ b/apps/web/public/TWD.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/USD.svg b/apps/web/public/USD.svg new file mode 100644 index 0000000000000..fd5a0d10e7fb7 --- /dev/null +++ b/apps/web/public/USD.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/VND.svg b/apps/web/public/VND.svg new file mode 100644 index 0000000000000..443d1c681b2d2 --- /dev/null +++ b/apps/web/public/VND.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/images/AUD.svg b/apps/web/public/images/AUD.svg new file mode 100644 index 0000000000000..d573e264e7ec5 --- /dev/null +++ b/apps/web/public/images/AUD.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/images/moneyBangs.svg b/apps/web/public/images/moneyBangs.svg new file mode 100644 index 0000000000000..0f1e68b9ff247 --- /dev/null +++ b/apps/web/public/images/moneyBangs.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/apps/web/public/images/onRampProviders/binanceConnectSvg.svg b/apps/web/public/images/onRampProviders/binanceConnectSvg.svg new file mode 100644 index 0000000000000..06bd1fdae5eef --- /dev/null +++ b/apps/web/public/images/onRampProviders/binanceConnectSvg.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/images/onRampProviders/mercuryoLogo.svg b/apps/web/public/images/onRampProviders/mercuryoLogo.svg new file mode 100644 index 0000000000000..3887337702543 --- /dev/null +++ b/apps/web/public/images/onRampProviders/mercuryoLogo.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/apps/web/public/images/onRampProviders/mercuryo_new_logo_black.png b/apps/web/public/images/onRampProviders/mercuryo_new_logo_black.png new file mode 100644 index 0000000000000..5bddf9b155a4b Binary files /dev/null and b/apps/web/public/images/onRampProviders/mercuryo_new_logo_black.png differ diff --git a/apps/web/public/images/onRampProviders/mercuryo_new_logo_white.png b/apps/web/public/images/onRampProviders/mercuryo_new_logo_white.png new file mode 100644 index 0000000000000..cafd0660e081f Binary files /dev/null and b/apps/web/public/images/onRampProviders/mercuryo_new_logo_white.png differ diff --git a/apps/web/public/images/onRampProviders/moonpaySvg.svg b/apps/web/public/images/onRampProviders/moonpaySvg.svg new file mode 100644 index 0000000000000..1431cae809ccf --- /dev/null +++ b/apps/web/public/images/onRampProviders/moonpaySvg.svg @@ -0,0 +1,53 @@ + + + + + + + + + diff --git a/apps/web/src/components/Card/index.tsx b/apps/web/src/components/Card/index.tsx index ad7e540867c46..4d6718f6edca8 100644 --- a/apps/web/src/components/Card/index.tsx +++ b/apps/web/src/components/Card/index.tsx @@ -28,6 +28,16 @@ export const LightGreyCard = styled(Card)` background-color: ${({ theme }) => theme.colors.background}; ` +export const CryptoCard = styled(Card)<{ isClicked: boolean; isDisabled: boolean }>` + border: 1px solid ${({ theme }) => theme.colors.cardBorder}; + background-color: ${({ theme, isClicked }) => (isClicked ? theme.colors.input : theme.colors.dropdown)}; + transition: height 0.4s ease-in-out, background-color 0.1s ease-in-out; + overflow: hidden; + &:hover { + cursor: ${({ isDisabled }) => (isDisabled ? 'not-allowed' : 'pointer')}; + } +` + export const GreyCard = styled(Card)` background-color: ${({ theme }) => theme.colors.dropdown}; ` diff --git a/apps/web/src/components/CurrencyInputPanel/index.tsx b/apps/web/src/components/CurrencyInputPanel/index.tsx index c38a8a1512e9b..d4f1ec3e94fdd 100644 --- a/apps/web/src/components/CurrencyInputPanel/index.tsx +++ b/apps/web/src/components/CurrencyInputPanel/index.tsx @@ -22,6 +22,7 @@ import { useStablecoinPriceAmount } from 'hooks/useBUSDPrice' import { formatNumber } from '@pancakeswap/utils/formatBalance' import { StablePair } from 'views/AddLiquidity/AddStableLiquidity/hooks/useStableLPDerivedMintInfo' +import { FiatLogo } from 'components/Logo/CurrencyLogo' import { useAccount } from 'wagmi' import { useCurrencyBalance } from '../../state/wallet/hooks' import CurrencySearchModal from '../SearchModal/CurrencySearchModal' @@ -81,6 +82,8 @@ interface CurrencyInputPanelProps { tokensToShow?: Token[] currencyLoading?: boolean inputLoading?: boolean + title?: React.ReactNode + hideBalanceComp?: boolean } const CurrencyInputPanel = memo(function CurrencyInputPanel({ value, @@ -111,11 +114,15 @@ const CurrencyInputPanel = memo(function CurrencyInputPanel({ tokensToShow, currencyLoading, inputLoading, + title, + hideBalanceComp, }: CurrencyInputPanelProps) { const { address: account } = useAccount() + const selectedCurrencyBalance = useCurrencyBalance(account ?? undefined, currency ?? undefined) const { t } = useTranslation() + const mode = id const token = pair ? pair.liquidityToken : currency?.isToken ? currency : null const tokenAddress = token ? isAddress(token.address) : null @@ -137,6 +144,7 @@ const CurrencyInputPanel = memo(function CurrencyInputPanel({ commonBasesType={commonBasesType} showSearchInput={showSearchInput} tokensToShow={tokensToShow} + mode={mode} />, ) @@ -168,7 +176,6 @@ const CurrencyInputPanel = memo(function CurrencyInputPanel({ const isAtPercentMax = (maxAmount && value === maxAmount.toExact()) || (lpPercent && lpPercent === '100') const balance = !hideBalance && !!currency && formatAmount(selectedCurrencyBalance, 6) - return ( + {title} {beforeButton} ) : currency ? ( - + id === 'onramp-input' ? ( + + ) : ( + + ) ) : currencyLoading ? ( ) : null} @@ -235,7 +247,7 @@ const CurrencyInputPanel = memo(function CurrencyInputPanel({ ) : null} - {account && ( + {account && !hideBalanceComp && ( ` + border-bottom-left-radius: 24px; + border-bottom-right-radius: 24px; + + height: calc(100% - 75px); + position: absolute; + width: 100%; +` + +interface FiatOnRampProps { + provider: string + inputCurrency: string + outputCurrency: string + amount: string +} + +interface FetchResponse { + urlWithSignature: string +} + +const LoadingBuffer = ({ theme }: { theme: DefaultTheme }) => { + return ( + +
+ + +
+
+ ) +} + +const fetchMoonPaySignedUrl = async ( + inputCurrency: string, + outputCurrency: string, + amount: string, + isDark: boolean, + account: string, +) => { + try { + const res = await fetch(`${ONRAMP_API_BASE_URL}/generate-moonpay-sig`, { + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + method: 'POST', + body: JSON.stringify({ + type: 'MOONPAY', + defaultCurrencyCode: inputCurrency.toLowerCase(), + baseCurrencyCode: outputCurrency.toLowerCase(), + baseCurrencyAmount: amount, + redirectUrl: 'https://pancakeswap.finance', + theme: isDark ? 'dark' : 'light', + walletAddresses: account, + }), + }) + const result: FetchResponse = await res.json() + return result.urlWithSignature + } catch (error) { + console.error('Error fetching signature:', error) + return '' // Return an empty string in case of an error + } +} + +const fetchBinanceConnectSignedUrl = async (inputCurrency, outputCurrency, amount, account) => { + try { + const res = await fetch(`${ONRAMP_API_BASE_URL}/generate-binance-connect-sig`, { + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + method: 'POST', + body: JSON.stringify({ + cryptoCurrency: inputCurrency.toUpperCase() === 'WBTC' ? 'BTC' : inputCurrency.toUpperCase(), + fiatCurrency: outputCurrency.toUpperCase(), + amount, + walletAddress: account, + }), + }) + + const result: FetchResponse = await res.json() + return result.urlWithSignature + } catch (error) { + console.error('Error fetching signature:', error) + return '' // Return an empty string in case of an error + } +} + +export const FiatOnRampModalButton = ({ + provider, + inputCurrency, + outputCurrency, + amount, + disabled, +}: FiatOnRampProps & { disabled: boolean }) => { + const { t } = useTranslation() + const [shouldCheck, setShouldCheck] = useState(false) + const [onPresentConfirmModal] = useModal( + , + ) + + const { fiatOnarampAvailability, availabilityChecked, loading, error } = useFiatOnrampAvailability( + shouldCheck, + onPresentConfirmModal, + ) + + const handleBuyCryptoClick = useCallback(() => { + if (!availabilityChecked) { + setShouldCheck(true) + } else if (fiatOnarampAvailability) { + onPresentConfirmModal() + setShouldCheck(false) + } + }, [fiatOnarampAvailability, availabilityChecked, onPresentConfirmModal]) + + const disableBuyCryptoButton = Boolean(error || (!fiatOnarampAvailability && availabilityChecked) || loading) + + let buttonText: ReactNode | string = t(`Buy with %provider%`, { provider }) + if (disabled) { + buttonText = ( + <> + + + {t('Fetching Quotes')} + + + + + ) + } + return ( + <> + {!disableBuyCryptoButton ? ( + + + {buttonText} + + + ) : null} + + ) +} + +export const FiatOnRampModal = memo(function ConfirmSwapModalComp({ + onDismiss, + inputCurrency, + outputCurrency, + amount, + provider, +}) { + const [scriptLoaded, setScriptOnLoad] = useState(Boolean(window?.mercuryoWidget)) + + const [error, setError] = useState(false) + const [signedIframeUrl, setSignedIframeUrl] = useState(null) + const [sig, setSig] = useState(null) + const [loading, setLoading] = useState(true) + const { t } = useTranslation() + const { chainId } = useActiveChainId() + + const theme = useTheme() + const account = useAccount() + // const { t } = useTranslation() + + const handleDismiss = useCallback(() => { + onDismiss?.() + }, [onDismiss]) + + const fetchSignedIframeUrl = useCallback(async () => { + if (!account.address) { + setError(t('Please connect an account before making a purchase.')) + return + } + setLoading(true) + setError(null) + + try { + let result = '' + if (provider === 'MoonPay') + result = await fetchMoonPaySignedUrl(inputCurrency, outputCurrency, amount, theme.isDark, account.address) + else result = await fetchBinanceConnectSignedUrl(inputCurrency, outputCurrency, amount, account.address) + + setSignedIframeUrl(result) + } catch (e) { + setError(e.toString()) + } finally { + setTimeout(() => setLoading(false), 2000) + } + }, [account.address, theme.isDark, inputCurrency, outputCurrency, amount, provider, t]) + + useEffect(() => { + const fetchSig = async () => { + setLoading(true) + setError(null) + try { + const res = await fetch(`${ONRAMP_API_BASE_URL}/generate-mercuryo-sig?walletAddress=${account.address}`, { + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + }) + const signature = await res.json() + setSig(signature.signature) + } catch (e) { + setError(e.toString()) + } finally { + setTimeout(() => setLoading(false), 2000) + } + } + fetchSig() + }, [account.address]) + + useEffect(() => { + if (provider === 'Mercuryo') { + if (sig && window?.mercuryoWidget) { + // @ts-ignore + const MC_WIDGET = window?.mercuryoWidget + MC_WIDGET.run({ + widgetId: MERCURYO_WIDGET_ID, + fiatCurrency: outputCurrency.toUpperCase(), + currency: inputCurrency.toUpperCase(), + fiatAmount: amount, + currencies: chainId === ChainId.ETHEREUM ? ETHEREUM_TOKENS : mercuryoWhitelist, + fiatCurrencies: SUPPORTED_MERCURYO_FIAT_CURRENCIES, + address: account.address, + signature: sig, + height: '820px', + width: '400px', + host: document.getElementById('mercuryo-widget'), + theme: theme.isDark ? 'xzen' : 'phemex', + }) + } + } else fetchSignedIframeUrl() + }, [ + fetchSignedIframeUrl, + provider, + sig, + account.address, + amount, + inputCurrency, + outputCurrency, + theme, + scriptLoaded, + chainId, + ]) + + return ( + <> + + {error ? ( + + + something went wrong! + + + ) : provider === 'Mercuryo' ? ( + <> + {loading && } +
+ + ) : ( + <> + {loading && } + + + )} + +