From 33bead5ed5fb04cad7efe7ff9cbf7a50c916b87e Mon Sep 17 00:00:00 2001 From: i-macharia Date: Wed, 7 Jan 2026 16:26:57 +0300 Subject: [PATCH] updated gitignore with lighthouse results, updated verify to use public rpc to not require wallet to verify, dashboard scan failed, to refactor logic --- frontend/avacertify-v2/.gitignore | 3 + frontend/avacertify-v2/app/dashboard/page.tsx | 45 ++++++++- frontend/avacertify-v2/app/verify/page.tsx | 8 +- frontend/avacertify-v2/utils/blockchain.ts | 95 ++++++++++++++----- 4 files changed, 120 insertions(+), 31 deletions(-) diff --git a/frontend/avacertify-v2/.gitignore b/frontend/avacertify-v2/.gitignore index fd3dbb5..1843fa1 100644 --- a/frontend/avacertify-v2/.gitignore +++ b/frontend/avacertify-v2/.gitignore @@ -34,3 +34,6 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +# files +/.dashboard_lighthouse_report.pdf \ No newline at end of file diff --git a/frontend/avacertify-v2/app/dashboard/page.tsx b/frontend/avacertify-v2/app/dashboard/page.tsx index 16598a5..b300d0b 100644 --- a/frontend/avacertify-v2/app/dashboard/page.tsx +++ b/frontend/avacertify-v2/app/dashboard/page.tsx @@ -74,10 +74,19 @@ export default function Dashboard() { const allCertificates: Certificate[] = []; const BLOCK_RANGE = 2000; // Stay under 2048 limit - const MAX_BLOCKS_TO_SCAN = 1000000000; // Scan up to 1000 million blocks back - + const MAX_BLOCKS_TO_SCAN = 50000000; // Scan up to 50 million blocks back (deployed block for first contract) + const currentBlock = await provider.getBlockNumber(); - const startBlock = Math.max(0, currentBlock - MAX_BLOCKS_TO_SCAN); + + // Load from cache + const lastScannedBlock = parseInt(localStorage.getItem("lastScannedBlock") || "0"); + const cachedCertificates = JSON.parse(localStorage.getItem("cachedCertificates") || "[]") as Certificate[]; + + allCertificates.push(...cachedCertificates); + + const startBlock = lastScannedBlock > 0 + ? lastScannedBlock + 1 + : Math.max(0, currentBlock - MAX_BLOCKS_TO_SCAN); console.log(`Scanning from block ${startBlock} to ${currentBlock}`); setLoadingProgress(`Scanning blocks ${startBlock} to ${currentBlock}...`); @@ -170,11 +179,18 @@ export default function Dashboard() { console.error("Failed to query NFT certificate events:", error); } + // Merge with cached certificates + const allCertificatesWithCache = [...cachedCertificates, ...allCertificates]; + // Remove duplicates const uniqueCertificates = Array.from( - new Map(allCertificates.map(cert => [cert.id, cert])).values() + new Map(allCertificatesWithCache.map(cert => [cert.id, cert])).values() ); + // Save to cache + localStorage.setItem("lastScannedBlock", currentBlock.toString()); + localStorage.setItem("cachedCertificates", JSON.stringify(uniqueCertificates)); + // Sort by issue date (newest first) uniqueCertificates.sort((a, b) => { const dateA = new Date(a.issueDate).getTime(); @@ -211,6 +227,8 @@ export default function Dashboard() { } }, [toast, checkNetwork]); + + /** * Filters certificates based on search query */ @@ -266,6 +284,17 @@ export default function Dashboard() { await fetchCertificatesFromBlockchain(); }; + // 3. Add Clear Cache handler: + + const handleClearCache = () => { + localStorage.removeItem("lastScannedBlock"); + localStorage.removeItem("cachedCertificates"); + toast({ + title: "Cache Cleared", + description: "Next refresh will perform a full rescan", + }); + }; + /** * Copies text to clipboard with user feedback */ @@ -325,6 +354,14 @@ export default function Dashboard() { )} + {isLoading && ( diff --git a/frontend/avacertify-v2/app/verify/page.tsx b/frontend/avacertify-v2/app/verify/page.tsx index d72b5ba..96e76e7 100644 --- a/frontend/avacertify-v2/app/verify/page.tsx +++ b/frontend/avacertify-v2/app/verify/page.tsx @@ -63,11 +63,11 @@ export default function Verify() { setVerificationStatus("loading"); - try { + try { await certificateService.init(); - - const isValid = await certificateService.verifyCertificate(id, isNFT); - const cert = await certificateService.getCertificate(id, isNFT); + + const isValid = await certificateService.verifyCertificateReadOnly(id, isNFT); + const cert = await certificateService.getCertificateReadOnly(id, isNFT); if (cert && isValid) { setCertificate({ ...cert, isNFT }); diff --git a/frontend/avacertify-v2/utils/blockchain.ts b/frontend/avacertify-v2/utils/blockchain.ts index 61b7687..faae926 100644 --- a/frontend/avacertify-v2/utils/blockchain.ts +++ b/frontend/avacertify-v2/utils/blockchain.ts @@ -56,6 +56,10 @@ declare global { export class CertificateService { private provider: ethers.BrowserProvider | null = null; + /** + * Public JSON-RPC provider for read-only operations (no wallet required) + */ + private publicProvider: ethers.JsonRpcProvider | null = null; private contract: (ethers.Contract & ContractMethods) | null = null; private nftContract: (ethers.Contract & NFTContractMethods) | null = null; private signer: ethers.Signer | null = null; @@ -69,21 +73,37 @@ export class CertificateService { return; } + // Set up a public read-only provider + try { + // Use the RPC from your network config, or hardcode if needed + const rpcUrl = + (Array.isArray(AVALANCHE_FUJI_CONFIG.rpcUrls) && + AVALANCHE_FUJI_CONFIG.rpcUrls[0]) || + (AVALANCHE_FUJI_CONFIG as any).rpcUrls?.[0] || + "https://api.avax-test.network/ext/bc/C/rpc"; + + this.publicProvider = new ethers.JsonRpcProvider(rpcUrl); + console.log("✅ Public RPC provider initialized"); + } catch (error) { + console.error("❌ Failed to initialize public RPC provider:", error); + } + + // Browser provider (wallet) is optional for read-only flows if (typeof window === "undefined" || !window.ethereum) { - console.warn("No Web3 provider found"); + console.warn("No Web3 wallet provider found (MetaMask/Core). Read-only RPC still available."); + this.isInitialized = true; return; } try { this.provider = new ethers.BrowserProvider(window.ethereum); this.isInitialized = true; - console.log("✅ Provider initialized"); + console.log("✅ Wallet provider initialized"); } catch (error) { - console.error("❌ Failed to initialize provider:", error); + console.error("❌ Failed to initialize wallet provider:", error); throw new Error("Failed to initialize wallet connection"); } } - private async validateConnection(): Promise { if (!this.isInitialized) { throw new Error("Service not initialized. Call init() first."); @@ -605,34 +625,32 @@ export class CertificateService { /** * Get a read-only instance of the certificate contract. - * Does not require a wallet connection, useful for querying events. + * Uses public RPC, does not require a wallet. */ async getReadOnlyContract(): Promise { - if (!this.provider) { - throw new Error("Provider not initialized. Call init() first."); + if (!this.publicProvider) { + throw new Error("Public provider not initialized. Call init() first."); } - // Create a read-only contract instance using the provider return new ethers.Contract( - CERTIFICATE_SYSTEM_ADDRESS, - CERTIFICATE_SYSTEM_ABI, - this.provider - ) as ethers.Contract & ContractMethods; + CERTIFICATE_SYSTEM_ADDRESS, + CERTIFICATE_SYSTEM_ABI, + this.publicProvider + ) as ethers.Contract & ContractMethods; } /** * Get a read-only instance of the NFT certificate contract. - * Does not require a wallet connection, useful for querying events. + * Uses public RPC, does not require a wallet. */ async getReadOnlyNFTContract(): Promise { - if (!this.provider) { - throw new Error("Provider not initialized. Call init() first."); + if (!this.publicProvider) { + throw new Error("Public provider not initialized. Call init() first."); } - // Create a read-only contract instance using the provider return new ethers.Contract( - NFT_CERTIFICATE_ADDRESS, - NFT_CERTIFICATE_ABI, - this.provider - ) as ethers.Contract & NFTContractMethods; + NFT_CERTIFICATE_ADDRESS, + NFT_CERTIFICATE_ABI, + this.publicProvider + ) as ethers.Contract & NFTContractMethods; } /** @@ -640,19 +658,20 @@ export class CertificateService { * Used for public certificate viewing. */ async getCertificateReadOnly(certificateId: string, isNFT: boolean = false): Promise { - if (!this.provider) { - throw new Error("Provider not initialized. Call init() first."); + if (!this.publicProvider) { + throw new Error("Public provider not initialized. Call init() first."); } if (!certificateId?.trim()) { throw new Error("Certificate ID is required"); } + try { if (isNFT) { const nftContract = await this.getReadOnlyNFTContract(); const owner = await nftContract.ownerOf(certificateId); - if (owner === ethers.ZeroAddress) { + if (!owner || owner === ethers.ZeroAddress) { return null; } const tokenURI = await nftContract.tokenURI(certificateId); @@ -666,6 +685,9 @@ export class CertificateService { issueDate: new Date().toISOString(), institutionName: "AvaCertify", status: "active", + transactionHash: undefined, + documentHash: undefined, + documentUrl: tokenURI, isNFT: true, }; } else { @@ -692,6 +714,33 @@ export class CertificateService { throw new Error("Failed to retrieve certificate"); } } + + /** + * Verify certificate without requiring wallet connection (read-only). + */ + async verifyCertificateReadOnly(certificateId: string, isNFT: boolean = false): Promise { + if (!this.publicProvider) { + throw new Error("Public provider not initialized. Call init() first."); + } + if (!certificateId?.trim()) { + throw new Error("Certificate ID is required"); + } + + try { + if (isNFT) { + const nftContract = await this.getReadOnlyNFTContract(); + const owner = await nftContract.ownerOf(certificateId); + return !!owner && owner !== ethers.ZeroAddress; + } else { + const contract = await this.getReadOnlyContract(); + const isValid = await contract.verifyCertificate(certificateId); + return Boolean(isValid); + } + } catch (error: any) { + console.error("Error verifying certificate (read-only):", error); + throw new Error("Failed to verify certificate"); + } + } }