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 delle dApp 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.
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.
Per lo styling userò React Typescript e TailwindCSS
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;
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 :
const [transactionId, setTransactionId] = useState("");
: Questa riga inizializza una variabile di stato transactionId
. Questa variabile di stato sarà responsabile dell'hash della transazione immesso che altre variabili e funzioni sfrutteranno per generare la ricevuta.const [transactionDetails, setTransactionDetails] = useState<TransactionDetails | null>(null);
: Questa riga inizializza una variabile di stato transactionDetails
con un valore null
e fornisce una funzione setTransactionDetails
per aggiornarne il valore. Lo stato può contenere un oggetto TransactionDetails
o null
.const [error, setError] = useState("");
: Questa riga inizializza una variabile di stato error
con una stringa vuota e fornisce una funzione setError
per aggiornarne il valore.Interfaccia TypeScript :
interface TransactionDetails
: definisce un'interfaccia TypeScript per la struttura dell'oggetto transaction details. Include proprietà come transactionHash
, from
, to
, cumulativeGasUsed
, blockNumber
e un contractAddress
facoltativo.Inizializzazione Web3 :
const web3 = new Web3(https://rpc.testnet.rootstock.io/${import.meta.env.VITE_API_KEY});
: 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 import.meta.env.VITE_API_KEY
.
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"); } } };
try { ... } catch (err) { ... }
: Questa struttura viene utilizzata per gestire eventuali errori che potrebbero verificarsi durante l'esecuzione della funzione.error
su una stringa vuota.transactionDetails
su null
.const receipt = await web3.eth.getTransactionReceipt(transactionId)
;: Questa riga utilizza il metodo web3js per recuperare la ricevuta della transazione per il transactionId immesso.if (!receipt) { throw new Error("Transaction not found!"); }
: Se la ricevuta non viene trovata (ad esempio, la ricevuta è null
o undefined
), viene generato un errore con il messaggio "Transazione non trovata!".setTransactionDetails(receipt)
: se la ricevuta viene trovata, aggiorna lo stato transactionDetails
con la ricevuta recuperata.catch (err) { ... }
: Questo blocco cattura tutti gli errori che si verificano durante l'esecuzione del blocco try
.if (err instanceof Error) { setError(err.message); } else { setError("An unknown error occurred"); }
: Se l'errore rilevato è un'istanza della classe Error, imposta lo stato error
sul messaggio di errore. Altrimenti, imposta lo stato error
su un messaggio di errore generico "Si è verificato un errore sconosciuto".
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"); };
if (!transactionDetails) return;
: Questo controlla se transactionDetails
è null
o undefined
. In caso affermativo, la funzione restituisce in anticipo e non fa nulla.
const { transactionHash, from, to, cumulativeGasUsed, blockNumber, contractAddress } = transactionDetails;
: Questo destruttura l'oggetto transactionDetails
per estrarre singole proprietà per un accesso più semplice.
const pdf = new jsPDF()
: Crea una nuova istanza della classe jsPDF, che rappresenta un documento PDF.
pdf.setFontSize(16)
: imposta la dimensione del carattere dell'intestazione a 16.
pdf.text("Transaction Receipt", 10, 10);
: Aggiunge il titolo "Ricevuta di transazione" alle coordinate (10, 10) nel documento PDF.
pdf.setFontSize(12);
: Imposta la dimensione del carattere a 12 per il resto del testo.
pdf.text(Transaction Hash
: ${transactionHash} , 10, 20);
: Aggiunge l'hash della transazione alle coordinate (10, 20).
pdf.text(From: ${from}, 10, 30);
: Aggiunge l'indirizzo del mittente alle coordinate (10, 30).
pdf.text(Contract Address: ${contractAddress}, 10, 40);
: Aggiunge l'indirizzo del contratto alle coordinate (10, 40). Nota: questa riga dovrebbe essere corretta per evitare sovrapposizioni di testo.
pdf.text(To: ${to}, 10, 50);
: Aggiunge l'indirizzo del destinatario alle coordinate (10, 50).
pdf.text(Gas cumulativo utilizzato: ${cumulativeGasUsed} , 10, 60);
: Aggiunge il gas cumulativo utilizzato alle coordinate (10, 60).
pdf.text(Block Number: ${blockNumber}, 10, 70);
: Aggiunge il numero del blocco alle coordinate (10, 70).
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.
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 TransactionReceipt
e visualizzalo nel tuo file App.tsx
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à.