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