今日の世界では、領収書は取引を検証し、購入の証拠を保管するために不可欠です。大手銀行でも小さな路面店でも、領収書は企業や個人が支出を整理し、追跡するのに役立ちます。
しかし、問題は次のとおりです。ほとんどのdApp は領収書を提供しておらず、トランザクションの詳細を確認するためにエクスプローラーに依存しています。それに依存する必要がなかったらどうなるでしょうか? ユーザーがウォレットを確認することなく領収書を直接生成できれば、どれほど便利になるか想像してみてください。
Rootstock で支払いベースの dApp を構築している場合、この記事では、Rootstock API と 1 つの RPC (リモート プロシージャ コール) メソッドを使用して、シンプルでありながら効果的な領収書ジェネレーターを作成する方法を説明します。このアプローチにより、プロセスが簡単になり、トランザクション レコードが正確で簡単にアクセスできるようになります。
スムーズな領収書生成エクスペリエンスを実現するために必要な手順とツールを学びましょう。
スタイリングには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 を初期化し、Rootstock テストネットの RPC エンドポイントに接続します。エンドポイント URL には、 import.meta.env.VITE_API_KEY
を使用して環境変数から取得される API キーが含まれます。
ここでのコードは、web3.js メソッドを使用して Rootstock から非同期関数を使用してトランザクションの詳細を取得します。
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
場合)、「Transaction not found!」というメッセージとともにエラーがスローされます。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()
: これは、PDF ドキュメントを表す jsPDF クラスの新しいインスタンスを作成します。
pdf.setFontSize(16)
: 見出しのフォントサイズを16に設定します。
pdf.text("Transaction Receipt", 10, 10);
: これにより、PDF ドキュメントの座標 (10, 10) にタイトル "Transaction Receipt" が追加されます。
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) にブロック番号を追加します。
ここでは、これらの機能コンポーネントをユーザー向けの UI としてレンダリングします。
このコードにはすでに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
ファイルにレンダリングします。
この記事では、Rootstock API キーと RPC メソッドを使用して、PDF または QR コードにレシート ジェネレーターを構築することができました。次の dApp プロジェクトでは、この機能が採用されることを期待しています。