updated payment logic

This commit is contained in:
shialoth 2025-05-04 08:34:17 +05:30
parent 42350c9acc
commit 5a2f5fdc6d
2 changed files with 258 additions and 63 deletions

View File

@ -17,78 +17,100 @@ export default function Boxes() {
const [selectedGpu, setSelectedGpu] = useState<IGPU | null>(null); const [selectedGpu, setSelectedGpu] = useState<IGPU | null>(null);
const gpus: IGPU[] = [ const gpus: IGPU[] = [
{
id: 'k80',
title: 'NVIDIA Tesla K80',
subTitle: '24 GB GDDR5 - 4992 CUDA Cores',
price: '$0.15/hour ($110/mo)',
price_usd: 110,
},
{
id: 't1000',
title: 'NVIDIA T1000',
subTitle: '4 GB GDDR6 - 896 CUDA Cores',
price: '$0.16/hr ($119/month)',
price_usd: 119,
},
{ {
id: 'a4000', id: 'a4000',
title: 'NVIDIA RTX A4000', title: 'NVIDIA RTX A4000',
subTitle: '16GB GDDR6 - 6144 CUDA Cores', subTitle: '16 GB GDDR6 - 6144 CUDA Cores',
price: '$0.20/hour ($147/mo)', price: '$0.17/hour ($125/mo)',
price_usd: 147, price_usd: 125,
isRecentlyPurchased: true, isRecentlyPurchased: true,
}, },
{ {
id: 'a5000', id: 'a5000',
title: 'NVIDIA RTX A5000', title: 'NVIDIA RTX A5000',
subTitle: '24 GB GDDR6 - 8192 CUDA Cores', subTitle: '24 GB GDDR6 - 8192 CUDA Cores',
price: '$0.26/hr ($190/month)', price: '$0.22/hr ($162/month)',
price_usd: 190, price_usd: 162,
},
{
id: '4x4090',
title: '4x NVIDIA RTX 4090',
subTitle: '24 GB GDDR6X - 16384 GPU CUDA Cores',
price: '$0.37/hr ($268/month)',
price_usd: 268,
}, },
{ {
id: 'v100', id: 'v100',
title: 'NVIDIA V100', title: 'NVIDIA V100',
subTitle: '32 GB HBM2 - 5120 CUDA Cores', subTitle: '32 GB HBM2 - 5120 CUDA Cores',
price: '$0.44/hr ($317/month)', price: '$0.23/hr ($170/month)',
price_usd: 317, price_usd: 170,
},
{
id: 'p100',
title: 'NVIDIA Tesla P100',
subTitle: '16 GB HBM2 - 3584 CUDA Cores',
price: '$0.27/hour ($196/mo)',
price_usd: 196,
},
{
id: '4x4090',
title: '4x NVIDIA RTX 4090',
subTitle: '24 GB GDDR6X - 16384 GPU CUDA Cores',
price: '$0.32/hr ($228/month)',
price_usd: 228,
}, },
{ {
id: 'l4ada', id: 'l4ada',
title: 'NVIDIA L4 Ada', title: 'NVIDIA L4 Ada',
subTitle: '24 GB GDDR6 - 7680 CUDA Cores', subTitle: '24 GB GDDR6 - 7680 CUDA Cores',
price: '$0.52/hr ($372/month)', price: '$0.44/hr ($316/month)',
price_usd: 372, price_usd: 316,
}, },
{ {
id: 'a6000', id: 'a6000',
title: 'NVIDIA RTX A6000', title: 'NVIDIA RTX A6000',
subTitle: '48 GB GDDR6 - 10752 CUDA Cores', subTitle: '48 GB GDDR6 - 10752 CUDA Cores',
price: '$0.53/hr ($380/month)', price: '$0.45/hr ($323/month)',
price_usd: 380, price_usd: 323,
}, },
{ {
id: 'l40sada', id: 'l40sada',
title: 'NVIDIA L40S Ada', title: 'NVIDIA L40S Ada',
subTitle: '48 GB GDDR6 - 18176 CUDA Cores', subTitle: '48 GB GDDR6 - 18176 CUDA Cores',
price: '$1.24/hr ($890/month)', price: '$1.04/hr ($756/month)',
price_usd: 890, price_usd: 756,
}, },
{ {
id: 'a100', id: 'a100',
title: 'NVIDIA A100 PCIe', title: 'NVIDIA A100 PCIe',
subTitle: '40 GB HBM2 - 6912 CUDA Cores', subTitle: '40 GB HBM2 - 6912 CUDA Cores',
price: '$1.62/hr ($1,166/month)', price: '$1.36/hr ($991/month)',
price_usd: 1166, price_usd: 991,
}, },
{ {
id: 'h100nvl', id: 'h100nvl',
title: 'NVIDIA H100 NVL', title: 'NVIDIA H100 NVL',
subTitle: '94 GB HBM3 - 14592 CUDA Cores', subTitle: '94 GB HBM3 - 14592 CUDA Cores',
price: '$3.35/hr ($2,410/month)', price: '$2.85/hr ($2,048/month)',
price_usd: 2410, price_usd: 2048,
}, },
{ {
id: 'h100sxm', id: 'h100sxm',
title: 'NVIDIA H100 SXM', title: 'NVIDIA H100 SXM',
subTitle: '80 GB HBM3 - 14592 CUDA Cores', subTitle: '80 GB HBM3 - 14592 CUDA Cores',
price: '$3.60/hr ($2,592/month)', price: '$3.06/hr ($2,203/month)',
price_usd: 2592, price_usd: 2203,
}, },
]; ];
return ( return (
<> <>

View File

@ -17,6 +17,7 @@ import {
getAssociatedTokenAddress, getAssociatedTokenAddress,
createTransferInstruction, createTransferInstruction,
TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID,
createAssociatedTokenAccountInstruction,
} from "@solana/spl-token"; } from "@solana/spl-token";
interface GpuPaymentModalProps { interface GpuPaymentModalProps {
@ -30,12 +31,18 @@ interface GpuPaymentModalProps {
}; };
} }
interface CustomToken {
address: string;
id: number;
project_name: string;
}
const SOLANA_RPC = process.env.NEXT_PUBLIC_SOLANA_RPC!; const SOLANA_RPC = process.env.NEXT_PUBLIC_SOLANA_RPC!;
const BUSINESS_WALLET = process.env.NEXT_PUBLIC_BUSINESS_WALLET!; const BUSINESS_WALLET = process.env.NEXT_PUBLIC_BUSINESS_WALLET!;
const EMAIL_API_URL = process.env.NEXT_PUBLIC_EMAIL_API_URL!; const EMAIL_API_URL = process.env.NEXT_PUBLIC_EMAIL_API_URL!;
const CUSTOM_TOKEN_API_URL = "https://catools.dev3vds1.link/get/vertex";
const USDC_MINT = new PublicKey("Es9vMFrzaCERJ8gLhEvX5yQceQ2uKcXfUrx2Wcikgqay"); const USDC_MINT = new PublicKey("Es9vMFrzaCERJ8gLhEvX5yQceQ2uKcXfUrx2Wcikgqay");
const JUP_MINT = new PublicKey("JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN");
const CornerEdge = ({ position }: { position: 'left-top' | 'right-top' | 'left-bottom' | 'right-bottom' }) => { const CornerEdge = ({ position }: { position: 'left-top' | 'right-top' | 'left-bottom' | 'right-bottom' }) => {
const paths = { const paths = {
@ -150,14 +157,15 @@ const SuccessIcon = () => {
const ErrorIcon = () => { const ErrorIcon = () => {
return ( return (
<div className="relative h-[54px] w-[54px]"> <div className="relative h-[54px] w-[54px]">
<div className="absolute bg-red-600 rounded-xl h-[54px] w-[54px]" /> <div className="absolute top-0 left-0 bg-red-600 rounded-xl h-[54px] w-[54px]" />
<svg <svg
width="39" width="38"
height="38" height="38"
viewBox="0 0 39 38" viewBox="0 0 38 38"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
className="absolute w-[38px] h-[38px] left-[9px] top-[8px]" className="absolute top-2 left-2"
aria-hidden="true"
> >
<path <path
d="M29 9.5L10 28.5M10 9.5L29 28.5" d="M29 9.5L10 28.5M10 9.5L29 28.5"
@ -171,6 +179,7 @@ const ErrorIcon = () => {
); );
}; };
const ErrorBox = () => { const ErrorBox = () => {
return ( return (
<div className="relative w-full max-w-[357px] h-[60px]"> <div className="relative w-full max-w-[357px] h-[60px]">
@ -222,6 +231,9 @@ export const GpuPaymentModal = ({ isOpen, onClose, gpu }: GpuPaymentModalProps)
const { wallets: privyWallets } = useSolanaWallets(); const { wallets: privyWallets } = useSolanaWallets();
const [solPrice, setSolPrice] = useState<number | null>(null); const [solPrice, setSolPrice] = useState<number | null>(null);
const [customToken, setCustomToken] = useState<CustomToken | null>(null);
const [customTokenPrice, setCustomTokenPrice] = useState<number | null>(null);
const [customTokenName, setCustomTokenName] = useState<string>('VERTEX');
const [emailInput, setEmailInput] = useState(''); const [emailInput, setEmailInput] = useState('');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [errorMsg, setErrorMsg] = useState<string | null>(null); const [errorMsg, setErrorMsg] = useState<string | null>(null);
@ -234,13 +246,16 @@ export const GpuPaymentModal = ({ isOpen, onClose, gpu }: GpuPaymentModalProps)
const userEmail = user?.email?.address || emailInput || null; const userEmail = user?.email?.address || emailInput || null;
const isValidEmail = (email: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); const isValidEmail = (email: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
const [emailTouched, setEmailTouched] = useState(false); const [emailTouched, setEmailTouched] = useState(false);
const [selectedToken, setSelectedToken] = useState<'SOL' | 'USDC' | 'JUP'>('SOL'); const [selectedToken, setSelectedToken] = useState<'SOL' | 'USDC' | 'VERTEX'>('SOL');
const supabase = createClient( const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
); );
// Calculate token amounts based on selected token
const [currentTokenAmount, setCurrentTokenAmount] = useState<string>('');
useEffect(() => { useEffect(() => {
const fetchSolPrice = async () => { const fetchSolPrice = async () => {
try { try {
@ -248,42 +263,183 @@ export const GpuPaymentModal = ({ isOpen, onClose, gpu }: GpuPaymentModalProps)
'https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd' 'https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd'
); );
setSolPrice(data.solana.usd); setSolPrice(data.solana.usd);
} catch { } catch (error) {
console.error("Failed to fetch SOL price:", error);
setErrorMsg('Failed to fetch SOL price.'); setErrorMsg('Failed to fetch SOL price.');
} }
}; };
const fetchCustomToken = async () => {
try {
const { data } = await axios.get(CUSTOM_TOKEN_API_URL);
if (data && Array.isArray(data) && data.length > 0) {
setCustomToken(data[0]);
// Fetch the actual token price from CoinGecko or Jupiter Aggregator
await fetchTokenPrice(data[0].address);
// Try to get token metadata using the address
fetchTokenMetadata(data[0].address);
}
} catch (error) {
console.error("Failed to fetch custom token:", error);
}
};
const fetchTokenPrice = async (tokenAddress: string) => {
try {
// First attempt: Try using CoinGecko if the token is listed there by contract address
// Convert Solana address to CoinGecko platform format
const platformId = 'solana';
const contractAddress = tokenAddress;
try {
const coinGeckoUrl = `https://api.coingecko.com/api/v3/simple/token_price/${platformId}?contract_addresses=${contractAddress}&vs_currencies=usd`;
const response = await axios.get(coinGeckoUrl);
// Check if we got a valid price
if (response.data && response.data[contractAddress.toLowerCase()] && response.data[contractAddress.toLowerCase()].usd) {
setCustomTokenPrice(response.data[contractAddress.toLowerCase()].usd);
return;
}
} catch (cgError) {
console.log("CoinGecko lookup failed, trying alternative source");
}
// Second attempt: Try using Jupiter Aggregator API for Solana tokens
try {
// Jupiter provides price data for most Solana tokens
const jupiterUrl = `https://price.jup.ag/v4/price?ids=${tokenAddress}`;
const jupResponse = await axios.get(jupiterUrl);
if (jupResponse.data &&
jupResponse.data.data &&
jupResponse.data.data[tokenAddress] &&
jupResponse.data.data[tokenAddress].price) {
setCustomTokenPrice(jupResponse.data.data[tokenAddress].price);
return;
}
} catch (jupError) {
console.log("Jupiter API lookup failed, trying another alternative");
}
// Third attempt: Try using Raydium API (another Solana DEX)
try {
const raydiumUrl = `https://api.raydium.io/v2/main/price?tokens=${tokenAddress}`;
const raydiumResponse = await axios.get(raydiumUrl);
if (raydiumResponse.data && raydiumResponse.data[tokenAddress]) {
setCustomTokenPrice(raydiumResponse.data[tokenAddress]);
return;
}
} catch (raydiumError) {
console.log("Raydium API lookup failed");
}
// Fallback: If all API calls fail, use a fallback price or calculate from an existing pool
console.warn("Could not fetch price from any API, using fallback price");
setCustomTokenPrice(0.5); // Fallback price as last resort
} catch (error) {
console.error("Failed to fetch token price:", error);
// Default price as absolute fallback
setCustomTokenPrice(0.5);
}
};
const fetchTokenMetadata = async (address: string) => {
try {
// First try to get metadata from Solana token registry
const tokenRegistryUrl = `https://cdn.jsdelivr.net/gh/solana-labs/token-list@main/src/tokens/solana.tokenlist.json`;
const registryResponse = await axios.get(tokenRegistryUrl);
const tokenList = registryResponse.data.tokens;
// Find token in the registry by address
const tokenInfo = tokenList.find((token: any) => token.address === address);
if (tokenInfo) {
// Use token info from the registry
setCustomTokenName(tokenInfo.symbol.toUpperCase());
return;
}
// If not found in registry, try to get on-chain metadata
const tokenPublicKey = new PublicKey(address);
const connection = new Connection(SOLANA_RPC);
// Attempt to get token account info
const tokenMint = await connection.getTokenSupply(tokenPublicKey);
if (tokenMint) {
// If we can't get a name from on-chain data, use project_name from our API
// or fall back to default name
if (customToken?.project_name) {
setCustomTokenName(customToken.project_name.toUpperCase());
} else {
// If all else fails, use the default name
setCustomTokenName('VERTEX');
}
return;
}
// If we couldn't get metadata, use the default name
setCustomTokenName('VERTEX');
} catch (error) {
console.error("Failed to fetch token metadata:", error);
// Default to VERTEX if we can't get the name
setCustomTokenName('VERTEX');
}
};
if (isOpen) { if (isOpen) {
fetchSolPrice(); fetchSolPrice();
fetchCustomToken();
setErrorMsg(null); setErrorMsg(null);
setSuccessMsg(null); setSuccessMsg(null);
setPaymentSuccess(false); setPaymentSuccess(false);
setPaymentFailure(false); setPaymentFailure(false);
} }
}, [isOpen]); }, [isOpen, customToken?.project_name]);
// Update token amount when price or selected token changes
useEffect(() => {
updateTokenAmount();
}, [selectedToken, solPrice, customTokenPrice, gpu.price_usd]);
const updateTokenAmount = () => {
if (selectedToken === 'SOL' && solPrice) {
const amount = (gpu.price_usd / solPrice).toFixed(6);
setCurrentTokenAmount(amount);
} else if (selectedToken === 'USDC') {
// USDC is a stablecoin, so 1 USDC ≈ 1 USD
setCurrentTokenAmount(gpu.price_usd.toFixed(2));
} else if (selectedToken === 'VERTEX' && customTokenPrice) {
const amount = (gpu.price_usd / customTokenPrice).toFixed(2);
setCurrentTokenAmount(amount);
}
};
const handlePayment = async () => { const handlePayment = async () => {
setLoading(true); setLoading(true);
setErrorMsg(null); setErrorMsg(null);
setSuccessMsg(null); setSuccessMsg(null);
try { try {
if (!userEmail || !isValidEmail(userEmail)) { if (!userEmail || !isValidEmail(userEmail)) {
throw new Error("Please enter a valid email address for your receipt."); throw new Error("Please enter a valid email address for your receipt.");
} }
const solWallet = privyWallets[0]; const solWallet = privyWallets[0];
if (!solWallet?.address || !solWallet.signTransaction) { if (!solWallet?.address || !solWallet.signTransaction) {
throw new Error("No connected Solana wallet found or wallet can't sign."); throw new Error("No connected Solana wallet found or wallet can't sign.");
} }
const fromPubKey = new PublicKey(solWallet.address); const fromPubKey = new PublicKey(solWallet.address);
const toPubKey = new PublicKey(BUSINESS_WALLET); const toPubKey = new PublicKey(BUSINESS_WALLET);
const transaction = new Transaction(); const transaction = new Transaction();
let txId = ""; let txId = "";
let tokenUsed = selectedToken; let tokenUsed = selectedToken;
let amountDisplay = ""; let amountDisplay = "";
if (selectedToken === "SOL") { if (selectedToken === "SOL") {
const { data } = await axios.get( const { data } = await axios.get(
"https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd" "https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd"
@ -291,7 +447,7 @@ export const GpuPaymentModal = ({ isOpen, onClose, gpu }: GpuPaymentModalProps)
const solPrice = data.solana.usd; const solPrice = data.solana.usd;
const solAmount = (gpu.price_usd / solPrice).toFixed(6); const solAmount = (gpu.price_usd / solPrice).toFixed(6);
amountDisplay = `${solAmount} SOL`; amountDisplay = `${solAmount} SOL`;
transaction.add( transaction.add(
SystemProgram.transfer({ SystemProgram.transfer({
fromPubkey: fromPubKey, fromPubkey: fromPubKey,
@ -300,23 +456,43 @@ export const GpuPaymentModal = ({ isOpen, onClose, gpu }: GpuPaymentModalProps)
}) })
); );
} else { } else {
const selectedMint = selectedToken === "USDC" ? USDC_MINT : JUP_MINT; // For both USDC and VERTEX tokens
const selectedMint = selectedToken === "USDC"
? USDC_MINT
: new PublicKey(customToken?.address || "");
const tokenDecimals = 6; const tokenDecimals = 6;
let tokenPrice = 1; let tokenPrice = 1;
if (selectedToken === "JUP") { if (selectedToken === "VERTEX") {
const { data } = await axios.get( tokenPrice = customTokenPrice || 0.5;
"https://api.coingecko.com/api/v3/simple/price?ids=jupiter-exchange&vs_currencies=usd"
);
tokenPrice = data["jupiter-exchange"]?.usd ?? 0.5;
} }
const tokenAmount = (gpu.price_usd / tokenPrice).toFixed(tokenDecimals); const tokenAmount = (gpu.price_usd / tokenPrice).toFixed(tokenDecimals);
amountDisplay = `${tokenAmount} ${selectedToken}`; amountDisplay = `${tokenAmount} ${selectedToken}`;
const fromTokenAccount = await getAssociatedTokenAddress(selectedMint, fromPubKey); const fromTokenAccount = await getAssociatedTokenAddress(selectedMint, fromPubKey);
const toTokenAccount = await getAssociatedTokenAddress(selectedMint, toPubKey); const toTokenAccount = await getAssociatedTokenAddress(selectedMint, toPubKey);
// Check if destination token account exists, if not create it
const toTokenAccountInfo = await connection.getAccountInfo(toTokenAccount);
if (!toTokenAccountInfo) {
// Import the necessary function
const { createAssociatedTokenAccountInstruction } = await import("@solana/spl-token");
// Add instruction to create the associated token account for the business wallet
transaction.add(
createAssociatedTokenAccountInstruction(
fromPubKey, // payer
toTokenAccount, // associated token account address
toPubKey, // owner of the new account
selectedMint // token mint
)
);
}
// Add the transfer instruction after ensuring the account exists
transaction.add( transaction.add(
createTransferInstruction( createTransferInstruction(
fromTokenAccount, fromTokenAccount,
@ -328,27 +504,26 @@ export const GpuPaymentModal = ({ isOpen, onClose, gpu }: GpuPaymentModalProps)
) )
); );
} }
transaction.feePayer = fromPubKey; transaction.feePayer = fromPubKey;
const { blockhash } = await connection.getLatestBlockhash(); const { blockhash } = await connection.getLatestBlockhash();
transaction.recentBlockhash = blockhash; transaction.recentBlockhash = blockhash;
const signedTx = await solWallet.signTransaction(transaction); const signedTx = await solWallet.signTransaction(transaction);
txId = await connection.sendRawTransaction(signedTx.serialize()); txId = await connection.sendRawTransaction(signedTx.serialize());
await connection.confirmTransaction(txId, "confirmed"); await connection.confirmTransaction(txId, "confirmed");
await axios.post(EMAIL_API_URL, { await axios.post(EMAIL_API_URL, {
email: userEmail, email: userEmail,
product: gpu.title, product: gpu.title,
price_hour: gpu.price_per_hour, price_hour: gpu.price_per_hour,
price: gpu.price_usd.toFixed(2), price: gpu.price_usd.toFixed(2)
token: selectedToken,
}); });
// Generate a random order ID (in production this would come from the backend) // Generate a random order ID (in production this would come from the backend)
const generatedOrderId = `${Math.floor(Math.random() * 900) + 100}-${Math.floor(Math.random() * 900) + 100}-${Math.floor(Math.random() * 900) + 100}`; const generatedOrderId = `${Math.floor(Math.random() * 900) + 100}-${Math.floor(Math.random() * 900) + 100}-${Math.floor(Math.random() * 900) + 100}`;
setOrderId(generatedOrderId); setOrderId(generatedOrderId);
const { error } = await supabase.from("orders").insert([ const { error } = await supabase.from("orders").insert([
{ {
gpu: `${gpu.id} ${gpu.title}`, gpu: `${gpu.id} ${gpu.title}`,
@ -361,7 +536,7 @@ export const GpuPaymentModal = ({ isOpen, onClose, gpu }: GpuPaymentModalProps)
order_id: generatedOrderId, order_id: generatedOrderId,
}, },
]); ]);
if (error) throw new Error(`Failed to save order: ${error.message}`); if (error) throw new Error(`Failed to save order: ${error.message}`);
setSuccessMsg(`Payment successful! Transaction ID: ${txId}`); setSuccessMsg(`Payment successful! Transaction ID: ${txId}`);
setPaymentSuccess(true); setPaymentSuccess(true);
@ -397,8 +572,6 @@ export const GpuPaymentModal = ({ isOpen, onClose, gpu }: GpuPaymentModalProps)
if (!isOpen) return null; if (!isOpen) return null;
const solAmount = solPrice ? (gpu.price_usd / solPrice).toFixed(6) : '...';
// Show failure screen if payment failed // Show failure screen if payment failed
if (paymentFailure) { if (paymentFailure) {
return ( return (
@ -531,7 +704,7 @@ export const GpuPaymentModal = ({ isOpen, onClose, gpu }: GpuPaymentModalProps)
<span className="text-base font-medium text-white">Total Price</span> <span className="text-base font-medium text-white">Total Price</span>
<div className="flex gap-3 items-center"> <div className="flex gap-3 items-center">
<span className="text-base font-bold text-[#0CE77E]">${gpu.price_usd}</span> <span className="text-base font-bold text-[#0CE77E]">${gpu.price_usd}</span>
<span className="text-base text-[#0CE77E]">(~{solAmount} SOL)</span> <span className="text-base text-[#0CE77E]">(~{currentTokenAmount} {selectedToken})</span>
</div> </div>
</div> </div>
</div> </div>
@ -572,12 +745,12 @@ export const GpuPaymentModal = ({ isOpen, onClose, gpu }: GpuPaymentModalProps)
<select <select
value={selectedToken} value={selectedToken}
onChange={(e) => setSelectedToken(e.target.value as 'SOL' | 'USDC' | 'JUP')} onChange={(e) => setSelectedToken(e.target.value as 'SOL' | 'USDC' | 'VERTEX')}
className="w-full p-3 bg-transparent text-[#0CE77E] focus:outline-none text-base cursor-pointer" className="w-full p-3 bg-transparent text-[#0CE77E] focus:outline-none text-base cursor-pointer"
> >
<option value="SOL" className="bg-[#0E1618] text-[#0CE77E]">SOL</option> <option value="SOL" className="bg-[#0E1618] text-[#0CE77E]">SOL</option>
<option value="USDC" className="bg-[#0E1618] text-[#0CE77E]">USDC</option> <option value="USDC" className="bg-[#0E1618] text-[#0CE77E]">USDC</option>
<option value="JUP" className="bg-[#0E1618] text-[#0CE77E]">JUP</option> <option value="VERTEX" className="bg-[#0E1618] text-[#0CE77E]">{customTokenName}</option>
</select> </select>
</div> </div>
</div> </div>