diff --git a/.env.local.example b/.env.example similarity index 60% rename from .env.local.example rename to .env.example index 746b184..26b84f4 100644 --- a/.env.local.example +++ b/.env.example @@ -1,16 +1,14 @@ # Client-side environment variables (must be prefixed with NEXT_PUBLIC_) -# ATOM Payment Configuration -NEXT_PUBLIC_RECIPIENT_ADDRESS=cosmos1yourrealaddress -NEXT_PUBLIC_COSMOS_RPC_URL=https://rpc.cosmos.network -NEXT_PUBLIC_COSMOS_API_URL=https://api.cosmos.network -NEXT_PUBLIC_COSMOS_CHAIN_ID=cosmoshub-4 - -# Solana/GOR Payment Configuration +# Solana Token Payment Configuration NEXT_PUBLIC_SOLANA_RPC_URL=https://skilled-prettiest-seed.solana-mainnet.quiknode.pro/eeecfebd04e345f69f1900cc3483cbbfea02a158 NEXT_PUBLIC_SOLANA_WEBSOCKET_URL=wss://skilled-prettiest-seed.solana-mainnet.quiknode.pro/ -NEXT_PUBLIC_GOR_MINT_ADDRESS=71Jvq4Epe2FCJ7JFSF7jLXdNk1Wy4Bhqd9iL6bEFELvg -NEXT_PUBLIC_GOR_RECIPIENT_ADDRESS= +NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS=71Jvq4Epe2FCJ7JFSF7jLXdNk1Wy4Bhqd9iL6bEFELvg +NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS= +NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL=GOR +NEXT_PUBLIC_SOLANA_TOKEN_NAME=GOR Token +NEXT_PUBLIC_SOLANA_TOKEN_DECIMALS=6 +NEXT_PUBLIC_SOLANA_PAYMENT_AMOUNT=50 # UI Configuration (optional) NEXT_PUBLIC_DOMAIN_SUFFIX= @@ -30,11 +28,9 @@ REGISTRY_FEES=900000alnt REGISTRY_GAS_PRICE=0.001 # Application Configuration -APP_NAME=atom-deploy +APP_NAME=gor-deploy DEPLOYER_LRN= -# LNT Transfer Configuration (required for both ATOM and GOR flows) +# LNT Transfer Configuration (required for Solana flow) # Note: REGISTRY_USER_KEY is used as the prefilled account for LNT transfers -# TODO: Use deployer lrn to determine the address -LACONIC_SERVICE_PROVIDER_ADDRESS= LACONIC_TRANSFER_AMOUNT=1000000alnt diff --git a/README.md b/README.md index 0fd2826..274f468 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,15 @@ -# ATOM Deploy - Laconic Registry +# GOR Deploy - Laconic Registry -A simple Next.js frontend that allows users to pay in ATOM cryptocurrency (using Keplr wallet) and paste a URL. The transaction hash and URL are used to create records in the Laconic Registry. +A simple Next.js frontend that allows users to pay in GOR tokens (configurable Solana SPL tokens) using Solana wallets and paste a URL. The transaction hash and URL are used to create records in the Laconic Registry. ## Features -- Keplr wallet integration for ATOM payments +- Solana wallet integration (Phantom & Solflare) for GOR token payments +- Configurable Solana SPL token support (defaults to GOR) - URL validation and submission -- Transaction verification +- Solana transaction verification with replay protection - Laconic Registry record creation using official `@cerc-io/registry-sdk` +- LNT token transfer integration for registry payments - Automatic salt addition to DNS names to prevent collisions - Error handling and validation throughout the application flow @@ -15,7 +17,7 @@ A simple Next.js frontend that allows users to pay in ATOM cryptocurrency (using - Node.js 18.x or later - npm or yarn -- Keplr wallet browser extension +- Solana wallet browser extension (Phantom or Solflare) - Access to a Laconic Registry node ## Environment Variables @@ -29,10 +31,14 @@ cp .env.local.example .env.local Required environment variables: Client-side (must be prefixed with NEXT_PUBLIC_): -- `NEXT_PUBLIC_RECIPIENT_ADDRESS` - The Cosmos address that will receive ATOM payments -- `NEXT_PUBLIC_COSMOS_RPC_URL` - The RPC URL for the Cosmos blockchain (used by Keplr for transactions) -- `NEXT_PUBLIC_COSMOS_API_URL` - The REST API URL for the Cosmos blockchain (used for transaction queries) -- `NEXT_PUBLIC_COSMOS_CHAIN_ID` - The chain ID for Keplr wallet (e.g., cosmoshub-4) +- `NEXT_PUBLIC_SOLANA_RPC_URL` - The RPC URL for the Solana blockchain +- `NEXT_PUBLIC_SOLANA_WEBSOCKET_URL` - The WebSocket URL for Solana (optional) +- `NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS` - The mint address of the SPL token to accept +- `NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS` - The Solana address that will receive token payments +- `NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL` - The token symbol to display (e.g., "GOR") +- `NEXT_PUBLIC_SOLANA_TOKEN_NAME` - The full token name (e.g., "GOR Token") +- `NEXT_PUBLIC_SOLANA_TOKEN_DECIMALS` - The number of decimals for the token (e.g., 6) +- `NEXT_PUBLIC_SOLANA_PAYMENT_AMOUNT` - The fixed payment amount required (e.g., 50) - `NEXT_PUBLIC_DOMAIN_SUFFIX` - Optional suffix to append to DNS names in the UI (e.g. ".example.com") - `NEXT_PUBLIC_EXAMPLE_URL` - Example URL to pre-fill in the URL form (e.g. "https://github.com/cerc-io/laconic-registry-cli") @@ -42,9 +48,11 @@ Server-side: - `REGISTRY_RPC_ENDPOINT` - The RPC endpoint for the Laconic Registry - `REGISTRY_BOND_ID` - The bond ID to use for Laconic Registry records - `REGISTRY_AUTHORITY` - The authority for Laconic Registry LRNs -- `REGISTRY_USER_KEY` - The private key for Laconic Registry transactions -- `APP_NAME` - The name of the application (used in record creation) +- `REGISTRY_USER_KEY` - The private key for Laconic Registry transactions (also used for LNT transfers) +- `APP_NAME` - The name of the application (used in record creation, defaults to "gor-deploy") - `DEPLOYER_LRN` - The LRN of the deployer +- `LACONIC_SERVICE_PROVIDER_ADDRESS` - The Laconic address to receive LNT transfers +- `LACONIC_TRANSFER_AMOUNT` - The amount of LNT to transfer (e.g., "1000000alnt") ## Installation @@ -74,24 +82,25 @@ npm start ## How It Works -1. User connects their Keplr wallet to the application +1. User connects their Solana wallet (Phantom or Solflare) to the application 2. User enters a URL they want to deploy to the Laconic Registry -3. User completes payment in ATOM to a specified address -4. The application verifies the transaction using the Cosmos RPC -5. The application calls a server-side API route which creates records in the Laconic Registry -6. The server generates a unique DNS name by adding a random salt to prevent name collisions -7. Two records are created in the Laconic Registry: +3. User completes payment in GOR tokens (or configured SPL token) to a specified Solana address +4. The application verifies the Solana transaction with replay protection +5. After payment verification, the server transfers LNT tokens from a prefilled account to the service provider +6. The application calls a server-side API route which creates records in the Laconic Registry using the LNT transfer hash +7. The server generates a unique DNS name by adding a random salt to prevent name collisions +8. Two records are created in the Laconic Registry: - An ApplicationRecord containing metadata about the URL - - An ApplicationDeploymentRequest linking the URL, DNS, and payment transaction + - An ApplicationDeploymentRequest linking the URL, DNS, and payment details with external_payment metadata ### Architecture This application uses a hybrid client/server approach: -- Client-side: Handles the user interface, Keplr wallet integration, and transaction verification -- Server-side: Next.js API route handles the communication with the Laconic Registry +- Client-side: Handles the user interface, Solana wallet integration, and transaction verification +- Server-side: Next.js API route handles LNT transfers and communication with the Laconic Registry -This architecture allows us to keep sensitive keys secure on the server side while providing a responsive user experience. +This architecture allows us to keep sensitive keys secure on the server side while providing a responsive user experience. The dual-payment system (Solana → LNT → Registry) enables cross-chain payment acceptance. ### Resource Name Formats @@ -115,7 +124,7 @@ The Laconic Resource Names (LRNs) are generated with the following format: lrn://{authority}/applications/{app-name}-{short-commit-hash}-{random-salt} ``` -For example: `lrn://atom/applications/github-abc123-xyz789` +For example: `lrn://gor/applications/github-abc123-xyz789` Including the commit hash and salt in the LRN ensures that each application record has a unique identifier, consistently matching the DNS naming pattern. @@ -123,7 +132,7 @@ Including the commit hash and salt in the LRN ensures that each application reco This application was built with reference to: - `snowballtools-base/packages/backend/src/registry.ts` -- `hosted-frontends/deploy-atom.sh` +- Original `hosted-frontends/deploy-atom.sh` (adapted for Solana/GOR) ## Deployment to Production @@ -158,24 +167,32 @@ CMD ["npm", "start"] Build and run the Docker container: ```bash -docker build -t atom-deploy . -docker run -p 3000:3000 --env-file .env.production atom-deploy +docker build -t gor-deploy . +docker run -p 3000:3000 --env-file .env.production gor-deploy ``` ## Known Issues - You may see a deprecated Buffer() warning during build. This comes from dependencies in the registry-sdk. This doesn't affect functionality. -- If using a custom Cosmos chain, ensure that your RPC endpoint supports CORS for client-side requests. -- The Keplr wallet integration requires HTTPS in production environments. +- Ensure that your Solana RPC endpoint supports CORS for client-side requests. +- Solana wallet integrations require HTTPS in production environments. ## Troubleshooting -### Keplr Wallet Issues +### Solana Wallet Issues -- **Keplr not detecting**: Install the Keplr browser extension and refresh the page. -- **Chain not found in Keplr**: The application will attempt to suggest the chain to Keplr, but if that fails, you may need to manually add the chain in your Keplr wallet settings. +- **Wallet not detecting**: Install the Phantom or Solflare browser extension and refresh the page. +- **Connection issues**: Ensure the wallet is unlocked and try refreshing the page. +- **Transaction failures**: Check that you have sufficient SOL for transaction fees and enough tokens for the payment. + +### Token Configuration + +- **Wrong token**: Verify the `NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS` matches your desired SPL token. +- **Incorrect decimals**: Ensure `NEXT_PUBLIC_SOLANA_TOKEN_DECIMALS` matches the token's actual decimal count. +- **Payment amount**: Adjust `NEXT_PUBLIC_SOLANA_PAYMENT_AMOUNT` to the desired payment amount. ### Laconic Registry Issues - **Failed to create record**: Check that your REGISTRY_USER_KEY and REGISTRY_BOND_ID are correctly set. -- **Transaction verification errors**: Ensure your COSMOS_RPC_URL and COSMOS_API_URL are accessible and return correct transaction data. +- **LNT transfer errors**: Ensure your REGISTRY_USER_KEY has sufficient LNT balance and the LACONIC_SERVICE_PROVIDER_ADDRESS is valid. +- **Transaction verification errors**: Ensure your SOLANA_RPC_URL is accessible and returns correct transaction data. diff --git a/package.json b/package.json index 5631c72..519bd9d 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "atom-deploy", + "name": "gor-deploy", "version": "0.1.0", "private": true, "scripts": { @@ -11,11 +11,9 @@ "dependencies": { "@cerc-io/registry-sdk": "^0.2.11", "@cosmjs/stargate": "^0.32.3", - "@keplr-wallet/types": "^0.12.71", "@solana/spl-token": "^0.4.13", "@solana/web3.js": "^1.98.2", "axios": "^1.6.8", - "bn.js": "^5.2.2", "next": "15.3.1", "react": "^19.0.0", "react-dom": "^19.0.0" diff --git a/src/app/api/registry/route.ts b/src/app/api/registry/route.ts index 8effcf0..85cfe0e 100644 --- a/src/app/api/registry/route.ts +++ b/src/app/api/registry/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from 'next/server'; -import { Account, Registry, parseGasAndFees } from '@cerc-io/registry-sdk'; +import { Registry } from '@cerc-io/registry-sdk'; import { GasPrice } from '@cosmjs/stargate'; import axios from 'axios'; import { verifyUnusedSolanaPayment } from '@/utils/solanaVerify'; @@ -8,114 +8,6 @@ import { transferLNTTokens } from '@/services/laconicTransfer'; // Sleep helper function const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); -// ATOM payment verification function -const verifyAtomPayment = async (txHash: string): Promise<{ - valid: boolean, - reason?: string, - amount?: string, - sender?: string -}> => { - try { - const apiEndpoint = process.env.NEXT_PUBLIC_COSMOS_API_URL; - const recipientAddress = process.env.NEXT_PUBLIC_RECIPIENT_ADDRESS; - const minPaymentUAtom = '100000'; // 0.1 ATOM in uatom - - if (!apiEndpoint) { - return { - valid: false, - reason: 'ATOM API endpoint not configured' - }; - } - - if (!recipientAddress) { - return { - valid: false, - reason: 'ATOM recipient address not configured' - }; - } - - // Fetch transaction from the ATOM API endpoint - const response = await axios.get(`${apiEndpoint}/cosmos/tx/v1beta1/txs/${txHash}`); - - if (!response.data || !response.data.tx || !response.data.tx_response) { - return { - valid: false, - reason: 'Invalid transaction data from API endpoint' - }; - } - - // Check if transaction was successful - const txResponse = response.data.tx_response; - if (txResponse.code !== 0) { - return { - valid: false, - reason: `Transaction failed with code ${txResponse.code}: ${txResponse.raw_log}` - }; - } - - // Check transaction timestamp (5-minute window) - const txTimestamp = new Date(txResponse.timestamp); - const now = new Date(); - const timeDiffMs = now.getTime() - txTimestamp.getTime(); - const timeWindowMs = 5 * 60 * 1000; // 5 minutes - - if (timeDiffMs > timeWindowMs) { - return { - valid: false, - reason: `Transaction is older than 5 minutes (${Math.round(timeDiffMs / 60000)} minutes old)` - }; - } - - // Extract the payment details - const tx = response.data.tx; - let foundValidPayment = false; - let paymentAmountUAtom = ''; - let sender = ''; - - // Get the sender address from the first signer - if (tx.auth_info && tx.auth_info.signer_infos && tx.auth_info.signer_infos.length > 0) { - sender = tx.auth_info.signer_infos[0].public_key.address || ''; - } - - // Find the send message in the transaction - for (const msg of tx.body.messages) { - if (msg['@type'] === '/cosmos.bank.v1beta1.MsgSend') { - if (msg.to_address === recipientAddress) { - for (const coin of msg.amount) { - if (coin.denom === 'uatom') { - // Get the amount in uatom - paymentAmountUAtom = coin.amount; - - if (parseInt(paymentAmountUAtom) >= parseInt(minPaymentUAtom)) { - foundValidPayment = true; - } - break; - } - } - } - } - } - - if (!foundValidPayment) { - return { - valid: false, - reason: `Payment amount (${paymentAmountUAtom || '0'}uatom) is less than required (${minPaymentUAtom}uatom) or not sent to the correct address (${recipientAddress})` - }; - } - - return { - valid: true, - amount: `${paymentAmountUAtom}uatom`, - sender - }; - } catch (error) { - console.error('Error verifying ATOM payment:', error); - return { - valid: false, - reason: `Failed to verify transaction: ${error instanceof Error ? error.message : 'Unknown error'}` - }; - } -}; // Extract repo name from URL const extractRepoInfo = (url: string): { repoName: string, repoUrl: string, provider: string } => { @@ -230,12 +122,11 @@ const registryTransactionWithRetry = async ( export async function POST(request: NextRequest) { try { // First check if the request body is valid JSON - let url, txHash, paymentType; + let url, txHash; try { const body = await request.json(); url = body.url; txHash = body.txHash; - paymentType = body.paymentType || 'ATOM'; // Default to ATOM for backward compatibility if (!url || !txHash) { return NextResponse.json({ @@ -243,13 +134,6 @@ export async function POST(request: NextRequest) { message: 'Missing required fields: url and txHash are required' }, { status: 400 }); } - - if (!['ATOM', 'GOR'].includes(paymentType)) { - return NextResponse.json({ - status: 'error', - message: 'Invalid payment type. Must be ATOM or GOR' - }, { status: 400 }); - } } catch (error) { return NextResponse.json({ status: 'error', @@ -257,42 +141,24 @@ export async function POST(request: NextRequest) { }, { status: 400 }); } - // Verify payment based on type - if (paymentType === 'ATOM') { - console.log('Step 0: Verifying ATOM payment...'); - const paymentVerificationResult = await verifyAtomPayment(txHash); + // Verify Solana payment + console.log('Step 0: Verifying Solana token payment...'); + const solanaPaymentResult = await verifyUnusedSolanaPayment(txHash); - if (!paymentVerificationResult.valid) { - console.error('ATOM payment verification failed:', paymentVerificationResult.reason); - return NextResponse.json({ - status: 'error', - message: `Payment verification failed: ${paymentVerificationResult.reason}` - }, { status: 400 }); - } - - console.log('ATOM payment verified successfully:', { - amount: paymentVerificationResult.amount, - sender: paymentVerificationResult.sender - }); - } else if (paymentType === 'GOR') { - console.log('Step 0: Verifying GOR payment...'); - const gorPaymentResult = await verifyUnusedSolanaPayment(txHash); - - if (!gorPaymentResult.valid) { - console.error('GOR payment verification failed:', gorPaymentResult.reason); - return NextResponse.json({ - status: 'error', - message: `Payment verification failed: ${gorPaymentResult.reason}` - }, { status: 400 }); - } - - console.log('GOR payment verified successfully:', { - amount: gorPaymentResult.amount, - sender: gorPaymentResult.sender - }); + if (!solanaPaymentResult.valid) { + console.error('Solana token payment verification failed:', solanaPaymentResult.reason); + return NextResponse.json({ + status: 'error', + message: `Payment verification failed: ${solanaPaymentResult.reason}` + }, { status: 400 }); } - // For both payment types, perform LNT transfer after payment verification + console.log('Solana token payment verified successfully:', { + amount: solanaPaymentResult.amount, + sender: solanaPaymentResult.sender + }); + + // Perform LNT transfer after payment verification console.log('Step 0.5: Performing LNT transfer from prefilled account to service provider...'); const lntTransferResult = await transferLNTTokens(); @@ -307,8 +173,8 @@ export async function POST(request: NextRequest) { console.log('LNT transfer completed:', lntTransferResult.transactionHash); const finalTxHash = lntTransferResult.transactionHash!; // Use LNT transfer hash for registry - // Validate required environment variables based on payment type - const baseRequiredEnvVars = [ + // Validate required environment variables for GOR/Solana payments + const requiredEnvVars = [ 'REGISTRY_CHAIN_ID', 'REGISTRY_GQL_ENDPOINT', 'REGISTRY_RPC_ENDPOINT', @@ -316,29 +182,15 @@ export async function POST(request: NextRequest) { 'REGISTRY_AUTHORITY', 'REGISTRY_USER_KEY', // This is the same as the prefilled account for LNT transfers 'DEPLOYER_LRN', - // LNT transfer variables now required for both payment types + // LNT transfer variables 'LACONIC_SERVICE_PROVIDER_ADDRESS', - 'LACONIC_TRANSFER_AMOUNT' - ]; - - const atomRequiredEnvVars = [ - 'NEXT_PUBLIC_RECIPIENT_ADDRESS', - 'NEXT_PUBLIC_COSMOS_API_URL' - ]; - - const gorRequiredEnvVars = [ + 'LACONIC_TRANSFER_AMOUNT', + // Solana/GOR specific variables 'NEXT_PUBLIC_SOLANA_RPC_URL', - 'NEXT_PUBLIC_GOR_MINT_ADDRESS', - 'NEXT_PUBLIC_GOR_RECIPIENT_ADDRESS' + 'NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS', + 'NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS' ]; - let requiredEnvVars = [...baseRequiredEnvVars]; - if (paymentType === 'ATOM') { - requiredEnvVars = [...requiredEnvVars, ...atomRequiredEnvVars]; - } else if (paymentType === 'GOR') { - requiredEnvVars = [...requiredEnvVars, ...gorRequiredEnvVars]; - } - for (const envVar of requiredEnvVars) { if (!process.env[envVar]) { console.error(`Missing environment variable: ${envVar}`); @@ -540,9 +392,16 @@ export async function POST(request: NextRequest) { } }, meta: { - note: `Added via ATOM-Deploy @ ${timestamp}`, + note: `Added via GOR-Deploy @ ${timestamp}`, repository: repoUrl, repository_ref: fullHash, + external_payment: { + // Use CAIP convention for chain ID: namespace + reference + chain_id: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', // Solana mainnet + tx_hash: txHash, + // TODO: Take pubkey from user and add it + // pubkey: '' + } }, payment: finalTxHash, }; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 42953a5..d8d2163 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,5 +1,6 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; + import "./globals.css"; import ErrorBoundaryWrapper from "../components/ErrorBoundaryWrapper"; @@ -14,8 +15,8 @@ const geistMono = Geist_Mono({ }); export const metadata: Metadata = { - title: "Deploy Frontends using ATOM and Laconic", - description: "Deploy URLs to Laconic Registry using ATOM payments", + title: "Deploy Frontends using GOR and Laconic", + description: "Deploy URLs to Laconic Registry using GOR payments", }; export default function RootLayout({ diff --git a/src/app/page.tsx b/src/app/page.tsx index ff72398..1cf7ee7 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,19 +1,19 @@ 'use client'; import { useState } from 'react'; -// Dynamically import components to avoid SSR issues with browser APIs -const KeplrConnect = dynamic(() => import('@/components/KeplrConnect'), { ssr: false }); -const SolanaConnect = dynamic(() => import('@/components/SolanaConnect'), { ssr: false }); -const PaymentModal = dynamic(() => import('@/components/PaymentModal'), { ssr: false }); +import dynamic from 'next/dynamic'; + import URLForm from '@/components/URLForm'; import StatusDisplay from '@/components/StatusDisplay'; import { createApplicationDeploymentRequest } from '@/services/registry'; -import { PaymentType, SolanaWalletState } from '@/types'; -import dynamic from 'next/dynamic'; +import { SolanaWalletState } from '@/types'; + +// Dynamically import components to avoid SSR issues with browser APIs +const SolanaConnect = dynamic(() => import('@/components/SolanaConnect'), { ssr: false }); +const PaymentModal = dynamic(() => import('@/components/PaymentModal'), { ssr: false }); export default function Home() { - const [paymentType, setPaymentType] = useState('ATOM'); - const [walletAddress, setWalletAddress] = useState(null); + const [showWalletConnection, setShowWalletConnection] = useState(false); const [solanaWalletState, setSolanaWalletState] = useState({ connected: false, publicKey: null, @@ -33,10 +33,6 @@ export default function Home() { const [shortCommitHash, setShortCommitHash] = useState(null); const [error, setError] = useState(null); - const handleConnect = (address: string) => { - setWalletAddress(address); - }; - const handleSolanaConnect = (walletState: SolanaWalletState) => { setSolanaWalletState(walletState); // Store wallet info globally for PaymentModal access (simplified approach) @@ -48,14 +44,8 @@ export default function Home() { } }; - const handlePaymentTypeChange = (type: PaymentType) => { - setPaymentType(type); - // Reset wallet states when switching payment types - if (type === 'ATOM') { - setSolanaWalletState({ connected: false, publicKey: null, walletType: null }); - } else { - setWalletAddress(null); - } + const handleConnectWallet = () => { + setShowWalletConnection(true); }; const handleUrlSubmit = (submittedUrl: string) => { @@ -67,12 +57,12 @@ export default function Home() { setTxHash(hash); setShowPaymentModal(false); setStatus('creating'); - + try { // Create the Laconic Registry record (payment verification is done in the API) if (url) { - const result = await createApplicationDeploymentRequest(url, hash, paymentType); - + const result = await createApplicationDeploymentRequest(url, hash); + if (result.status === 'success') { setRecordId(result.id); if (result.applicationRecordId) { @@ -114,85 +104,69 @@ export default function Home() { return (
-

- Deploy Frontends with ATOM/GOR + Laconic + Deploy Frontends with GOR + Laconic

- - {/* Payment Type Selection */} -
-

- 1 - Select Payment Method -

-
- - -
-
- +

- 2 + 1 Connect Your Wallet

- {paymentType === 'ATOM' ? ( - + {!showWalletConnection ? ( +
+

+ Payment method: + GOR (Solana) + +

+ +
) : ( )}
- -

3 + style={{ background: 'var(--primary)', color: 'var(--primary-foreground)' }}>2 Enter URL to Deploy

-
- + {status !== 'idle' && (

4 + style={{ background: 'var(--primary)', color: 'var(--primary-foreground)' }}>3 Deployment Status

-
)}
- - {showPaymentModal && url && (paymentType === 'ATOM' ? walletAddress : solanaWalletState.connected) && ( - )} diff --git a/src/components/KeplrConnect.tsx b/src/components/KeplrConnect.tsx deleted file mode 100644 index 490c570..0000000 --- a/src/components/KeplrConnect.tsx +++ /dev/null @@ -1,74 +0,0 @@ -'use client'; - -import { useState, useEffect } from 'react'; -import { connectKeplr } from '@/services/keplr'; - -interface KeplrConnectProps { - onConnect: (address: string) => void; -} - -export default function KeplrConnect({ onConnect }: KeplrConnectProps) { - const [connecting, setConnecting] = useState(false); - const [address, setAddress] = useState(null); - - const handleConnect = async () => { - setConnecting(true); - try { - const userAddress = await connectKeplr(); - if (userAddress) { - setAddress(userAddress); - onConnect(userAddress); - } - } catch (error) { - console.error('Failed to connect to Keplr:', error); - } finally { - setConnecting(false); - } - }; - - useEffect(() => { - // Check if Keplr is available - if (typeof window !== 'undefined' && window.keplr) { - // Auto-connect on page load - handleConnect(); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return ( -
- {address ? ( -
-
- -

Connected

-
-
-

{address}

-
-
- ) : ( - - )} -
- ); -} \ No newline at end of file diff --git a/src/components/PaymentModal.tsx b/src/components/PaymentModal.tsx index 3ab9dfc..e2ea4a9 100644 --- a/src/components/PaymentModal.tsx +++ b/src/components/PaymentModal.tsx @@ -1,76 +1,44 @@ 'use client'; import { useState } from 'react'; -import { sendAtomPayment } from '@/services/keplr'; -import { sendGorPayment } from '@/services/solana'; + +import { sendSolanaTokenPayment } from '@/services/solana'; import { PaymentModalProps } from '@/types'; -import { getSolanaConfig, GOR_PAYMENT_AMOUNT } from '@/config'; +import { getSolanaConfig } from '@/config'; export default function PaymentModal({ isOpen, onClose, url, - paymentType, onPaymentComplete, }: PaymentModalProps) { - const [amount, setAmount] = useState(paymentType === 'ATOM' ? '0.01' : GOR_PAYMENT_AMOUNT.toString()); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); - - // Get recipient addresses from environment variables - const atomRecipientAddress = process.env.NEXT_PUBLIC_RECIPIENT_ADDRESS || 'cosmos1yourrealaddress'; - const gorRecipientAddress = paymentType === 'GOR' ? - (process.env.NEXT_PUBLIC_GOR_RECIPIENT_ADDRESS || 'solana_recipient_address') : ''; - - const recipientAddress = paymentType === 'ATOM' ? atomRecipientAddress : gorRecipientAddress; - // Validate amount on change - const handleAmountChange = (e: React.ChangeEvent) => { - const value = e.target.value; - setAmount(value); - - // Clear error when user types - if (error) { - setError(''); - } - }; + // Get configuration + const solanaConfig = getSolanaConfig(); + const amount = solanaConfig.paymentAmount; + const recipientAddress = solanaConfig.tokenRecipientAddress; const handlePayment = async () => { - // Validate amount before sending - const parsedAmount = parseFloat(amount); - if (isNaN(parsedAmount) || parsedAmount <= 0) { - setError('Please enter a valid positive amount'); - return; - } - setLoading(true); setError(''); - + try { - if (paymentType === 'ATOM') { - const result = await sendAtomPayment(recipientAddress, amount); - - if (result.status === 'success' && result.hash) { - onPaymentComplete(result.hash); - } else { - setError(result.message || 'ATOM payment failed. Please try again.'); - } - } else if (paymentType === 'GOR') { - // For GOR payments, we need wallet info from parent component - // This is a simplified approach - in a real implementation, you'd pass wallet state - const walletInfo = (window as any).solanaWalletInfo; - if (!walletInfo || !walletInfo.publicKey || !walletInfo.walletType) { - setError('Solana wallet not connected. Please connect your wallet first.'); - return; - } - - const result = await sendGorPayment(walletInfo.publicKey, walletInfo.walletType); - - if (result.success && result.transactionSignature) { - onPaymentComplete(result.transactionSignature); - } else { - setError(result.error || 'GOR payment failed. Please try again.'); - } + // For Solana payments, we need wallet info from parent component + // This is a simplified approach - in a real implementation, you'd pass wallet state + const walletInfo = (window as any).solanaWalletInfo; + if (!walletInfo || !walletInfo.publicKey || !walletInfo.walletType) { + setError('Solana wallet not connected. Please connect your wallet first.'); + return; + } + + const result = await sendSolanaTokenPayment(walletInfo.publicKey, walletInfo.walletType); + + if (result.success && result.transactionSignature) { + onPaymentComplete(result.transactionSignature); + } else { + setError(result.error || 'GOR payment failed. Please try again.'); } } catch (error) { setError(error instanceof Error ? error.message : 'Payment failed. Please try again.'); @@ -83,14 +51,14 @@ export default function PaymentModal({ return (
-

- Complete {paymentType} Payment + Complete GOR Payment

- +

URL to be deployed:

@@ -98,58 +66,54 @@ export default function PaymentModal({ {url}
- +

Recipient Address:

{recipientAddress}
- +
- {paymentType} + GOR
- {paymentType === 'GOR' && ( -

- Fixed amount required for deployment -

- )} +

+ Fixed amount required for deployment +

- + {error && (
{error}
)}
- +
diff --git a/src/components/StatusDisplay.tsx b/src/components/StatusDisplay.tsx index df34cc4..857e595 100644 --- a/src/components/StatusDisplay.tsx +++ b/src/components/StatusDisplay.tsx @@ -125,7 +125,7 @@ export default function StatusDisplay({ )} - {txHash && } + {txHash && } {appRecordId && } {recordId && } {lrn && } diff --git a/src/config/index.ts b/src/config/index.ts index 247b124..5381b09 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -40,35 +40,35 @@ export const getDeployerLrn = (): string => { }; export const getAppName = (): string => { - return process.env.APP_NAME || 'atom-deploy'; + return process.env.APP_NAME || 'gor-deploy'; }; -export const COSMOS_DENOM = 'uatom'; - -// Solana/GOR Token Configuration -export const GOR_PAYMENT_AMOUNT = 50; // 50 GOR tokens -export const GOR_TOKEN_DECIMALS = 9; // Standard SPL token decimals - +// Solana Token Configuration (configurable) export const getSolanaConfig = () => { - const requiredEnvVars = [ - 'NEXT_PUBLIC_SOLANA_RPC_URL', - 'NEXT_PUBLIC_GOR_MINT_ADDRESS', - 'NEXT_PUBLIC_GOR_RECIPIENT_ADDRESS' - ]; + // Direct validation without loop to avoid the strange undefined behavior + const rpcUrl = process.env.NEXT_PUBLIC_SOLANA_RPC_URL; + const tokenMintAddress = process.env.NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS; + const tokenRecipientAddress = process.env.NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS; - for (const envVar of requiredEnvVars) { - if (!process.env[envVar]) { - throw new Error(`Missing environment variable: ${envVar}`); - } + if (!rpcUrl) { + throw new Error(`Missing environment variable: NEXT_PUBLIC_SOLANA_RPC_URL`); + } + if (!tokenMintAddress) { + throw new Error(`Missing environment variable: NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS`); + } + if (!tokenRecipientAddress) { + throw new Error(`Missing environment variable: NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS`); } return { - rpcUrl: process.env.NEXT_PUBLIC_SOLANA_RPC_URL!, + rpcUrl, websocketUrl: process.env.NEXT_PUBLIC_SOLANA_WEBSOCKET_URL, - gorMintAddress: process.env.NEXT_PUBLIC_GOR_MINT_ADDRESS!, - gorRecipientAddress: process.env.NEXT_PUBLIC_GOR_RECIPIENT_ADDRESS!, - paymentAmount: GOR_PAYMENT_AMOUNT, - tokenDecimals: GOR_TOKEN_DECIMALS + tokenMintAddress, + tokenRecipientAddress, + tokenSymbol: process.env.NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL || 'TOKEN', + tokenName: process.env.NEXT_PUBLIC_SOLANA_TOKEN_NAME || 'Solana Token', + paymentAmount: parseInt(process.env.NEXT_PUBLIC_SOLANA_PAYMENT_AMOUNT || '50'), + tokenDecimals: parseInt(process.env.NEXT_PUBLIC_SOLANA_TOKEN_DECIMALS || '9') }; }; @@ -92,6 +92,3 @@ export const getLaconicTransferConfig = () => { }; }; - - - diff --git a/src/services/keplr.ts b/src/services/keplr.ts deleted file mode 100644 index fe9242f..0000000 --- a/src/services/keplr.ts +++ /dev/null @@ -1,260 +0,0 @@ -import { SigningStargateClient } from '@cosmjs/stargate'; -import { TransactionResponse } from '../types'; -import { COSMOS_DENOM } from '../config'; - -export const connectKeplr = async (): Promise => { - if (!window.keplr) { - alert('Keplr wallet extension is not installed!'); - return null; - } - - try { - const chainId = process.env.NEXT_PUBLIC_COSMOS_CHAIN_ID || 'cosmoshub-4'; - - // Try to suggest chain if custom network - if (chainId !== 'cosmoshub-4') { - try { - // Check if we need to suggest the chain to Keplr - await window.keplr.getKey(chainId).catch(async () => { - // Chain needs to be suggested - if (process.env.NEXT_PUBLIC_COSMOS_RPC_URL) { - await window.keplr.experimentalSuggestChain({ - chainId: chainId, - chainName: chainId, - rpc: process.env.NEXT_PUBLIC_COSMOS_RPC_URL, - rest: process.env.NEXT_PUBLIC_COSMOS_REST_URL || process.env.NEXT_PUBLIC_COSMOS_RPC_URL, - bip44: { - coinType: 118, - }, - bech32Config: { - bech32PrefixAccAddr: "cosmos", - bech32PrefixAccPub: "cosmospub", - bech32PrefixValAddr: "cosmosvaloper", - bech32PrefixValPub: "cosmosvaloperpub", - bech32PrefixConsAddr: "cosmosvalcons", - bech32PrefixConsPub: "cosmosvalconspub", - }, - currencies: [ - { - coinDenom: "ATOM", - coinMinimalDenom: "uatom", - coinDecimals: 6, - }, - ], - feeCurrencies: [ - { - coinDenom: "ATOM", - coinMinimalDenom: "uatom", - coinDecimals: 6, - }, - ], - stakeCurrency: { - coinDenom: "ATOM", - coinMinimalDenom: "uatom", - coinDecimals: 6, - }, - gasPriceStep: { - low: 0.01, - average: 0.025, - high: 0.04, - }, - }); - } - }); - } catch (suggestError) { - console.warn("Failed to suggest chain to Keplr:", suggestError); - // Continue anyway, as enable might still work - } - } - - // Enable Keplr for the specified chain - await window.keplr.enable(chainId); - const offlineSigner = window.keplr.getOfflineSigner(chainId); - - // Get the user's account - const accounts = await offlineSigner.getAccounts(); - if (!accounts || accounts.length === 0) { - console.error('No accounts found in Keplr wallet'); - return null; - } - return accounts[0].address; - } catch (error) { - console.error('Failed to connect to Keplr wallet:', error); - return null; - } -}; - -export const sendAtomPayment = async ( - recipientAddress: string, - amount: string -): Promise => { - try { - if (!window.keplr) { - return { - hash: '', - status: 'error', - message: 'Keplr wallet extension is not installed!' - }; - } - - // Validate recipient address is a valid cosmos address - if (!recipientAddress || !recipientAddress.startsWith('cosmos1')) { - return { - hash: '', - status: 'error', - message: 'Invalid recipient address. Must be a valid Cosmos address starting with cosmos1' - }; - } - - // Validate amount is a positive number - const parsedAmount = parseFloat(amount); - if (isNaN(parsedAmount) || parsedAmount <= 0) { - return { - hash: '', - status: 'error', - message: 'Invalid amount. Must be a positive number' - }; - } - - // Get the chain ID from environment variables or use default - const chainId = process.env.NEXT_PUBLIC_COSMOS_CHAIN_ID || 'cosmoshub-4'; - - // Enable the chain in Keplr, following same logic as connectKeplr - if (chainId !== 'cosmoshub-4') { - try { - // Check if we need to suggest the chain to Keplr - await window.keplr.getKey(chainId).catch(async () => { - // Chain needs to be suggested - if (process.env.NEXT_PUBLIC_COSMOS_RPC_URL) { - await window.keplr.experimentalSuggestChain({ - chainId: chainId, - chainName: chainId, - rpc: process.env.NEXT_PUBLIC_COSMOS_RPC_URL, - rest: process.env.NEXT_PUBLIC_COSMOS_REST_URL || process.env.NEXT_PUBLIC_COSMOS_RPC_URL, - bip44: { coinType: 118 }, - bech32Config: { - bech32PrefixAccAddr: "cosmos", - bech32PrefixAccPub: "cosmospub", - bech32PrefixValAddr: "cosmosvaloper", - bech32PrefixValPub: "cosmosvaloperpub", - bech32PrefixConsAddr: "cosmosvalcons", - bech32PrefixConsPub: "cosmosvalconspub", - }, - currencies: [{ coinDenom: "ATOM", coinMinimalDenom: "uatom", coinDecimals: 6 }], - feeCurrencies: [{ coinDenom: "ATOM", coinMinimalDenom: "uatom", coinDecimals: 6 }], - stakeCurrency: { coinDenom: "ATOM", coinMinimalDenom: "uatom", coinDecimals: 6 }, - gasPriceStep: { low: 0.01, average: 0.025, high: 0.04 }, - }); - } - }); - } catch (suggestError) { - console.warn("Failed to suggest chain to Keplr:", suggestError); - // Continue anyway, as enable might still work - } - } - - // Enable the chain in Keplr - await window.keplr.enable(chainId); - const offlineSigner = window.keplr.getOfflineSigner(chainId); - - // Create the Stargate client - const rpcEndpoint = process.env.NEXT_PUBLIC_COSMOS_RPC_URL; - if (!rpcEndpoint) { - return { - hash: '', - status: 'error', - message: 'NEXT_PUBLIC_COSMOS_RPC_URL environment variable is not set' - }; - } - - const client = await SigningStargateClient.connectWithSigner( - rpcEndpoint, - offlineSigner - ); - - // Get the user's account - const accounts = await offlineSigner.getAccounts(); - if (!accounts || accounts.length === 0) { - return { - hash: '', - status: 'error', - message: 'No accounts found in Keplr wallet' - }; - } - const sender = accounts[0].address; - - // Convert amount to microdenom (e.g., ATOM to uatom) - const microAmount = convertToMicroDenom(amount); - - // Send the transaction - const result = await client.sendTokens( - sender, - recipientAddress, - [{ denom: COSMOS_DENOM, amount: microAmount }], - { - amount: [{ denom: COSMOS_DENOM, amount: '5000' }], - gas: '200000', - } - ); - - if (!result || !result.transactionHash) { - return { - hash: '', - status: 'error', - message: 'Transaction did not return a valid hash' - }; - } - - return { - hash: result.transactionHash, - status: 'success', - }; - } catch (error) { - console.error('Failed to send ATOM payment:', error); - // Provide more descriptive error messages for common errors - if (error instanceof Error) { - const errorMessage = error.message.toLowerCase(); - - if (errorMessage.includes('insufficient funds')) { - return { - hash: '', - status: 'error', - message: 'Insufficient funds in your Keplr wallet to complete this transaction' - }; - } else if (errorMessage.includes('rejected')) { - return { - hash: '', - status: 'error', - message: 'Transaction was rejected in the Keplr wallet' - }; - } else if (errorMessage.includes('timeout')) { - return { - hash: '', - status: 'error', - message: 'Transaction timed out. Please try again' - }; - } - - return { - hash: '', - status: 'error', - message: error.message - }; - } - - return { - hash: '', - status: 'error', - message: 'Unknown error occurred while sending payment' - }; - } -}; - -// Helper function to convert from ATOM to uatom (1 ATOM = 1,000,000 uatom) -export const convertToMicroDenom = (amount: string): string => { - const parsedAmount = parseFloat(amount); - if (isNaN(parsedAmount)) { - throw new Error('Invalid amount'); - } - return Math.floor(parsedAmount * 1_000_000).toString(); -}; \ No newline at end of file diff --git a/src/services/laconicTransfer.ts b/src/services/laconicTransfer.ts index 0237f88..8d5c1af 100644 --- a/src/services/laconicTransfer.ts +++ b/src/services/laconicTransfer.ts @@ -25,7 +25,51 @@ export const transferLNTTokens = async (): Promise => { const transferConfig = getLaconicTransferConfig(); const registry = getRegistry(); - console.log('Initiating LNT transfer from prefilled account to service provider...'); + console.log('Resolving deployer LRN to get payment address...'); + + // Resolve the deployer LRN to get the payment address + const deployerLrn = process.env.DEPLOYER_LRN; + if (!deployerLrn) { + return { + success: false, + error: 'DEPLOYER_LRN environment variable is required' + }; + } + + const resolveResult = await registry.resolveNames([deployerLrn]); + console.log('Resolve result:', resolveResult); + + if (!resolveResult || resolveResult.length === 0) { + return { + success: false, + error: `Failed to resolve deployer LRN: ${deployerLrn}` + }; + } + + const deployerRecord = resolveResult[0]; + if (!deployerRecord.attributes) { + return { + success: false, + error: 'Deployer record has no attributes' + }; + } + + // Find the paymentAddress attribute + const paymentAddressAttr = deployerRecord.attributes.find( + (attr: any) => attr.key === 'paymentAddress' + ); + + if (!paymentAddressAttr || !paymentAddressAttr.value?.string) { + return { + success: false, + error: 'paymentAddress attribute not found in deployer record' + }; + } + + const paymentAddress = paymentAddressAttr.value.string; + console.log('Found payment address:', paymentAddress); + + console.log('Initiating LNT transfer from prefilled account to payment address...'); // Create fee for transaction const fee = { @@ -33,10 +77,10 @@ export const transferLNTTokens = async (): Promise => { gas: registryConfig.fee.gas || '900000', }; - // Send tokens from prefilled account to service provider + // Send tokens from prefilled account to payment address const transferResult = await registry.sendCoins( { - destinationAddress: transferConfig.serviceProviderAddress, + destinationAddress: paymentAddress, amount: transferConfig.transferAmount, denom: 'alnt' }, diff --git a/src/services/registry.ts b/src/services/registry.ts index f6bb80d..2ce2bb8 100644 --- a/src/services/registry.ts +++ b/src/services/registry.ts @@ -1,13 +1,11 @@ -import axios from 'axios'; -import { CreateRecordResponse, PaymentType } from '../types'; +import { CreateRecordResponse } from '../types'; export const createApplicationDeploymentRequest = async ( url: string, - txHash: string, - paymentType: PaymentType = 'ATOM' + txHash: string ): Promise => { try { - console.log(`Creating deployment request for URL: ${url} with transaction: ${txHash} using ${paymentType} payment`); + console.log(`Creating deployment request for URL: ${url} with transaction: ${txHash} using GOR payment`); // Call our serverless API endpoint to handle the registry interaction const response = await fetch('/api/registry', { @@ -15,7 +13,7 @@ export const createApplicationDeploymentRequest = async ( headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ url, txHash, paymentType }), + body: JSON.stringify({ url, txHash }), }); const result = await response.json(); @@ -51,63 +49,3 @@ export const createApplicationDeploymentRequest = async ( } }; -export const verifyTransaction = async (txHash: string): Promise => { - try { - // Use the public Cosmos API URL for verification queries - const apiEndpoint = process.env.NEXT_PUBLIC_COSMOS_API_URL; - if (!apiEndpoint) { - console.error('NEXT_PUBLIC_COSMOS_API_URL environment variable not set'); - return false; - } - - // Use Axios to directly query the Cosmos transaction via REST API - const response = await axios.get(`${apiEndpoint}/cosmos/tx/v1beta1/txs/${txHash}`); - - // Check if transaction exists and was successful - // The Cosmos API returns a tx_response object with a code field - 0 means success - if (response.data && - response.data.tx_response && - response.data.tx_response.code === 0) { - return true; - } - - // Also check for successful transactions with code === undefined (some nodes report it this way) - if (response.data && - response.data.tx_response && - response.data.tx_response.code === undefined && - response.data.tx_response.height) { - return true; - } - - // Also fallback to checking if the transaction has a height (was included in a block) - if (response.data && - response.data.tx_response && - response.data.tx_response.height && - !response.data.tx_response.code) { - return true; - } - - return false; - } catch (error) { - console.error('Failed to verify transaction:', error); - - // If the API call fails, try checking a public explorer API as fallback - try { - // Try a different URL format that some RPC nodes might use - const rpcEndpoint = process.env.NEXT_PUBLIC_COSMOS_RPC_URL; - const fallbackResponse = await axios.get(`${rpcEndpoint}/tx?hash=0x${txHash}`); - - if (fallbackResponse.data && - fallbackResponse.data.result && - (fallbackResponse.data.result.height || - (fallbackResponse.data.result.tx_result && - fallbackResponse.data.result.tx_result.code === 0))) { - return true; - } - } catch (fallbackError) { - console.error('Fallback verification also failed:', fallbackError); - } - - return false; - } -}; \ No newline at end of file diff --git a/src/services/solana.ts b/src/services/solana.ts index c77dea8..1a86653 100644 --- a/src/services/solana.ts +++ b/src/services/solana.ts @@ -3,10 +3,8 @@ import { TOKEN_PROGRAM_ID, createTransferInstruction, createAssociatedTokenAccountInstruction, - ASSOCIATED_TOKEN_PROGRAM_ID, getAssociatedTokenAddress } from '@solana/spl-token'; -import BN from 'bn.js'; import { getSolanaConfig } from '../config'; import { SolanaPaymentResult, SolanaWalletType, SolanaWalletState } from '../types'; @@ -79,7 +77,7 @@ export const disconnectSolanaWallet = async (walletType: SolanaWalletType): Prom } }; -export const sendGorPayment = async ( +export const sendSolanaTokenPayment = async ( walletPublicKey: string, walletType: SolanaWalletType ): Promise => { @@ -102,14 +100,15 @@ export const sendGorPayment = async ( const connection = getConnection(); const senderPublicKey = new PublicKey(walletPublicKey); - const mintPublicKey = new PublicKey(config.gorMintAddress); - const receiverPublicKey = new PublicKey(config.gorRecipientAddress); + const mintPublicKey = new PublicKey(config.tokenMintAddress); + const receiverPublicKey = new PublicKey(config.tokenRecipientAddress); - console.log('Processing GOR payment with keys:', { + console.log(`Processing ${config.tokenSymbol} payment with keys:`, { sender: senderPublicKey.toBase58(), mint: mintPublicKey.toBase58(), receiver: receiverPublicKey.toBase58(), - amount: config.paymentAmount + amount: config.paymentAmount, + token: config.tokenSymbol }); // Get associated token addresses @@ -180,7 +179,7 @@ export const sendGorPayment = async ( transaction.recentBlockhash = latestBlockhash.blockhash; transaction.feePayer = senderPublicKey; - console.log('Sending GOR payment transaction...'); + console.log(`Sending ${config.tokenSymbol} payment transaction...`); const { signature } = await wallet.signAndSendTransaction(transaction); console.log('Transaction sent:', signature); @@ -201,7 +200,7 @@ export const sendGorPayment = async ( transactionSignature: signature }; } catch (error) { - console.error('GOR payment error:', error); + console.error('Solana token payment error:', error); return { success: false, error: error instanceof Error ? error.message : 'Payment failed' diff --git a/src/types/index.ts b/src/types/index.ts index 4e9c089..f2287d0 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,8 +1,6 @@ -import { Window as KeplrWindow } from "@keplr-wallet/types"; - -// extend the global Window interface to include Keplr and Solana wallets +// extend the global Window interface to include Solana wallets declare global { - interface Window extends KeplrWindow { + interface Window { phantom?: { solana?: { signAndSendTransaction(transaction: any): Promise<{ signature: string }>; @@ -34,27 +32,6 @@ export interface RegistryConfig { }; } -export interface TransactionResponse { - hash: string; - status: 'success' | 'error'; - message?: string; -} - -export interface LaconicRecordData { - type: string; - version: string; - name: string; - application: string; - deployer: string; - dns: string; - meta: { - note: string; - repository: string; - repository_ref: string; - }; - payment: string; -} - export interface CreateRecordResponse { id: string; applicationRecordId?: string; @@ -68,9 +45,6 @@ export interface CreateRecordResponse { message?: string; } -// Payment types -export type PaymentType = 'ATOM' | 'GOR'; - export type SolanaWalletType = 'phantom' | 'solflare'; export interface SolanaPaymentResult { @@ -83,7 +57,6 @@ export interface PaymentModalProps { isOpen: boolean; onClose: () => void; url: string; - paymentType: PaymentType; onPaymentComplete: (txHash: string) => void; } diff --git a/src/utils/solanaVerify.ts b/src/utils/solanaVerify.ts index 8118612..fc20581 100644 --- a/src/utils/solanaVerify.ts +++ b/src/utils/solanaVerify.ts @@ -115,9 +115,8 @@ export const verifySolanaPayment = async ( if (parsed.type === 'transferChecked' || parsed.type === 'transfer') { const destination = parsed.info.destination; - // We need to verify this transfer was to our expected recipient - // For now, we'll check if the amount matches and trust the verification - if (parsed.info.amount === amount) { + // Verify both amount and destination address + if (parsed.info.amount === amount && destination === config.tokenRecipientAddress) { foundValidTransfer = true; break; } @@ -128,7 +127,7 @@ export const verifySolanaPayment = async ( if (!foundValidTransfer) { return { valid: false, - reason: 'Valid GOR transfer not found in transaction' + reason: 'Valid Solana token transfer not found in transaction' }; }