Remove usage of hardcoded native GOR token amount in backend

This commit is contained in:
Shreerang Kale 2025-07-23 18:45:55 +05:30
parent 375f33973e
commit 00dbc9ae8c
7 changed files with 49 additions and 52 deletions

View File

@ -4,11 +4,11 @@
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_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_PAYMENT_AMOUNT_USD=5 # Payment amount in USD
# Multisig address # Multisig address
NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS=FFDx3SdAEeXrp6BTmStB4BDHpctGsaasZq4FFcowRobY NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS=FFDx3SdAEeXrp6BTmStB4BDHpctGsaasZq4FFcowRobY
NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL=GOR
NEXT_PUBLIC_SOLANA_PAYMENT_AMOUNT_USD=5 # Payment amount in USD
# UI Configuration # UI Configuration
NEXT_PUBLIC_EXAMPLE_URL=https://git.vdb.to/cerc-io/test-progressive-web-app NEXT_PUBLIC_EXAMPLE_URL=https://git.vdb.to/cerc-io/test-progressive-web-app

View File

@ -10,8 +10,8 @@ import { DENOM as ALNT_DENOM } from '@cerc-io/registry-sdk';
import { verifyUnusedSolanaPayment } from '@/utils/solana-verify'; 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 { getRequiredNativeGorInfo, getRequiredTokenInfo } from '@/services/jupiter-price';
import { PaymentMethod, SOL_PAYMENT_AMOUNT_LAMPORTS } from '@/constants/payments'; import { PaymentMethod } from '@/constants/payments';
import { getRecipientAddress } from '@/services/solana'; import { getRecipientAddress } from '@/services/solana';
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');
@ -203,42 +203,43 @@ export async function POST(request: NextRequest) {
// Verify Solana payment based on method // Verify Solana payment based on method
console.log(`Step 0: Verifying Solana ${paymentMethod} payment...`); console.log(`Step 0: Verifying Solana ${paymentMethod} payment...`);
let expectedAmount: BN;
// Calculate expected token amount based on current price
let expectedRecipientAddress: string; let expectedRecipientAddress: string;
let requiredAmountInBaseUnits: number;
if (paymentMethod === 'nat-gor') { const targetUsdAmount = parseFloat(process.env.NEXT_PUBLIC_SOLANA_PAYMENT_AMOUNT_USD!);
expectedAmount = new BN(SOL_PAYMENT_AMOUNT_LAMPORTS);
expectedRecipientAddress = getRecipientAddress('nat-gor');
} else if (paymentMethod === 'spl-token') {
const targetUsdAmount = parseFloat(process.env.NEXT_PUBLIC_SOLANA_PAYMENT_AMOUNT_USD!);
const mintAddress = process.env.NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS!;
// Calculate expected token amount based on current price try {
let expectedTokenAmount: number; if (paymentMethod === 'nat-gor') {
try { const requiredNativeGorInfo = await getRequiredNativeGorInfo(targetUsdAmount);
const { requiredAmountInBaseUnits } = await getRequiredTokenInfo(targetUsdAmount, mintAddress); requiredAmountInBaseUnits = requiredNativeGorInfo.requiredAmountInBaseUnits;
expectedTokenAmount = Math.round(requiredAmountInBaseUnits - ALLOWED_SLIPPAGE_FACTOR * requiredAmountInBaseUnits); expectedRecipientAddress = getRecipientAddress('nat-gor');
} catch (error) { } else if (paymentMethod === 'spl-token') {
console.error('Error calculating token amount:', error); const mintAddress = process.env.NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS!;
const requiredTokenInfo = await getRequiredTokenInfo(targetUsdAmount, mintAddress);
requiredAmountInBaseUnits = requiredTokenInfo.requiredAmountInBaseUnits;
expectedRecipientAddress = getRecipientAddress('spl-token');
} else {
return NextResponse.json({ return NextResponse.json({
status: 'error', status: 'error',
message: 'Unable to verify payment due to price calculation error' message: 'Unsupported payment method'
}, { status: 500 }); }, { status: 400 });
} }
} catch (error) {
expectedAmount = new BN(expectedTokenAmount); console.error('Error calculating expected amount:', error);
expectedRecipientAddress = getRecipientAddress('spl-token');
} else {
return NextResponse.json({ return NextResponse.json({
status: 'error', status: 'error',
message: 'Unsupported payment method' message: 'Unable to verify payment due to price calculation error'
}, { status: 400 }); }, { status: 500 });
} }
const expectedTokenAmount = Math.round(requiredAmountInBaseUnits - ALLOWED_SLIPPAGE_FACTOR * requiredAmountInBaseUnits);
const solanaPaymentResult = await verifyUnusedSolanaPayment( const solanaPaymentResult = await verifyUnusedSolanaPayment(
connection, connection,
txHash, txHash,
expectedAmount, new BN(expectedTokenAmount),
paymentMethod, paymentMethod,
expectedRecipientAddress expectedRecipientAddress
); );

View File

@ -21,7 +21,7 @@ interface WalletProvidersProps {
export default function WalletProviders({ children }: WalletProvidersProps) { export default function WalletProviders({ children }: WalletProvidersProps) {
const { selectedPaymentMethod } = usePaymentMethod(); const { selectedPaymentMethod } = usePaymentMethod();
// Configure the Solana network endpoint // Configure the Solana network endpoint
const endpoint = useMemo(() => { const endpoint = useMemo(() => {
return SOLANA_RPC_URL; return SOLANA_RPC_URL;
@ -46,7 +46,7 @@ export default function WalletProviders({ children }: WalletProvidersProps) {
return allWallets.filter(wallet => { return allWallets.filter(wallet => {
const isBackpack = wallet.name.toLowerCase().includes('backpack'); const isBackpack = wallet.name.toLowerCase().includes('backpack');
if (selectedPaymentMethod === 'nat-gor') { if (selectedPaymentMethod === 'nat-gor') {
return isBackpack; // Only Backpack for native GOR return isBackpack; // Only Backpack for native GOR
} else { } else {

View File

@ -1,8 +1,5 @@
// Payment configuration constants // Payment configuration constants
// Native GOR payment amount in lamports (1 GOR = 1,000,000,000 lamports)
export const SOL_PAYMENT_AMOUNT_LAMPORTS = 10000000; // 0.01 GOR native
// Payment method types // Payment method types
export type PaymentMethod = 'nat-gor' | 'spl-token'; export type PaymentMethod = 'nat-gor' | 'spl-token';

View File

@ -17,6 +17,8 @@ interface RequiredTokenInfo {
decimals: number; decimals: number;
} }
const WRAPPED_SOL_MINT_ADDRESS = 'So11111111111111111111111111111111111111112';
/** /**
* Fetches token price from Jupiter aggregator API * Fetches token price from Jupiter aggregator API
* @param mintAddress The Solana token mint address * @param mintAddress The Solana token mint address
@ -73,10 +75,7 @@ export async function getRequiredTokenInfo(targetUsdAmount: number, mintAddress:
* @returns The GOR amount in lamports needed and decimals (always 9 for SOL/GOR) * @returns The GOR amount in lamports needed and decimals (always 9 for SOL/GOR)
*/ */
export async function getRequiredNativeGorInfo(targetUsdAmount: number): Promise<RequiredTokenInfo> { export async function getRequiredNativeGorInfo(targetUsdAmount: number): Promise<RequiredTokenInfo> {
// Wrapped SOL mint address const priceInfo = await getTokenInfo(WRAPPED_SOL_MINT_ADDRESS);
const wrappedSolMint = 'So11111111111111111111111111111111111111112';
const priceInfo = await getTokenInfo(wrappedSolMint);
// Calculate GOR amount needed (same as SOL) // Calculate GOR amount needed (same as SOL)
const gorAmount = targetUsdAmount / priceInfo.usdPrice; const gorAmount = targetUsdAmount / priceInfo.usdPrice;

View File

@ -11,7 +11,7 @@ import {
import { WalletAdapter } from '@solana/wallet-adapter-base'; import { WalletAdapter } from '@solana/wallet-adapter-base';
import { SolanaPaymentResult, PaymentRequest } from '../types'; import { SolanaPaymentResult, PaymentRequest } from '../types';
import { PaymentMethod, SOL_PAYMENT_AMOUNT_LAMPORTS } from '../constants/payments'; import { PaymentMethod } from '../constants/payments';
assert(process.env.NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS, 'SOLANA_TOKEN_MINT_ADDRESS is required'); assert(process.env.NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS, 'SOLANA_TOKEN_MINT_ADDRESS is required');
assert(process.env.NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS, 'SOLANA_TOKEN_RECIPIENT_ADDRESS is required'); assert(process.env.NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS, 'SOLANA_TOKEN_RECIPIENT_ADDRESS is required');
@ -38,7 +38,8 @@ async function findAssociatedTokenAddress(
export async function sendSolPayment( export async function sendSolPayment(
wallet: WalletAdapter, wallet: WalletAdapter,
connection: Connection, connection: Connection,
walletPublicKey: string walletPublicKey: string,
tokenAmount: BN
): Promise<SolanaPaymentResult> { ): Promise<SolanaPaymentResult> {
try { try {
if (!wallet.connected || !wallet.publicKey) { if (!wallet.connected || !wallet.publicKey) {
@ -51,14 +52,14 @@ export async function sendSolPayment(
console.log('Processing native GOR payment:', { console.log('Processing native GOR payment:', {
sender: senderPublicKey.toBase58(), sender: senderPublicKey.toBase58(),
receiver: receiverPublicKey.toBase58(), receiver: receiverPublicKey.toBase58(),
amount: SOL_PAYMENT_AMOUNT_LAMPORTS amount: tokenAmount.toString()
}); });
const transaction = new Transaction().add( const transaction = new Transaction().add(
SystemProgram.transfer({ SystemProgram.transfer({
fromPubkey: senderPublicKey, fromPubkey: senderPublicKey,
toPubkey: receiverPublicKey, toPubkey: receiverPublicKey,
lamports: SOL_PAYMENT_AMOUNT_LAMPORTS, lamports: BigInt(tokenAmount.toString()),
}) })
); );
@ -226,11 +227,12 @@ export async function sendSolanaPayment(
throw new Error('Wallet not connected'); throw new Error('Wallet not connected');
} }
const tokenAmount = new BN(paymentRequest.amount);
switch (paymentRequest.paymentMethod) { switch (paymentRequest.paymentMethod) {
case 'nat-gor': case 'nat-gor':
return await sendSolPayment(wallet, connection, walletPublicKey); return await sendSolPayment(wallet, connection, walletPublicKey, tokenAmount);
case 'spl-token': case 'spl-token':
const tokenAmount = new BN(paymentRequest.amount);
return await sendSplTokenPayment(wallet, connection, walletPublicKey, tokenAmount); return await sendSplTokenPayment(wallet, connection, walletPublicKey, tokenAmount);
default: default:
throw new Error(`Unsupported payment method: ${paymentRequest.paymentMethod}`); throw new Error(`Unsupported payment method: ${paymentRequest.paymentMethod}`);

View File

@ -5,7 +5,7 @@ import { PaymentMethod } from '../constants/payments';
// Extract transaction info for native GOR transfers // Extract transaction info for native GOR transfers
const extractSolTransferInfo = async ( const extractSolTransferInfo = async (
connection: Connection, connection: Connection,
transactionSignature: string transactionSignature: string
): Promise<{ authority: string; amount: string; destination: string }> => { ): Promise<{ authority: string; amount: string; destination: string }> => {
const result = await connection.getParsedTransaction(transactionSignature, 'confirmed'); const result = await connection.getParsedTransaction(transactionSignature, 'confirmed');
@ -29,7 +29,7 @@ const extractSolTransferInfo = async (
// Extract transaction info for SPL token transfers // Extract transaction info for SPL token transfers
const extractSplTokenTransferInfo = async ( const extractSplTokenTransferInfo = async (
connection: Connection, connection: Connection,
transactionSignature: string transactionSignature: string
): Promise<{ authority: string; amount: string; destination: string }> => { ): Promise<{ authority: string; amount: string; destination: string }> => {
const result = await connection.getParsedTransaction(transactionSignature, 'confirmed'); const result = await connection.getParsedTransaction(transactionSignature, 'confirmed');
@ -48,14 +48,14 @@ const extractSplTokenTransferInfo = async (
} }
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;
@ -83,8 +83,6 @@ export const verifyUnusedSolanaPayment = async (
// Fetch transaction details // Fetch transaction details
const transactionResult = await connection.getParsedTransaction(transactionSignature, 'confirmed'); const transactionResult = await connection.getParsedTransaction(transactionSignature, 'confirmed');
console.dir(transactionResult, {depth: null});
if (!transactionResult) { if (!transactionResult) {
return { return {
valid: false, valid: false,
@ -167,7 +165,7 @@ export const verifyUnusedSolanaPayment = async (
recipientPublicKey, recipientPublicKey,
true // Allow off-curve addresses true // Allow off-curve addresses
); );
validRecipient = destination === expectedTokenAccount.toBase58(); validRecipient = destination === expectedTokenAccount.toBase58();
} }