導入 Web3 DApp 開発の技術スタックを理解することで、Web3 dApp 開発のコア技術スタック、dApp 開発における RPC の役割、dRPC を使用してアカウントを作成し、API キー、エンドポイント、エンドポイント分析を生成し、dRPC アカウントに資金を追加し、残高を確認する方法を学習しているはずです。 スマート コントラクトの展開における dRPC の役割は、Ethereum ノードのセットアップ プロセスを簡素化し、開発者が 1 行のコードで簡単に操作および展開できるようにすることです。 この記事では、dRPC エンドポイントと API キーを使用して、コーヒー支払いスマート コントラクトを記述、コンパイル、テストし、Ethereum Sepolia Testnet にデプロイします。 機能は次のとおりです: コーヒー代金 コーヒーの価格の見直し 販売されたコーヒーの総数と総収益の取得 さあ、手を汚してみましょう。 前提条件 Sepolia Faucet ですでにアカウントに資金を入金しています。 ウォレット(例:Metamask)を用意します。 コードエディター。 選択した Js ライブラリまたはフレームワーク (React.js、Next.js など) がすでにインストールされています。 水の入った瓶。 必要な技術とツール 堅実性。 Vite.js(Typescript) を使用した React.js ヘルメット。 Web3.js です。 ドテンヴ。 dRPC API キーとエンドポイント。 アカウントの秘密鍵。 メタマスク コーヒー決済スマートコントラクトの作成 ルート ディレクトリの下にフォルダーを作成し、 という名前を付けます。 contracts フォルダーの下にファイルを作成し、 という名前を付けます。 contracts coffee.sol スマート コントラクトを記述するには、Solidity を使用します。Solidity ファイルは、Solidity ソース コードの標準ファイル拡張子であるため、 拡張子で命名されます。 .sol 次のソース コードを に追加します。 coffee.sol // SPDX-License-Identifier: MIT pragma solidity >=0.8.0 <0.9.0; contract Coffee { uint256 public constant coffeePrice = 0.0002 ether; uint256 public totalCoffeesSold; uint256 public totalEtherReceived; // Custom error definitions error QuantityMustBeGreaterThanZero(); error InsufficientEtherSent(uint256 required, uint256 sent); error DirectEtherTransferNotAllowed(); // Event to log coffee purchases event CoffeePurchased(address indexed buyer, uint256 quantity, uint256 totalCost); // Function to buy coffee function buyCoffee(uint256 quantity) external payable { if (quantity <= 0) { revert QuantityMustBeGreaterThanZero(); } uint256 totalCost = coffeePrice * quantity; if (msg.value > totalCost) { revert InsufficientEtherSent(totalCost, msg.value); } // Update the total coffees sold and total ether received totalCoffeesSold += quantity; totalEtherReceived += totalCost; console.log("Total ether received updated:", totalEtherReceived); console.log("Total coffee sold updated:", totalCoffeesSold); // Emit the purchase event emit CoffeePurchased(msg.sender, quantity, totalCost); // Refund excess Ether sent if (msg.value > totalCost) { uint256 refundAmount = msg.value - totalCost; payable(msg.sender).transfer(refundAmount); } } // Fallback function to handle Ether sent directly to the contract receive() external payable { revert DirectEtherTransferNotAllowed(); } // Public view functions to get totals function getTotalCoffeesSold() external view returns (uint256) { console.log("getTotalCoffeesSold :", totalCoffeesSold); return totalCoffeesSold; } function getTotalEtherReceived() external view returns (uint256) { console.log("getTotalEtherReceived :", totalEtherReceived); return totalEtherReceived; } } プラグマ : このライセンス識別子は、コードが ライセンスに基づいてライセンスされていることを示します。 //SPDX-License-Identifier: MIT マサチューセッツ工科大学 (MIT) : コードが 0.8.0 (含む) から 0.9.0 (含まない) までの Solidity バージョン用に記述されていることを指定します。 pragma solidity >=0.8.0 <0.9.0; 状態変数 uint256 public constant coffeePrice = 0.0002 ether; uint256 public totalCoffeesSold; uint256 public totalEtherReceived; : の定数値として設定します。 coffeePrice 0.0002 ether : 販売されたコーヒーの数を追跡します。 totalCoffeesSold : 契約によって受信された Ether の合計を追跡します。 totalEtherReceived カスタムエラー Solidity のカスタム エラーは です。ユーザー エクスペリエンスの向上に役立つだけでなく、スマート コントラクトのデバッグや保守にも役立ちます。 、プログラミング言語によって提供されるデフォルトのエラー メッセージではなく、特定のユース ケースに合わせて調整されたエラー メッセージ Solidity でカスタム エラーを定義するには、次の構文を使用できます。 : このキーワードはカスタムエラーを定義するために使用されます error 一意の名前: エラーには一意の名前が必要です パラメータ: エラー メッセージに特定の詳細またはパラメータを含める場合は、エラー名の後に括弧で囲んで追加できます。 error QuantityMustBeGreaterThanZero(); error InsufficientEtherSent(uint256 required, uint256 sent); error DirectEtherTransferNotAllowed(); : 数量がゼロより大きいことを確認します。 QuantityMustBeGreaterThanZero() : 送信された Ether が十分であることを確認します。 InsufficientEtherSent(uint256 required, uint256 sent) : 契約への直接 Ether 転送を防止します。 DirectEtherTransferNotAllowed() イベント イベントは、発行時にトランザクション ログに渡された引数を保存するコントラクトの一部です。イベントは通常、EVM のログ機能を使用して、呼び出し元のアプリケーションにコントラクトの現在の状態を通知するために使用されます。イベントは、コントラクトに加えられた変更をアプリケーションに通知し、関連するロジックを実行するために使用できます。 event CoffeePurchased(address indexed buyer, uint256 quantity, uint256 totalCost); : コーヒーの購入を記録します。 CoffeePurchased(address indexed buyer, uint256 quantity, uint256 totalCost) 機能 同じコードを書き直すという冗長性が排除されます。代わりに、開発者は必要に応じてプログラム内の関数を呼び出すことができます。 関数は、特定のタスクを実行する自己完結型のコード モジュールです。 function buyCoffee(uint256 quantity) external payable { if (quantity <= 0) { revert QuantityMustBeGreaterThanZero(); } uint256 totalCost = coffeePrice * quantity; if (msg.value > totalCost) { revert InsufficientEtherSent(totalCost, msg.value); } // Update the total coffees sold and total ether received totalCoffeesSold += quantity; totalEtherReceived += totalCost; console.log("Total ether received updated:", totalEtherReceived); console.log("Total coffee sold updated:", totalCoffeesSold); // Emit the purchase event emit CoffeePurchased(msg.sender, quantity, totalCost); // Refund excess Ether sent if (msg.value > totalCost) { uint256 refundAmount = msg.value - totalCost; payable(msg.sender).transfer(refundAmount); } } receive() external payable { revert DirectEtherTransferNotAllowed(); } function getTotalCoffeesSold() external view returns (uint256) { console.log("getTotalCoffeesSold :", totalCoffeesSold); return totalCoffeesSold; } function getTotalEtherReceived() external view returns (uint256) { console.log("getTotalEtherReceived :", totalEtherReceived); return totalEtherReceived; } : コーヒーの購入を処理し、次の操作を実行します。 buyCoffee(uint256 quantity) external payable 数量が有効かどうかを確認します。 合計コストを計算します。 十分な Ether が送信されるようにします。 状態変数を更新します。 購入イベントを発行します。 余剰のEtherを返金します。 : 誰かが契約アドレスに直接資金を送金した場合に、直接の Ether 転送を元に戻します。 receive() external payable : 販売されたコーヒーの合計数を返します。 getTotalCoffeesSold() external view returns (uint256) : 受信した Ether の合計を返します。 getTotalEtherReceived() external view returns (uint256) コーヒー決済スマートコントラクトのコンパイル ここでは、Hardhat を使用してスマート コントラクトをコンパイルします。 次のコマンド プロンプトを使用して Hardhat をインストールします。 npm install --save-dev hardhat インストールが成功すると、以下の応答が返されます。 このコマンド プロンプトを使用してハードハットを初期化するのと同じディレクトリで、次の操作を実行します。 npx hardhat init 、Enter キーを押します。 下矢印ボタンを使用して「 Create a Javascript project を選択し ルートフォルダにインストールするにはEnterキーを押してください を使用して、 を含む キーボードの y @nomicfoundation/hardhat-toolbox 依存関係 すべてのプロンプトを受け入れます。 下記の応答は初期化に成功したことを示しています など いくつかの新しいフォルダーとファイルがプロジェクトに追加されたことがわかります。たとえば、 Lock.sol 、 iginition/modules 、 test/Lock.js 、 hardhat.config.cjs です。これらについては心配しないでください。 です と は削除してもかまいません。 唯一役に立つのは、 iginition/modules と hardhat.config.cjs 。これらが何に使われるかは後でわかります。contracts フォルダー の下の Lock.sol contracts iginition/modules フォルダー の下の Lock.js 次のコマンド プロンプトを使用して契約をコンパイルします。 npx hardhat compile 次のような追加のフォルダーとファイルが表示されます。 ファイル Coffee.json 内には、 スマート コントラクトと対話するときに呼び出す JSON 形式の ABI コードがあります。 { "_format": "hh-sol-artifact-1", "contractName": "Coffee", "sourceName": "contracts/coffee.sol", "abi": [ { "inputs": [], "name": "DirectEtherTransferNotAllowed", "type": "error" }, { "inputs": [ { "internalType": "uint256", "name": "required", "type": "uint256" }, { "internalType": "uint256", "name": "sent", "type": "uint256" } ], "name": "InsufficientEtherSent", "type": "error" }, { "inputs": [], "name": "QuantityMustBeGreaterThanZero", "type": "error" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "buyer", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "quantity", "type": "uint256" }, { "indexed": false, "internalType": "uint256", "name": "totalCost", "type": "uint256" } ], "name": "CoffeePurchased", "type": "event" }, { "inputs": [ { "internalType": "uint256", "name": "quantity", "type": "uint256" } ], "name": "buyCoffee", "outputs": [], "stateMutability": "payable", "type": "function" }, { "inputs": [], "name": "coffeePrice", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getTotalCoffeesSold", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getTotalEtherReceived", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "totalCoffeesSold", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "totalEtherReceived", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "stateMutability": "payable", "type": "receive" } ], "bytecode": "", "deployedBytecode": "", "linkReferences": {}, "deployedLinkReferences": {} } スマートコントラクトのテスト スマート コントラクトの構築中に自動テスト スクリプトを記述することは非常に重要であり、強く推奨されます。これは 2 要素認証 (2FA) のように機能し、ライブ ネットワークに展開する前にスマート コントラクトが期待どおりに動作することを確認します。 フォルダー という名前を付けます。ファイル内に、以下のコードを貼り付けます。 test の下に 新しいファイルを作成し、 Coffee. const { loadFixture } = require("@nomicfoundation/hardhat-toolbox/network-helpers.js"); const { expect } = require("chai"); const pkg = require("hardhat"); const ABI = require('../artifacts/contracts/coffee.sol/Coffee.json'); const { web3 } = pkg; describe("Coffee Contract", function () { // Fixture to deploy the Coffee contract async function deployCoffeeFixture() { const coffeeContract = new web3.eth.Contract(ABI.abi); coffeeContract.handleRevert = true; const [deployer, buyer] = await web3.eth.getAccounts(); const rawContract = coffeeContract.deploy({ data: ABI.bytecode, }); // Estimate gas for the deployment const estimateGas = await rawContract.estimateGas({ from: deployer }); // Deploy the contract const coffee = await rawContract.send({ from: deployer, gas: estimateGas.toString(), gasPrice: "10000000000", }); console.log("Coffee contract deployed to: ", coffee.options.address); return { coffee, deployer, buyer, rawContract }; } describe("Deployment", function () { // Test to check initial values after deployment it("Should set the initial values correctly", async function () { const { coffee } = await loadFixture(deployCoffeeFixture); const totalCoffeesSold = await coffee.methods.totalCoffeesSold().call(); const totalEtherReceived = await coffee.methods.totalEtherReceived().call(); expect(totalCoffeesSold).to.equal("0"); expect(totalEtherReceived).to.equal("0"); }); }); describe("Buying Coffee", function () { // Test to check coffee purchase and event emission it("Should purchase coffee and emit an event", async function () { const { coffee, buyer } = await loadFixture(deployCoffeeFixture); const quantity = 3; const totalCost = web3.utils.toWei("0.0006", "ether"); // Buyer purchases coffee const receipt = await coffee.methods.buyCoffee(quantity).send({ from: buyer, value: totalCost }); // Check event const event = receipt.events.CoffeePurchased; expect(event).to.exist; expect(event.returnValues.buyer).to.equal(buyer); expect(event.returnValues.quantity).to.equal(String(quantity)); expect(event.returnValues.totalCost).to.equal(totalCost); }); // Test to check revert when quantity is zero it("Should revert if the quantity is zero", async function () { const { coffee, buyer } = await loadFixture(deployCoffeeFixture); expect( coffee.methods.buyCoffee(0).send({ from: buyer, value: web3.utils.toWei("0.0002", "ether") }) ).to.be.revertedWith("QuantityMustBeGreaterThanZero"); }); // Test to check if totalCoffeesSold and totalEtherReceived are updated correctly it("Should update totalCoffeesSold and totalEtherReceived correctly", async function () { const { coffee, buyer } = await loadFixture(deployCoffeeFixture); const quantity = 5; const totalCost = web3.utils.toWei("0.001", "ether"); await coffee.methods.buyCoffee(quantity).send({ from: buyer, value: totalCost }); const totalCoffeesSold = await coffee.methods.totalCoffeesSold().call(); const totalEtherReceived = await coffee.methods.totalEtherReceived().call(); expect(totalCoffeesSold).to.equal(String(quantity)); expect(totalEtherReceived).to.equal(totalCost); }); }); describe("Fallback function", function () { // Test to check revert when ether is sent directly to the contract it("Should revert if ether is sent directly to the contract", async function () { const { coffee, buyer } = await loadFixture(deployCoffeeFixture); expect( web3.eth.sendTransaction({ from: buyer, to: coffee.options.address, value: web3.utils.toWei("0.001", "ether"), }) ).to.be.revertedWith("DirectEtherTransferNotAllowed"); }); }); }); このコードは、コーヒー スマート コントラクトの機能をテストします。デプロイメント、コーヒーの購入、コントラクトへの直接 Ether 転送の処理のテストが含まれます。 内訳は次のとおりです。 フィクスチャ関数: deployCoffeeFixture async function deployCoffeeFixture() { const coffeeContract = new web3.eth.Contract(ABI.abi); coffeeContract.handleRevert = true; const [deployer, buyer] = await web3.eth.getAccounts(); const rawContract = coffeeContract.deploy({ data: ABI.bytecode, }); const estimateGas = await rawContract.estimateGas({ from: deployer }); const coffee = await rawContract.send({ from: deployer, gas: estimateGas.toString(), gasPrice: "10000000000", }); console.log("Coffee contract deployed to: ", coffee.options.address); return { coffee, deployer, buyer, rawContract }; } 。新しいコントラクト インスタンスを作成し、デプロイヤーのアカウントを使用してデプロイします。 Coffee コントラクトをデプロイします : 展開に必要なガスを見積もります。 ガスの見積もり : デプロイされた契約インスタンス、デプロイヤー、および購入者のアカウント。 戻り値 展開テスト describe("Deployment", function () { it("Should set the initial values correctly", async function () { const { coffee } = await loadFixture(deployCoffeeFixture); const totalCoffeesSold = await coffee.methods.totalCoffeesSold().call(); const totalEtherReceived = await coffee.methods.totalEtherReceived().call(); expect(totalCoffeesSold).to.equal("0"); expect(totalEtherReceived).to.equal("0"); }); }); 。デプロイ後に と がゼロに設定されていることを確認します。 初期値を確認します totalCoffeesSold totalEtherReceived コーヒーテストの購入 describe("Buying Coffee", function () { it("Should purchase coffee and emit an event", async function () { const { coffee, buyer } = await loadFixture(deployCoffeeFixture); const quantity = 3; const totalCost = web3.utils.toWei("0.0006", "ether"); const receipt = await coffee.methods.buyCoffee(quantity).send({ from: buyer, value: totalCost }); const event = receipt.events.CoffeePurchased; expect(event).to.exist; expect(event.returnValues.buyer).to.equal(buyer); expect(event.returnValues.quantity).to.equal(String(quantity)); expect(event.returnValues.totalCost).to.equal(totalCost); }); it("Should revert if the quantity is zero", async function () { const { coffee, buyer } = await loadFixture(deployCoffeeFixture); expect( coffee.methods.buyCoffee(0).send({ from: buyer, value: web3.utils.toWei("0.0002", "ether") }) ).to.be.revertedWith("QuantityMustBeGreaterThanZero"); }); it("Should update totalCoffeesSold and totalEtherReceived correctly", async function () { const { coffee, buyer } = await loadFixture(deployCoffeeFixture); const quantity = 5; const totalCost = web3.utils.toWei("0.001", "ether"); await coffee.methods.buyCoffee(quantity).send({ from: buyer, value: totalCost }); const totalCoffeesSold = await coffee.methods.totalCoffeesSold().call(); const totalEtherReceived = await coffee.methods.totalEtherReceived().call(); expect(totalCoffeesSold).to.equal(String(quantity)); expect(totalEtherReceived).to.equal(totalCost); }); }); : コーヒーを購入すると状態が更新され、 イベントが発行されることをテストします。 コーヒーを購入してイベントを発行する CoffeePurchased : 数量がゼロの場合にトランザクションが元に戻ることを保証します。 数量がゼロの場合に元に戻す : 購入後に と が正しく更新されていることを確認します。 状態を正しく更新する totalCoffeesSold totalEtherReceived フォールバック機能テスト describe("Fallback function", function () { it("Should revert if ether is sent directly to the contract", async function () { const { coffee, buyer } = await loadFixture(deployCoffeeFixture); expect( web3.eth.sendTransaction({ from: buyer, to: coffee.options.address, value: web3.utils.toWei("0.001", "ether"), }) ).to.be.revertedWith("DirectEtherTransferNotAllowed"); }); }); : Ether をコントラクトに直接送信すると (関数を呼び出さずに)、トランザクションが元に戻ることを保証します。 直接 Ether 転送を元に戻す スマートコントラクトのテスト テスト スクリプトを記述したら、次の操作を行います 。 Hardhat Network でコントラクトとテストを実行する場合、Solidity コードから 。これを使用するには、次のようにコントラクト コードに console.log() を呼び出して、ログ メッセージとコントラクト変数を出力できます hardhat/console.sol をインポートする必要があります 。 //SPDX-License-Identifier: MIT pragma solidity >=0.8.0 <0.9.0; import "hardhat/console.sol"; contract Coffee { //... } 契約をテストするには、ターミナルで次のコマンドを実行します。 npx hardhat test 以下のような出力が得られるはずです: これは、スマート コントラクトが期待どおりに機能していることを示しています。 npx hardhat test を実行すると、 スマート コントラクトが自動的にコンパイルされ、テストされます。ぜひ試してみて、 コメント セクションでお知らせください。 スマートコントラクト のデプロイ ここでは、スマート コントラクトを テストネットを使用すると、大きなコストをかけずに、Ethereum メインネットを模倣した環境でスマート コントラクトをテストできます。dApp の機能に問題がなければ、Ethereum メインネットに再デプロイできます。 Sepolia テストネットにデプロイします。 dotenv パッケージとこれらの依存関係をインストールします。 npm install dotenv npm install --save-dev @nomicfoundation/hardhat-web3-v4 'web3@4' これにより、Web3.Js と Dotenv が 'node_modules' フォルダーに含まれ、プロジェクトに追加されます。 それらを hardhat.config.cjs ファイル にインポートします。 require('dotenv').config(); require("@nomicfoundation/hardhat-toolbox"); require("@nomicfoundation/hardhat-web3-v4"); const HardhatUserConfig = require("hardhat/config"); module.exports = { solidity: "0.8.24", } }; ファイル 。 ルート フォルダーに .env を作成します MetaMask ウォレットからアカウントの秘密鍵と dRPC API キーを取得します。 それらを ファイル .env に保存します 。 DRPC_API_KEY=your_drpc_api_key PRIVATE_KEY=your_wallet_private_key ファイル hardhat.config.cjs を更新して 、Sepolia テストネット構成を含めます。 require('dotenv').config(); require("@nomicfoundation/hardhat-toolbox"); require("@nomicfoundation/hardhat-web3-v4"); const HardhatUserConfig = require("hardhat/config"); const dRPC_API_KEY = process.env.VITE_dRPC_API_KEY; const PRIVATE_KEY = process.env.VITE_PRIVATE_KEY; module.exports = { solidity: "0.8.24", networks: { sepolia: { url: `https://lb.drpc.org/ogrpc?network=sepolia&dkey=${dRPC_API_KEY}`, accounts: [`0x${PRIVATE_KEY}`], } } }; フォルダー という名前を付けます ignition/module の下に新しいスクリプト ファイルを作成し 、 deploy.cjs 。次のコードを追加して、スマート コントラクトをデプロイします。 const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules"); const CoffeeModule = buildModule("CoffeeModule", (m) => { const coffee = m.contract("Coffee"); return { coffee }; }); module.exports = CoffeeModule; ターミナルで次のコマンドを実行して、スマート コントラクトをデプロイします。 npx hardhat ignition deploy ./ignition/modules/deploy.cjs --network sepolia コマンド プロンプトを実行すると、 ので、 と入力します。デプロイが成功すると、ターミナルにデプロイされたスマート コントラクトのアドレスが表示されます。 Confirm deploy to network sepolia (11155111)? (y/n) y また、 ファイル内のコントラクト アドレスにアクセスすることもできます。 deployed_addresses.json おめでとうございます。スマート コントラクトを Sepolia テストネットに正常にデプロイしました。🎉 結論 この記事では、Hardhat CLI を使用して支払いスマート コントラクトを作成し、テスト、コンパイル、およびデプロイする方法を説明しました。 次の記事では、この dApp のフロントエンドの構築方法を学びます。この UI は次の要素で構成されます。 購入したコーヒーの数を入力するフィールド。 支払い取引を開始し、アカウントから引き落とすボタン。 購入したコーヒーの合計と受け取った金額をイーサリアムと米ドルで表示します コーヒー自体の価格(イーサリアムと米ドルの両方)。 参照 デフォルト メッセージを超えて: Solidity でのカスタム エラーのマスター Solidity のカスタム エラーをマスターする: デフォルト メッセージを超えてスマート コントラクトを向上させる Solidity 関数とは何ですか? Solidity のイベントとは何ですか? ← 前の記事 次の記事 →