work in progress
This commit is contained in:
parent
d766cd8c06
commit
765fe3d49a
@ -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) {
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user