Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@cosmjs/stargate": "^0.32.2",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@moonpay/moonpay-react": "^1.10.6",
"@noble/hashes": "^1.3.3",
"@noble/secp256k1": "^2.0.0",
"bip32": "^4.0.0",
Expand Down
148 changes: 148 additions & 0 deletions src/popup/components/MoonPaySDKWidget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import React, { useState } from 'react';
import { Box, Text, Button, VStack } from '@chakra-ui/react';
import { MoonPayProvider, MoonPayBuyWidget, MoonPaySellWidget } from '@moonpay/moonpay-react';

interface MoonPaySDKWidgetProps {
flow: 'buy' | 'sell';
cryptoCode?: string;
walletAddress?: string;
amount?: string;
colorCode?: string;
onClose?: () => void;
}

// MoonPay API Key - only from environment variable
const MOONPAY_API_KEY = import.meta.env.VITE_MOONPAY_API_KEY || '';

/**
* MoonPay SDK Widget for Web App
*
* Uses the official @moonpay/moonpay-react SDK with overlay variant.
* This opens as a popup/modal over the app.
* This component is ONLY used in the web app build, NOT in the extension.
*/
const MoonPaySDKWidget: React.FC<MoonPaySDKWidgetProps> = ({
flow,
cryptoCode = 'usdc_base',
walletAddress,
colorCode = '#3182CE',
onClose,
}) => {
const [showWidget, setShowWidget] = useState(false);
const [showMaintenance, setShowMaintenance] = useState(false);

const handleWidgetClose = () => {
setShowWidget(false);
onClose?.();
};

const handleOpenWidget = () => {
if (!MOONPAY_API_KEY) {
setShowMaintenance(true);
return;
}
setShowWidget(true);
};

const isBuy = flow === 'buy';
const colorScheme = isBuy ? 'teal' : 'orange';

// Show maintenance message if no API key
if (showMaintenance) {
return (
<VStack spacing={4} align="stretch">
<Box
bg="#141414"
borderRadius="xl"
p={6}
borderWidth="1px"
borderColor="#2a2a2a"
textAlign="center"
>
<Text fontSize="2xl" mb={3}>
🔧
</Text>
<Text fontSize="lg" fontWeight="bold" color="white" mb={2}>
Under Maintenance
</Text>
<Text fontSize="sm" color="gray.400" mb={4}>
{isBuy ? 'Buy' : 'Sell'} functionality is temporarily unavailable. Please check back
later.
</Text>
<Text fontSize="xs" color="gray.500">
We apologize for any inconvenience.
</Text>
</Box>
<Button variant="outline" colorScheme="gray" onClick={() => setShowMaintenance(false)}>
Go Back
</Button>
</VStack>
);
}

// Common configuration - using overlay variant for popup experience
const commonConfig = {
apiKey: MOONPAY_API_KEY,
variant: 'overlay' as const,
colorCode: colorCode.replace('#', ''),
language: 'en',
};

// Buy widget configuration
const buyConfig = {
...commonConfig,
defaultCurrencyCode: cryptoCode,
walletAddress: walletAddress || undefined,
baseCurrencyCode: 'usd',
};

// Sell widget configuration
const sellConfig = {
...commonConfig,
defaultBaseCurrencyCode: cryptoCode,
refundWalletAddress: walletAddress || undefined,
quoteCurrencyCode: 'usd',
};

return (
<MoonPayProvider apiKey={MOONPAY_API_KEY || 'placeholder'}>
<VStack spacing={4} align="stretch">
<Box bg="#141414" borderRadius="xl" p={4} borderWidth="1px" borderColor="#2a2a2a">
<Text fontSize="sm" color="gray.300" mb={2}>
{isBuy
? 'Buy crypto with credit card, debit card, or bank transfer.'
: 'Sell crypto and receive funds to your bank account.'}
</Text>
<Text fontSize="xs" color="gray.500">
Powered by MoonPay • Secure & Fast
</Text>
</Box>

<Button colorScheme={colorScheme} size="lg" onClick={handleOpenWidget}>
{isBuy ? 'Buy Crypto' : 'Sell Crypto'}
</Button>

{/* Render the widget (it shows as overlay when visible) */}
{showWidget && MOONPAY_API_KEY && (
<>
{isBuy ? (
<MoonPayBuyWidget
{...buyConfig}
visible={showWidget}
onCloseOverlay={handleWidgetClose}
/>
) : (
<MoonPaySellWidget
{...sellConfig}
visible={showWidget}
onCloseOverlay={handleWidgetClose}
/>
)}
</>
)}
</VStack>
</MoonPayProvider>
);
};

export default MoonPaySDKWidget;
105 changes: 91 additions & 14 deletions src/popup/components/MoonPayWidget.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,116 @@
import React, { useState } from 'react';
import { Box, Spinner, Text } from '@chakra-ui/react';
import React, { useState, lazy, Suspense } from 'react';
import { Box, Spinner, Text, VStack, Button } from '@chakra-ui/react';

// Lazy load SDK widget only for web builds
const MoonPaySDKWidgetLazy = __IS_WEB_BUILD__ ? lazy(() => import('./MoonPaySDKWidget')) : null;

interface MoonPayWidgetProps {
flow: 'buy' | 'sell';
cryptoCode: string;
walletAddress: string;
amount?: string;
colorCode?: string;
onClose?: () => void;
}

// MoonPay API Key
const MOONPAY_API_KEY =
import.meta.env.VITE_MOONPAY_API_KEY || 'pk_test_pKULLlqQbOAEd7usXz7yUiVCc8yNBNGY';
// MoonPay API Key - only from environment variable
const MOONPAY_API_KEY = import.meta.env.VITE_MOONPAY_API_KEY || '';

/**
* MoonPay Widget using iframe embedding
*
* Note: Chrome extension Manifest V3 prohibits loading remote scripts,
* so we use iframe-based embedding instead of the MoonPay Web SDK.
* MoonPay Widget - Universal Component
*
* - Web App: Uses @moonpay/moonpay-react SDK for embedded widget experience
* - Extension: Not used (Deposit/Withdraw pages open MoonPay in new tab instead)
*
* Note: This component includes iframe fallback for extension builds, but is
* currently only utilized by the web app. Extension builds use direct tab navigation
* for MoonPay transactions to avoid iframe CSP restrictions.
*/
const MoonPayWidget: React.FC<MoonPayWidgetProps> = ({
const MoonPayWidget: React.FC<MoonPayWidgetProps> = (props) => {
const { flow, cryptoCode, walletAddress, amount, colorCode = '#3182CE', onClose } = props;

// For web builds, use the SDK widget with lazy loading
if (__IS_WEB_BUILD__ && MoonPaySDKWidgetLazy) {
return (
<Suspense
fallback={
<Box textAlign="center" py={8}>
<Spinner size="lg" color={flow === 'buy' ? 'teal.400' : 'orange.400'} />
<Text color="gray.400" fontSize="sm" mt={3}>
Loading MoonPay SDK...
</Text>
</Box>
}
>
<MoonPaySDKWidgetLazy
flow={flow}
cryptoCode={cryptoCode}
walletAddress={walletAddress}
amount={amount}
colorCode={colorCode}
onClose={onClose}
/>
</Suspense>
);
}

// Fallback: use iframe-based embedding (not currently used in extension builds)
return <MoonPayIframeWidget {...props} />;
};

/**
* Iframe-based MoonPay Widget for Extension
*/
const MoonPayIframeWidget: React.FC<MoonPayWidgetProps> = ({
flow,
cryptoCode,
walletAddress,
amount,
colorCode = '#3182CE',
onClose,
}) => {
const [loading, setLoading] = useState(true);

// Show maintenance message if no API key
if (!MOONPAY_API_KEY) {
const isBuy = flow === 'buy';
return (
<VStack spacing={4} align="stretch">
<Box
bg="#141414"
borderRadius="xl"
p={6}
borderWidth="1px"
borderColor="#2a2a2a"
textAlign="center"
>
<Text fontSize="2xl" mb={3}>
🔧
</Text>
<Text fontSize="lg" fontWeight="bold" color="white" mb={2}>
Under Maintenance
</Text>
<Text fontSize="sm" color="gray.400" mb={4}>
{isBuy ? 'Buy' : 'Sell'} functionality is temporarily unavailable. Please check back
later.
</Text>
<Text fontSize="xs" color="gray.500">
We apologize for any inconvenience.
</Text>
</Box>
{onClose && (
<Button variant="outline" colorScheme="gray" onClick={onClose}>
Go Back
</Button>
)}
</VStack>
);
}

// Build the MoonPay iframe URL
const buildIframeUrl = (): string => {
const baseUrl = flow === 'buy'
? 'https://buy.moonpay.com'
: 'https://sell.moonpay.com';

const baseUrl = flow === 'buy' ? 'https://buy.moonpay.com' : 'https://sell.moonpay.com';

const params = new URLSearchParams({
apiKey: MOONPAY_API_KEY,
theme: 'dark',
Expand Down
Loading