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.
Requisits previs
- Heu de tenir el node instal·lat al vostre dispositiu
- coneixements en Javascript
- S'ha instal·lat el marc Js que escolliu
- Editor de codi, per exemple, VScode
Faré servir React Typescript i TailwindCSS per a l'estil
Eina i Tecnologies
- Clau de l'API d'arreel
- Web3js : per interactuar amb RPC
- QRCode React : per generar un codi QR perquè els usuaris puguin escanejar i obtenir el seu rebut
- Jspdf : per generar el rebut en PDF
Instal·leu, importeu els paquets i creeu el component funcional
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;
Gestió de l'Estat i Iniciació Web3
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'estattransactionId
. 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'estattransactionDetails
amb un valornull
i proporciona una funciósetTransactionDetails
per actualitzar-ne el valor. L'estat pot contenir un objecteTransactionDetails
onull
. -
const [error, setError] = useState("");
: Aquesta línia inicialitza unerror
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 comtransactionHash
,from
,to
,cumulativeGasUsed
,blockNumber
i uncontractAddress
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çantimport.meta.env.VITE_API_KEY
.
-
Funció per obtenir els detalls de la transacció
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"); } } };
- Configuració de gestió d'errors :
-
try { ... } catch (err) { ... }
: aquesta estructura s'utilitza per gestionar qualsevol error que es pugui produir durant l'execució de la funció.
-
- Restableix l'estat :
- setError("");: Això esborra qualsevol missatge d'error anterior establint l'estat
error
en una cadena buida. - setTransactionDetails(null);: això esborra qualsevol detall de transacció anterior establint l'estat
transactionDetails
anull
.
- setError("");: Això esborra qualsevol missatge d'error anterior establint l'estat
- Obteniu el rebut de la transacció :
-
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.
-
- Comprovar el rebut :
-
if (!receipt) { throw new Error("Transaction not found!"); }
: Si no es troba el rebut (és a dir, el rebut ésnull
oundefined
), es genera un error amb el missatge "Transacció no trobada!".
-
- Estableix els detalls de la transacció :
-
setTransactionDetails(receipt)
: si es troba el rebut, actualitza l'estattransactionDetails
amb el rebut obtingut.
-
- Gestió d'errors :
-
catch (err) { ... }
: aquest bloc detecta qualsevol error que es produeixi durant l'execució del bloctry
. -
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'estaterror
al missatge de l'error. En cas contrari, estableix l'estaterror
en un missatge d'error genèric "S'ha produït un error desconegut".
-
Funcions per generar PDF de rebut
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"); };
- Comproveu els detalls de la transacció :
if (!transactionDetails) return;
: Això comprova sitransactionDetails
ésnull
oundefined
. Si és així, la funció torna aviat i no fa res.
- Detalls de la transacció de desestructuració :
const { transactionHash, from, to, cumulativeGasUsed, blockNumber, contractAddress } = transactionDetails;
: Això desestructura l'objectetransactionDetails
per extreure propietats individuals per facilitar l'accés.
- Crea un document PDF :
const pdf = new jsPDF()
: Això crea una nova instància de la classe jsPDF, que representa un document PDF.
- Estableix la mida de la lletra i afegiu el títol :
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.
- Afegeix els detalls de la transacció al 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).
- Desa el document PDF :
- pdf.save("Transaction_Receipt.pdf");: Això desarà el document PDF amb el nom de fitxer "Transaction_Receipt.pdf" .
La interfície d'usuari
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.
Base de codi final i sortida
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
Demostració
Conclusió
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.