From 6c2ee093a272ba4c98cb15aec6a3b20604929162 Mon Sep 17 00:00:00 2001 From: Shreerang Kale Date: Tue, 22 Jul 2025 17:11:39 +0530 Subject: [PATCH] Take deployment cost from published pricing record --- .env.example | 11 +++-- CLAUDE.md | 6 +-- src/app/api/registry/route.ts | 6 +-- src/components/PaymentModal.tsx | 74 +++++++++++++++++++++++++++------ src/config/index.ts | 12 +++--- 5 files changed, 78 insertions(+), 31 deletions(-) diff --git a/.env.example b/.env.example index fb8fd7a..81a004c 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,4 @@ -# Client-side environment variables (must be prefixed with NEXT_PUBLIC_) +# Client-side environment variables must be prefixed with NEXT_PUBLIC_ # Solana Payment Configuration # TODO: Use different RPC URL or use browser wallet @@ -17,12 +17,11 @@ NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS=FFDx3SdAEeXrp6BTmStB4BDHpctGsaasZq4FF # UI Configuration NEXT_PUBLIC_EXAMPLE_URL=https://git.vdb.to/cerc-io/test-progressive-web-app -# Server-side environment variables - # Laconic Registry Configuration -REGISTRY_CHAIN_ID=laconic-mainnet -REGISTRY_RPC_ENDPOINT=https://laconicd-mainnet-1.laconic.com -REGISTRY_GQL_ENDPOINT=https://laconicd-mainnet-1.laconic.com/graphql +NEXT_PUBLIC_REGISTRY_CHAIN_ID=laconic-mainnet +NEXT_PUBLIC_REGISTRY_RPC_ENDPOINT=https://laconicd-mainnet-1.laconic.com +NEXT_PUBLIC_REGISTRY_GQL_ENDPOINT=https://laconicd-mainnet-1.laconic.com/graphql +NEXT_PUBLIC_PRICING_RECORD_LRN= REGISTRY_GAS_PRICE=0.001 REGISTRY_BOND_ID= REGISTRY_AUTHORITY= diff --git a/CLAUDE.md b/CLAUDE.md index dc64720..10e3e28 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -106,9 +106,9 @@ NEXT_PUBLIC_EXAMPLE_URL=https://git.vdb.to/cerc-io/test-progressive-web-app ### Server-side Variables ```bash -REGISTRY_CHAIN_ID=laconic-mainnet -REGISTRY_GQL_ENDPOINT=https://laconicd-mainnet-1.laconic.com/api -REGISTRY_RPC_ENDPOINT=https://laconicd-mainnet-1.laconic.com +NEXT_PUBLIC_REGISTRY_CHAIN_ID=laconic-mainnet +NEXT_PUBLIC_REGISTRY_GQL_ENDPOINT=https://laconicd-mainnet-1.laconic.com/api +NEXT_PUBLIC_REGISTRY_RPC_ENDPOINT=https://laconicd-mainnet-1.laconic.com REGISTRY_BOND_ID= REGISTRY_AUTHORITY= REGISTRY_USER_KEY= diff --git a/src/app/api/registry/route.ts b/src/app/api/registry/route.ts index 8e2b74c..1f14775 100644 --- a/src/app/api/registry/route.ts +++ b/src/app/api/registry/route.ts @@ -284,9 +284,9 @@ export async function POST(request: NextRequest) { // Validate required environment variables for Solana payments const requiredEnvVars = [ - 'REGISTRY_CHAIN_ID', - 'REGISTRY_GQL_ENDPOINT', - 'REGISTRY_RPC_ENDPOINT', + 'NEXT_PUBLIC_REGISTRY_CHAIN_ID', + 'NEXT_PUBLIC_REGISTRY_GQL_ENDPOINT', + 'NEXT_PUBLIC_REGISTRY_RPC_ENDPOINT', 'REGISTRY_BOND_ID', 'REGISTRY_AUTHORITY', 'REGISTRY_USER_KEY', // This is the same as the prefilled account for LNT transfers diff --git a/src/components/PaymentModal.tsx b/src/components/PaymentModal.tsx index 9bcd753..bcec2f4 100644 --- a/src/components/PaymentModal.tsx +++ b/src/components/PaymentModal.tsx @@ -1,11 +1,12 @@ 'use client'; -import { useCallback, useState, useEffect } from 'react'; +import { useCallback, useState, useEffect, useMemo } from 'react'; import assert from 'assert'; import { Connection } from '@solana/web3.js'; import { useConnection, useWallet } from '@solana/wallet-adapter-react'; +import { getRegistry } from '@/config'; import { sendSolanaPayment } from '@/services/solana'; import { getRequiredTokenInfo, RequiredTokenInfo } from '@/services/jupiter-price'; import { PaymentMethod, PaymentModalProps, PaymentRequest } from '@/types'; @@ -16,6 +17,26 @@ assert(!IS_NAT_GOR_TRANSFER_ENABLED || process.env.NEXT_PUBLIC_GORBAGANA_RPC_URL const GORBAGANA_RPC_URL = process.env.NEXT_PUBLIC_GORBAGANA_RPC_URL; +assert(process.env.NEXT_PUBLIC_SOLANA_RPC_URL, 'SOLANA_RPC_URL is required'); +assert(process.env.NEXT_PUBLIC_PRICING_RECORD_LRN, 'DEPLOYMENT_RECORD_LRN is required'); + +const PRICING_RECORD_LRN = process.env.NEXT_PUBLIC_PRICING_RECORD_LRN; +const SUPPORTED_CURRENCY = "USD"; +const VALID_PRICING_RECORD_TYPE = "webapp-deployment"; + +interface DeploymentCostInfo { + amount: string; + currency: string; +} + +interface PricingRecordAttributes { + amount: string; + currency: string; + for: string; + type: string; + version: string; +} + export default function PaymentModal({ isOpen, onClose, @@ -23,28 +44,55 @@ export default function PaymentModal({ onPaymentComplete, }: PaymentModalProps) { const { selectedPaymentMethod: paymentMethod } = usePaymentMethod(); - const { connection: solanaConnection } = useConnection(); + const { wallet, publicKey } = useWallet(); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [tokenAmount, setTokenAmount] = useState(0); const [tokenDecimals, setTokenDecimals] = useState(6); // Default fallback - const [loadingPrice, setLoadingPrice] = useState(false); + const [loadingPrice, setLoadingPrice] = useState(true); + const [deploymentCostInfo, setDeploymentCostInfo] = useState(); - const { wallet, publicKey } = useWallet(); + useEffect(() => { + const registry = getRegistry(); + + const resolveDeploymentCostInfo = async () => { + const result = await registry.resolveNames([PRICING_RECORD_LRN!]) + const PricingRecordAttributes: PricingRecordAttributes = result[0].attributes; + + if (PricingRecordAttributes.type !== VALID_PRICING_RECORD_TYPE) { + throw new Error(`Incorrect pricing record type: ${PricingRecordAttributes.type}. Please provide correct pricing record lrn`) + } + + if (PricingRecordAttributes.currency !== SUPPORTED_CURRENCY) { + throw new Error(`Unsupported currency found in pricing record: ${PricingRecordAttributes.currency}`) + } + + setDeploymentCostInfo({ + amount: PricingRecordAttributes.amount, + currency:PricingRecordAttributes.currency + }) + } + + resolveDeploymentCostInfo(); + }, []); // Get configuration from environment variables - const targetUsdAmount = parseFloat(process.env.NEXT_PUBLIC_SOLANA_PAYMENT_AMOUNT_USD!); + const deploymentCost = useMemo(() => { + if (!deploymentCostInfo) { + return; + } + + return parseInt(deploymentCostInfo.amount, 10); + }, [deploymentCostInfo]) + const mintAddress = process.env.NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS!; const tokenSymbol = process.env.NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL; // Fetch payment amount based on USD price for both payment methods useEffect(() => { - if (!isOpen || !paymentMethod) { - setLoadingPrice(false); - return; - } + if (!isOpen || !deploymentCost || !paymentMethod) return; const fetchPaymentAmount = async () => { setLoadingPrice(true); @@ -54,10 +102,10 @@ export default function PaymentModal({ let requiredTokenInfo: RequiredTokenInfo if (paymentMethod === PaymentMethod.NAT_GOR) { // Fetch native GOR amount using solana GOR token price - requiredTokenInfo = await getRequiredTokenInfo(targetUsdAmount, SOLANA_GOR_MINT_ADDRESS); + requiredTokenInfo = await getRequiredTokenInfo(deploymentCost, SOLANA_GOR_MINT_ADDRESS); } else if (paymentMethod === PaymentMethod.SPL_TOKEN) { // Fetch SPL token amount using token mint price - requiredTokenInfo = await getRequiredTokenInfo(targetUsdAmount, mintAddress); + requiredTokenInfo = await getRequiredTokenInfo(deploymentCost, mintAddress); } else { setError('Invalid payment method'); return; @@ -74,7 +122,7 @@ export default function PaymentModal({ }; fetchPaymentAmount(); - }, [isOpen, paymentMethod, targetUsdAmount, mintAddress]); + }, [isOpen, paymentMethod, deploymentCost, mintAddress]); // Initialize state when modal opens useEffect(() => { @@ -186,7 +234,7 @@ export default function PaymentModal({
{ export const getRegistryConfig = (): RegistryConfig => { // Validate required environment variables const requiredEnvVars = [ - 'REGISTRY_CHAIN_ID', - 'REGISTRY_GQL_ENDPOINT', - 'REGISTRY_RPC_ENDPOINT', + 'NEXT_PUBLIC_REGISTRY_CHAIN_ID', + 'NEXT_PUBLIC_REGISTRY_GQL_ENDPOINT', + 'NEXT_PUBLIC_REGISTRY_RPC_ENDPOINT', 'REGISTRY_BOND_ID', 'REGISTRY_AUTHORITY', 'REGISTRY_USER_KEY' @@ -37,9 +37,9 @@ export const getRegistryConfig = (): RegistryConfig => { } return { - chainId: process.env.REGISTRY_CHAIN_ID!, - rpcEndpoint: process.env.REGISTRY_RPC_ENDPOINT!, - gqlEndpoint: process.env.REGISTRY_GQL_ENDPOINT!, + chainId: process.env.NEXT_PUBLIC_REGISTRY_CHAIN_ID!, + rpcEndpoint: process.env.NEXT_PUBLIC_REGISTRY_RPC_ENDPOINT!, + gqlEndpoint: process.env.NEXT_PUBLIC_REGISTRY_GQL_ENDPOINT!, bondId: process.env.REGISTRY_BOND_ID!, authority: process.env.REGISTRY_AUTHORITY!, privateKey: process.env.REGISTRY_USER_KEY!,