In today’s world, receipts are crucial for validating transactions and keeping proof of purchases. Whether it’s a big bank or a small roadside shop, receipts help businesses and individuals stay organized and track their spending. But here’s the thing: most dApps don’t provide receipts and rely on explorers to verify transaction details. What if you didn’t have to rely on that? Imagine how much more convenient it would be for your users to generate receipts directly, without needing to check their wallets. If you’re building a payment-based dApp on Rootstock, this article will show you how to create a simple yet effective receipt generator using the Rootstock API and just one RPC (Remote Procedure Call) method. This approach makes the process easier and ensures your transaction records are accurate and easy to access. Let's learn the steps and tools needed to create a smooth receipt generation experience. Prerequisites You must have node installed on your device knowledge in Javascript Installed Js framework of your choice Code Editor e.g., VScode I will be using React Typescript and TailwindCSS for styling Tool and Technologies Rootstock API Key Web3js: to interact with RPC QRCode React: to generate a QR code for users to scan and get their receipt Jspdf: to generate the receipt into PDFs Install, Import the Packages and Create the functional component Install all the dependencies using this command : npm i web3js jspdf qrcode.react Create a new file or use the App.jsx Import the packages into the file with the following: import { useState } from "react"; import Web3 from "web3"; import { jsPDF } from "jspdf"; import { QRCodeSVG } from "qrcode.react"; Initialize the functional component const TransactionReceipt = () => { /......./ } export default TransactionReceipt; State Management and Web3 Intilaiztion The code snippet here will manage the state and functionality for fetching and displaying transaction details using useState hook, Web3js, Rootstock RPC and 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} ); State Management: const [transactionId, setTransactionId] = useState("");: This line initializes a state variable transactionId . This state variable will be responsible for the inputted transaction hash which other variables and functions will leverage to generate the receipt. const [transactionDetails, setTransactionDetails] = useState<TransactionDetails | null>(null);: This line initializes a state variable transactionDetails with a null value and provides a function setTransactionDetails to update its value. The state can hold either a TransactionDetails object or null. const [error, setError] = useState("");: This line initializes a state variable error with an empty string and provides a function setError to update its value. TypeScript Interface: interface TransactionDetails: This defines a TypeScript interface for the structure of the transaction details object. It includes properties like transactionHash, from, to, cumulativeGasUsed, blockNumber, and an optional contractAddress. Web3 Initialization: const web3 = new Web3(https://rpc.testnet.rootstock.io/${import.meta.env.VITE_API_KEY});: This line initializes Web3js, connecting to the RPC endpoint to the Rootstock testnet. The endpoint URL includes an API key that is retrieved from environment variables using import.meta.env.VITE_API_KEY. Function to Fetch the Transaction Details The code here will fetch the transaction details using an asynchronous function from Rootstock with the web3.js method. const fetchTransactionDetails = async () => { try { setError(""); setTransactionDetails(null); const receipt = await web3.eth.getTransactionReceipt(transactionId); if (!receipt) { throw new Error("Transaction not found!"); } setTransactionDetails({ ...receipt, cumulativeGasUsed: Number(receipt.cumulativeGasUsed), blockNumber: Number(receipt.blockNumber), }) } catch (err) { if (err instanceof Error) { setError(err.message); } else { setError("An unknown error occurred"); } } }; Error Handling Setup: try { ... } catch (err) { ... }: This structure is used to handle any errors that might occur during the execution of the function. Reset State: setError("");: This clears any previous error messages by setting the error state to an empty string. setTransactionDetails(null);: This clears any previous transaction details by setting the transactionDetailsstate to null. Fetch Transaction Receipt: const receipt = await web3.eth.getTransactionReceipt(transactionId);: This line uses the web3js method to fetch the transaction receipt for the inputted transactionId. Check for Receipt: if (!receipt) { throw new Error("Transaction not found!"); }: If the receipt is not found (i.e., receipt is null or undefined), an error is thrown with the message "Transaction not found!". Set Transaction Details: setTransactionDetails(receipt): If the receipt is found, it updates the transactionDetails state with the fetched receipt. Error Handling: catch (err) { ... }: This block catches any errors that occur during the execution of the try block. if (err instanceof Error) { setError(err.message); } else { setError("An unknown error occurred"); }: If the caught error is an instance of the Error class, it sets the error state to the error's message. Otherwise, it sets the error state to a generic error message "An unknown error occurred". Functions to Generate Receipt PDF Here the Jspdf package will be used to to generate the PDF containing the transaction details. 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"); }; Check for Transaction Details: if (!transactionDetails) return;: This checks if transactionDetails is null or undefined. If it is, the function returns early and does nothing. Destructure Transaction Details: const { transactionHash, from, to, cumulativeGasUsed, blockNumber, contractAddress } = transactionDetails;: This destructures the transactionDetails object to extract individual properties for easier access. Create PDF Document: const pdf = new jsPDF(): This creates a new instance of the jsPDF class, which represents a PDF document. Set Font Size and Add Title: pdf.setFontSize(16): This sets the font size of the heading to 16. pdf.text("Transaction Receipt", 10, 10);: This adds the title "Transaction Receipt" at coordinates (10, 10) in the PDF document. Add Transaction Details to PDF: pdf.setFontSize(12);: This sets the font size to 12 for the rest of the text. pdf.text(Transaction Hash: ${transactionHash}, 10, 20);: This adds the transaction hash at coordinates (10, 20). pdf.text(From: ${from}, 10, 30);: This adds the sender address at coordinates (10, 30). pdf.text(Contract Address: ${contractAddress}, 10, 40);: This adds the contract address at coordinates (10, 40). Note: This line should be corrected to avoid overlapping text. pdf.text(To: ${to}, 10, 50);: This adds the recipient address at coordinates (10, 50). pdf.text(Cumulative Gas Used: ${cumulativeGasUsed}, 10, 60);: This adds the cumulative gas used at coordinates (10, 60). pdf.text(Block Number: ${blockNumber}, 10, 70);: This adds the block number at coordinates (10, 70). Save PDF Document: pdf.save("Transaction_Receipt.pdf");: This will saves the PDF document with the filename "Transaction_Receipt.pdf" . The User Interface Here you will be rendering those functional components as a UI to the users. This code has already included the styling using 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> For the QR code generator, qrcode.react library was used, and the transaction details were encrypted into it the QR code SVG. Final codebase and Output If you follow the step, your codebase should look like this: 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, cumulativeGasUsed: Number(receipt.cumulativeGasUsed), blockNumber: Number(receipt.blockNumber), }) } 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; Then, import the TransactionReceipt and render it in your App.tsx file Demo https://youtu.be/Xwkl9pu8UiM?embedable=true Live Site: https://receipt-generator-six.vercel.app/ Conclusion In this article, you have been able to build a receipt generator into a PDF or QR code using the Rootstock API Key and RPC method. So in your next dApp project, I hope to see this feature in it. In today’s world, receipts are crucial for validating transactions and keeping proof of purchases. Whether it’s a big bank or a small roadside shop, receipts help businesses and individuals stay organized and track their spending. But here’s the thing: most dApps don’t provide receipts and rely on explorers to verify transaction details. What if you didn’t have to rely on that? Imagine how much more convenient it would be for your users to generate receipts directly, without needing to check their wallets. dApps If you’re building a payment-based dApp on Rootstock, this article will show you how to create a simple yet effective receipt generator using the Rootstock API and just one RPC (Remote Procedure Call) method. This approach makes the process easier and ensures your transaction records are accurate and easy to access. Let's learn the steps and tools needed to create a smooth receipt generation experience. Prerequisites You must have node installed on your device knowledge in Javascript Installed Js framework of your choice Code Editor e.g., VScode You must have node installed on your device knowledge in Javascript Installed Js framework of your choice Code Editor e.g., VScode I will be using React Typescript and TailwindCSS for styling Tool and Technologies Rootstock API Key Web3js: to interact with RPC QRCode React: to generate a QR code for users to scan and get their receipt Jspdf: to generate the receipt into PDFs Rootstock API Key Rootstock API Key Web3js : to interact with RPC Web3js QRCode React : to generate a QR code for users to scan and get their receipt QRCode React Jspdf : to generate the receipt into PDFs Jspdf Install, Import the Packages and Create the functional component Install all the dependencies using this command : Install all the dependencies using this command : npm i web3js jspdf qrcode.react Create a new file or use the App.jsx Import the packages into the file with the following: Create a new file or use the App.jsx App.jsx Import the packages into the file with the following: import { useState } from "react"; import Web3 from "web3"; import { jsPDF } from "jspdf"; import { QRCodeSVG } from "qrcode.react"; Initialize the functional component Initialize the functional component const TransactionReceipt = () => { /......./ } export default TransactionReceipt; State Management and Web3 Intilaiztion The code snippet here will manage the state and functionality for fetching and displaying transaction details using useState hook, Web3js, Rootstock RPC and 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} ); https://rpc.testnet.rootstock.io/${import.meta.env.VITE_API_KEY} State Management: State Management : State Management const [transactionId, setTransactionId] = useState("");: This line initializes a state variable transactionId . This state variable will be responsible for the inputted transaction hash which other variables and functions will leverage to generate the receipt. const [transactionDetails, setTransactionDetails] = useState<TransactionDetails | null>(null);: This line initializes a state variable transactionDetails with a null value and provides a function setTransactionDetails to update its value. The state can hold either a TransactionDetails object or null. const [error, setError] = useState("");: This line initializes a state variable error with an empty string and provides a function setError to update its value. const [transactionId, setTransactionId] = useState(""); : This line initializes a state variable transactionId . This state variable will be responsible for the inputted transaction hash which other variables and functions will leverage to generate the receipt. const [transactionId, setTransactionId] = useState(""); transactionId const [transactionDetails, setTransactionDetails] = useState<TransactionDetails | null>(null); : This line initializes a state variable transactionDetails with a null value and provides a function setTransactionDetails to update its value. The state can hold either a TransactionDetails object or null . const [transactionDetails, setTransactionDetails] = useState<TransactionDetails | null>(null); transactionDetails null setTransactionDetails TransactionDetails null const [error, setError] = useState(""); : This line initializes a state variable error with an empty string and provides a function setError to update its value. const [error, setError] = useState(""); error setError TypeScript Interface: TypeScript Interface : TypeScript Interface interface TransactionDetails: This defines a TypeScript interface for the structure of the transaction details object. It includes properties like transactionHash, from, to, cumulativeGasUsed, blockNumber, and an optional contractAddress. interface TransactionDetails : This defines a TypeScript interface for the structure of the transaction details object. It includes properties like transactionHash , from , to , cumulativeGasUsed , blockNumber , and an optional contractAddress . interface TransactionDetails transactionHash from to cumulativeGasUsed blockNumber contractAddress Web3 Initialization: Web3 Initialization : Web3 Initialization const web3 = new Web3(https://rpc.testnet.rootstock.io/${import.meta.env.VITE_API_KEY});: This line initializes Web3js, connecting to the RPC endpoint to the Rootstock testnet. The endpoint URL includes an API key that is retrieved from environment variables using import.meta.env.VITE_API_KEY. const web3 = new Web3(https://rpc.testnet.rootstock.io/${import.meta.env.VITE_API_KEY}); : This line initializes Web3js, connecting to the RPC endpoint to the Rootstock testnet. The endpoint URL includes an API key that is retrieved from environment variables using import.meta.env.VITE_API_KEY . const web3 = new Web3(https://rpc.testnet.rootstock.io/${import.meta.env.VITE_API_KEY}); import.meta.env.VITE_API_KEY Function to Fetch the Transaction Details The code here will fetch the transaction details using an asynchronous function from Rootstock with the web3.js method. const fetchTransactionDetails = async () => { try { setError(""); setTransactionDetails(null); const receipt = await web3.eth.getTransactionReceipt(transactionId); if (!receipt) { throw new Error("Transaction not found!"); } setTransactionDetails({ ...receipt, cumulativeGasUsed: Number(receipt.cumulativeGasUsed), blockNumber: Number(receipt.blockNumber), }) const receipt = await web3.eth.getTransactionReceipt(transactionId); if (!receipt) { throw new Error("Transaction not found!"); } setTransactionDetails({ ...receipt, cumulativeGasUsed: Number(receipt.cumulativeGasUsed), blockNumber: Number(receipt.blockNumber), }) } catch (err) { if (err instanceof Error) { setError(err.message); } else { setError("An unknown error occurred"); } } }; Error Handling Setup: Error Handling Setup : Error Handling Setup try { ... } catch (err) { ... }: This structure is used to handle any errors that might occur during the execution of the function. try { ... } catch (err) { ... } : This structure is used to handle any errors that might occur during the execution of the function. try { ... } catch (err) { ... } Reset State: Reset State : Reset State setError("");: This clears any previous error messages by setting the error state to an empty string. setTransactionDetails(null);: This clears any previous transaction details by setting the transactionDetailsstate to null. setError("");: This clears any previous error messages by setting the error state to an empty string. error setTransactionDetails(null);: This clears any previous transaction details by setting the transactionDetails state to null . transactionDetails null Fetch Transaction Receipt: Fetch Transaction Receipt : Fetch Transaction Receipt const receipt = await web3.eth.getTransactionReceipt(transactionId);: This line uses the web3js method to fetch the transaction receipt for the inputted transactionId. const receipt = await web3.eth.getTransactionReceipt(transactionId) ;: This line uses the web3js method to fetch the transaction receipt for the inputted transactionId. const receipt = await web3.eth.getTransactionReceipt(transactionId) Check for Receipt: Check for Receipt : Check for Receipt if (!receipt) { throw new Error("Transaction not found!"); }: If the receipt is not found (i.e., receipt is null or undefined), an error is thrown with the message "Transaction not found!". if (!receipt) { throw new Error("Transaction not found!"); } : If the receipt is not found (i.e., receipt is null or undefined ), an error is thrown with the message "Transaction not found!". if (!receipt) { throw new Error("Transaction not found!"); } null undefined Set Transaction Details: Set Transaction Details : Set Transaction Details setTransactionDetails(receipt): If the receipt is found, it updates the transactionDetails state with the fetched receipt. setTransactionDetails(receipt) : If the receipt is found, it updates the transactionDetails state with the fetched receipt. setTransactionDetails(receipt) transactionDetails Error Handling: Error Handling : Error Handling catch (err) { ... }: This block catches any errors that occur during the execution of the try block. if (err instanceof Error) { setError(err.message); } else { setError("An unknown error occurred"); }: If the caught error is an instance of the Error class, it sets the error state to the error's message. Otherwise, it sets the error state to a generic error message "An unknown error occurred". catch (err) { ... } : This block catches any errors that occur during the execution of the try block. catch (err) { ... } try if (err instanceof Error) { setError(err.message); } else { setError("An unknown error occurred"); } : If the caught error is an instance of the Error class, it sets the error state to the error's message. Otherwise, it sets the error state to a generic error message "An unknown error occurred". if (err instanceof Error) { setError(err.message); } else { setError("An unknown error occurred"); } error error Functions to Generate Receipt PDF Here the Jspdf package will be used to to generate the PDF containing the transaction details. 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"); 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"); }; Check for Transaction Details: Check for Transaction Details : Check for Transaction Details if (!transactionDetails) return;: This checks if transactionDetails is null or undefined. If it is, the function returns early and does nothing. if (!transactionDetails) return; : This checks if transactionDetails is null or undefined . If it is, the function returns early and does nothing. if (!transactionDetails) return; transactionDetails null undefined Destructure Transaction Details: Destructure Transaction Details : Destructure Transaction Details const { transactionHash, from, to, cumulativeGasUsed, blockNumber, contractAddress } = transactionDetails;: This destructures the transactionDetails object to extract individual properties for easier access. const { transactionHash, from, to, cumulativeGasUsed, blockNumber, contractAddress } = transactionDetails; : This destructures the transactionDetails object to extract individual properties for easier access. const { transactionHash, from, to, cumulativeGasUsed, blockNumber, contractAddress } = transactionDetails; transactionDetails Create PDF Document: Create PDF Document : Create PDF Document const pdf = new jsPDF(): This creates a new instance of the jsPDF class, which represents a PDF document. const pdf = new jsPDF() : This creates a new instance of the jsPDF class, which represents a PDF document. const pdf = new jsPDF() Set Font Size and Add Title: Set Font Size and Add Title : Set Font Size and Add Title pdf.setFontSize(16): This sets the font size of the heading to 16. pdf.text("Transaction Receipt", 10, 10);: This adds the title "Transaction Receipt" at coordinates (10, 10) in the PDF document. pdf.setFontSize(16) : This sets the font size of the heading to 16. pdf.setFontSize(16) pdf.text("Transaction Receipt", 10, 10); : This adds the title "Transaction Receipt" at coordinates (10, 10) in the PDF document. pdf.text("Transaction Receipt", 10, 10); Add Transaction Details to PDF: Add Transaction Details to PDF : Add Transaction Details to PDF pdf.setFontSize(12);: This sets the font size to 12 for the rest of the text. pdf.text(Transaction Hash: ${transactionHash}, 10, 20);: This adds the transaction hash at coordinates (10, 20). pdf.text(From: ${from}, 10, 30);: This adds the sender address at coordinates (10, 30). pdf.text(Contract Address: ${contractAddress}, 10, 40);: This adds the contract address at coordinates (10, 40). Note: This line should be corrected to avoid overlapping text. pdf.text(To: ${to}, 10, 50);: This adds the recipient address at coordinates (10, 50). pdf.text(Cumulative Gas Used: ${cumulativeGasUsed}, 10, 60);: This adds the cumulative gas used at coordinates (10, 60). pdf.text(Block Number: ${blockNumber}, 10, 70);: This adds the block number at coordinates (10, 70). pdf.setFontSize(12); : This sets the font size to 12 for the rest of the text. pdf.setFontSize(12); pdf.text(Transaction Hash : ${transactionHash} , 10, 20); : This adds the transaction hash at coordinates (10, 20). pdf.text(Transaction Hash , 10, 20); pdf.text(From: ${from}, 10, 30); : This adds the sender address at coordinates (10, 30). pdf.text(From: ${from}, 10, 30); pdf.text(Contract Address: ${contractAddress}, 10, 40); : This adds the contract address at coordinates (10, 40). Note: This line should be corrected to avoid overlapping text. pdf.text(Contract Address: ${contractAddress}, 10, 40); pdf.text(To: ${to}, 10, 50); : This adds the recipient address at coordinates (10, 50). pdf.text(To: ${to}, 10, 50); pdf.text(Cumulative Gas Used: ${cumulativeGasUsed} , 10, 60); : This adds the cumulative gas used at coordinates (10, 60). , 10, 60); pdf.text(Block Number: ${blockNumber}, 10, 70); : This adds the block number at coordinates (10, 70). pdf.text(Block Number: ${blockNumber}, 10, 70); Save PDF Document: Save PDF Document : Save PDF Document pdf.save("Transaction_Receipt.pdf");: This will saves the PDF document with the filename "Transaction_Receipt.pdf" . pdf.save("Transaction_Receipt.pdf");: This will saves the PDF document with the filename "Transaction_Receipt.pdf" . The User Interface Here you will be rendering those functional components as a UI to the users. This code has already included the styling using 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> <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> For the QR code generator, qrcode.react library was used, and the transaction details were encrypted into it the QR code SVG. Final codebase and Output If you follow the step, your codebase should look like this: 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} ); 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, cumulativeGasUsed: Number(receipt.cumulativeGasUsed), blockNumber: Number(receipt.blockNumber), }) } catch (err) { if (err instanceof Error) { setError(err.message); } else { setError("An unknown error occurred"); } } const receipt = await web3.eth.getTransactionReceipt(transactionId); if (!receipt) { throw new Error("Transaction not found!"); } setTransactionDetails({ ...receipt, cumulativeGasUsed: Number(receipt.cumulativeGasUsed), blockNumber: Number(receipt.blockNumber), }) } 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"); 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> <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; Then, import the TransactionReceipt and render it in your App.tsx file TransactionReceipt App.tsx Demo https://youtu.be/Xwkl9pu8UiM?embedable=true https://youtu.be/Xwkl9pu8UiM?embedable=true Live Site: https://receipt-generator-six.vercel.app/ https://receipt-generator-six.vercel.app/ Conclusion In this article, you have been able to build a receipt generator into a PDF or QR code using the Rootstock API Key and RPC method. So in your next dApp project, I hope to see this feature in it.