work in progress

This commit is contained in:
zramsay 2025-07-09 07:04:28 -04:00
parent d766cd8c06
commit 765fe3d49a
3 changed files with 139 additions and 22 deletions

View File

@ -6,6 +6,115 @@ import axios from 'axios';
// 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.message || 'Unknown error'}`
};
}
};
// Extract repo name from URL
const extractRepoInfo = (url: string): { repoName: string, repoUrl: string, provider: string } => {
try {
@ -138,6 +247,23 @@ export async function POST(request: NextRequest) {
}, { status: 400 });
}
// First, verify the ATOM payment before doing anything else
console.log('Step 0: Verifying ATOM payment...');
const paymentVerificationResult = await verifyAtomPayment(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
});
// Validate required environment variables
const requiredEnvVars = [
'REGISTRY_CHAIN_ID',
@ -146,7 +272,9 @@ export async function POST(request: NextRequest) {
'REGISTRY_BOND_ID',
'REGISTRY_AUTHORITY',
'REGISTRY_USER_KEY',
'DEPLOYER_LRN'
'DEPLOYER_LRN',
'NEXT_PUBLIC_RECIPIENT_ADDRESS',
'NEXT_PUBLIC_COSMOS_API_URL'
];
for (const envVar of requiredEnvVars) {

View File

@ -7,14 +7,14 @@ import URLForm from '@/components/URLForm';
// Dynamically import PaymentModal component to avoid SSR issues with browser APIs
const PaymentModal = dynamic(() => import('@/components/PaymentModal'), { ssr: false });
import StatusDisplay from '@/components/StatusDisplay';
import { verifyTransaction, createApplicationDeploymentRequest } from '@/services/registry';
import { createApplicationDeploymentRequest } from '@/services/registry';
import dynamic from 'next/dynamic';
export default function Home() {
const [walletAddress, setWalletAddress] = useState<string | null>(null);
const [url, setUrl] = useState<string | null>(null);
const [showPaymentModal, setShowPaymentModal] = useState(false);
const [status, setStatus] = useState<'idle' | 'verifying' | 'creating' | 'success' | 'error'>('idle');
const [status, setStatus] = useState<'idle' | 'creating' | 'success' | 'error'>('idle');
const [txHash, setTxHash] = useState<string | null>(null);
const [recordId, setRecordId] = useState<string | null>(null);
const [appRecordId, setAppRecordId] = useState<string | null>(null);
@ -38,20 +38,10 @@ export default function Home() {
const handlePaymentComplete = async (hash: string) => {
setTxHash(hash);
setShowPaymentModal(false);
setStatus('verifying');
setStatus('creating');
try {
// Verify the transaction
const isValid = await verifyTransaction(hash);
if (!isValid) {
console.warn('Transaction verification via API failed, but will attempt to create registry record anyway as the transaction might still be valid');
// We'll continue anyway, as the transaction might be valid but our verification failed
}
// Create the Laconic Registry record
setStatus('creating');
// Create the Laconic Registry record (payment verification is done in the API)
if (url) {
const result = await createApplicationDeploymentRequest(url, hash);
@ -119,7 +109,7 @@ export default function Home() {
</h2>
<URLForm
onSubmit={handleUrlSubmit}
disabled={!walletAddress || status === 'verifying' || status === 'creating'}
disabled={!walletAddress || status === 'creating'}
/>
</div>

View File

@ -1,7 +1,7 @@
'use client';
interface StatusDisplayProps {
status: 'idle' | 'verifying' | 'creating' | 'success' | 'error';
status: 'idle' | 'creating' | 'success' | 'error';
txHash?: string;
recordId?: string;
appRecordId?: string;
@ -31,15 +31,14 @@ export default function StatusDisplay({
const domainSuffix = process.env.NEXT_PUBLIC_DOMAIN_SUFFIX || '';
if (status === 'idle') return null;
const StatusBadge = ({ type }: { type: 'verifying' | 'creating' | 'success' | 'error' }) => {
const StatusBadge = ({ type }: { type: 'creating' | 'success' | 'error' }) => {
const getBadgeStyles = () => {
switch (type) {
case 'verifying':
case 'creating':
return {
bg: 'var(--warning-light)',
color: 'var(--warning)',
text: type === 'verifying' ? 'Verifying' : 'Creating Record'
text: 'Creating Record'
};
case 'success':
return {
@ -80,7 +79,7 @@ export default function StatusDisplay({
return (
<div>
<div className="flex items-center justify-between mb-4">
{(status === 'verifying' || status === 'creating') && (
{status === 'creating' && (
<div className="flex items-center">
<StatusBadge type={status} />
<div className="ml-3 flex items-center">
@ -89,7 +88,7 @@ export default function StatusDisplay({
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span style={{ color: 'var(--warning)' }}>
{status === 'verifying' ? 'Verifying transaction...' : 'Creating Laconic Registry record...'}
Creating Laconic Registry record...
</span>
</div>
</div>