Nel mondo odierno, le ricevute sono essenziali per convalidare le transazioni e conservare la prova degli acquisti. Che si tratti di una grande banca o di un piccolo negozio di alimentari, le ricevute aiutano le aziende e gli individui a rimanere organizzati e a tenere traccia delle proprie spese. Ma ecco il punto: la maggior parte non fornisce ricevute e si affida agli explorer per verificare i dettagli delle transazioni. E se non dovessi affidarti a questo? Immagina quanto sarebbe più comodo per i tuoi utenti generare ricevute direttamente, senza dover controllare i loro wallet. delle dApp Se stai creando una dApp basata sui pagamenti su Rootstock, questo articolo ti mostrerà come creare un generatore di ricevute semplice ma efficace utilizzando la Rootstock API e un solo metodo RPC (Remote Procedure Call). Questo approccio semplifica il processo e garantisce che i tuoi record di transazione siano accurati e facili da accedere. Scopriamo i passaggi e gli strumenti necessari per creare un'esperienza di generazione di ricevute fluida. Prerequisiti Devi avere il nodo installato sul tuo dispositivo conoscenza di Javascript Framework Js installato di tua scelta Editor di codice, ad esempio VScode Per lo styling userò React Typescript e TailwindCSS Strumenti e tecnologie Chiave API del portainnesto : per interagire con RPC Web3js : per generare un codice QR che gli utenti possono scansionare e ottenere la ricevuta QRCode React : per generare la ricevuta in PDF Jspdf Installa, importa i pacchetti e crea il componente funzionale Installa tutte le dipendenze usando questo comando: npm i web3js jspdf qrcode.react Crea un nuovo file o usa App.jsx Importare i pacchetti nel file con quanto segue: import { useState } from "react"; import Web3 from "web3"; import { jsPDF } from "jspdf"; import { QRCodeSVG } from "qrcode.react"; Inizializzare il componente funzionale const TransactionReceipt = () => { /......./ } export default TransactionReceipt; Gestione dello Stato e integrazione Web3 Il frammento di codice qui presente gestirà lo stato e la funzionalità per il recupero e la visualizzazione dei dettagli delle transazioni utilizzando l'hook useState, Web3js, Rootstock RPC e API. const [transactionId, setTransactionId] = useState(""); interface TransactionDetails { transactionHash: string; from: string; to: string; cumulativeGasUsed: number; blockNumber: number; contractAddress?: string; } const [transactionDetails, setTransactionDetails] = useState<TransactionDetails | null>(null); const [error, setError] = useState(""); const web3 = new Web3( `https://rpc.testnet.rootstock.io/${import.meta.env.VITE_API_KEY}` ); : Gestione dello Stato : Questa riga inizializza una variabile di stato . Questa variabile di stato sarà responsabile dell'hash della transazione immesso che altre variabili e funzioni sfrutteranno per generare la ricevuta. const [transactionId, setTransactionId] = useState(""); transactionId : Questa riga inizializza una variabile di stato con un valore e fornisce una funzione per aggiornarne il valore. Lo stato può contenere un oggetto o . const [transactionDetails, setTransactionDetails] = useState<TransactionDetails | null>(null); transactionDetails null setTransactionDetails TransactionDetails null : Questa riga inizializza una variabile di stato con una stringa vuota e fornisce una funzione per aggiornarne il valore. const [error, setError] = useState(""); error setError : Interfaccia TypeScript : definisce un'interfaccia TypeScript per la struttura dell'oggetto transaction details. Include proprietà come , , , , e un facoltativo. interface TransactionDetails transactionHash from to cumulativeGasUsed blockNumber contractAddress : Inizializzazione Web3 : Questa riga inizializza Web3js, connettendosi all'endpoint RPC alla testnet Rootstock. L'URL dell'endpoint include una chiave API che viene recuperata dalle variabili di ambiente tramite . const web3 = new Web3(https://rpc.testnet.rootstock.io/${import.meta.env.VITE_API_KEY}); import.meta.env.VITE_API_KEY Funzione per recuperare i dettagli della transazione Il codice qui recupererà i dettagli della transazione utilizzando una funzione asincrona da Rootstock con il metodo web3.js. const fetchTransactionDetails = async () => { try { setError(""); setTransactionDetails(null); const receipt = await web3.eth.getTransactionReceipt(transactionId); if (!receipt) { throw new Error("Transaction not found!"); } setTransactionDetails(receipt); } catch (err) { if (err instanceof Error) { setError(err.message); } else { setError("An unknown error occurred"); } } }; : Impostazione della gestione degli errori : Questa struttura viene utilizzata per gestire eventuali errori che potrebbero verificarsi durante l'esecuzione della funzione. try { ... } catch (err) { ... } : Ripristina stato setError("");: cancella tutti i messaggi di errore precedenti impostando lo stato su una stringa vuota. error setTransactionDetails(null);: cancella tutti i dettagli delle transazioni precedenti impostando lo stato su . transactionDetails null : Ottieni la ricevuta della transazione ;: Questa riga utilizza il metodo web3js per recuperare la ricevuta della transazione per il transactionId immesso. const receipt = await web3.eth.getTransactionReceipt(transactionId) : Controllare la ricevuta : Se la ricevuta non viene trovata (ad esempio, la ricevuta è o ), viene generato un errore con il messaggio "Transazione non trovata!". if (!receipt) { throw new Error("Transaction not found!"); } null undefined : Imposta i dettagli della transazione : se la ricevuta viene trovata, aggiorna lo stato con la ricevuta recuperata. setTransactionDetails(receipt) transactionDetails : Gestione degli errori : Questo blocco cattura tutti gli errori che si verificano durante l'esecuzione del blocco . catch (err) { ... } try : Se l'errore rilevato è un'istanza della classe Error, imposta lo stato sul messaggio di errore. Altrimenti, imposta lo stato su un messaggio di errore generico "Si è verificato un errore sconosciuto". if (err instanceof Error) { setError(err.message); } else { setError("An unknown error occurred"); } error error Funzioni per generare la ricevuta PDF Qui verrà utilizzato il pacchetto Jspdf per generare il PDF contenente i dettagli della transazione. const generatePDF = () => { if (!transactionDetails) return; const { transactionHash, from, to, cumulativeGasUsed, blockNumber, contractAddress, } = transactionDetails; const pdf = new jsPDF(); pdf.setFontSize(16); pdf.text("Transaction Receipt", 10, 10); pdf.setFontSize(12); pdf.text(`Transaction Hash: ${transactionHash}`, 10, 20); pdf.text(`From: ${from}`, 10, 30); pdf.text(`Contract Address: ${contractAddress}`, 10, 40); pdf.text(`To: ${to}`, 10, 40); pdf.text(`Cumulative Gas Used: ${cumulativeGasUsed}`, 10, 50); pdf.text(`Block Number: ${blockNumber}`, 10, 60); pdf.save("Transaction_Receipt.pdf"); }; : Controlla i dettagli della transazione : Questo controlla se è o . In caso affermativo, la funzione restituisce in anticipo e non fa nulla. if (!transactionDetails) return; transactionDetails null undefined : Dettagli della transazione di destrutturazione : Questo destruttura l'oggetto per estrarre singole proprietà per un accesso più semplice. const { transactionHash, from, to, cumulativeGasUsed, blockNumber, contractAddress } = transactionDetails; transactionDetails : Crea documento PDF : Crea una nuova istanza della classe jsPDF, che rappresenta un documento PDF. const pdf = new jsPDF() : Imposta la dimensione del carattere e aggiungi il titolo : imposta la dimensione del carattere dell'intestazione a 16. pdf.setFontSize(16) : Aggiunge il titolo "Ricevuta di transazione" alle coordinate (10, 10) nel documento PDF. pdf.text("Transaction Receipt", 10, 10); : Aggiungi i dettagli della transazione al PDF : Imposta la dimensione del carattere a 12 per il resto del testo. pdf.setFontSize(12); : ${transactionHash} : Aggiunge l'hash della transazione alle coordinate (10, 20). pdf.text(Transaction Hash , 10, 20); : Aggiunge l'indirizzo del mittente alle coordinate (10, 30). pdf.text(From: ${from}, 10, 30); : Aggiunge l'indirizzo del contratto alle coordinate (10, 40). Nota: questa riga dovrebbe essere corretta per evitare sovrapposizioni di testo. pdf.text(Contract Address: ${contractAddress}, 10, 40); : Aggiunge l'indirizzo del destinatario alle coordinate (10, 50). pdf.text(To: ${to}, 10, 50); pdf.text(Gas cumulativo utilizzato: ${cumulativeGasUsed} : Aggiunge il gas cumulativo utilizzato alle coordinate (10, 60). , 10, 60); : Aggiunge il numero del blocco alle coordinate (10, 70). pdf.text(Block Number: ${blockNumber}, 10, 70); : Salva documento PDF pdf.save("Transaction_Receipt.pdf");: Questo salverà il documento PDF con il nome file "Transaction_Receipt.pdf". L'interfaccia utente Qui renderizzerai quei componenti funzionali come interfaccia utente per gli utenti. Questo codice ha già incluso lo stile utilizzando Tailwindcss return ( <div className="p-8 font-sans bg-gray-100 min-h-screen"> <div className="max-w-3xl m-auto bg-white p-6 rounded-lg shadow-lg"> <h1 className="text-3xl font-bold mb-6 text-center text-blue-600"> Transaction Receipt Generator </h1> <div className="mb-6"> <div className="flex"> <input type="text" id="transactionId" value={transactionId} onChange={(e) => setTransactionId(e.target.value)} placeholder="Enter transaction hash" className="border p-2 w-full rounded-l-lg" /> <button onClick={fetchTransactionDetails} className="p-2 bg-blue-500 text-white rounded-r-lg" > Fetch Details </button> </div> </div> {error && ( <p className="text-red-500 mt-4 text-center">Error: {error}</p> )} {transactionDetails && ( <div className="mt-6 flex flex-row gap-8"> <div className="w-2/3"> <h2 className="text-2xl font-semibold mb-4 text-center"> Transaction Details </h2> <div className="bg-gray-50 p-4 rounded-lg shadow-inner w-[460px]"> <p> <strong>Transaction Hash:</strong>{" "} {`${transactionDetails.transactionHash.slice( 0, 6 )}...${transactionDetails.transactionHash.slice(-6)}`} </p> <p> <strong>From:</strong> {transactionDetails.from} </p> <p> <strong>Contract Address:</strong>{" "} {transactionDetails.contractAddress} </p> <p> <strong>To:</strong> {transactionDetails.to} </p> <p> <strong>Cumulative Gas Used:</strong>{" "} {transactionDetails.cumulativeGasUsed.toString()} </p> <p> <strong>Block Number:</strong>{" "} {transactionDetails.blockNumber.toString()} </p> </div> <button onClick={generatePDF} className="mt-6 w-full p-3 bg-green-500 text-white rounded-lg" > Download PDF Receipt </button> </div> <div className="w-1/2 text-center"> <h3 className="text-xl font-semibold mb-4">QR Code</h3> <QRCodeSVG value={`Transaction Hash: ${ transactionDetails.transactionHash }, From: ${transactionDetails.from}, To: ${transactionDetails.to}, Contract Address: ${transactionDetails.contractAddress}, Cumulative Gas Used: ${transactionDetails.cumulativeGasUsed.toString()}, Block Number: ${transactionDetails.blockNumber.toString()}`} size={200} className="mx-auto" /> </div> </div> )} </div> </div> Per il generatore di codici QR è stata utilizzata la libreria qrcode.react e i dettagli della transazione sono stati crittografati al suo interno nel codice QR SVG. Codice di base finale e output Se segui i passaggi, la tua base di codice dovrebbe apparire così: import { useState } from "react"; import Web3 from "web3"; import { jsPDF } from "jspdf"; import { QRCodeSVG } from "qrcode.react"; const TransactionReceipt = () => { const [transactionId, setTransactionId] = useState(""); interface TransactionDetails { transactionHash: string; from: string; to: string; cumulativeGasUsed: number; blockNumber: number; contractAddress?: string; } const [transactionDetails, setTransactionDetails] = useState<TransactionDetails | null>(null); const [error, setError] = useState(""); const web3 = new Web3( `https://rpc.testnet.rootstock.io/${import.meta.env.VITE_API_KEY}` ); const fetchTransactionDetails = async () => { try { setError(""); setTransactionDetails(null); const receipt = await web3.eth.getTransactionReceipt(transactionId); if (!receipt) { throw new Error("Transaction not found!"); } setTransactionDetails(receipt); } catch (err) { if (err instanceof Error) { setError(err.message); } else { setError("An unknown error occurred"); } } }; const generatePDF = () => { if (!transactionDetails) return; const { transactionHash, from, to, cumulativeGasUsed, blockNumber, contractAddress, } = transactionDetails; const pdf = new jsPDF(); pdf.setFontSize(16); pdf.text("Transaction Receipt", 10, 10); pdf.setFontSize(12); pdf.text(`Transaction Hash: ${transactionHash}`, 10, 20); pdf.text(`From: ${from}`, 10, 30); pdf.text(`Contract Address: ${contractAddress}`, 10, 40); pdf.text(`To: ${to}`, 10, 40); pdf.text(`Cumulative Gas Used: ${cumulativeGasUsed}`, 10, 50); pdf.text(`Block Number: ${blockNumber}`, 10, 60); pdf.save("Transaction_Receipt.pdf"); }; return ( <div className="p-8 font-sans bg-gray-100 min-h-screen"> <div className="max-w-3xl m-auto bg-white p-6 rounded-lg shadow-lg"> <h1 className="text-3xl font-bold mb-6 text-center text-blue-600"> Transaction Receipt Generator </h1> <div className="mb-6"> <div className="flex"> <input type="text" id="transactionId" value={transactionId} onChange={(e) => setTransactionId(e.target.value)} placeholder="Enter transaction hash" className="border p-2 w-full rounded-l-lg" /> <button onClick={fetchTransactionDetails} className="p-2 bg-blue-500 text-white rounded-r-lg" > Fetch Details </button> </div> </div> {error && ( <p className="text-red-500 mt-4 text-center">Error: {error}</p> )} {transactionDetails && ( <div className="mt-6 flex flex-row gap-8"> <div className="w-2/3"> <h2 className="text-2xl font-semibold mb-4 text-center"> Transaction Details </h2> <div className="bg-gray-50 p-4 rounded-lg shadow-inner w-[460px]"> <p> <strong>Transaction Hash:</strong>{" "} {`${transactionDetails.transactionHash.slice( 0, 6 )}...${transactionDetails.transactionHash.slice(-6)}`} </p> <p> <strong>From:</strong> {transactionDetails.from} </p> <p> <strong>Contract Address:</strong>{" "} {transactionDetails.contractAddress} </p> <p> <strong>To:</strong> {transactionDetails.to} </p> <p> <strong>Cumulative Gas Used:</strong>{" "} {transactionDetails.cumulativeGasUsed.toString()} </p> <p> <strong>Block Number:</strong>{" "} {transactionDetails.blockNumber.toString()} </p> </div> <button onClick={generatePDF} className="mt-6 w-full p-3 bg-green-500 text-white rounded-lg" > Download PDF Receipt </button> </div> <div className="w-1/2 text-center"> <h3 className="text-xl font-semibold mb-4">QR Code</h3> <QRCodeSVG value={`Transaction Hash: ${ transactionDetails.transactionHash }, From: ${transactionDetails.from}, To: ${transactionDetails.to}, Contract Address: ${transactionDetails.contractAddress}, Cumulative Gas Used: ${transactionDetails.cumulativeGasUsed.toString()}, Block Number: ${transactionDetails.blockNumber.toString()}`} size={200} className="mx-auto" /> </div> </div> )} </div> </div> ); }; export default TransactionReceipt; Quindi, importa e visualizzalo nel tuo file TransactionReceipt App.tsx Dimostrazione https://youtu.be/Xwkl9pu8UiM?embedable=true Conclusione In questo articolo, sei riuscito a creare un generatore di ricevute in un PDF o codice QR usando la chiave API Rootstock e il metodo RPC. Quindi, nel tuo prossimo progetto dApp, spero di vedere questa funzionalità.