diff --git a/.env.example b/.env.example index a45a019..50e7bc0 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 Token Payment Configuration # TODO: Use different RPC URL or use browser wallet @@ -13,12 +13,11 @@ NEXT_PUBLIC_SOLANA_PAYMENT_AMOUNT_USD=5 # Payment amount in USD # 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 f51c233..3ce2902 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -82,9 +82,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 0cda0b6..561e41d 100644 --- a/src/app/api/registry/route.ts +++ b/src/app/api/registry/route.ts @@ -231,9 +231,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 4a1bfdb..8a71c72 100644 --- a/src/components/PaymentModal.tsx +++ b/src/components/PaymentModal.tsx @@ -9,9 +9,28 @@ import { Connection } from '@solana/web3.js'; import { sendSolanaTokenPayment } from '@/services/solana'; import { getRequiredTokenInfo } from '@/services/jupiter-price'; import { PaymentModalProps } from '@/types'; +import { getRegistry } from '@/config'; 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 SOLANA_RPC_URL = process.env.NEXT_PUBLIC_SOLANA_RPC_URL; +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, @@ -25,24 +44,56 @@ export default function PaymentModal({ const [tokenAmount, setTokenAmount] = useState(0); const [tokenDecimals, setTokenDecimals] = useState(6); // Default fallback const [loadingPrice, setLoadingPrice] = useState(true); + const [deploymentCostInfo, setDeploymentCostInfo] = useState(); - const connection = useMemo(() => new Connection(SOLANA_RPC_URL), []) + const connection = useMemo(() => new Connection(SOLANA_RPC_URL), []); + + 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 recipientAddress = process.env.NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS; // Fetch token amount based on USD price useEffect(() => { - if (!isOpen) return; + if (!isOpen || !deploymentCost) return; const fetchTokenAmount = async () => { setLoadingPrice(true); setError(''); try { - const {requiredAmountInBaseUnits, decimals} = await getRequiredTokenInfo(targetUsdAmount, mintAddress) + const {requiredAmountInBaseUnits, decimals} = await getRequiredTokenInfo(deploymentCost, mintAddress); setTokenAmount(requiredAmountInBaseUnits); setTokenDecimals(decimals); } catch (error) { @@ -54,7 +105,7 @@ export default function PaymentModal({ }; fetchTokenAmount(); - }, [isOpen, targetUsdAmount, mintAddress]); + }, [isOpen, deploymentCost, mintAddress]); const handlePayment = useCallback(async () => { if (tokenAmount === 0 || loadingPrice) { @@ -117,7 +168,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!,