Perkenalan
Dari pemahaman tumpukan teknologi untuk pengembangan DApp Web3, Anda pasti telah mempelajari tumpukan teknologi inti untuk pengembangan dApp web3, peran RPC dalam pengembangan dApp, dan cara menggunakan dRPC untuk membuat akun, menghasilkan kunci API, titik akhir, analisis titik akhir, menambahkan dana ke Akun dRPC Anda, dan memeriksa saldo Anda.
Peran dRPC dalam penerapan kontrak pintar adalah untuk menyederhanakan proses penyiapan node Ethereum, sehingga memudahkan pengembang untuk berinteraksi dan menerapkan hanya dengan satu baris kode.
Dalam artikel ini, Anda akan menulis, mengompilasi, menguji, dan menyebarkan kontrak pintar pembayaran kopi ke Ethereum Sepolia Testnet menggunakan titik akhir dRPC dan kunci API.
Fitur-fiturnya meliputi:
- Pembayaran Kopi
- Meninjau Harga Kopi
- Mengambil Jumlah Total Kopi yang terjual dan Jumlah Total Uang yang dihasilkan
Ayo kita kotori tanganmu.
Prasyarat
- Danai akun Anda dengan Sepolia Faucet.
- Punya Dompet misalnya Metamask.
- Editor Kode.
- Sudah menginstal pustaka Js atau kerangka kerja pilihan Anda (misalnya React.js, Next.js, dll).
- Toples Air.
Teknologi dan Alat yang Dibutuhkan
- Kepadatan.
- React.js menggunakan Vite.js (Typescript)
- Helm.
- Web3.js.
- Dotenv.
- Kunci dan Titik Akhir API dRPC.
- Kunci pribadi akun Anda.
- Masker Meta
Menulis Kontrak Cerdas Pembayaran Kopi
Buat Folder di bawah direktori root Anda, dan beri nama
contracts
.
Buat File di bawah folder
contracts
, dan beri namacoffee.sol
.
Anda akan menggunakan solidity untuk menulis kontrak pintar. File solidity diberi nama dengan ekstensi
.sol
karena merupakan ekstensi file standar untuk kode sumber Solidity.
Tambahkan kode sumber berikut ke
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
-
//SPDX-License-Identifier: MIT
: Pengidentifikasi lisensi ini menunjukkan bahwa kode tersebut dilisensikan di bawah Lisensi Massachusetts Institute of Technology (MIT) .
-
pragma solidity >=0.8.0 <0.9.0;
: Menentukan bahwa kode ditulis untuk versi Solidity antara 0.8.0 (inklusif) dan 0.9.0 (eksklusif).
Variabel Negara
uint256 public constant coffeePrice = 0.0002 ether; uint256 public totalCoffeesSold; uint256 public totalEtherReceived;
-
coffeePrice
: Ditetapkan sebagai nilai konstan0.0002 ether
. -
totalCoffeesSold
: Melacak jumlah kopi yang terjual. -
totalEtherReceived
: Melacak total Ether yang diterima oleh kontrak.
Kesalahan Kustom
Kesalahan khusus dalam Solidity adalah pesan kesalahan yang disesuaikan dengan kasus penggunaan tertentu, bukan pesan kesalahan default yang disediakan oleh bahasa pemrograman . Pesan kesalahan ini dapat membantu meningkatkan pengalaman pengguna, dan juga dapat membantu dalam debugging dan pemeliharaan kontrak pintar.
Untuk menentukan kesalahan khusus di Solidity, Anda dapat menggunakan sintaks berikut:
-
error
: Kata kunci ini digunakan untuk menentukan kesalahan khusus - Nama unik: Kesalahan harus memiliki nama yang unik
- Parameter: Jika Anda ingin menyertakan rincian atau parameter tertentu dalam pesan kesalahan, Anda dapat menambahkannya dalam tanda kurung setelah nama kesalahan.
error QuantityMustBeGreaterThanZero(); error InsufficientEtherSent(uint256 required, uint256 sent); error DirectEtherTransferNotAllowed();
-
QuantityMustBeGreaterThanZero()
: Memastikan kuantitas lebih besar dari nol. -
InsufficientEtherSent(uint256 required, uint256 sent)
: Memastikan Ether yang dikirim mencukupi. -
DirectEtherTransferNotAllowed()
: Mencegah transfer Ether langsung ke kontrak.
Acara
Peristiwa adalah bagian dari kontrak yang menyimpan argumen yang diteruskan dalam log transaksi saat dipancarkan. Peristiwa biasanya digunakan untuk memberi tahu aplikasi pemanggil tentang status kontrak saat ini menggunakan fitur pencatatan EVM. Peristiwa memberi tahu aplikasi tentang perubahan yang dibuat pada kontrak, yang kemudian dapat digunakan untuk menjalankan logika terkait.
event CoffeePurchased(address indexed buyer, uint256 quantity, uint256 totalCost);
-
CoffeePurchased(address indexed buyer, uint256 quantity, uint256 totalCost)
: Mencatat pembelian kopi.
Fungsi
Fungsi adalah modul kode mandiri yang menyelesaikan tugas tertentu. Fungsi menghilangkan redundansi penulisan ulang bagian kode yang sama. Sebaliknya, pengembang dapat memanggil fungsi dalam program saat diperlukan.
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
: Menangani pembelian kopi dan melakukan operasi berikut:- Periksa apakah jumlahnya valid.
- Menghitung total biaya.
- Memastikan Ether yang dikirim cukup.
- Memperbarui variabel status.
- Memancarkan peristiwa pembelian.
- Mengembalikan kelebihan Ether.
-
receive() external payable
: Mengembalikan transfer Ether langsung jika seseorang mengirim dana ke alamat kontrak secara langsung. -
getTotalCoffeesSold() external view returns (uint256)
: Mengembalikan total kopi yang terjual. -
getTotalEtherReceived() external view returns (uint256)
: Mengembalikan total Ether yang diterima.
Menyusun Kontrak Cerdas Pembayaran Kopi
Di sini, Anda akan menggunakan Hardhat untuk mengkompilasi kontrak pintar.
Instal Hardhat menggunakan prompt perintah berikut.
npm install --save-dev hardhat
Anda akan mendapatkan respons di bawah ini setelah instalasi berhasil.
Di direktori yang sama tempat Anda menginisialisasi hardhat menggunakan command prompt ini:
npx hardhat init
Pilih
Create a Javascript project
dengan menggunakan tombol panah bawah dan tekan enter.Tekan enter untuk menginstal di folder root
Terima semua perintah menggunakan
y
pada keyboard Anda termasuk dependensi@nomicfoundation/hardhat-toolbox
Anda melihat respons di bawah ini yang menunjukkan bahwa Anda telah berhasil menginisialisasi
Anda akan melihat beberapa folder dan file baru telah ditambahkan ke proyek Anda. Misalnya,
Lock.sol
,iginition/modules
,test/Lock.js
, danhardhat.config.cjs
. Jangan khawatir tentang hal itu.
Satu-satunya yang berguna adalah
iginition/modules
danhardhat.config.cjs
. Anda akan tahu kegunaannya nanti. Jangan ragu untuk menghapusLock.sol
di foldercontracts
danLock.js
di folderiginition/modules
.
Kompilasi kontrak menggunakan prompt perintah berikut:
npx hardhat compile
- Anda melihat folder dan file tambahan seperti ini.
- Di dalam file
Coffee.json
terdapat kode ABI dalam format JSON yang akan Anda panggil saat berinteraksi dengan kontrak pintar.
{ "_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": {} }
Menguji Kontrak Cerdas
Menulis skrip pengujian otomatis saat membangun kontrak pintar Anda sangat penting dan sangat direkomendasikan. Skrip ini berfungsi seperti autentikasi dua faktor (2FA), yang memastikan bahwa kontrak pintar Anda berfungsi seperti yang diharapkan sebelum menerapkannya ke jaringan aktif.
Di bawah folder test
buat file baru, dan beri nama Coffee.
. Di dalam file tersebut, tempel kode berikut ini:
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"); }); }); });
Kode ini menguji fungsionalitas kontrak pintar Coffee. Kode ini mencakup pengujian untuk penerapan, pembelian kopi, dan penanganan transfer Ether langsung ke kontrak.
Berikut rinciannya:
Fungsi Perlengkapan: 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 }; }
- Menyebarkan kontrak Kopi : Membuat contoh kontrak baru dan menyebarkannya menggunakan akun penyebar.
- Perkiraan gas : Memperkirakan gas yang dibutuhkan untuk penyebaran.
- Pengembalian : Akun instansi kontrak yang diterapkan, akun penyebar, dan akun pembeli.
Uji Penerapan
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"); }); });
- Memeriksa nilai awal : Memastikan bahwa
totalCoffeesSold
dantotalEtherReceived
ditetapkan ke nol setelah penerapan.
Membeli Tes Kopi
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); }); });
- Membeli kopi dan memancarkan suatu peristiwa : Menguji bahwa pembelian kopi memperbarui status dan memancarkan peristiwa
CoffeePurchased
. - Mengembalikan pada jumlah nol : Memastikan transaksi kembali jika jumlahnya nol.
- Memperbarui status dengan benar : Memverifikasi bahwa
totalCoffeesSold
dantotalEtherReceived
diperbarui dengan benar setelah pembelian.
Uji Fungsi Fallback
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"); }); });
- Mengembalikan pada transfer Ether langsung : Memastikan bahwa pengiriman Ether langsung ke kontrak (tanpa memanggil fungsi) mengembalikan transaksi.
Menguji Kontrak Cerdas
Setelah Anda menulis skrip pengujian, Anda akan :
- Saat menjalankan kontrak dan pengujian di Hardhat Network, Anda dapat mencetak pesan pencatatan dan variabel kontrak yang memanggil
console.log()
dari kode Solidity Anda. Untuk menggunakannya, Anda harus mengimporhardhat/console.sol
dalam kode kontrak Anda seperti ini:
//SPDX-License-Identifier: MIT pragma solidity >=0.8.0 <0.9.0; import "hardhat/console.sol"; contract Coffee { //... }
Untuk menguji kontrak, jalankan perintah berikut di terminal Anda:
npx hardhat test
Anda seharusnya mendapatkan keluaran seperti di bawah ini:
Ini menunjukkan bahwa kontrak pintar Anda berfungsi sebagaimana diharapkan.
Jika Anda menjalankan
npx hardhat test
ia akan secara otomatis mengkompilasi dan menguji kontrak pintar. Anda dapat mencobanya dan memberi tahu saya di bagian komentar.
Menerapkan Kontrak Cerdas
Di sini, Anda akan menerapkan kontrak pintar Anda ke Sepolia Testnet. Testnet memungkinkan Anda menguji kontrak pintar Anda di lingkungan yang menyerupai mainnet Ethereum tanpa menimbulkan biaya yang signifikan. Jika Anda merasa nyaman dengan fungsi dApp, Anda dapat menerapkannya kembali ke Ethereum Mainnet.
Instal paket dotenv dan dependensi ini.
npm install dotenv npm install --save-dev @nomicfoundation/hardhat-web3-v4 'web3@4'
Ini akan menambahkan Web3.Js dan Dotenv ke proyek Anda dengan memasukkannya dalam folder 'node_modules'.
impor mereka ke file
hardhat.config.cjs
Andarequire('dotenv').config(); require("@nomicfoundation/hardhat-toolbox"); require("@nomicfoundation/hardhat-web3-v4"); const HardhatUserConfig = require("hardhat/config"); module.exports = { solidity: "0.8.24", } };
Buat file
.env
di folder root Anda.
Dapatkan kunci pribadi akun Anda dari dompet MetaMask dan kunci API dRPC Anda.
Simpan dalam file
.env
Anda .DRPC_API_KEY=your_drpc_api_key PRIVATE_KEY=your_wallet_private_key
Perbarui file
hardhat.config.cjs
untuk menyertakan Konfigurasi Testnet 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}`], } } };
Buat file skrip baru di folder
ignition/module
, dan beri namadeploy.cjs
. Tambahkan kode berikut untuk menerapkan kontrak pintar Anda:const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules"); const CoffeeModule = buildModule("CoffeeModule", (m) => { const coffee = m.contract("Coffee"); return { coffee }; }); module.exports = CoffeeModule;
Terapkan kontrak pintar dengan menjalankan perintah berikut di terminal Anda:
npx hardhat ignition deploy ./ignition/modules/deploy.cjs --network sepolia
Setelah menjalankan command prompt, Anda akan diminta untuk
Confirm deploy to network sepolia (11155111)? (y/n)
, ketiky
. Anda akan melihat alamat kontrak pintar yang Anda deploy di terminal setelah deploy berhasil.Anda juga dapat mengakses alamat kontrak di file
deployed_addresses.json
.Selamat, Anda telah berhasil menerapkan kontrak pintar Anda ke Sepolia Testnet.
Kesimpulan
Artikel ini mengajarkan Anda cara menulis kontrak pintar pembayaran, menguji, mengompilasi, dan menyebarkan kontrak pintar menggunakan hardhat CLI.
Pada artikel berikutnya, Anda akan mempelajari cara membangun front end untuk dApp ini. UI ini akan terdiri dari:
- Kolom input untuk jumlah Kopi yang dibeli.
- Tombol yang memicu transaksi pembayaran dan memotongnya dari akun Anda.
- Menampilkan Total kopi yang dibeli dan jumlah yang diterima dalam ether dan USD
- Harga Kopi itu sendiri baik dalam ether maupun USD.
Referensi
Melampaui Pesan Default: Menguasai Kesalahan Kustom di Solidity
Menguasai Kesalahan Kustom di Solidity: Tingkatkan Kontrak Cerdas Anda Melampaui Pesan Default