updated payment logic
This commit is contained in:
parent
42350c9acc
commit
5a2f5fdc6d
@ -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 (
|
||||||
<>
|
<>
|
||||||
|
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user