En el món actual, els rebuts són crucials per validar les transaccions i conservar el comprovant de les compres. Tant si es tracta d'un gran banc com d'una petita botiga a la carretera, els rebuts ajuden les empreses i els particulars a organitzar-se i fer un seguiment de les seves despeses.
Però aquí està la cosa: la majoria de les dApps no proporcionen rebuts i confien en els exploradors per verificar els detalls de la transacció. I si no haguessis de confiar en això? Imagineu-vos com de més convenient seria per als vostres usuaris generar rebuts directament, sense necessitat de revisar les seves carteres.
Si esteu creant una dApp basada en pagaments a Rootstock, aquest article us mostrarà com crear un generador de rebuts senzill però eficaç mitjançant l'API Rootstock i només un mètode RPC (Remote Procedure Call). Aquest enfocament facilita el procés i garanteix que els registres de transaccions siguin precisos i de fàcil accés.
Aprenem els passos i les eines necessàries per crear una experiència de generació de rebuts sense problemes.
Faré servir React Typescript i TailwindCSS per a l'estil
Instal·leu totes les dependències mitjançant aquesta comanda:
npm i web3js jspdf qrcode.react
Creeu un fitxer nou o utilitzeu App.jsx
Importeu els paquets al fitxer amb el següent:
import { useState } from "react"; import Web3 from "web3"; import { jsPDF } from "jspdf"; import { QRCodeSVG } from "qrcode.react";
Inicialitzar el component funcional
const TransactionReceipt = () => { /......./ } export default TransactionReceipt;
El fragment de codi aquí gestionarà l'estat i la funcionalitat per obtenir i mostrar els detalls de la transacció mitjançant useState hook, Web3js, Rootstock RPC i 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}` );
Gestió estatal :
const [transactionId, setTransactionId] = useState("");
: Aquesta línia inicialitza una variable d'estat transactionId
. Aquesta variable d'estat serà responsable del hash de transacció introduït que altres variables i funcions aprofitaran per generar el rebut.const [transactionDetails, setTransactionDetails] = useState<TransactionDetails | null>(null);
: Aquesta línia inicialitza una variable d'estat transactionDetails
amb un valor null
i proporciona una funció setTransactionDetails
per actualitzar-ne el valor. L'estat pot contenir un objecte TransactionDetails
o null
.const [error, setError] = useState("");
: Aquesta línia inicialitza un error
de variable d'estat amb una cadena buida i proporciona una funció setError
per actualitzar-ne el valor.Interfície TypeScript :
interface TransactionDetails
: defineix una interfície TypeScript per a l'estructura de l'objecte de detalls de transacció. Inclou propietats com transactionHash
, from
, to
, cumulativeGasUsed
, blockNumber
i un contractAddress
opcional.Inicialització Web3 :
const web3 = new Web3(https://rpc.testnet.rootstock.io/${import.meta.env.VITE_API_KEY});
: Aquesta línia inicialitza Web3js, connectant-se al punt final RPC a la xarxa de prova de Rootstock. L'URL del punt final inclou una clau d'API que es recupera de les variables d'entorn mitjançant import.meta.env.VITE_API_KEY
.
El codi aquí obtindrà els detalls de la transacció mitjançant una funció asíncrona de Rootstock amb el mètode 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) { ... }
: aquesta estructura s'utilitza per gestionar qualsevol error que es pugui produir durant l'execució de la funció.error
en una cadena buida.transactionDetails
a null
.const receipt = await web3.eth.getTransactionReceipt(transactionId)
;: Aquesta línia utilitza el mètode web3js per obtenir el rebut de transacció per a l'identificador de transacció introduït.if (!receipt) { throw new Error("Transaction not found!"); }
: Si no es troba el rebut (és a dir, el rebut és null
o undefined
), es genera un error amb el missatge "Transacció no trobada!".setTransactionDetails(receipt)
: si es troba el rebut, actualitza l'estat transactionDetails
amb el rebut obtingut.catch (err) { ... }
: aquest bloc detecta qualsevol error que es produeixi durant l'execució del bloc try
.if (err instanceof Error) { setError(err.message); } else { setError("An unknown error occurred"); }
: si l'error detectat és una instància de la classe Error, estableix l'estat error
al missatge de l'error. En cas contrari, estableix l'estat error
en un missatge d'error genèric "S'ha produït un error desconegut".
Aquí s'utilitzarà el paquet Jspdf per generar el PDF que conté els detalls de la transacció.
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;
: Això comprova si transactionDetails
és null
o undefined
. Si és així, la funció torna aviat i no fa res.
const { transactionHash, from, to, cumulativeGasUsed, blockNumber, contractAddress } = transactionDetails;
: Això desestructura l'objecte transactionDetails
per extreure propietats individuals per facilitar l'accés.
const pdf = new jsPDF()
: Això crea una nova instància de la classe jsPDF, que representa un document PDF.
pdf.setFontSize(16)
: Això estableix la mida de la lletra de l'encapçalament a 16.
pdf.text("Transaction Receipt", 10, 10);
: Això afegeix el títol "Rebut de transacció" a les coordenades (10, 10) al document PDF.
pdf.setFontSize(12);
: Això estableix la mida de la lletra en 12 per a la resta del text.
pdf.text(Transaction Hash
: ${transactionHash} , 10, 20);
: Això afegeix el hash de la transacció a les coordenades (10, 20).
pdf.text(From: ${from}, 10, 30);
: Això afegeix l'adreça del remitent a les coordenades (10, 30).
pdf.text(Contract Address: ${contractAddress}, 10, 40);
: Això afegeix l'adreça del contracte a les coordenades (10, 40). Nota: aquesta línia s'ha de corregir per evitar la superposició de text.
pdf.text(To: ${to}, 10, 50);
: Això afegeix l'adreça del destinatari a les coordenades (10, 50).
pdf.text(Gas acumulat utilitzat: ${cumulativeGasUsed} , 10, 60);
: Això afegeix el gas acumulat utilitzat a les coordenades (10, 60).
pdf.text(Block Number: ${blockNumber}, 10, 70);
: Això afegeix el número de bloc a les coordenades (10, 70).
Aquí presentareu aquests components funcionals com a interfície d'usuari als usuaris.
Aquest codi ja ha inclòs l'estil amb 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 al generador de codis QR, es va utilitzar la biblioteca qrcode.react i els detalls de la transacció es van xifrar amb el codi QR SVG.
Si seguiu el pas, la vostra base de codi hauria de ser així:
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;
A continuació, importeu el TransactionReceipt
i representeu-lo al vostre fitxer App.tsx
En aquest article, heu pogut crear un generador de rebuts en un codi PDF o QR mitjançant el mètode Rootstock API Key i RPC. Així que en el vostre proper projecte dApp, espero veure-hi aquesta característica.