Bevezetés A Web3 DApp fejlesztés technológiai veremének megértése során meg kell tanulnia a web3 dApp fejlesztés alapvető technológiai veremét, az RPC szerepét a dApp fejlesztésben, valamint a dRPC használatával fiók létrehozását, API-kulcs generálását, végpontok és végpontok elemzését. , adjon hozzá pénzt dRPC-számlájához, és ellenőrizze egyenlegét. A dRPC szerepe az intelligens szerződések telepítésében, hogy leegyszerűsítse az Ethereum csomópont beállításának folyamatát, megkönnyítve a fejlesztők számára az interakciót és a telepítést egyetlen kódsor segítségével. Ebben a cikkben egy kávéfizetési intelligens szerződést fog írni, összeállítani, tesztelni és telepíteni az Ethereum Sepolia Testnetre a dRPC végpont és az API kulcs használatával. A szolgáltatások közé tartozik: Fizetés a kávéért A kávé árának felülvizsgálata Az eladott kávé teljes számának és a megtermelt teljes összegnek a lekérése Mossuk be a kezünket. Előfeltételek Már finanszírozza számláját a Sepolia Faucet segítségével. Legyen pénztárcád, pl. Metamask. Kódszerkesztő. Már telepítette az Ön által választott Js-könyvtárakat vagy keretrendszereket (pl. React.js, Next.js stb.). Jar of Water. Szükséges technológiák és eszközök Szilárdság. React.js a Vite.js(Typescript) használatával Hardhat. Web3.js. Dotenv. dRPC API kulcs és végpont. A fiók privát kulcsa. MetaMask A kávéfizetési intelligens szerződés megírása Hozzon létre egy mappát a gyökérkönyvtárban, és nevezze el . contracts Hozzon létre egy fájlt a mappában, és nevezze el -nak. contracts coffee.sol a szilárdságot fogja használni az intelligens szerződés megírásához. A Solidity fájlok kiterjesztéssel vannak elnevezve, mivel ez a Solidity forráskód szabványos fájlkiterjesztése. .sol Adja hozzá a következő forráskódot a fájlhoz: 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; } } Pragma : Ez a licencazonosító azt jelzi, hogy a kód a licence alapján van licencelve. //SPDX-License-Identifier: MIT Massachusetts Institute of Technology (MIT) : Megadja, hogy a kód a 0.8.0 (beleértve) és a 0.9.0 (kizárólagos) közötti Solidity verziókhoz legyen írva. pragma solidity >=0.8.0 <0.9.0; Állapotváltozó uint256 public constant coffeePrice = 0.0002 ether; uint256 public totalCoffeesSold; uint256 public totalEtherReceived; : Állandó értéke . coffeePrice 0.0002 ether : Nyomon követi az eladott kávék számát. totalCoffeesSold : Nyomon követi a szerződés által kapott teljes étert. totalEtherReceived Egyéni hibák A Solidity egyéni hibái olyan . Segíthetnek javítani a felhasználói élményt, és segíthetnek a hibakeresésben és az intelligens szerződések karbantartásában is. hibaüzenetek, amelyek egy adott használati esetre vannak szabva, nem pedig a programozási nyelv által biztosított alapértelmezett hibaüzenetek Egyéni hiba meghatározásához a Solidityben a következő szintaxist használhatja: : Ez a kulcsszó egyéni hiba meghatározására szolgál error Egyedi név: A hibának egyedi névvel kell rendelkeznie Paraméterek: Ha konkrét részleteket vagy paramétereket szeretne szerepeltetni a hibaüzenetben, akkor ezeket a hiba neve után zárójelben adhatja hozzá. error QuantityMustBeGreaterThanZero(); error InsufficientEtherSent(uint256 required, uint256 sent); error DirectEtherTransferNotAllowed(); : Biztosítja, hogy a mennyiség nagyobb legyen nullánál. QuantityMustBeGreaterThanZero() : Biztosítja, hogy a küldött éter elegendő. InsufficientEtherSent(uint256 required, uint256 sent) : Megakadályozza a közvetlen Ether átvitelt a szerződésbe. DirectEtherTransferNotAllowed() Események Az esemény a szerződés része, amely a kibocsátáskor tárolja a tranzakciós naplókban átadott argumentumokat. Az események általában arra szolgálnak, hogy az EVM naplózási funkciójával tájékoztassák a hívó alkalmazást a szerződés aktuális állapotáról. Értesítik az alkalmazásokat a szerződésekben végrehajtott változtatásokról, amelyek azután a kapcsolódó logikát futtathatják. event CoffeePurchased(address indexed buyer, uint256 quantity, uint256 totalCost); : naplózza a kávévásárlásokat. CoffeePurchased(address indexed buyer, uint256 quantity, uint256 totalCost) Funkciók Megszüntetik ugyanazon kódrészlet újraírásának redundanciáját. Ehelyett a fejlesztők szükség esetén meghívhatnak egy függvényt a programban. A függvények önálló kódmodulok, amelyek egy adott feladatot hajtanak végre. 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; } : Kezeli a kávévásárlást és a következő műveleteket végzi: buyCoffee(uint256 quantity) external payable Ellenőrizze, hogy a mennyiség érvényes-e. Kiszámítja a teljes költséget. Biztosítja, hogy elegendő étert küldjön. Frissíti az állapotváltozókat. Kiadja a vásárlási eseményt. A felesleges étert visszatéríti. : Visszaállítja a közvetlen éteres átutalásokat abban az esetben, ha valaki közvetlenül a szerződés címére küld pénzt. receive() external payable : Az összes eladott kávét adja vissza. getTotalCoffeesSold() external view returns (uint256) : A teljes fogadott Ether összegét adja vissza. getTotalEtherReceived() external view returns (uint256) A Kávéfizetési Okos Szerződés összeállítása Itt a Hardhat segítségével fogja összeállítani az intelligens szerződést. Telepítse a Hardhatot a következő parancssor segítségével. npm install --save-dev hardhat A sikeres telepítés után az alábbi választ kapja meg. Ugyanabban a könyvtárban, ahol a hardhat inicializálását ezzel a parancssorral: npx hardhat init Válassza Create a Javascript project lehetőséget a lefelé mutató nyíl gombbal, és nyomja meg az enter billentyűt. Nyomja meg az Enter billentyűt a gyökérmappába való telepítéshez gombjával, beleértve a is Fogadjon el minden felszólítást a billentyűzet y @nomicfoundation/hardhat-toolbox függőségeket Az alábbi válasz azt mutatja, hogy sikeresen inicializált Észre fogja venni, hogy néhány új mappa és fájl került hozzáadásra a projekthez. pl. Lock.sol , iginition/modules , test/Lock.js és hardhat.config.cjs . Ne törődj velük. fájlt Az egyetlen hasznos az iginition/modules és hardhat.config.cjs . Később tudni fogja, mire használják őket. Nyugodtan törölje Lock.sol contracts mappából és Lock.js iginition/modules mappából . Állítsa össze a szerződést a következő parancssor segítségével: npx hardhat compile További ehhez hasonló mappákat és fájlokat lát. A Coffee.json fájlban található az ABI-kód JSON formátumban, amelyet az intelligens szerződéssel való interakció során hív meg. { "_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": {} } Az intelligens szerződés tesztelése Az intelligens szerződés felépítése közben egy automatizált tesztszkript megírása kulcsfontosságú és erősen ajánlott. Kéttényezős hitelesítésként (2FA) működik, amely biztosítja, hogy az intelligens szerződés a várt módon működjön, mielőtt üzembe helyezné az élő hálózaton. A cjs. Illessze be a következő kódot a fájlba: test alatt hozzon létre egy új fájlt, és nevezze el 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"); }); }); }); Ez a kód teszteli a Coffee smart contract működését. Tartalmazza a telepítési teszteket, a kávévásárlást és a szerződésbe történő közvetlen Ether-átvitelek kezelését. Itt van egy bontás: Fixture funkció: 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 }; } : Létrehoz egy új szerződéspéldányt, és üzembe helyezi azt a telepítői fiók használatával. Telepíti a Coffee-szerződést : Becsli a telepítéshez szükséges gáz mennyiségét. Becsült gáz : A telepített szerződéspéldány, a telepítői és a vevői fiókok. Visszatér Telepítési tesztek 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"); }); }); : biztosítja, hogy és nullára legyen állítva a telepítés után. Ellenőrzi a kezdeti értékeket totalCoffeesSold totalEtherReceived Kávé tesztek vásárlása 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); }); }); : Azt teszteli, hogy a kávévásárlás frissíti-e az állapotot, és kiadja a eseményt. Kávé vásárlása és esemény kibocsátása CoffeePurchased : Biztosítja, hogy a tranzakció visszaálljon, ha a mennyiség nulla. Nulla mennyiség visszaállítása : Ellenőrzi, hogy és megfelelően frissül-e a vásárlás után. Frissítési állapot helyesen totalCoffeesSold totalEtherReceived Tartalékfunkció teszt 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"); }); }); : Biztosítja, hogy az Ether közvetlenül a szerződéshez történő küldése (függvény hívása nélkül) visszaállítja a tranzakciót. Visszaállítás közvetlen éteres átvitelre Az intelligens szerződés tesztelése Miután megírta a tesztszkriptet, a következőket fogja tenni : függvényt hívják Amikor szerződéseit és tesztjeit a Hardhat hálózaton futtatja, kinyomtathatja a naplózási üzeneteket és a szerződésváltozókat, amelyek console.log() a Solidity kódból. Használatához importálnia kell hardhat/console.sol fájlt a szerződés kódjába, így: //SPDX-License-Identifier: MIT pragma solidity >=0.8.0 <0.9.0; import "hardhat/console.sol"; contract Coffee { //... } A szerződés teszteléséhez futtassa a következő parancsot a terminálon: npx hardhat test Az alábbiakhoz hasonló kimenettel kell rendelkeznie: Ez azt mutatja, hogy az intelligens szerződés a várt módon működik. Ha npx hardhat test futtatja, az automatikusan lefordítja és teszteli az intelligens szerződést. Kipróbálhatod, és tudasd velem a megjegyzés rovatban. Az intelligens szerződés bevezetése Itt helyezi üzembe intelligens szerződését A Testnet lehetővé teszi az intelligens szerződés tesztelését olyan környezetben, amely az Ethereum főhálózatát utánozza jelentős költségek nélkül. Ha jól ismeri a dApp funkcióját, akkor újratelepítheti az Ethereum Mainnetre. a Sepolia Testnet számára. Telepítse a dotenv csomagot és ezeket a függőségeket. npm install dotenv npm install --save-dev @nomicfoundation/hardhat-web3-v4 'web3@4' Ezzel hozzáadja a Web3.Js-t és a Dotenv-t a projektjéhez a „node_modules” mappába helyezve. importálja őket hardhat.config.cjs fájlba require('dotenv').config(); require("@nomicfoundation/hardhat-toolbox"); require("@nomicfoundation/hardhat-web3-v4"); const HardhatUserConfig = require("hardhat/config"); module.exports = { solidity: "0.8.24", } }; Hozzon létre egy .env fájlt a gyökérmappában. Szerezze be fiókja privát kulcsát MetaMask pénztárcájából és dRPC API-kulcsából. Tárolja őket az .env fájljában. DRPC_API_KEY=your_drpc_api_key PRIVATE_KEY=your_wallet_private_key Frissítse a hardhat.config.cjs fájlt, hogy tartalmazza a Sepolia Testnet konfigurációt: 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}`], } } }; Hozzon létre egy új szkriptfájlt az ignition/module mappában, és nevezze el deploy.cjs . Adja hozzá a következő kódot az intelligens szerződés üzembe helyezéséhez: const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules"); const CoffeeModule = buildModule("CoffeeModule", (m) => { const coffee = m.contract("Coffee"); return { coffee }; }); module.exports = CoffeeModule; Telepítse az intelligens szerződést a következő parancs futtatásával a terminálon: npx hardhat ignition deploy ./ignition/modules/deploy.cjs --network sepolia A parancssor futtatása után a rendszer felkéri , írja be az . Sikeres üzembe helyezéskor látnia kell a telepített intelligens szerződés címét a terminálban. Confirm deploy to network sepolia (11155111)? (y/n) y A szerződés címét a fájlban is elérheti. deployed_addresses.json Gratulálunk, sikeresen telepítette intelligens szerződését a Sepolia Testnet rendszerbe. 🎉 Következtetés Ez a cikk megtanította Önnek, hogyan írjon fizetési intelligens szerződést, hogyan tesztelje, fordítsa le és telepítse az intelligens szerződést a hardhat CLI használatával. A következő cikkben megtudhatja, hogyan kell elkészíteni a dApp kezelőfelületét. Ez a felhasználói felület a következőkből áll majd: A vásárolt kávék számának beviteli mezője. Egy gomb, amely fizetési tranzakciót indít el, és levonja azt a számlájáról. Megjelenítés Az összes vásárolt kávé és a kapott összeg éterben és USD-ben Maga a kávé ára éterben és USD-ben egyaránt. Referencia Az alapértelmezett üzeneteken túl: Az egyéni hibák elsajátítása a Solidityben Az egyéni hibák elsajátítása a Solidityben: Emelje az intelligens szerződéseit az alapértelmezett üzeneteken túl Mik azok a szilárdsági függvények? Mik azok az események a Solidityben? ← Előző cikk Következő cikk →