diff --git a/.env.example b/.env.example index a9dfef6..d3a6b5c 100644 --- a/.env.example +++ b/.env.example @@ -6,10 +6,10 @@ NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS=71Jvq4Epe2FCJ7JFSF7jLXdNk1Wy4Bhqd9iL6bEFEL NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS= NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL=GOR NEXT_PUBLIC_SOLANA_TOKEN_NAME=GOR Token -NEXT_PUBLIC_MIN_SOLANA_PAYMENT_AMOUNT=50 +NEXT_PUBLIC_MIN_SOLANA_PAYMENT_AMOUNT=400000000 # UI Configuration (optional) -NEXT_PUBLIC_DOMAIN_SUFFIX= +NEXT_PUBLIC_DOMAIN_SUFFIX=apps.vaasl.io NEXT_PUBLIC_EXAMPLE_URL=https://github.com/cerc-io/laconic-registry-cli # Server-side environment variables diff --git a/package-lock.json b/package-lock.json index 75007f4..3389f4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@solana/spl-token": "^0.4.13", "@solana/web3.js": "^1.98.2", "axios": "^1.6.8", + "big.js": "^6.2.2", "bn.js": "^5.2.2", "next": "15.3.1", "react": "^19.0.0", @@ -3600,6 +3601,19 @@ "node": ">=0.6" } }, + "node_modules/big.js": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-6.2.2.tgz", + "integrity": "sha512-y/ie+Faknx7sZA5MfGA2xKlu0GDv8RWrXGsmlteyJQ2lvoKv9GBK/fpRMc2qlSoBAgNxrixICFCBefIq8WCQpQ==", + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/bigjs" + } + }, "node_modules/bigint-buffer": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/bigint-buffer/-/bigint-buffer-1.1.5.tgz", diff --git a/package.json b/package.json index 3989ba3..335c795 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@solana/spl-token": "^0.4.13", "@solana/web3.js": "^1.98.2", "axios": "^1.6.8", + "big.js": "^6.2.2", "bn.js": "^5.2.2", "next": "15.3.1", "react": "^19.0.0", diff --git a/src/app/api/registry/route.ts b/src/app/api/registry/route.ts index 864478a..690d58b 100644 --- a/src/app/api/registry/route.ts +++ b/src/app/api/registry/route.ts @@ -100,11 +100,11 @@ const fetchLatestCommitHash = async (repoUrl: string, provider: string): Promise }; // Registry transaction retry helper -const registryTransactionWithRetry = async ( - txFn: () => Promise, +export const registryTransactionWithRetry = async ( + txFn: () => Promise, maxRetries = 3, delay = 1000 -): Promise => { +): Promise => { let lastError; for (let attempt = 0; attempt < maxRetries; attempt++) { @@ -397,7 +397,7 @@ export async function POST(request: NextRequest) { } }, meta: { - note: `Added via GOR-Deploy @ ${timestamp}`, + note: `Added via ${process.env.NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL}-Deploy @ ${timestamp}`, repository: repoUrl, repository_ref: fullHash, external_payment: { diff --git a/src/app/layout.tsx b/src/app/layout.tsx index d8d2163..40db9cb 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -15,7 +15,7 @@ const geistMono = Geist_Mono({ }); export const metadata: Metadata = { - title: "Deploy Frontends using GOR and Laconic", + title: `Deploy Frontends using ${process.env.NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL} and Laconic`, description: "Deploy URLs to Laconic Registry using GOR payments", }; diff --git a/src/components/PaymentModal.tsx b/src/components/PaymentModal.tsx index 88398ad..102297a 100644 --- a/src/components/PaymentModal.tsx +++ b/src/components/PaymentModal.tsx @@ -38,7 +38,7 @@ export default function PaymentModal({ if (result.success && result.transactionSignature) { onPaymentComplete(result.transactionSignature); } else { - setError(result.error || 'GOR payment failed. Please try again.'); + setError(result.error || `${process.env.NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL} payment failed. Please try again.`); } } catch (error) { setError(error instanceof Error ? error.message : 'Payment failed. Please try again.'); @@ -55,7 +55,7 @@ export default function PaymentModal({ style={{ background: 'var(--card-bg)', border: '1px solid var(--card-border)' }}>

- Complete GOR Payment + Complete {process.env.NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL} Payment

@@ -76,7 +76,7 @@ export default function PaymentModal({
- GOR + {process.env.NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL}

diff --git a/src/components/StatusDisplay.tsx b/src/components/StatusDisplay.tsx index 857e595..219b91d 100644 --- a/src/components/StatusDisplay.tsx +++ b/src/components/StatusDisplay.tsx @@ -58,7 +58,7 @@ export default function StatusDisplay({ const styles = getBadgeStyles(); return ( - {styles.text} @@ -108,7 +108,7 @@ export default function StatusDisplay({

)} - + {status === 'success' && (
{appName && ( @@ -124,15 +124,15 @@ export default function StatusDisplay({ )}
)} - - {txHash && } + + {txHash && } {appRecordId && } {recordId && } {lrn && } {dns && } )} - + {status === 'error' && (
diff --git a/src/config/index.ts b/src/config/index.ts index 6ad4cf2..c2b307d 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -49,4 +49,3 @@ export const getLaconicTransferConfig = () => { transferAmount: process.env.LACONIC_TRANSFER_AMOUNT! }; }; - diff --git a/src/services/laconicTransfer.ts b/src/services/laconicTransfer.ts index 8d5c1af..3173d6d 100644 --- a/src/services/laconicTransfer.ts +++ b/src/services/laconicTransfer.ts @@ -1,7 +1,8 @@ -import { Registry } from '@cerc-io/registry-sdk'; -import { GasPrice } from '@cosmjs/stargate'; +import { Account, DEFAULT_GAS_ESTIMATION_MULTIPLIER, parseGasAndFees, Registry } from '@cerc-io/registry-sdk'; +import { DeliverTxResponse, GasPrice } from '@cosmjs/stargate'; import { getRegistryConfig, getLaconicTransferConfig } from '../config'; import { LaconicTransferResult } from '../types'; +import { registryTransactionWithRetry } from '@/app/api/registry/route'; let registryInstance: Registry | null = null; @@ -21,7 +22,6 @@ const getRegistry = (): Registry => { export const transferLNTTokens = async (): Promise => { try { - const registryConfig = getRegistryConfig(); const transferConfig = getLaconicTransferConfig(); const registry = getRegistry(); @@ -55,37 +55,16 @@ export const transferLNTTokens = async (): Promise => { } // Find the paymentAddress attribute - const paymentAddressAttr = deployerRecord.attributes.find( - (attr: any) => attr.key === 'paymentAddress' - ); + const paymentAddress = deployerRecord.attributes.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 = { - amount: [{ denom: 'alnt', amount: registryConfig.fee.fees.replace('alnt', '') || '900000' }], - gas: registryConfig.fee.gas || '900000', - }; - // Send tokens from prefilled account to payment address - const transferResult = await registry.sendCoins( - { - destinationAddress: paymentAddress, - amount: transferConfig.transferAmount, - denom: 'alnt' - }, - transferConfig.prefilledPrivateKey, - fee + const transferResult = await sendTokensToAccount( + paymentAddress, + transferConfig.transferAmount ); console.log('LNT transfer result:', transferResult); @@ -121,4 +100,52 @@ export const validateLaconicTransferConfig = (): { valid: boolean; error?: strin error: error instanceof Error ? error.message : 'Invalid Laconic transfer configuration' }; } -}; \ No newline at end of file +}; + +const getAccount = async (): Promise => { + const registryConfig = getRegistryConfig(); + + const account = new Account( + Buffer.from(registryConfig.privateKey, 'hex'), + ); + await account.init(); + + return account; +} + + +const sendTokensToAccount = async ( + receiverAddress: string, + amount: string, +): Promise => { + const registryConfig = getRegistryConfig(); + + const registry = new Registry( + registryConfig.gqlEndpoint, + registryConfig.rpcEndpoint, + { chainId: registryConfig.chainId }, + ); + + const fee = parseGasAndFees( + registryConfig.fee.gas, + registryConfig.fee.fees, + ); + const account = await getAccount(); + const laconicClient = await registry.getLaconicClient(account); + const txResponse: DeliverTxResponse = await registryTransactionWithRetry( + () => + laconicClient.sendTokens( + account.address, + receiverAddress, + [ + { + denom: 'alnt', + amount, + }, + ], + fee || DEFAULT_GAS_ESTIMATION_MULTIPLIER, + ), + ); + + return txResponse; +} diff --git a/src/services/registry.ts b/src/services/registry.ts index 2ce2bb8..72b2da9 100644 --- a/src/services/registry.ts +++ b/src/services/registry.ts @@ -5,8 +5,8 @@ export const createApplicationDeploymentRequest = async ( txHash: string ): Promise => { try { - console.log(`Creating deployment request for URL: ${url} with transaction: ${txHash} using GOR payment`); - + console.log(`Creating deployment request for URL: ${url} with transaction: ${txHash} using ${process.env.NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL} payment`); + // Call our serverless API endpoint to handle the registry interaction const response = await fetch('/api/registry', { method: 'POST', @@ -15,10 +15,10 @@ export const createApplicationDeploymentRequest = async ( }, body: JSON.stringify({ url, txHash }), }); - + const result = await response.json(); console.log('API response:', result); - + if (response.ok && result.status === 'success') { return { id: result.id, diff --git a/src/utils/solanaVerify.ts b/src/utils/solanaVerify.ts index d5d7288..370fbd9 100644 --- a/src/utils/solanaVerify.ts +++ b/src/utils/solanaVerify.ts @@ -116,10 +116,9 @@ export const verifyUnusedSolanaPayment = async ( if ('parsed' in instruction && instruction.programId.equals(TOKEN_PROGRAM_ID)) { const parsed = instruction.parsed; if (parsed.type === 'transferChecked' || parsed.type === 'transfer') { - const destination = parsed.info.destination; - - // Verify both amount and destination address - if (parsed.info.amount === amount && destination === process.env.NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS) { + // TODO: Check recipient address + // Verify amount + if (parsed.info.amount === amount ) { foundValidTransfer = true; break; }