Giới thiệu Từ việc hiểu về công nghệ phát triển DApp Web3, bạn hẳn đã học được công nghệ cốt lõi để phát triển dApp web3, vai trò của RPC trong phát triển dApp và cách sử dụng dRPC để tạo tài khoản, tạo khóa API, điểm cuối, phân tích điểm cuối, thêm tiền vào Tài khoản dRPC và kiểm tra số dư của bạn. Vai trò của dRPC trong việc triển khai hợp đồng thông minh là đơn giản hóa quy trình thiết lập một nút Ethereum, giúp các nhà phát triển dễ dàng tương tác và triển khai chỉ bằng một dòng mã. Trong bài viết này, bạn sẽ viết, biên dịch, kiểm tra và triển khai hợp đồng thông minh thanh toán cà phê lên Ethereum Sepolia Testnet bằng điểm cuối dRPC và khóa API. Các tính năng bao gồm: Thanh toán cho cà phê Xem lại giá cà phê Lấy tổng số lượng cà phê đã bán và tổng số tiền kiếm được Hãy cùng bắt tay vào làm nhé. Điều kiện tiên quyết Đã nạp tiền vào tài khoản của bạn với Sepolia Faucet. Có ví như Metamask. Trình biên tập mã. Đã cài đặt bất kỳ thư viện Js hoặc framework nào bạn chọn (ví dụ: React.js, Next.js, v.v.). Bình nước. Công nghệ và công cụ cần thiết Sự vững chắc. React.js sử dụng Vite.js(Typescript) Mũ cứng. Web3.js. Dotenv. Khóa API và điểm cuối dRPC. Khóa riêng tư của tài khoản của bạn. Mặt nạ siêu việt Viết hợp đồng thông minh thanh toán cà phê Tạo một Thư mục trong thư mục gốc của bạn và đặt tên là . contracts Tạo một File trong thư mục và đặt tên là . contracts coffee.sol bạn sẽ sử dụng solidity để viết hợp đồng thông minh. Các tệp Solidity được đặt tên với phần mở rộng vì đây là phần mở rộng tệp chuẩn cho mã nguồn Solidity. .sol Thêm mã nguồn sau vào : 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; } } Thực dụng : Mã định danh giấy phép này cho biết mã được cấp phép theo Giấy phép . //SPDX-License-Identifier: MIT của Viện Công nghệ Massachusetts (MIT) : Chỉ định rằng mã được viết cho các phiên bản Solidity từ 0.8.0 (bao gồm) đến 0.9.0 (không bao gồm). pragma solidity >=0.8.0 <0.9.0; Biến trạng thái uint256 public constant coffeePrice = 0.0002 ether; uint256 public totalCoffeesSold; uint256 public totalEtherReceived; : Đặt giá trị hằng số là . coffeePrice 0.0002 ether : Theo dõi số lượng cà phê đã bán. totalCoffeesSold : Theo dõi tổng số Ether nhận được theo hợp đồng. totalEtherReceived Lỗi tùy chỉnh Lỗi tùy chỉnh trong Solidity là . Chúng có thể giúp cải thiện trải nghiệm của người dùng và cũng có thể giúp gỡ lỗi và duy trì hợp đồng thông minh. thông báo lỗi được điều chỉnh theo trường hợp sử dụng cụ thể, thay vì thông báo lỗi mặc định do ngôn ngữ lập trình cung cấp Để xác định lỗi tùy chỉnh trong Solidity, bạn có thể sử dụng cú pháp sau: : Từ khóa này được sử dụng để xác định lỗi tùy chỉnh error Tên duy nhất: Lỗi phải có tên duy nhất Tham số: Nếu bạn muốn đưa thông tin chi tiết hoặc tham số cụ thể vào thông báo lỗi, bạn có thể thêm chúng trong dấu ngoặc đơn sau tên lỗi. error QuantityMustBeGreaterThanZero(); error InsufficientEtherSent(uint256 required, uint256 sent); error DirectEtherTransferNotAllowed(); : Đảm bảo số lượng lớn hơn không. QuantityMustBeGreaterThanZero() : Đảm bảo Ether được gửi là đủ. InsufficientEtherSent(uint256 required, uint256 sent) : Ngăn chặn việc chuyển Ether trực tiếp vào hợp đồng. DirectEtherTransferNotAllowed() Sự kiện Sự kiện là một phần của hợp đồng lưu trữ các đối số được truyền trong nhật ký giao dịch khi được phát ra. Sự kiện thường được sử dụng để thông báo cho ứng dụng gọi về trạng thái hiện tại của hợp đồng bằng cách sử dụng tính năng ghi nhật ký của EVM. Chúng thông báo cho ứng dụng về những thay đổi được thực hiện đối với hợp đồng, sau đó có thể được sử dụng để chạy logic liên quan. event CoffeePurchased(address indexed buyer, uint256 quantity, uint256 totalCost); : Ghi lại nhật ký mua cà phê. CoffeePurchased(address indexed buyer, uint256 quantity, uint256 totalCost) Chức năng Chúng loại bỏ sự dư thừa của việc viết lại cùng một đoạn mã. Thay vào đó, các nhà phát triển có thể gọi một hàm trong chương trình khi cần thiết. Hàm là các mô-đun mã độc lập thực hiện một nhiệm vụ cụ thể. 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; } : Xử lý việc mua cà phê và thực hiện các hoạt động sau: buyCoffee(uint256 quantity) external payable Kiểm tra xem số lượng có hợp lệ không. Tính tổng chi phí. Đảm bảo gửi đủ Ether. Cập nhật các biến trạng thái. Phát ra sự kiện mua hàng. Hoàn lại số Ether dư thừa. : Hoàn lại các khoản chuyển Ether trực tiếp trong trường hợp có người gửi tiền trực tiếp đến địa chỉ hợp đồng. receive() external payable : Trả về tổng số cà phê đã bán. getTotalCoffeesSold() external view returns (uint256) : Trả về tổng số Ether đã nhận. getTotalEtherReceived() external view returns (uint256) Biên soạn Hợp đồng thông minh thanh toán cà phê Ở đây, bạn sẽ sử dụng Hardhat để biên dịch hợp đồng thông minh. Cài đặt Hardhat bằng dấu nhắc lệnh sau. npm install --save-dev hardhat Bạn sẽ nhận được phản hồi bên dưới sau khi cài đặt thành công. Trong cùng thư mục mà bạn khởi tạo hardhat bằng dấu nhắc lệnh này: npx hardhat init Chọn Create a Javascript project bằng nút mũi tên xuống và nhấn Enter. Nhấn enter để cài đặt vào thư mục gốc Chấp nhận tất cả các lời nhắc bằng cách sử dụng phím y trên bàn phím của bạn bao gồm cả các phụ thuộc @nomicfoundation/hardhat-toolbox Bạn thấy phản hồi bên dưới cho thấy bạn đã khởi tạo thành công Bạn sẽ thấy một số thư mục và tệp mới đã được thêm vào dự án của bạn. Ví dụ: Lock.sol , iginition/modules , test/Lock.js và hardhat.config.cjs . Đừng lo lắng về chúng. thư mục thư mục Những cái hữu ích duy nhất là iginition/modules và hardhat.config.cjs . Bạn sẽ biết chúng được sử dụng để làm gì sau này. Hãy thoải mái xóa Lock.sol trong contracts và Lock.js trong iginition/modules . Biên dịch hợp đồng bằng cách sử dụng dấu nhắc lệnh sau: npx hardhat compile Bạn sẽ thấy thêm các thư mục và tập tin như thế này. tệp Bên trong Coffee.json là mã ABI ở định dạng JSON mà bạn sẽ gọi khi tương tác với hợp đồng thông minh. { "_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": {} } Kiểm tra hợp đồng thông minh Viết một tập lệnh kiểm tra tự động trong khi xây dựng hợp đồng thông minh của bạn là rất quan trọng và được khuyến khích. Nó hoạt động như xác thực hai yếu tố (2FA), đảm bảo hợp đồng thông minh của bạn hoạt động như mong đợi trước khi triển khai nó vào mạng trực tiếp. thư mục . Bên trong tệp, dán mã này bên dưới: Trong test , tạo một tệp mới và đặt tên là 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"); }); }); }); Mã này kiểm tra chức năng của hợp đồng thông minh Coffee. Nó bao gồm các bài kiểm tra về triển khai, mua cà phê và xử lý chuyển Ether trực tiếp vào hợp đồng. Sau đây là bảng phân tích: Chức năng Fixture: 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 }; } : Tạo một phiên bản hợp đồng mới và triển khai nó bằng tài khoản của người triển khai. Triển khai hợp đồng Coffee : Ước tính lượng khí cần thiết cho việc triển khai. Ước tính khí : Phiên bản hợp đồng đã triển khai, người triển khai và tài khoản người mua. Trả về Kiểm tra triển khai 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"); }); }); : Đảm bảo rằng và được đặt thành 0 sau khi triển khai. Kiểm tra các giá trị ban đầu totalCoffeesSold totalEtherReceived Mua thử nghiệm cà phê 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); }); }); : Kiểm tra xem việc mua cà phê có cập nhật trạng thái và phát ra sự kiện hay không. Mua cà phê và phát ra sự kiện CoffeePurchased : Đảm bảo giao dịch được hoàn nguyên nếu số lượng bằng 0. Hoàn nguyên khi số lượng bằng 0 : Xác minh rằng và được cập nhật chính xác sau khi mua hàng. Cập nhật trạng thái chính xác totalCoffeesSold totalEtherReceived Kiểm tra chức năng dự phòng 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"); }); }); : Đảm bảo rằng việc gửi Ether trực tiếp đến hợp đồng (không gọi hàm) sẽ hoàn nguyên giao dịch. Hoàn nguyên khi chuyển Ether trực tiếp Kiểm tra hợp đồng thông minh Sau khi bạn đã viết xong kịch bản kiểm tra, bạn sẽ : Khi chạy hợp đồng và thử nghiệm của bạn trên Hardhat Network, bạn có thể in các thông báo ghi nhật ký và các biến hợp đồng gọi console.log() từ mã Solidity của bạn. Để sử dụng nó, bạn phải nhập hardhat/console.sol vào mã hợp đồng của bạn như thế này: //SPDX-License-Identifier: MIT pragma solidity >=0.8.0 <0.9.0; import "hardhat/console.sol"; contract Coffee { //... } Để kiểm tra hợp đồng, hãy chạy lệnh sau trong terminal của bạn: npx hardhat test Bạn sẽ có kết quả đầu ra như bên dưới: Điều này cho thấy hợp đồng thông minh của bạn hoạt động theo đúng mong đợi. Nếu bạn chạy npx hardhat test nó sẽ tự động biên dịch và kiểm tra hợp đồng thông minh. Bạn có thể dùng thử và cho tôi biết trong phần bình luận. Triển khai hợp đồng thông minh Tại đây, bạn sẽ triển khai hợp đồng thông minh của mình lên Testnet cho phép bạn kiểm tra hợp đồng thông minh của mình trong môi trường mô phỏng mạng chính Ethereum mà không phải chịu chi phí đáng kể. Nếu bạn giỏi về chức năng của dApp, sau đó bạn có thể triển khai lại lên Ethereum Mainnet. Sepolia Testnet. Cài đặt gói dotenv và các gói phụ thuộc này. npm install dotenv npm install --save-dev @nomicfoundation/hardhat-web3-v4 'web3@4' Thao tác này sẽ thêm Web3.Js và Dotenv vào dự án của bạn bằng cách đưa nó vào thư mục 'node_modules'. của bạn nhập chúng vào tệp 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", } }; tệp Tạo một .env trong thư mục gốc của bạn. Nhận khóa riêng tư của tài khoản từ ví MetaMask và khóa API dRPC. của bạn. Lưu trữ chúng trong tệp .env DRPC_API_KEY=your_drpc_api_key PRIVATE_KEY=your_wallet_private_key tệp Cập nhật hardhat.config.cjs để bao gồm Cấu hình Sepolia Testnet: 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}`], } } }; thư mục Tạo một tệp tập lệnh mới trong ignition/module và đặt tên là deploy.cjs . Thêm mã sau để triển khai hợp đồng thông minh của bạn: const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules"); const CoffeeModule = buildModule("CoffeeModule", (m) => { const coffee = m.contract("Coffee"); return { coffee }; }); module.exports = CoffeeModule; Triển khai hợp đồng thông minh bằng cách chạy lệnh sau trong thiết bị đầu cuối của bạn: npx hardhat ignition deploy ./ignition/modules/deploy.cjs --network sepolia Sau khi chạy dấu nhắc lệnh, bạn sẽ được yêu cầu , nhập . Bạn sẽ thấy địa chỉ hợp đồng thông minh đã triển khai của mình trong thiết bị đầu cuối sau khi triển khai thành công. Confirm deploy to network sepolia (11155111)? (y/n) y Bạn cũng có thể truy cập địa chỉ hợp đồng trong tệp . deployed_addresses.json Xin chúc mừng, bạn đã triển khai thành công hợp đồng thông minh của mình lên Sepolia Testnet. 🎉 Phần kết luận Bài viết này hướng dẫn bạn cách viết hợp đồng thanh toán thông minh, kiểm tra, biên dịch và triển khai hợp đồng thông minh bằng cách sử dụng hardhat CLI. Trong bài viết tiếp theo, bạn sẽ học cách xây dựng front-end cho dApp này. Giao diện người dùng này sẽ bao gồm: Nhập số lượng cà phê đã mua. Nút kích hoạt giao dịch thanh toán và khấu trừ số tiền đó vào tài khoản của bạn. Hiển thị Tổng số cà phê đã mua và số tiền nhận được bằng ether và USD Giá của cà phê tính theo ether và USD. Thẩm quyền giải quyết Vượt ra ngoài các thông báo mặc định: Làm chủ các lỗi tùy chỉnh trong Solidity Làm chủ lỗi tùy chỉnh trong Solidity: Nâng cao hợp đồng thông minh của bạn vượt xa các thông báo mặc định Hàm Solidity là gì? Sự kiện trong Solidity là gì? ← Bài viết trước Bài viết tiếp theo →