В современном мире чеки имеют решающее значение для подтверждения транзакций и сохранения доказательств покупок. Будь то крупный банк или небольшой придорожный магазин, чеки помогают компаниям и частным лицам оставаться организованными и отслеживать свои расходы.
Но вот в чем дело: большинство dApps не предоставляют квитанции и полагаются на explorers для проверки деталей транзакции. А что, если бы вам не пришлось полагаться на это? Представьте, насколько удобнее было бы вашим пользователям генерировать квитанции напрямую, без необходимости проверять свои кошельки.
Если вы создаете платежное dApp на Rootstock, эта статья покажет вам, как создать простой, но эффективный генератор квитанций с использованием API Rootstock и всего одного метода RPC (Remote Procedure Call). Такой подход упрощает процесс и гарантирует точность и простоту доступа к записям транзакций.
Давайте изучим шаги и инструменты, необходимые для создания бесперебойного процесса генерации чеков.
Я буду использовать React Typescript и TailwindCSS для стилизации.
Установите все зависимости с помощью этой команды:
npm i web3js jspdf qrcode.react
Создайте новый файл или используйте App.jsx
Импортируйте пакеты в файл следующим образом:
import { useState } from "react"; import Web3 from "web3"; import { jsPDF } from "jspdf"; import { QRCodeSVG } from "qrcode.react";
Инициализируйте функциональный компонент
const TransactionReceipt = () => { /......./ } export default TransactionReceipt;
Приведенный здесь фрагмент кода будет управлять состоянием и функциональностью для извлечения и отображения сведений о транзакции с использованием хука useState, Web3js, Rootstock RPC и 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}` );
Государственное управление :
const [transactionId, setTransactionId] = useState("");
: Эта строка инициализирует переменную состояния transactionId
. Эта переменная состояния будет отвечать за введенный хэш транзакции, который другие переменные и функции будут использовать для генерации квитанции.const [transactionDetails, setTransactionDetails] = useState<TransactionDetails | null>(null);
: Эта строка инициализирует переменную состояния transactionDetails
со значением null
и предоставляет функцию setTransactionDetails
для обновления ее значения. Состояние может содержать либо объект TransactionDetails
, либо null
.const [error, setError] = useState("");
: эта строка инициализирует переменную состояния error
пустой строкой и предоставляет функцию setError
для обновления ее значения.Интерфейс TypeScript :
interface TransactionDetails
: определяет интерфейс TypeScript для структуры объекта деталей транзакции. Он включает такие свойства, как transactionHash
, from
, to
, cumulativeGasUsed
, blockNumber
и необязательный contractAddress
.Инициализация Web3 :
const web3 = new Web3(https://rpc.testnet.rootstock.io/${import.meta.env.VITE_API_KEY});
: эта строка инициализирует Web3js, подключаясь к конечной точке RPC в тестовой сети Rootstock. URL конечной точки включает ключ API, который извлекается из переменных среды с помощью import.meta.env.VITE_API_KEY
.
Приведенный здесь код извлечет данные транзакции, используя асинхронную функцию из Rootstock с методом 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) { ... }
: эта структура используется для обработки любых ошибок, которые могут возникнуть во время выполнения функции.error
в пустую строку.transactionDetails
в null
.const receipt = await web3.eth.getTransactionReceipt(transactionId)
;: Эта строка использует метод web3js для получения квитанции о транзакции для введенного transactionId.if (!receipt) { throw new Error("Transaction not found!"); }
: Если квитанция не найдена (т. е. квитанция имеет значение null
или undefined
), выдается ошибка с сообщением "Транзакция не найдена!".setTransactionDetails(receipt)
: если квитанция найдена, он обновляет состояние transactionDetails
с учетом извлеченной квитанции.catch (err) { ... }
: этот блок перехватывает любые ошибки, возникающие во время выполнения блока try
.if (err instanceof Error) { setError(err.message); } else { setError("An unknown error occurred"); }
: Если перехваченная ошибка является экземпляром класса Error, он устанавливает состояние error
на сообщение об ошибке. В противном случае он устанавливает состояние error
на общее сообщение об ошибке "Произошла неизвестная ошибка".
Здесь пакет Jspdf будет использоваться для создания PDF-файла, содержащего сведения о транзакции.
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;
: Проверяет, является ли transactionDetails
null
или undefined
. Если это так, функция возвращается раньше времени и ничего не делает.
const { transactionHash, from, to, cumulativeGasUsed, blockNumber, contractAddress } = transactionDetails;
: Это деструктурирует объект transactionDetails
для извлечения отдельных свойств для более легкого доступа.
const pdf = new jsPDF()
: создает новый экземпляр класса jsPDF, представляющий PDF-документ.
pdf.setFontSize(16)
: устанавливает размер шрифта заголовка равным 16.
pdf.text("Transaction Receipt", 10, 10);
: добавляет заголовок "Квитанция о транзакции" в точке с координатами (10, 10) в документе PDF.
pdf.setFontSize(12);
: устанавливает размер шрифта 12 для всего остального текста.
pdf.text(Transaction Hash
: ${transactionHash} , 10, 20);
: добавляет хэш транзакции в координатах (10, 20).
pdf.text(From: ${from}, 10, 30);
: добавляет адрес отправителя в координаты (10, 30).
pdf.text(Contract Address: ${contractAddress}, 10, 40);
: добавляет адрес контракта в координатах (10, 40). Примечание: эту строку следует исправить, чтобы избежать наложения текста.
pdf.text(To: ${to}, 10, 50);
: добавляет адрес получателя в координаты (10, 50).
pdf.text(Совокупное использованное количество газа: ${cumulativeGasUsed} , 10, 60);
: добавляет совокупное использованное количество газа в точке с координатами (10, 60).
pdf.text(Block Number: ${blockNumber}, 10, 70);
: добавляет номер блока в координатах (10, 70).
Здесь вы будете отображать эти функциональные компоненты в виде пользовательского интерфейса для пользователей.
В этом коде уже включен стиль с использованием 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>
Для генератора QR-кода использовалась библиотека qrcode.react, а данные транзакции были зашифрованы в ней в QR-код SVG.
Если вы выполните этот шаг, ваша кодовая база должна выглядеть следующим образом:
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;
Затем импортируйте TransactionReceipt
и отобразите его в файле App.tsx
В этой статье вы смогли встроить генератор квитанций в PDF или QR-код с помощью ключа API Rootstock и метода RPC. Поэтому в вашем следующем проекте dApp я надеюсь увидеть эту функцию.