Dans le monde d'aujourd'hui, les reçus sont essentiels pour valider les transactions et conserver la preuve des achats. Qu'il s'agisse d'une grande banque ou d'un petit magasin de bord de route, les reçus aident les entreprises et les particuliers à s'organiser et à suivre leurs dépenses.
Mais voilà le problème : la plupart des dApps ne fournissent pas de reçus et s'appuient sur des explorateurs pour vérifier les détails des transactions. Et si vous n'aviez pas à vous y fier ? Imaginez à quel point il serait plus pratique pour vos utilisateurs de générer des reçus directement, sans avoir à vérifier leurs portefeuilles.
Si vous créez une dApp basée sur le paiement sur Rootstock, cet article vous montrera comment créer un générateur de reçus simple mais efficace à l'aide de l'API Rootstock et d'une seule méthode RPC (Remote Procedure Call). Cette approche facilite le processus et garantit que vos enregistrements de transactions sont précis et faciles d'accès.
Apprenons les étapes et les outils nécessaires pour créer une expérience de génération de reçus fluide.
J'utiliserai React Typescript et TailwindCSS pour le style
Installez toutes les dépendances à l’aide de cette commande :
npm i web3js jspdf qrcode.react
Créez un nouveau fichier ou utilisez App.jsx
Importez les packages dans le fichier avec les éléments suivants :
import { useState } from "react"; import Web3 from "web3"; import { jsPDF } from "jspdf"; import { QRCodeSVG } from "qrcode.react";
Initialiser le composant fonctionnel
const TransactionReceipt = () => { /......./ } export default TransactionReceipt;
L'extrait de code ici gérera l'état et la fonctionnalité de récupération et d'affichage des détails de transaction à l'aide du hook useState, de Web3js, de Rootstock RPC et de l'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}` );
Gestion de l'État :
const [transactionId, setTransactionId] = useState("");
: Cette ligne initialise une variable d'état transactionId
. Cette variable d'état sera responsable du hachage de transaction saisi que d'autres variables et fonctions exploiteront pour générer le reçu.const [transactionDetails, setTransactionDetails] = useState<TransactionDetails | null>(null);
: Cette ligne initialise une variable d'état transactionDetails
avec une valeur null
et fournit une fonction setTransactionDetails
pour mettre à jour sa valeur. L'état peut contenir soit un objet TransactionDetails
, soit null
.const [error, setError] = useState("");
: Cette ligne initialise une variable d'état error
avec une chaîne vide et fournit une fonction setError
pour mettre à jour sa valeur.Interface TypeScript :
interface TransactionDetails
: cela définit une interface TypeScript pour la structure de l'objet de détails de transaction. Il comprend des propriétés telles que transactionHash
, from
, to
, cumulativeGasUsed
, blockNumber
et une contractAddress
facultative.Initialisation Web3 :
const web3 = new Web3(https://rpc.testnet.rootstock.io/${import.meta.env.VITE_API_KEY});
: Cette ligne initialise Web3js, en se connectant au point de terminaison RPC du réseau de test Rootstock. L'URL du point de terminaison inclut une clé API qui est récupérée à partir des variables d'environnement à l'aide de import.meta.env.VITE_API_KEY
.
Le code ici récupérera les détails de la transaction à l'aide d'une fonction asynchrone de Rootstock avec la méthode 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) { ... }
: Cette structure est utilisée pour gérer les erreurs qui pourraient survenir lors de l'exécution de la fonction.error
sur une chaîne vide.transactionDetails
sur null
.const receipt = await web3.eth.getTransactionReceipt(transactionId)
;: Cette ligne utilise la méthode web3js pour récupérer le reçu de transaction pour le transactionId saisi.if (!receipt) { throw new Error("Transaction not found!"); }
: Si le reçu n'est pas trouvé (c'est-à-dire que le reçu est null
ou undefined
), une erreur est générée avec le message "Transaction not found!".setTransactionDetails(receipt)
: Si le reçu est trouvé, il met à jour l'état transactionDetails
avec le reçu récupéré.catch (err) { ... }
: Ce bloc intercepte toutes les erreurs qui se produisent pendant l'exécution du bloc try
.if (err instanceof Error) { setError(err.message); } else { setError("An unknown error occurred"); }
: Si l'erreur détectée est une instance de la classe Error, elle définit l'état error
sur le message d'erreur. Sinon, elle définit l'état error
sur un message d'erreur générique "Une erreur inconnue s'est produite".
Ici, le package Jspdf sera utilisé pour générer le PDF contenant les détails de la transaction.
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;
: Ceci vérifie si transactionDetails
est null
ou undefined
. Si c'est le cas, la fonction retourne plus tôt que prévu et ne fait rien.
const { transactionHash, from, to, cumulativeGasUsed, blockNumber, contractAddress } = transactionDetails;
: Cela déstructure l'objet transactionDetails
pour extraire les propriétés individuelles pour un accès plus facile.
const pdf = new jsPDF()
: Ceci crée une nouvelle instance de la classe jsPDF, qui représente un document PDF.
pdf.setFontSize(16)
: Ceci définit la taille de police du titre sur 16.
pdf.text("Transaction Receipt", 10, 10);
: Ceci ajoute le titre "Reçu de transaction" aux coordonnées (10, 10) dans le document PDF.
pdf.setFontSize(12);
: Ceci définit la taille de la police à 12 pour le reste du texte.
pdf.text(Transaction Hash
: ${transactionHash} , 10, 20);
: Ceci ajoute le hachage de transaction aux coordonnées (10, 20).
pdf.text(From: ${from}, 10, 30);
: Ceci ajoute l'adresse de l'expéditeur aux coordonnées (10, 30).
pdf.text(Contract Address: ${contractAddress}, 10, 40);
: Ceci ajoute l'adresse du contrat aux coordonnées (10, 40). Remarque : cette ligne doit être corrigée pour éviter tout chevauchement de texte.
pdf.text(To: ${to}, 10, 50);
: Ceci ajoute l'adresse du destinataire aux coordonnées (10, 50).
pdf.text(Gaz cumulé utilisé : ${cumulativeGasUsed} , 10, 60);
: Ceci ajoute le gaz cumulé utilisé aux coordonnées (10, 60).
pdf.text(Block Number: ${blockNumber}, 10, 70);
: Ceci ajoute le numéro de bloc aux coordonnées (10, 70).
Ici, vous restituerez ces composants fonctionnels sous forme d'interface utilisateur aux utilisateurs.
Ce code a déjà inclus le style à l'aide de 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>
Pour le générateur de code QR, la bibliothèque qrcode.react a été utilisée et les détails de la transaction ont été cryptés dans le code QR SVG.
Si vous suivez les étapes, votre base de code devrait ressembler à ceci :
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;
Ensuite, importez le TransactionReceipt
et restituez-le dans votre fichier App.tsx
Dans cet article, vous avez pu créer un générateur de reçus dans un code PDF ou QR à l'aide de la clé API Rootstock et de la méthode RPC. J'espère donc voir cette fonctionnalité intégrée dans votre prochain projet d'application décentralisée.