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:::info I will be using React Typescript and TailwindCSS for styling
:::
Tool and Technologies\
State Management and Web3 IntilaiztionThe 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] = useStateconst 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.
\
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); } catch (err) { if (err instanceof Error) { setError(err.message); } else { setError("An unknown error occurred"); } } };\
Functions to Generate Receipt PDFHere 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"); };if (!transactionDetails) return;: This checks if transactionDetails is null or undefined. If it is, the function returns early and does nothing.
\
const { transactionHash, from, to, cumulativeGasUsed, blockNumber, contractAddress } = transactionDetails;: This destructures the transactionDetails object to extract individual properties for easier access.
\
const pdf = new jsPDF(): This creates a new instance of the jsPDF class, which represents a PDF document.
\
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(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).
\
Here you will be rendering those functional components as a UI to the users.
:::warning This code has already included the styling using Tailwindcss
:::
return (Error: {error}
)} {transactionDetails && (Transaction Hash:{" "} {`${transactionDetails.transactionHash.slice( 0, 6 )}...${transactionDetails.transactionHash.slice(-6)}`}
From: {transactionDetails.from}
Contract Address:{" "} {transactionDetails.contractAddress}
To: {transactionDetails.to}
Cumulative Gas Used:{" "} {transactionDetails.cumulativeGasUsed.toString()}
Block Number:{" "} {transactionDetails.blockNumber.toString()}
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 OutputIf 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] = useStateError: {error}
)} {transactionDetails && (Transaction Hash:{" "} {`${transactionDetails.transactionHash.slice( 0, 6 )}...${transactionDetails.transactionHash.slice(-6)}`}
From: {transactionDetails.from}
Contract Address:{" "} {transactionDetails.contractAddress}
To: {transactionDetails.to}
Cumulative Gas Used:{" "} {transactionDetails.cumulativeGasUsed.toString()}
Block Number:{" "} {transactionDetails.blockNumber.toString()}
\ Then, import the TransactionReceipt and render it in your App.tsx file
Demohttps://youtu.be/Xwkl9pu8UiM?embedable=true
ConclusionIn 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.
\n
All Rights Reserved. Copyright , Central Coast Communications, Inc.