Refactor paymentModal code

This commit is contained in:
Shreerang Kale 2025-07-24 18:41:33 +05:30
parent 161a333642
commit 32b583a637
7 changed files with 50 additions and 60 deletions

View File

@ -3,13 +3,15 @@
# Solana Payment Configuration # Solana Payment Configuration
# TODO: Use different RPC URL or use browser wallet # TODO: Use different RPC URL or use browser wallet
NEXT_PUBLIC_SOLANA_RPC_URL=https://skilled-prettiest-seed.solana-mainnet.quiknode.pro/eeecfebd04e345f69f1900cc3483cbbfea02a158 NEXT_PUBLIC_SOLANA_RPC_URL=https://skilled-prettiest-seed.solana-mainnet.quiknode.pro/eeecfebd04e345f69f1900cc3483cbbfea02a158
NEXT_PUBLIC_GORBAGANA_RPC_URL=https://rpc.gorbagana.wtf
NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS=71Jvq4Epe2FCJ7JFSF7jLXdNk1Wy4Bhqd9iL6bEFELvg NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS=71Jvq4Epe2FCJ7JFSF7jLXdNk1Wy4Bhqd9iL6bEFELvg
NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL=GOR NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL=GOR
NEXT_PUBLIC_SOLANA_PAYMENT_AMOUNT_USD=5 # Payment amount in USD NEXT_PUBLIC_SOLANA_PAYMENT_AMOUNT_USD=5
# Gorbagana Chain Configuration
NEXT_PUBLIC_GORBAGANA_RPC_URL=https://rpc.gorbagana.wtf
NEXT_PUBLIC_ENABLE_NATIVE_GOR_TRANSFER=true NEXT_PUBLIC_ENABLE_NATIVE_GOR_TRANSFER=true
# Multisig address # Multisig Address
NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS=FFDx3SdAEeXrp6BTmStB4BDHpctGsaasZq4FFcowRobY NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS=FFDx3SdAEeXrp6BTmStB4BDHpctGsaasZq4FFcowRobY
# UI Configuration # UI Configuration

View File

@ -11,11 +11,11 @@ import { verifyUnusedSolanaPayment } from '@/utils/solana-verify';
import { transferLNTTokens } from '@/services/laconic-transfer'; import { transferLNTTokens } from '@/services/laconic-transfer';
import { getRegistry, getRegistryConfig } from '@/config'; import { getRegistry, getRegistryConfig } from '@/config';
import { getRequiredTokenInfo } from '@/services/jupiter-price'; import { getRequiredTokenInfo } from '@/services/jupiter-price';
import { WRAPPED_SOL_MINT_ADDRESS } from '@/constants/payments'; import { IS_NAT_GOR_TRANSFER_ENABLED, WRAPPED_SOL_MINT_ADDRESS } from '@/constants/payments';
import { PaymentMethod } from '@/types'; import { PaymentMethod } from '@/types';
assert(process.env.NEXT_PUBLIC_SOLANA_RPC_URL, 'SOLANA_RPC_URL is required'); assert(process.env.NEXT_PUBLIC_SOLANA_RPC_URL, 'SOLANA_RPC_URL is required');
assert(process.env.NEXT_PUBLIC_GORBAGANA_RPC_URL, 'GORBAGANA_RPC_URL is required'); assert(!IS_NAT_GOR_TRANSFER_ENABLED || process.env.NEXT_PUBLIC_GORBAGANA_RPC_URL, 'GORBAGANA_RPC_URL is required when NAT GOR transfer is enabled');
const SOLANA_RPC_URL = process.env.NEXT_PUBLIC_SOLANA_RPC_URL; const SOLANA_RPC_URL = process.env.NEXT_PUBLIC_SOLANA_RPC_URL;
const GORBAGANA_RPC_URL = process.env.NEXT_PUBLIC_GORBAGANA_RPC_URL; const GORBAGANA_RPC_URL = process.env.NEXT_PUBLIC_GORBAGANA_RPC_URL;

View File

@ -10,7 +10,7 @@ import { BackpackWalletName } from '@solana/wallet-adapter-backpack';
import URLForm from '@/components/URLForm'; import URLForm from '@/components/URLForm';
import StatusDisplay from '@/components/StatusDisplay'; import StatusDisplay from '@/components/StatusDisplay';
import { createApplicationDeploymentRequest } from '@/services/registry'; import { createApplicationDeploymentRequest } from '@/services/registry';
import { PAYMENT_METHOD_LABELS } from '@/constants/payments'; import { IS_NAT_GOR_TRANSFER_ENABLED, PAYMENT_METHOD_LABELS } from '@/constants/payments';
import { usePaymentMethod } from '@/contexts/PaymentMethodContext'; import { usePaymentMethod } from '@/contexts/PaymentMethodContext';
import { PaymentMethod } from '@/types'; import { PaymentMethod } from '@/types';
@ -36,16 +36,14 @@ export default function Home() {
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [incorrectChainWarining, setIncorrectChainWarining] = useState<string | null>(null); const [incorrectChainWarining, setIncorrectChainWarining] = useState<string | null>(null);
const isNatGorEnabled = process.env.NEXT_PUBLIC_ENABLE_NATIVE_GOR_TRANSFER === "true";
useEffect(() => { useEffect(() => {
if (!isNatGorEnabled) { if (!IS_NAT_GOR_TRANSFER_ENABLED) {
setSelectedPaymentMethod(PaymentMethod.SPL_TOKEN); setSelectedPaymentMethod(PaymentMethod.SPL_TOKEN);
} }
}, [isNatGorEnabled, setSelectedPaymentMethod]); }, [setSelectedPaymentMethod]);
useEffect(() => { useEffect(() => {
if (!wallet || wallet.adapter.name !== BackpackWalletName) { if (!wallet || wallet.adapter.name !== BackpackWalletName || selectedPaymentMethod !== PaymentMethod.NAT_GOR) {
return; return;
} }
@ -58,7 +56,7 @@ export default function Home() {
} }
warnOnIncorrectChain(); warnOnIncorrectChain();
}, [wallet]); }, [wallet, selectedPaymentMethod]);
// Track previous payment method to detect switches // Track previous payment method to detect switches
const previousPaymentMethodRef = useRef<PaymentMethod | null>(null); const previousPaymentMethodRef = useRef<PaymentMethod | null>(null);
@ -151,7 +149,7 @@ export default function Home() {
</h1> </h1>
{/* Step 1: Payment Method Selection */} {/* Step 1: Payment Method Selection */}
{ isNatGorEnabled && { IS_NAT_GOR_TRANSFER_ENABLED &&
<div className="mb-10 p-6 rounded-lg" style={{ background: 'var(--muted-light)', borderLeft: '4px solid var(--primary)' }}> <div className="mb-10 p-6 rounded-lg" style={{ background: 'var(--muted-light)', borderLeft: '4px solid var(--primary)' }}>
<h2 className="text-lg font-semibold mb-4 flex items-center"> <h2 className="text-lg font-semibold mb-4 flex items-center">
Choose Payment Method Choose Payment Method

View File

@ -1,18 +1,18 @@
'use client'; 'use client';
import { useCallback, useMemo, useState, useEffect } from 'react'; import { useCallback, useState, useEffect } from 'react';
import assert from 'assert'; import assert from 'assert';
import { Connection, LAMPORTS_PER_SOL } from '@solana/web3.js'; import { Connection, LAMPORTS_PER_SOL } from '@solana/web3.js';
import { useConnection, useWallet } from '@solana/wallet-adapter-react'; import { useConnection, useWallet } from '@solana/wallet-adapter-react';
import { sendSolanaPayment, getRecipientAddress } from '@/services/solana'; import { sendSolanaPayment } from '@/services/solana';
import { getRequiredTokenInfo, RequiredTokenInfo } from '@/services/jupiter-price'; import { getRequiredTokenInfo, RequiredTokenInfo } from '@/services/jupiter-price';
import { PaymentMethod, PaymentModalProps, PaymentRequest } from '@/types'; import { PaymentMethod, PaymentModalProps, PaymentRequest } from '@/types';
import { PAYMENT_METHOD_LABELS, WRAPPED_SOL_MINT_ADDRESS } from '@/constants/payments'; import { IS_NAT_GOR_TRANSFER_ENABLED, PAYMENT_METHOD_LABELS, WRAPPED_SOL_MINT_ADDRESS } from '@/constants/payments';
import { usePaymentMethod } from '@/contexts/PaymentMethodContext'; import { usePaymentMethod } from '@/contexts/PaymentMethodContext';
assert(process.env.NEXT_PUBLIC_GORBAGANA_RPC_URL, 'GORBAGANA_RPC_URL is required'); assert(!IS_NAT_GOR_TRANSFER_ENABLED || process.env.NEXT_PUBLIC_GORBAGANA_RPC_URL, 'GORBAGANA_RPC_URL is required when NAT GOR transfer is enabled');
const GORBAGANA_RPC_URL = process.env.NEXT_PUBLIC_GORBAGANA_RPC_URL; const GORBAGANA_RPC_URL = process.env.NEXT_PUBLIC_GORBAGANA_RPC_URL;
@ -24,7 +24,7 @@ export default function PaymentModal({
}: PaymentModalProps) { }: PaymentModalProps) {
const { selectedPaymentMethod: paymentMethod } = usePaymentMethod(); const { selectedPaymentMethod: paymentMethod } = usePaymentMethod();
const { connection } = useConnection(); const { connection: solanaConnection } = useConnection();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState(''); const [error, setError] = useState('');
@ -34,17 +34,10 @@ export default function PaymentModal({
const { wallet, publicKey } = useWallet(); const { wallet, publicKey } = useWallet();
const solanaConnection = connection;
const gorbaganaConnection = useMemo(() =>
GORBAGANA_RPC_URL ? new Connection(GORBAGANA_RPC_URL) : solanaConnection,
[solanaConnection]
);
// Get configuration from environment variables // Get configuration from environment variables
const targetUsdAmount = parseFloat(process.env.NEXT_PUBLIC_SOLANA_PAYMENT_AMOUNT_USD!); const targetUsdAmount = parseFloat(process.env.NEXT_PUBLIC_SOLANA_PAYMENT_AMOUNT_USD!);
const mintAddress = process.env.NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS!; const mintAddress = process.env.NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS!;
const tokenSymbol = process.env.NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL || 'TOKEN'; const tokenSymbol = process.env.NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL;
// Fetch payment amount based on USD price for both payment methods // Fetch payment amount based on USD price for both payment methods
useEffect(() => { useEffect(() => {
@ -113,11 +106,10 @@ export default function PaymentModal({
const paymentRequest: PaymentRequest = { const paymentRequest: PaymentRequest = {
paymentMethod: paymentMethod, paymentMethod: paymentMethod,
amount: tokenAmount, amount: tokenAmount,
recipientAddress: getRecipientAddress(paymentMethod)
}; };
// Use different RPC connection based on payment method // Use different RPC connection based on payment method
const connectionToUse = paymentMethod === PaymentMethod.NAT_GOR ? gorbaganaConnection : solanaConnection; const connectionToUse = paymentMethod === PaymentMethod.NAT_GOR ? new Connection(GORBAGANA_RPC_URL!) : solanaConnection;
const result = await sendSolanaPayment( const result = await sendSolanaPayment(
wallet.adapter, wallet.adapter,
@ -136,7 +128,7 @@ export default function PaymentModal({
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [paymentMethod, tokenAmount, loadingPrice, wallet, solanaConnection, gorbaganaConnection, publicKey, onPaymentComplete]); }, [paymentMethod, tokenAmount, loadingPrice, wallet, solanaConnection, publicKey, onPaymentComplete]);
const getPaymentAmountDisplay = () => { const getPaymentAmountDisplay = () => {
if (loadingPrice) return 'Loading...'; if (loadingPrice) return 'Loading...';
@ -187,7 +179,7 @@ export default function PaymentModal({
<div> <div>
<p className="text-sm mb-2 font-medium" style={{ color: 'var(--muted)' }}>Recipient Address:</p> <p className="text-sm mb-2 font-medium" style={{ color: 'var(--muted)' }}>Recipient Address:</p>
<div className="p-3 rounded-md overflow-hidden" style={{ background: 'var(--muted-light)', color: 'var(--foreground)' }}> <div className="p-3 rounded-md overflow-hidden" style={{ background: 'var(--muted-light)', color: 'var(--foreground)' }}>
<code className="text-sm font-mono break-all block">{getRecipientAddress(paymentMethod)}</code> <code className="text-sm font-mono break-all block">{process.env.NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS}</code>
</div> </div>
</div> </div>

View File

@ -3,10 +3,9 @@ import { PaymentMethod } from "@/types";
// Payment method labels for UI // Payment method labels for UI
export const PAYMENT_METHOD_LABELS: Record<PaymentMethod, string> = { export const PAYMENT_METHOD_LABELS: Record<PaymentMethod, string> = {
[PaymentMethod.NAT_GOR]: 'GOR (native)', [PaymentMethod.NAT_GOR]: 'GOR (native)',
[PaymentMethod.SPL_TOKEN]: process.env.NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL || 'SPL Token' [PaymentMethod.SPL_TOKEN]: process.env.NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL!
}; };
// Default payment method (none selected initially)
export const DEFAULT_PAYMENT_METHOD: PaymentMethod | null = null;
export const WRAPPED_SOL_MINT_ADDRESS = 'So11111111111111111111111111111111111111112'; export const WRAPPED_SOL_MINT_ADDRESS = 'So11111111111111111111111111111111111111112';
export const IS_NAT_GOR_TRANSFER_ENABLED = process.env.NEXT_PUBLIC_ENABLE_NATIVE_GOR_TRANSFER === "true";

View File

@ -46,7 +46,6 @@ export interface PaymentModalProps {
export interface PaymentRequest { export interface PaymentRequest {
paymentMethod: PaymentMethod; paymentMethod: PaymentMethod;
amount: number; // in base units (lamports for native GOR, token base units for SPL) amount: number; // in base units (lamports for native GOR, token base units for SPL)
recipientAddress: string;
} }
export interface LaconicTransferResult { export interface LaconicTransferResult {

View File

@ -35,31 +35,31 @@ const extractTxInfo = async (
return { authority: source, amount: lamports.toString(), destination }; return { authority: source, amount: lamports.toString(), destination };
case PaymentMethod.SPL_TOKEN: case PaymentMethod.SPL_TOKEN:
// Look for token transfer instruction using TOKEN_PROGRAM_ID // Look for token transfer instruction using TOKEN_PROGRAM_ID
transferInstruction = result.transaction.message.instructions.find( transferInstruction = result.transaction.message.instructions.find(
(instr) => 'parsed' in instr && instr.programId.equals(TOKEN_PROGRAM_ID) (instr) => 'parsed' in instr && instr.programId.equals(TOKEN_PROGRAM_ID)
); );
if (!transferInstruction || !('parsed' in transferInstruction)) { if (!transferInstruction || !('parsed' in transferInstruction)) {
throw new Error('SPL token transfer instruction not found'); throw new Error('SPL token transfer instruction not found');
} }
const parsed = transferInstruction.parsed; const parsed = transferInstruction.parsed;
// Handle both transferChecked and transfer types // Handle both transferChecked and transfer types
if (parsed.type === 'transferChecked') { if (parsed.type === 'transferChecked') {
const { info: { tokenAmount, authority, destination } } = parsed; const { info: { tokenAmount, authority, destination } } = parsed;
return { return {
authority, authority,
amount: tokenAmount.amount, amount: tokenAmount.amount,
destination destination
}; };
} else if (parsed.type === 'transfer') { } else if (parsed.type === 'transfer') {
const { info: { amount, authority, destination } } = parsed; const { info: { amount, authority, destination } } = parsed;
return { authority, amount, destination }; return { authority, amount, destination };
} }
throw new Error('Unsupported token transfer type'); throw new Error('Unsupported token transfer type');
default: default:
throw new Error('Invalid payment method'); throw new Error('Invalid payment method');
@ -119,10 +119,10 @@ export const verifyUnusedSolanaPayment = async (
} }
// Extract transaction info based on payment method // Extract transaction info based on payment method
const transferInfo = await extractTxInfo(connection, transactionSignature, paymentMethod); const transferInfo = await extractTxInfo(connection, transactionSignature, paymentMethod);
const amount = transferInfo.amount; const amount = transferInfo.amount;
const authority = transferInfo.authority; const authority = transferInfo.authority;
const destination = transferInfo.destination; const destination = transferInfo.destination;
// Verify amount using BN comparison // Verify amount using BN comparison
const transactionAmount = new BN(amount); const transactionAmount = new BN(amount);