From dd44f0a91f746f4306a7fc0cb9b36565df1b8b6e Mon Sep 17 00:00:00 2001 From: Fionna <13184582+fionnachan@users.noreply.github.com> Date: Tue, 16 Sep 2025 18:28:15 +0100 Subject: [PATCH 01/22] move moonpay into its own file --- .../components/{ => BuyPanel}/BuyPanel.tsx | 130 +++--------------- .../src/components/BuyPanel/MoonPayPanel.tsx | 110 +++++++++++++++ 2 files changed, 132 insertions(+), 108 deletions(-) rename packages/arb-token-bridge-ui/src/components/{ => BuyPanel}/BuyPanel.tsx (59%) create mode 100644 packages/arb-token-bridge-ui/src/components/BuyPanel/MoonPayPanel.tsx diff --git a/packages/arb-token-bridge-ui/src/components/BuyPanel.tsx b/packages/arb-token-bridge-ui/src/components/BuyPanel/BuyPanel.tsx similarity index 59% rename from packages/arb-token-bridge-ui/src/components/BuyPanel.tsx rename to packages/arb-token-bridge-ui/src/components/BuyPanel/BuyPanel.tsx index 1b28764de..540924df4 100644 --- a/packages/arb-token-bridge-ui/src/components/BuyPanel.tsx +++ b/packages/arb-token-bridge-ui/src/components/BuyPanel/BuyPanel.tsx @@ -1,7 +1,7 @@ import { ChevronDownIcon } from '@heroicons/react/24/outline'; import { BigNumber, utils } from 'ethers'; import dynamic from 'next/dynamic'; -import React, { PropsWithChildren, memo, useCallback } from 'react'; +import React, { PropsWithChildren, memo } from 'react'; import { twMerge } from 'tailwind-merge'; import { Chain } from 'viem'; import { useAccount, useBalance } from 'wagmi'; @@ -10,69 +10,23 @@ import { shallow } from 'zustand/shallow'; import { getProviderForChainId } from '@/token-bridge-sdk/utils'; -import { useETHPrice } from '../hooks/useETHPrice'; -import { useMode } from '../hooks/useMode'; -import { useNativeCurrency } from '../hooks/useNativeCurrency'; -import { ChainId } from '../types/ChainId'; -import { getAPIBaseUrl } from '../util'; -import { formatAmount, formatUSD } from '../util/NumberUtils'; -import { isOnrampEnabled, isOnrampServiceEnabled } from '../util/featureFlag'; -import { getNetworkName } from '../util/networks'; -import { TokenLogoFallback } from './TransferPanel/TokenInfo'; -import { Button } from './common/Button'; -import { Dialog } from './common/Dialog'; -import { DialogProps, DialogWrapper, useDialog2 } from './common/Dialog2'; -import { NetworkImage } from './common/NetworkImage'; -import { NetworksPanel } from './common/NetworkSelectionContainer'; -import { SafeImage } from './common/SafeImage'; -import { SearchPanel } from './common/SearchPanel/SearchPanel'; -import { Loader } from './common/atoms/Loader'; - -function MoonPaySkeleton({ children }: PropsWithChildren) { - const { embedMode } = useMode(); - - return ( -
-
-
-
- } - /> -

MoonPay

-

- PayPal, Debit Card, Apple Pay -

-
-
div]:!m-0 [&>div]:!w-full [&>div]:!border-x-0 [&>div]:!border-none [&>div]:!p-0 sm:[&>div]:!rounded sm:[&>div]:!border-x', - '[&_iframe]:rounded-xl', - )} - > - {children} -
-

- On-Ramps are not directly endorsed by Arbitrum. Please use at your own risk. -

-
- ); -} +import { useETHPrice } from '../../hooks/useETHPrice'; +import { useMode } from '../../hooks/useMode'; +import { useNativeCurrency } from '../../hooks/useNativeCurrency'; +import { ChainId } from '../../types/ChainId'; +import { formatAmount, formatUSD } from '../../util/NumberUtils'; +import { isOnrampEnabled, isOnrampServiceEnabled } from '../../util/featureFlag'; +import { getNetworkName } from '../../util/networks'; +import { TokenLogoFallback } from '../TransferPanel/TokenInfo'; +import { Button } from '../common/Button'; +import { Dialog } from '../common/Dialog'; +import { DialogProps, DialogWrapper, useDialog2 } from '../common/Dialog2'; +import { NetworkImage } from '../common/NetworkImage'; +import { NetworksPanel } from '../common/NetworkSelectionContainer'; +import { SafeImage } from '../common/SafeImage'; +import { SearchPanel } from '../common/SearchPanel/SearchPanel'; +import { Loader } from '../common/atoms/Loader'; +import { MoonPayPanel, MoonPaySkeleton } from './MoonPayPanel'; const MoonPayProvider = dynamic( () => import('@moonpay/moonpay-react').then((mod) => mod.MoonPayProvider), @@ -169,7 +123,7 @@ function BuyPanelNetworkButton({ const selectedChainId = useBuyPanelStore((state) => state.selectedChainId); return ( - ) } -export function Homepage({ children }: PropsWithChildren) { - return
+function MoonPayTile() { + return ( + + ) +} + +export function Homepage() { + return ( +
+ + {onrampServices.map(service => ( + + ))} +
+ ) } diff --git a/packages/arb-token-bridge-ui/src/components/BuyPanel/MoonPayPanel.tsx b/packages/arb-token-bridge-ui/src/components/BuyPanel/MoonPayPanel.tsx index f057b3847..e5db2e80d 100644 --- a/packages/arb-token-bridge-ui/src/components/BuyPanel/MoonPayPanel.tsx +++ b/packages/arb-token-bridge-ui/src/components/BuyPanel/MoonPayPanel.tsx @@ -52,15 +52,6 @@ export function MoonPaySkeleton({ children }: PropsWithChildren) { > {children} -

- On-Ramps are not directly endorsed by Arbitrum. Please use at your own - risk. -

) } diff --git a/packages/arb-token-bridge-ui/tailwind.config.js b/packages/arb-token-bridge-ui/tailwind.config.js index 1ec6e6a25..79539e5e6 100644 --- a/packages/arb-token-bridge-ui/tailwind.config.js +++ b/packages/arb-token-bridge-ui/tailwind.config.js @@ -53,7 +53,7 @@ module.exports = { 'dark': '#1A1C1D', // (or default-black) 'dark-hover': '#2b2e30', // (or default-black-hover) - 'bg-gray-1': '#191919', + 'gray-neutral-100': '#212121', // BRAND 'eth-dark': '#1A1C33', From 71d52e449aa8c38446be78ef782feb7503eee35b Mon Sep 17 00:00:00 2001 From: Fionna <13184582+fionnachan@users.noreply.github.com> Date: Thu, 2 Oct 2025 13:42:42 +0100 Subject: [PATCH 05/22] fix import issue --- .../src/components/BuyPanel/BuyPanel.tsx | 11 +++-------- .../src/components/MainContent/MainContent.tsx | 2 +- .../src/components/Widget/WidgetBuyPanel.tsx | 2 +- .../src/components/common/Dialog2.tsx | 2 +- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/components/BuyPanel/BuyPanel.tsx b/packages/arb-token-bridge-ui/src/components/BuyPanel/BuyPanel.tsx index f03a65b17..d31750949 100644 --- a/packages/arb-token-bridge-ui/src/components/BuyPanel/BuyPanel.tsx +++ b/packages/arb-token-bridge-ui/src/components/BuyPanel/BuyPanel.tsx @@ -196,18 +196,13 @@ const BalanceWrapper = memo(function BalanceWrapper() { }); function OnrampDisclaimer() { - const { embedMode } = useMode() + const { embedMode } = useMode(); return ( -

+

On-Ramps are not endorsed by Arbitrum. Please use at your own risk.

- ) + ); } export function BuyPanel() { diff --git a/packages/arb-token-bridge-ui/src/components/MainContent/MainContent.tsx b/packages/arb-token-bridge-ui/src/components/MainContent/MainContent.tsx index 9db18eaaf..4dceee2da 100644 --- a/packages/arb-token-bridge-ui/src/components/MainContent/MainContent.tsx +++ b/packages/arb-token-bridge-ui/src/components/MainContent/MainContent.tsx @@ -8,7 +8,7 @@ import { isOnrampFeatureEnabled } from '@/bridge/util/queryParamUtils'; import { useArbQueryParams } from '../../hooks/useArbQueryParams'; import { useMode } from '../../hooks/useMode'; -import { BuyPanel } from '../BuyPanel'; +import { BuyPanel } from '../BuyPanel/BuyPanel'; import { RecoverFunds } from '../RecoverFunds'; import { TopNavBar } from '../TopNavBar'; import { TransactionHistory } from '../TransactionHistory/TransactionHistory'; diff --git a/packages/arb-token-bridge-ui/src/components/Widget/WidgetBuyPanel.tsx b/packages/arb-token-bridge-ui/src/components/Widget/WidgetBuyPanel.tsx index a51e4ef95..9426afb7a 100644 --- a/packages/arb-token-bridge-ui/src/components/Widget/WidgetBuyPanel.tsx +++ b/packages/arb-token-bridge-ui/src/components/Widget/WidgetBuyPanel.tsx @@ -1,6 +1,6 @@ import { twMerge } from 'tailwind-merge'; -import { BuyPanel } from '../BuyPanel'; +import { BuyPanel } from '../BuyPanel/BuyPanel'; import { DialogProps, DialogWrapper, OpenDialogFunction } from '../common/Dialog2'; import { WidgetHeaderRow } from './WidgetHeaderRow'; diff --git a/packages/arb-token-bridge-ui/src/components/common/Dialog2.tsx b/packages/arb-token-bridge-ui/src/components/common/Dialog2.tsx index bc9be1b22..e5c19e4c3 100644 --- a/packages/arb-token-bridge-ui/src/components/common/Dialog2.tsx +++ b/packages/arb-token-bridge-ui/src/components/common/Dialog2.tsx @@ -5,7 +5,7 @@ import { useNativeCurrency } from '../../hooks/useNativeCurrency'; import { useNetworks } from '../../hooks/useNetworks'; import { useNetworksRelationship } from '../../hooks/useNetworksRelationship'; import { useSelectedToken } from '../../hooks/useSelectedToken'; -import { BuyPanelNetworkSelectionContainer } from '../BuyPanel'; +import { BuyPanelNetworkSelectionContainer } from '../BuyPanel/BuyPanel'; import { RecoverFundsDialog } from '../RecoverFunds'; import { CustomDestinationAddressConfirmationDialog } from '../TransferPanel/CustomDestinationAddressConfirmationDialog'; import { CustomFeeTokenApprovalDialog } from '../TransferPanel/CustomFeeTokenApprovalDialog'; From 60bda28e1958a1c44c14c536588b4c731cba0de8 Mon Sep 17 00:00:00 2001 From: Fionna <13184582+fionnachan@users.noreply.github.com> Date: Thu, 2 Oct 2025 17:45:05 +0100 Subject: [PATCH 06/22] wip --- .../(with-sidebar)/bridge/buy/[slug]/page.tsx | 93 ++++++++++++++++ .../src/components/BuyPanel/BuyPanel.tsx | 27 +++-- .../src/components/BuyPanel/Homepage.tsx | 103 ++++++++++-------- .../src/components/BuyPanel/MoonPayPanel.tsx | 93 +++++++++------- .../src/components/TopNavBar.tsx | 4 +- 5 files changed, 223 insertions(+), 97 deletions(-) create mode 100644 packages/app/src/app/(with-sidebar)/bridge/buy/[slug]/page.tsx diff --git a/packages/app/src/app/(with-sidebar)/bridge/buy/[slug]/page.tsx b/packages/app/src/app/(with-sidebar)/bridge/buy/[slug]/page.tsx new file mode 100644 index 000000000..c8f07e523 --- /dev/null +++ b/packages/app/src/app/(with-sidebar)/bridge/buy/[slug]/page.tsx @@ -0,0 +1,93 @@ +import type { Metadata } from 'next'; +import { sanitizeAndRedirect } from 'packages/app/src/utils/sanitizeAndRedirect'; + +import { PORTAL_DOMAIN } from '@/bridge/constants'; +import { ChainKeyQueryParam, getChainForChainKeyQueryParam } from '@/bridge/types/ChainQueryParam'; +import { isNetwork } from '@/bridge/util/networks'; + +import { addOrbitChainsToArbitrumSDK } from '../../../../../initialization'; +import BridgeClient from '../../BridgeClient'; + +type Props = { + searchParams: { [key: string]: string | string[] | undefined }; + params: { slug: string }; +}; + +export async function generateMetadata({ searchParams }: Props): Promise { + const sourceChainSlug = ( + typeof searchParams.sourceChain === 'string' ? searchParams.sourceChain : 'ethereum' + ) as ChainKeyQueryParam; + const destinationChainSlug = ( + typeof searchParams.destinationChain === 'string' + ? searchParams.destinationChain + : 'arbitrum-one' + ) as ChainKeyQueryParam; + + let sourceChainInfo; + let destinationChainInfo; + + try { + sourceChainInfo = getChainForChainKeyQueryParam(sourceChainSlug); + destinationChainInfo = getChainForChainKeyQueryParam(destinationChainSlug); + } catch (error) { + sourceChainInfo = getChainForChainKeyQueryParam('ethereum'); + destinationChainInfo = getChainForChainKeyQueryParam('arbitrum-one'); + } + + const { isOrbitChain: isSourceOrbitChain } = isNetwork(sourceChainInfo.id); + const { isOrbitChain: isDestinationOrbitChain } = isNetwork(destinationChainInfo.id); + + const siteTitle = `Bridge to ${destinationChainInfo.name}`; + const siteDescription = `Bridge from ${sourceChainInfo.name} to ${destinationChainInfo.name} using the Arbitrum Bridge. Built to scale Ethereum, Arbitrum brings you 10x lower costs while inheriting Ethereum's security model. Arbitrum is a Layer 2 Optimistic Rollup.`; + const siteDomain = PORTAL_DOMAIN; + + let metaImagePath = `${sourceChainInfo.id}-to-${destinationChainInfo.id}.jpg`; + + if (isSourceOrbitChain) { + metaImagePath = `${sourceChainInfo.id}.jpg`; + } + + if (isDestinationOrbitChain) { + metaImagePath = `${destinationChainInfo.id}.jpg`; + } + + const imageUrl = `${siteDomain}/images/__auto-generated/open-graph/${metaImagePath}`; + + return { + title: siteTitle, + description: siteDescription, + openGraph: { + url: `${siteDomain}/bridge`, + type: 'website', + title: siteTitle, + description: siteDescription, + images: [imageUrl], + }, + twitter: { + card: 'summary_large_image', + site: siteDomain, + title: siteTitle, + description: siteDescription, + images: [imageUrl], + }, + }; +} + +export default async function BridgeBuyOnrampServicePage({ searchParams, params }: Props) { + /** + * This code is run on every query param change, + * we don't want to sanitize every query param change. + * It should only be executed once per user per session. + */ + if (searchParams.sanitized !== 'true') { + addOrbitChainsToArbitrumSDK(); + await sanitizeAndRedirect(searchParams, `/bridge/buy/${params.slug}`); + } + + return ( +
+ + {params.slug} +
+ ); +} diff --git a/packages/arb-token-bridge-ui/src/components/BuyPanel/BuyPanel.tsx b/packages/arb-token-bridge-ui/src/components/BuyPanel/BuyPanel.tsx index d31750949..33f589e54 100644 --- a/packages/arb-token-bridge-ui/src/components/BuyPanel/BuyPanel.tsx +++ b/packages/arb-token-bridge-ui/src/components/BuyPanel/BuyPanel.tsx @@ -1,6 +1,7 @@ import { ChevronDownIcon } from '@heroicons/react/24/outline'; import { BigNumber, utils } from 'ethers'; import dynamic from 'next/dynamic'; +import { usePathname } from 'next/navigation'; import React, { PropsWithChildren, memo } from 'react'; import { twMerge } from 'tailwind-merge'; import { Chain } from 'viem'; @@ -27,7 +28,7 @@ import { SafeImage } from '../common/SafeImage'; import { SearchPanel } from '../common/SearchPanel/SearchPanel'; import { Loader } from '../common/atoms/Loader'; import { Homepage } from './Homepage'; -import { MoonPaySkeleton } from './MoonPayPanel'; +import { MoonPayPanel, MoonPaySkeleton } from './MoonPayPanel'; const MoonPayProvider = dynamic( () => import('@moonpay/moonpay-react').then((mod) => mod.MoonPayProvider), @@ -39,7 +40,6 @@ const MoonPayProvider = dynamic( const isMoonPayEnabled = isOnrampServiceEnabled('moonpay'); -// eslint-disable-next-line @typescript-eslint/no-unused-vars function OnRampProviders({ children }: PropsWithChildren) { if (!isOnrampEnabled()) { return children; @@ -205,6 +205,21 @@ function OnrampDisclaimer() { ); } +function OnrampServicePanel() { + const pathname = usePathname(); + const onrampService = pathname.split('/').pop(); + + switch (onrampService) { + case 'moonpay': + if (!isMoonPayEnabled) { + return null; + } + return ; + default: + return ; + } +} + export function BuyPanel() { const { embedMode } = useMode(); @@ -217,11 +232,9 @@ export function BuyPanel() { > - - - {/* - - */} + + + diff --git a/packages/arb-token-bridge-ui/src/components/BuyPanel/Homepage.tsx b/packages/arb-token-bridge-ui/src/components/BuyPanel/Homepage.tsx index b4b298ab3..25374d129 100644 --- a/packages/arb-token-bridge-ui/src/components/BuyPanel/Homepage.tsx +++ b/packages/arb-token-bridge-ui/src/components/BuyPanel/Homepage.tsx @@ -1,85 +1,89 @@ -import { useCallback } from 'react' -import Image from 'next/image' -import { ChainId } from '@/bridge/types/ChainId' -import { twMerge } from 'tailwind-merge' -import MoonPay from '@/images/onramp/moonpay.svg' +import { ArrowUpRightIcon } from '@heroicons/react/24/outline'; +import Image from 'next/image'; +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { useCallback } from 'react'; +import { twMerge } from 'tailwind-merge'; -import { Button } from '../common/Button' -import { ArrowUpRightIcon } from '@heroicons/react/24/outline' +import { ChainId } from '@/bridge/types/ChainId'; +import MoonPay from '@/images/onramp/moonpay.svg'; + +import { Button } from '../common/Button'; const onrampServices = [ { name: 'Transak', + slug: 'transak', logo: '/images/onramp/transak.webp', link: 'https://global.transak.com', - chains: [ChainId.Ethereum, ChainId.ArbitrumOne] + chains: [ChainId.Ethereum, ChainId.ArbitrumOne], }, { name: 'Ramp', + slug: 'ramp', logo: '/images/onramp/ramp.webp', link: 'https://ramp.network/buy', - chains: [ChainId.Ethereum, ChainId.ArbitrumOne] + chains: [ChainId.Ethereum, ChainId.ArbitrumOne], }, { name: 'Mt Pelerin', + slug: 'mt-pelerin', logo: '/images/onramp/mt_pelerin.webp', link: 'https://www.mtpelerin.com/buy-crypto', - chains: [ChainId.Ethereum, ChainId.ArbitrumOne] + chains: [ChainId.Ethereum, ChainId.ArbitrumOne], }, { name: 'Coinbase Pay', + slug: 'coinbase-pay', logo: '/images/onramp/coinbase.webp', link: 'https://login.coinbase.com/signin?client_id=258660e1-9cfe-4202-9eda-d3beedb3e118&oauth_challenge=851bae2a-c907-413d-9a12-71c1dfaa5d4f', - chains: [ChainId.Ethereum, ChainId.ArbitrumOne] + chains: [ChainId.Ethereum, ChainId.ArbitrumOne], }, { name: 'Onramp', + slug: 'onramp', logo: '/images/onramp/onramp.webp', link: 'https://onramp.money', - chains: [ChainId.Ethereum, ChainId.ArbitrumOne] + chains: [ChainId.Ethereum, ChainId.ArbitrumOne], }, { name: 'Banxa', + slug: 'banxa', logo: '/images/onramp/banxa.webp', link: 'https://checkout.banxa.com', - chains: [ChainId.Ethereum, ChainId.ArbitrumOne] + chains: [ChainId.Ethereum, ChainId.ArbitrumOne], }, { name: 'Simplex', + slug: 'simplex', logo: '/images/onramp/simplex.webp', link: 'https://buy.simplex.com', - chains: [ChainId.Ethereum] + chains: [ChainId.Ethereum], }, { name: 'Kado', + slug: 'kado', logo: '/images/onramp/kado.webp', link: 'https://swapped.com/', - chains: [ChainId.Ethereum, ChainId.ArbitrumOne] + chains: [ChainId.Ethereum, ChainId.ArbitrumOne], }, { name: 'Alchemy Pay', + slug: 'alchemy-pay', logo: '/images/onramp/alchemy_pay.webp', link: 'https://ramp.alchemypay.org/#/index', - chains: [ChainId.Ethereum, ChainId.ArbitrumOne] - } -] as const + chains: [ChainId.Ethereum, ChainId.ArbitrumOne], + }, +] as const; -function OnrampServiceTile({ - name, - logo, - link -}: { - name: string - logo: string - link: string -}) { - const openDetails = useCallback(() => {}, []) +function OnrampServiceTile({ name, logo }: { name: string; logo: string }) { + const openDetails = useCallback(() => {}, []); return ( - ) + ); } function MoonPayTile() { + const pathname = usePathname(); + return ( - - ) + + + ); } export function Homepage() { return (
- {onrampServices.map(service => ( + {onrampServices.map((service) => ( ))}
- ) + ); } diff --git a/packages/arb-token-bridge-ui/src/components/BuyPanel/MoonPayPanel.tsx b/packages/arb-token-bridge-ui/src/components/BuyPanel/MoonPayPanel.tsx index e5db2e80d..6312df7e2 100644 --- a/packages/arb-token-bridge-ui/src/components/BuyPanel/MoonPayPanel.tsx +++ b/packages/arb-token-bridge-ui/src/components/BuyPanel/MoonPayPanel.tsx @@ -1,29 +1,33 @@ -import dynamic from 'next/dynamic' -import React, { memo, PropsWithChildren, useCallback } from 'react' -import { twMerge } from 'tailwind-merge' -import { useAccount } from 'wagmi' +import { ChevronLeftIcon } from '@heroicons/react/24/outline'; +import dynamic from 'next/dynamic'; +import Link from 'next/link'; +import React, { PropsWithChildren, memo, useCallback } from 'react'; +import { twMerge } from 'tailwind-merge'; +import { useAccount } from 'wagmi'; -import { getAPIBaseUrl } from '../../util' -import { isOnrampServiceEnabled } from '../../util/featureFlag' +import { BUY_EMBED_PATHNAME, BUY_PATHNAME } from '@/bridge/constants'; +import { useMode } from '@/bridge/hooks/useMode'; -import { useMode } from '@/bridge/hooks/useMode' -import { SafeImage } from '../common/SafeImage' +import { getAPIBaseUrl } from '../../util'; +import { isOnrampServiceEnabled } from '../../util/featureFlag'; +import { Button } from '../common/Button'; +import { SafeImage } from '../common/SafeImage'; export function MoonPaySkeleton({ children }: PropsWithChildren) { - const { embedMode } = useMode() + const { embedMode } = useMode(); return (
@@ -32,13 +36,9 @@ export function MoonPaySkeleton({ children }: PropsWithChildren) { alt="MoonPay" width={embedMode ? 45 : 65} height={embedMode ? 45 : 65} - fallback={ -
- } + fallback={
} /> -

- MoonPay -

+

MoonPay

PayPal, Debit Card, Apple Pay

@@ -47,44 +47,53 @@ export function MoonPaySkeleton({ children }: PropsWithChildren) { className={twMerge( 'relative h-full min-h-[600px] w-full', '[&>div]:!m-0 [&>div]:!w-full [&>div]:!border-x-0 [&>div]:!border-none [&>div]:!p-0 sm:[&>div]:!rounded sm:[&>div]:!border-x', - '[&_iframe]:rounded-xl' + '[&_iframe]:rounded-xl', )} > {children}
+ + + Back +
- ) + ); } export const MoonPayPanel = memo(function MoonPayPanel() { - const { address } = useAccount() - const showMoonPay = isOnrampServiceEnabled('moonpay') + const { address } = useAccount(); + const showMoonPay = isOnrampServiceEnabled('moonpay'); - const handleGetSignature = useCallback( - async (widgetUrl: string): Promise => { - const response = await fetch(`${getAPIBaseUrl()}/api/moonpay`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ url: widgetUrl }) - }) - const { signature } = await response.json() - return signature - }, - [] - ) + const handleGetSignature = useCallback(async (widgetUrl: string): Promise => { + const response = await fetch(`${getAPIBaseUrl()}/api/moonpay`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ url: widgetUrl }), + }); + const { signature } = await response.json(); + return signature; + }, []); if (!showMoonPay) { - return null + return null; } const MoonPayBuyWidget = dynamic( - () => import('@moonpay/moonpay-react').then(mod => mod.MoonPayBuyWidget), + () => import('@moonpay/moonpay-react').then((mod) => mod.MoonPayBuyWidget), { - ssr: false - } - ) + ssr: false, + }, + ); return ( @@ -97,5 +106,5 @@ export const MoonPayPanel = memo(function MoonPayPanel() { visible /> - ) -}) + ); +}); diff --git a/packages/arb-token-bridge-ui/src/components/TopNavBar.tsx b/packages/arb-token-bridge-ui/src/components/TopNavBar.tsx index 854669f6f..e3270c98d 100644 --- a/packages/arb-token-bridge-ui/src/components/TopNavBar.tsx +++ b/packages/arb-token-bridge-ui/src/components/TopNavBar.tsx @@ -24,7 +24,7 @@ function StyledTab({ hrefQuery?: string; }>) { const pathname = usePathname(); - const isBuyTab = pathname === PathnameEnum.BUY; + const isBuyTab = pathname.startsWith(PathnameEnum.BUY); const { embedMode } = useMode(); return ( @@ -56,7 +56,7 @@ export function TopNavBar() { const showBuyPanel = isOnrampFeatureEnabled({ disabledFeatures }); const { embedMode } = useMode(); const pathname = usePathname(); - const isBuyTab = pathname === PathnameEnum.BUY; + const isBuyTab = pathname.startsWith(PathnameEnum.BUY); const searchParams = useSearchParams(); const searchParamsWithoutTab = useMemo(() => { From 2a3c6ed1084b406d8bc161e8837ac45141c14240 Mon Sep 17 00:00:00 2001 From: Fionna <13184582+fionnachan@users.noreply.github.com> Date: Fri, 3 Oct 2025 14:21:53 +0100 Subject: [PATCH 07/22] fix merge conflicts --- .../src/components/BuyPanel/MoonPayPanel.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/components/BuyPanel/MoonPayPanel.tsx b/packages/arb-token-bridge-ui/src/components/BuyPanel/MoonPayPanel.tsx index 6312df7e2..c8f5a9b17 100644 --- a/packages/arb-token-bridge-ui/src/components/BuyPanel/MoonPayPanel.tsx +++ b/packages/arb-token-bridge-ui/src/components/BuyPanel/MoonPayPanel.tsx @@ -5,7 +5,7 @@ import React, { PropsWithChildren, memo, useCallback } from 'react'; import { twMerge } from 'tailwind-merge'; import { useAccount } from 'wagmi'; -import { BUY_EMBED_PATHNAME, BUY_PATHNAME } from '@/bridge/constants'; +import { PathnameEnum } from '@/bridge/constants'; import { useMode } from '@/bridge/hooks/useMode'; import { getAPIBaseUrl } from '../../util'; @@ -53,7 +53,7 @@ export function MoonPaySkeleton({ children }: PropsWithChildren) { {children}
diff --git a/packages/arb-token-bridge-ui/src/components/MainContent/MainContent.tsx b/packages/arb-token-bridge-ui/src/components/MainContent/MainContent.tsx index 4dceee2da..0f8efa1f4 100644 --- a/packages/arb-token-bridge-ui/src/components/MainContent/MainContent.tsx +++ b/packages/arb-token-bridge-ui/src/components/MainContent/MainContent.tsx @@ -28,7 +28,7 @@ export function MainContent() { // `tab` from useArbQueryParams will never be 0 when showBuyPanel is true // because we use /buy and don't use ?tab=buy // so we need to hardcode to return 0 rather than `tab` - if (pathname === PathnameEnum.BUY) { + if (pathname.startsWith(PathnameEnum.BUY)) { return 0; } return tab; @@ -52,7 +52,7 @@ export function MainContent() { {}}> - + {showBuyPanel && ( From 8c039b8053c86edcf3ed4a99aa5694b25888b516 Mon Sep 17 00:00:00 2001 From: Fionna <13184582+fionnachan@users.noreply.github.com> Date: Fri, 3 Oct 2025 18:14:13 +0100 Subject: [PATCH 09/22] add details screen --- .../src/components/BuyPanel/BackButton.tsx | 25 ++++ .../src/components/BuyPanel/BuyPanel.tsx | 9 +- .../src/components/BuyPanel/Homepage.tsx | 129 ++++++------------ .../BuyPanel/LinkoutOnrampPanel.tsx | 54 ++++++++ .../src/components/BuyPanel/MoonPayPanel.tsx | 20 +-- .../src/components/BuyPanel/utils.ts | 67 +++++++++ .../src/components/common/Button.tsx | 6 +- .../arb-token-bridge-ui/tailwind.config.js | 2 - 8 files changed, 202 insertions(+), 110 deletions(-) create mode 100644 packages/arb-token-bridge-ui/src/components/BuyPanel/BackButton.tsx create mode 100644 packages/arb-token-bridge-ui/src/components/BuyPanel/LinkoutOnrampPanel.tsx create mode 100644 packages/arb-token-bridge-ui/src/components/BuyPanel/utils.ts diff --git a/packages/arb-token-bridge-ui/src/components/BuyPanel/BackButton.tsx b/packages/arb-token-bridge-ui/src/components/BuyPanel/BackButton.tsx new file mode 100644 index 000000000..ad93467ac --- /dev/null +++ b/packages/arb-token-bridge-ui/src/components/BuyPanel/BackButton.tsx @@ -0,0 +1,25 @@ +import { ChevronLeftIcon } from '@heroicons/react/24/outline'; +import Link from 'next/link'; + +import { PathnameEnum } from '@/bridge/constants'; +import { useMode } from '@/bridge/hooks/useMode'; + +import { Button } from '../common/Button'; + +export function BackButton() { + const { embedMode } = useMode(); + return ( + + + Back + + ); +} diff --git a/packages/arb-token-bridge-ui/src/components/BuyPanel/BuyPanel.tsx b/packages/arb-token-bridge-ui/src/components/BuyPanel/BuyPanel.tsx index 33f589e54..745a33ee3 100644 --- a/packages/arb-token-bridge-ui/src/components/BuyPanel/BuyPanel.tsx +++ b/packages/arb-token-bridge-ui/src/components/BuyPanel/BuyPanel.tsx @@ -28,7 +28,9 @@ import { SafeImage } from '../common/SafeImage'; import { SearchPanel } from '../common/SearchPanel/SearchPanel'; import { Loader } from '../common/atoms/Loader'; import { Homepage } from './Homepage'; +import { LinkoutOnrampPanel } from './LinkoutOnrampPanel'; import { MoonPayPanel, MoonPaySkeleton } from './MoonPayPanel'; +import { onrampServices } from './utils'; const MoonPayProvider = dynamic( () => import('@moonpay/moonpay-react').then((mod) => mod.MoonPayProvider), @@ -199,7 +201,7 @@ function OnrampDisclaimer() { const { embedMode } = useMode(); return ( -

+

On-Ramps are not endorsed by Arbitrum. Please use at your own risk.

); @@ -208,6 +210,7 @@ function OnrampDisclaimer() { function OnrampServicePanel() { const pathname = usePathname(); const onrampService = pathname.split('/').pop(); + const allOnrampServices = onrampServices.map((service) => service.slug); switch (onrampService) { case 'moonpay': @@ -215,6 +218,8 @@ function OnrampServicePanel() { return null; } return ; + case allOnrampServices.find((service) => service === onrampService): + return ; default: return ; } @@ -226,7 +231,7 @@ export function BuyPanel() { return (
diff --git a/packages/arb-token-bridge-ui/src/components/BuyPanel/Homepage.tsx b/packages/arb-token-bridge-ui/src/components/BuyPanel/Homepage.tsx index 25374d129..b3c2e0ad4 100644 --- a/packages/arb-token-bridge-ui/src/components/BuyPanel/Homepage.tsx +++ b/packages/arb-token-bridge-ui/src/components/BuyPanel/Homepage.tsx @@ -1,116 +1,57 @@ import { ArrowUpRightIcon } from '@heroicons/react/24/outline'; import Image from 'next/image'; import Link from 'next/link'; -import { usePathname } from 'next/navigation'; +import { usePathname, useRouter, useSearchParams } from 'next/navigation'; import { useCallback } from 'react'; import { twMerge } from 'tailwind-merge'; -import { ChainId } from '@/bridge/types/ChainId'; import MoonPay from '@/images/onramp/moonpay.svg'; import { Button } from '../common/Button'; +import { onrampServices } from './utils'; -const onrampServices = [ - { - name: 'Transak', - slug: 'transak', - logo: '/images/onramp/transak.webp', - link: 'https://global.transak.com', - chains: [ChainId.Ethereum, ChainId.ArbitrumOne], - }, - { - name: 'Ramp', - slug: 'ramp', - logo: '/images/onramp/ramp.webp', - link: 'https://ramp.network/buy', - chains: [ChainId.Ethereum, ChainId.ArbitrumOne], - }, - { - name: 'Mt Pelerin', - slug: 'mt-pelerin', - logo: '/images/onramp/mt_pelerin.webp', - link: 'https://www.mtpelerin.com/buy-crypto', - chains: [ChainId.Ethereum, ChainId.ArbitrumOne], - }, - { - name: 'Coinbase Pay', - slug: 'coinbase-pay', - logo: '/images/onramp/coinbase.webp', - link: 'https://login.coinbase.com/signin?client_id=258660e1-9cfe-4202-9eda-d3beedb3e118&oauth_challenge=851bae2a-c907-413d-9a12-71c1dfaa5d4f', - chains: [ChainId.Ethereum, ChainId.ArbitrumOne], - }, - { - name: 'Onramp', - slug: 'onramp', - logo: '/images/onramp/onramp.webp', - link: 'https://onramp.money', - chains: [ChainId.Ethereum, ChainId.ArbitrumOne], - }, - { - name: 'Banxa', - slug: 'banxa', - logo: '/images/onramp/banxa.webp', - link: 'https://checkout.banxa.com', - chains: [ChainId.Ethereum, ChainId.ArbitrumOne], - }, - { - name: 'Simplex', - slug: 'simplex', - logo: '/images/onramp/simplex.webp', - link: 'https://buy.simplex.com', - chains: [ChainId.Ethereum], - }, - { - name: 'Kado', - slug: 'kado', - logo: '/images/onramp/kado.webp', - link: 'https://swapped.com/', - chains: [ChainId.Ethereum, ChainId.ArbitrumOne], - }, - { - name: 'Alchemy Pay', - slug: 'alchemy-pay', - logo: '/images/onramp/alchemy_pay.webp', - link: 'https://ramp.alchemypay.org/#/index', - chains: [ChainId.Ethereum, ChainId.ArbitrumOne], - }, -] as const; - -function OnrampServiceTile({ name, logo }: { name: string; logo: string }) { - const openDetails = useCallback(() => {}, []); +function OnrampServiceTile({ name, logo, slug }: { name: string; logo: string; slug: string }) { + const pathname = usePathname(); + const searchParams = useSearchParams(); return ( - + + + + + ); } function MoonPayTile() { const pathname = usePathname(); + const searchParams = useSearchParams(); return (
); } diff --git a/packages/arb-token-bridge-ui/src/components/BuyPanel/LinkoutOnrampPanel.tsx b/packages/arb-token-bridge-ui/src/components/BuyPanel/LinkoutOnrampPanel.tsx new file mode 100644 index 000000000..c934c0d2e --- /dev/null +++ b/packages/arb-token-bridge-ui/src/components/BuyPanel/LinkoutOnrampPanel.tsx @@ -0,0 +1,54 @@ +import { ArrowUpRightIcon } from '@heroicons/react/24/outline'; +import Image from 'next/image'; +import { twMerge } from 'tailwind-merge'; + +import { useMode } from '@/bridge/hooks/useMode'; + +import { Button } from '../common/Button'; +import { ExternalLink } from '../common/ExternalLink'; +import { BackButton } from './BackButton'; +import { onrampServices } from './utils'; + +export function LinkoutOnrampPanel({ serviceSlug }: { serviceSlug: string }) { + const { embedMode } = useMode(); + const service = onrampServices.find((s) => s.slug === serviceSlug); + + if (!service) { + return null; + } + + return ( +
+
+
+ {service.name} +
+
+

{service.name}

+

+ Buy and transfer instantly using your debit card, bank account with Thirdweb{' '} + {service.name}. +

+
+
+ + + + +
+ ); +} diff --git a/packages/arb-token-bridge-ui/src/components/BuyPanel/MoonPayPanel.tsx b/packages/arb-token-bridge-ui/src/components/BuyPanel/MoonPayPanel.tsx index 03a227c99..1d9679626 100644 --- a/packages/arb-token-bridge-ui/src/components/BuyPanel/MoonPayPanel.tsx +++ b/packages/arb-token-bridge-ui/src/components/BuyPanel/MoonPayPanel.tsx @@ -1,17 +1,14 @@ -import { ChevronLeftIcon } from '@heroicons/react/24/outline'; import dynamic from 'next/dynamic'; -import Link from 'next/link'; -import React, { PropsWithChildren, memo, useCallback } from 'react'; +import { PropsWithChildren, memo, useCallback } from 'react'; import { twMerge } from 'tailwind-merge'; import { useAccount } from 'wagmi'; -import { PathnameEnum } from '@/bridge/constants'; import { useMode } from '@/bridge/hooks/useMode'; import { getAPIBaseUrl } from '../../util'; import { isOnrampServiceEnabled } from '../../util/featureFlag'; -import { Button } from '../common/Button'; import { SafeImage } from '../common/SafeImage'; +import { BackButton } from './BackButton'; export function MoonPaySkeleton({ children }: PropsWithChildren) { const { embedMode } = useMode(); @@ -52,18 +49,7 @@ export function MoonPaySkeleton({ children }: PropsWithChildren) { > {children}
- - - Back - + ); } diff --git a/packages/arb-token-bridge-ui/src/components/BuyPanel/utils.ts b/packages/arb-token-bridge-ui/src/components/BuyPanel/utils.ts new file mode 100644 index 000000000..a50d538eb --- /dev/null +++ b/packages/arb-token-bridge-ui/src/components/BuyPanel/utils.ts @@ -0,0 +1,67 @@ +import { ChainId } from '@/bridge/types/ChainId'; + +export const onrampServices = [ + { + name: 'Transak', + slug: 'transak', + logo: '/images/onramp/transak.webp', + link: 'https://global.transak.com', + chains: [ChainId.Ethereum, ChainId.ArbitrumOne], + }, + { + name: 'Ramp', + slug: 'ramp', + logo: '/images/onramp/ramp.webp', + link: 'https://ramp.network/buy', + chains: [ChainId.Ethereum, ChainId.ArbitrumOne], + }, + { + name: 'Mt Pelerin', + slug: 'mt-pelerin', + logo: '/images/onramp/mt_pelerin.webp', + link: 'https://www.mtpelerin.com/buy-crypto', + chains: [ChainId.Ethereum, ChainId.ArbitrumOne], + }, + { + name: 'Coinbase Pay', + slug: 'coinbase-pay', + logo: '/images/onramp/coinbase.webp', + link: 'https://login.coinbase.com/signin?client_id=258660e1-9cfe-4202-9eda-d3beedb3e118&oauth_challenge=851bae2a-c907-413d-9a12-71c1dfaa5d4f', + chains: [ChainId.Ethereum, ChainId.ArbitrumOne], + }, + { + name: 'Onramp', + slug: 'onramp', + logo: '/images/onramp/onramp.webp', + link: 'https://onramp.money', + chains: [ChainId.Ethereum, ChainId.ArbitrumOne], + }, + { + name: 'Banxa', + slug: 'banxa', + logo: '/images/onramp/banxa.webp', + link: 'https://checkout.banxa.com', + chains: [ChainId.Ethereum, ChainId.ArbitrumOne], + }, + { + name: 'Simplex', + slug: 'simplex', + logo: '/images/onramp/simplex.webp', + link: 'https://buy.simplex.com', + chains: [ChainId.Ethereum], + }, + { + name: 'Kado', + slug: 'kado', + logo: '/images/onramp/kado.webp', + link: 'https://swapped.com/', + chains: [ChainId.Ethereum, ChainId.ArbitrumOne], + }, + { + name: 'Alchemy Pay', + slug: 'alchemy-pay', + logo: '/images/onramp/alchemy_pay.webp', + link: 'https://ramp.alchemypay.org/#/index', + chains: [ChainId.Ethereum, ChainId.ArbitrumOne], + }, +] as const; diff --git a/packages/arb-token-bridge-ui/src/components/common/Button.tsx b/packages/arb-token-bridge-ui/src/components/common/Button.tsx index e040b84d0..2734b700e 100644 --- a/packages/arb-token-bridge-ui/src/components/common/Button.tsx +++ b/packages/arb-token-bridge-ui/src/components/common/Button.tsx @@ -1,4 +1,4 @@ -import { ArrowRightIcon } from '@heroicons/react/24/outline'; +import { ChevronRightIcon } from '@heroicons/react/24/outline'; import React, { forwardRef, useState } from 'react'; import { twMerge } from 'tailwind-merge'; @@ -74,9 +74,9 @@ export const Button = forwardRef( {children} {props.showArrow && ( - Date: Fri, 3 Oct 2025 18:18:43 +0100 Subject: [PATCH 10/22] widget buy details page --- .../app/(embed)/bridge/embed/buy/[slug]/page.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 packages/app/src/app/(embed)/bridge/embed/buy/[slug]/page.tsx diff --git a/packages/app/src/app/(embed)/bridge/embed/buy/[slug]/page.tsx b/packages/app/src/app/(embed)/bridge/embed/buy/[slug]/page.tsx new file mode 100644 index 000000000..308813345 --- /dev/null +++ b/packages/app/src/app/(embed)/bridge/embed/buy/[slug]/page.tsx @@ -0,0 +1,15 @@ +import EmbedPageWrapper from '../../EmbedPageWrapper'; + +type Props = { + searchParams: { [key: string]: string | string[] | undefined }; + params: { slug: string }; +}; + +export default async function EmbeddedBuyOnrampServicePage({ searchParams, params }: Props) { + return ( + + ); +} From b79345454a7aecea38260a57c0f5943a99826db0 Mon Sep 17 00:00:00 2001 From: Fionna <13184582+fionnachan@users.noreply.github.com> Date: Mon, 6 Oct 2025 13:33:42 +0100 Subject: [PATCH 11/22] add search query params to back button link --- .../src/components/BuyPanel/BackButton.tsx | 8 +++++++- .../src/components/BuyPanel/LinkoutOnrampPanel.tsx | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/components/BuyPanel/BackButton.tsx b/packages/arb-token-bridge-ui/src/components/BuyPanel/BackButton.tsx index ad93467ac..f16855865 100644 --- a/packages/arb-token-bridge-ui/src/components/BuyPanel/BackButton.tsx +++ b/packages/arb-token-bridge-ui/src/components/BuyPanel/BackButton.tsx @@ -1,5 +1,6 @@ import { ChevronLeftIcon } from '@heroicons/react/24/outline'; import Link from 'next/link'; +import { useSearchParams } from 'next/navigation'; import { PathnameEnum } from '@/bridge/constants'; import { useMode } from '@/bridge/hooks/useMode'; @@ -8,9 +9,14 @@ import { Button } from '../common/Button'; export function BackButton() { const { embedMode } = useMode(); + const searchParams = useSearchParams(); + return ( ); From e6ff41afc4b0d96d31773affef7aad2397aa79a9 Mon Sep 17 00:00:00 2001 From: Fionna <13184582+fionnachan@users.noreply.github.com> Date: Thu, 9 Oct 2025 13:25:15 +0100 Subject: [PATCH 17/22] clean up --- .../app/(with-sidebar)/bridge/buy/[slug]/page.tsx | 12 +----------- .../src/components/BuyPanel/BuyPanel.tsx | 2 +- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/packages/app/src/app/(with-sidebar)/bridge/buy/[slug]/page.tsx b/packages/app/src/app/(with-sidebar)/bridge/buy/[slug]/page.tsx index 767e91af3..59910d289 100644 --- a/packages/app/src/app/(with-sidebar)/bridge/buy/[slug]/page.tsx +++ b/packages/app/src/app/(with-sidebar)/bridge/buy/[slug]/page.tsx @@ -1,5 +1,5 @@ import type { Metadata } from 'next'; -import { Slug } from 'packages/app/src/utils/bridgePageUtils'; +import { Slug, initializeBridgePage } from 'packages/app/src/utils/bridgePageUtils'; import { sanitizeAndRedirect } from 'packages/app/src/utils/sanitizeAndRedirect'; import { PathnameEnum } from '@/bridge/constants'; @@ -19,16 +19,6 @@ export const metadata: Metadata = { }; export default async function BridgeBuyOnrampServicePage({ searchParams, params }: Props) { - /** - * This code is run on every query param change, - * we don't want to sanitize every query param change. - * It should only be executed once per user per session. - */ - if (searchParams.sanitized !== 'true') { - addOrbitChainsToArbitrumSDK(); - await sanitizeAndRedirect(searchParams, `${PathnameEnum.BUY}/${params.slug}`); - } - return ( Date: Thu, 9 Oct 2025 15:51:04 +0100 Subject: [PATCH 18/22] fix format --- .../app/src/app/(with-sidebar)/bridge/buy/[slug]/page.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/app/src/app/(with-sidebar)/bridge/buy/[slug]/page.tsx b/packages/app/src/app/(with-sidebar)/bridge/buy/[slug]/page.tsx index 59910d289..3afcd041e 100644 --- a/packages/app/src/app/(with-sidebar)/bridge/buy/[slug]/page.tsx +++ b/packages/app/src/app/(with-sidebar)/bridge/buy/[slug]/page.tsx @@ -1,10 +1,8 @@ import type { Metadata } from 'next'; -import { Slug, initializeBridgePage } from 'packages/app/src/utils/bridgePageUtils'; -import { sanitizeAndRedirect } from 'packages/app/src/utils/sanitizeAndRedirect'; +import { Slug } from 'packages/app/src/utils/bridgePageUtils'; import { PathnameEnum } from '@/bridge/constants'; -import { addOrbitChainsToArbitrumSDK } from '../../../../../initialization'; import BridgePageWrapper from '../../BridgePageWrapper'; type Props = { From 7da2a5ddd5371c73d97833c687354dc09fb22410 Mon Sep 17 00:00:00 2001 From: Fionna <13184582+fionnachan@users.noreply.github.com> Date: Fri, 10 Oct 2025 14:24:05 +0100 Subject: [PATCH 19/22] onramp update --- .../src/components/BuyPanel/Homepage.tsx | 11 ++++++++--- .../arb-token-bridge-ui/src/util/AnalyticsUtils.ts | 3 +++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/components/BuyPanel/Homepage.tsx b/packages/arb-token-bridge-ui/src/components/BuyPanel/Homepage.tsx index e2cc6d3d5..38f3c4c5f 100644 --- a/packages/arb-token-bridge-ui/src/components/BuyPanel/Homepage.tsx +++ b/packages/arb-token-bridge-ui/src/components/BuyPanel/Homepage.tsx @@ -5,6 +5,7 @@ import { usePathname, useRouter, useSearchParams } from 'next/navigation'; import { useCallback } from 'react'; import { twMerge } from 'tailwind-merge'; +import { trackEvent } from '@/bridge/util/AnalyticsUtils'; import MoonPay from '@/images/onramp/moonpay.svg'; import { Button } from '../common/Button'; @@ -18,6 +19,9 @@ function OnrampServiceTile({ name, logo, slug }: { name: string; logo: string; s { + trackEvent('Onramp Service Click', { service: name }); + }} >