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

  1. You must have node installed on your device
  2. knowledge in Javascript
  3. Installed Js framework of your choice
  4. Code Editor e.g., VScode

I will be using React Typescript and TailwindCSS for styling

Tool and Technologies

  1. Rootstock API Key
  2. Web3js: to interact with RPC
  3. QRCode React: to generate a QR code for users to scan and get their receipt
  4. Jspdf: to generate the receipt into PDFs

Install, Import the Packages and Create the functional component

  1. Install all the dependencies using this command :

npm i web3js jspdf qrcode.react

  1. Create a new file or use the App.jsx
  2. 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";

  1. 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} );

  1. State Management:
  1. TypeScript Interface:
  1. Web3 Initialization:

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"); } } };

  1. Error Handling Setup:
  1. Reset State:
  1. Fetch Transaction Receipt:
  1. Check for Receipt:
  1. Set Transaction Details:
  1. Error Handling:

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");

};

  1. Check for Transaction Details:
  1. Destructure Transaction Details:
  1. Create PDF Document:
  1. Set Font Size and Add Title:
  1. Add Transaction Details to PDF:
  1. Save PDF Document:

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.