This content originally appeared on HackerNoon and was authored by Ileolami
Introduction
From understanding the tech stack for Web3 DApp development, you must have learned the core tech stack for web3 dApp development, the role of RPC in dApp development, and how to use dRPC to create an account, generate an API key, endpoints, endpoints analytics, add funds to your dRPC Account, and check your balance.
\ The role of dRPC in deploying smart contracts is to simplify the process of setting up an Ethereum node, making it easier for developers to interact and deploy with just one line of code.
\ In this article, you will write, compile, test, and deploy a coffee payment smart contract to Ethereum Sepolia Testnet using dRPC endpoint and API key.
\ The features include:
- Payment for Coffee
- Reviewing the price of Coffee
- Retrieving Total number of Coffee sold and Total amount of money made
\ Let’s get your hands dirty.
Prerequisites
- Already fund your account with Sepolia Faucet.
- Have a Wallet e.g., Metamask.
- Code Editor.
- Already installed any Js libraries or frameworks of your choice (e.g React.js, Next.js etc).
- Jar of Water.
Technologies and Tools Needed
- Solidity.
- React.js using Vite.js(Typescript)
- Hardhat.
- Web3.js.
- Dotenv.
- dRPC API Key and Endpoint.
- Your account private key.
- MetaMask
Writing the Coffee Payment Smart Contract
Create a Folder under your root directory, and name it
contracts
.\
Create a File under the
contracts
folder, and name itcoffee.sol
.
you will be using solidity to write the smart contract. Solidity files are named with the
.sol
extension because it is the standard file extension for Solidity source code.
\
- Add the following source code to the
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
: This license identifier indicates that the code is licensed under the Massachusetts Institute of Technology (MIT) License.
\
pragma solidity >=0.8.0 <0.9.0;
: Specifies that the code is written for Solidity versions between 0.8.0 (inclusive) and 0.9.0 (exclusive).
State Variable
uint256 public constant coffeePrice = 0.0002 ether;
uint256 public totalCoffeesSold;
uint256 public totalEtherReceived;
coffeePrice
: Set as a constant value of0.0002 ether
.totalCoffeesSold
: Tracks the number of coffees sold.totalEtherReceived
: Tracks the total Ether received by the contract.
Custom Errors
Custom errors in Solidity are error messages that are tailored to a specific use case, rather than the default error messages that are provided by the programming language. They can help improve the user experience, and can also help with debugging and maintaining smart contracts.
\ To define a custom error in Solidity, you can use the following syntax:
error
: This keyword is used to define a custom error- Unique name: The error must have a unique name
- Parameters: If you want to include specific details or parameters in the error message, you can add them in parentheses after the error name.
error QuantityMustBeGreaterThanZero();
error InsufficientEtherSent(uint256 required, uint256 sent);
error DirectEtherTransferNotAllowed();
QuantityMustBeGreaterThanZero()
: Ensures the quantity is greater than zero.InsufficientEtherSent(uint256 required, uint256 sent)
: Ensures the Ether sent is sufficient.DirectEtherTransferNotAllowed()
: Prevents direct Ether transfers to the contract.
Events
An event is a part of the contract that stores the arguments passed in the transaction logs when emitted. Events are usually used to inform the calling application about the contract's current state using the EVM's logging feature. They notify applications about changes made to the contracts, which can then be used to run related logic.
event CoffeePurchased(address indexed buyer, uint256 quantity, uint256 totalCost);
CoffeePurchased(address indexed buyer, uint256 quantity, uint256 totalCost)
: Logs coffee purchases.
Functions
Functions are self-contained modules of code that accomplish a specific task. They eliminate the redundancy of rewriting the same piece of code. Instead, devs can call a function in the program when it’s necessary.
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
: Handles coffee purchases and carries out the following operations:- Check if the quantity is valid.
- Calculates the total cost.
- Ensures sufficient Ether is sent.
- Updates state variables.
- Emits the purchase event.
- Refunds excess Ether.
receive() external payable
: Reverts direct Ether transfers in case someone send funds to the contract address directly.getTotalCoffeesSold() external view returns (uint256)
: Returns the total coffees sold.getTotalEtherReceived() external view returns (uint256)
: Returns the total Ether received.
Compiling the Coffee Payment Smart Contract
Here, you will be using Hardhat to compile the smart contract.
\
- Install Hardhat using the following command prompt.
npm install --save-dev hardhat
\ You will get the response below after a successful installation.
![A terminal displaying the output of installing hardhat..](https://cdn.hackernoon.com/images/h9zh5laZcuVhjnaK4lBLPxYH14f2-2024-09-11T21:19:21.984Z-iwm2mblxptkarw3fjvhwdoac)
\
- In the same directory where you initialize hardhat using this command prompt:
npx hardhat init
\
Select
Create a Javascript project
by using the arrow down button and press enter.Press enter to install in the root folder
Accept all the prompts using the
y
on your keyboard including the@nomicfoundation/hardhat-toolbox
dependenciesYou see this response below showcasing that you've successfully initialize
You will notce some new folders and files have been added to your project. e.g.,
Lock.sol
,iginition/modules
,test/Lock.js
andhardhat.config.cjs
. Don't worry about them.\ The only useful one are the
iginition/modules
andhardhat.config.cjs
. You will know what they are used for later on. Feel free to deleteLock.sol
undercontracts
folder andLock.js
underiginition/modules
folder.
\
- Compile the contract using the following command prompt:
npx hardhat compile
You see additional folders and files like this.
Inside the
Coffee.json
file is the ABI code in JSON format which you will call when interacting with the smart contract.
{
"_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": "0x608060405234801561001057600080fd5b506107d7806100206000396000f3fe6080604052600436106100595760003560e01c80631c8a403814610094578063657b2d89146100bf5780637ef3e741146100ea5780639fd66f9014610115578063b03b4a2914610140578063e926b8d01461015c5761008f565b3661008f576040517ebbbfa300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600080fd5b3480156100a057600080fd5b506100a9610187565b6040516100b69190610537565b60405180910390f35b3480156100cb57600080fd5b506100d4610191565b6040516100e19190610537565b60405180910390f35b3480156100f657600080fd5b506100ff6101dc565b60405161010c9190610537565b60405180910390f35b34801561012157600080fd5b5061012a6101e2565b6040516101379190610537565b60405180910390f35b61015a60048036038101906101559190610583565b6101e8565b005b34801561016857600080fd5b506101716103e7565b60405161017e9190610537565b60405180910390f35b65b5e620f4800081565b60006101d46040518060400160405280601781526020017f676574546f74616c45746865725265636569766564203a000000000000000000815250600154610432565b600154905090565b60015481565b60005481565b60008111610222576040517f0cdcd02000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008165b5e620f4800061023691906105df565b90508034111561027f5780346040517f8ad0844f000000000000000000000000000000000000000000000000000000008152600401610276929190610621565b60405180910390fd5b81600080828254610290919061064a565b9250508190555080600160008282546102a9919061064a565b925050819055506102f16040518060400160405280601d81526020017f546f74616c20657468657220726563656976656420757064617465643a000000815250600154610432565b6103326040518060400160405280601a81526020017f546f74616c20636f6666656520736f6c6420757064617465643a000000000000815250600054610432565b3373ffffffffffffffffffffffffffffffffffffffff167fb706f7a46856e7a0e4f8f504c23f2ac26950209db23c2125108eed5ef9e333d3838360405161037a929190610621565b60405180910390a2803411156103e35760008134610398919061067e565b90503373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f193505050501580156103e0573d6000803e3d6000fd5b50505b5050565b600061042a6040518060400160405280601581526020017f676574546f74616c436f6666656573536f6c64203a0000000000000000000000815250600054610432565b600054905090565b6104ca8282604051602401610448929190610742565b6040516020818303038152906040527fb60e72cc000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506104ce565b5050565b6104e5816104dd6104e8610509565b63ffffffff16565b50565b60006a636f6e736f6c652e6c6f679050600080835160208501845afa505050565b610514819050919050565b61051c610772565b565b6000819050919050565b6105318161051e565b82525050565b600060208201905061054c6000830184610528565b92915050565b600080fd5b6105608161051e565b811461056b57600080fd5b50565b60008135905061057d81610557565b92915050565b60006020828403121561059957610598610552565b5b60006105a78482850161056e565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006105ea8261051e565b91506105f58361051e565b92508282026106038161051e565b9150828204841483151761061a576106196105b0565b5b5092915050565b60006040820190506106366000830185610528565b6106436020830184610528565b9392505050565b60006106558261051e565b91506106608361051e565b9250828201905080821115610678576106776105b0565b5b92915050565b60006106898261051e565b91506106948361051e565b92508282039050818111156106ac576106ab6105b0565b5b92915050565b600081519050919050565b600082825260208201905092915050565b60005b838110156106ec5780820151818401526020810190506106d1565b60008484015250505050565b6000601f19601f8301169050919050565b6000610714826106b2565b61071e81856106bd565b935061072e8185602086016106ce565b610737816106f8565b840191505092915050565b6000604082019050818103600083015261075c8185610709565b905061076b6020830184610528565b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052605160045260246000fdfea264697066735822122006c7d91368c8390cb4f21f6314ccd362b6d56cb17994af7009b53e7fb92411a864736f6c63430008180033",
"deployedBytecode": "0x6080604052600436106100595760003560e01c80631c8a403814610094578063657b2d89146100bf5780637ef3e741146100ea5780639fd66f9014610115578063b03b4a2914610140578063e926b8d01461015c5761008f565b3661008f576040517ebbbfa300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600080fd5b3480156100a057600080fd5b506100a9610187565b6040516100b69190610537565b60405180910390f35b3480156100cb57600080fd5b506100d4610191565b6040516100e19190610537565b60405180910390f35b3480156100f657600080fd5b506100ff6101dc565b60405161010c9190610537565b60405180910390f35b34801561012157600080fd5b5061012a6101e2565b6040516101379190610537565b60405180910390f35b61015a60048036038101906101559190610583565b6101e8565b005b34801561016857600080fd5b506101716103e7565b60405161017e9190610537565b60405180910390f35b65b5e620f4800081565b60006101d46040518060400160405280601781526020017f676574546f74616c45746865725265636569766564203a000000000000000000815250600154610432565b600154905090565b60015481565b60005481565b60008111610222576040517f0cdcd02000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008165b5e620f4800061023691906105df565b90508034111561027f5780346040517f8ad0844f000000000000000000000000000000000000000000000000000000008152600401610276929190610621565b60405180910390fd5b81600080828254610290919061064a565b9250508190555080600160008282546102a9919061064a565b925050819055506102f16040518060400160405280601d81526020017f546f74616c20657468657220726563656976656420757064617465643a000000815250600154610432565b6103326040518060400160405280601a81526020017f546f74616c20636f6666656520736f6c6420757064617465643a000000000000815250600054610432565b3373ffffffffffffffffffffffffffffffffffffffff167fb706f7a46856e7a0e4f8f504c23f2ac26950209db23c2125108eed5ef9e333d3838360405161037a929190610621565b60405180910390a2803411156103e35760008134610398919061067e565b90503373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f193505050501580156103e0573d6000803e3d6000fd5b50505b5050565b600061042a6040518060400160405280601581526020017f676574546f74616c436f6666656573536f6c64203a0000000000000000000000815250600054610432565b600054905090565b6104ca8282604051602401610448929190610742565b6040516020818303038152906040527fb60e72cc000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506104ce565b5050565b6104e5816104dd6104e8610509565b63ffffffff16565b50565b60006a636f6e736f6c652e6c6f679050600080835160208501845afa505050565b610514819050919050565b61051c610772565b565b6000819050919050565b6105318161051e565b82525050565b600060208201905061054c6000830184610528565b92915050565b600080fd5b6105608161051e565b811461056b57600080fd5b50565b60008135905061057d81610557565b92915050565b60006020828403121561059957610598610552565b5b60006105a78482850161056e565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006105ea8261051e565b91506105f58361051e565b92508282026106038161051e565b9150828204841483151761061a576106196105b0565b5b5092915050565b60006040820190506106366000830185610528565b6106436020830184610528565b9392505050565b60006106558261051e565b91506106608361051e565b9250828201905080821115610678576106776105b0565b5b92915050565b60006106898261051e565b91506106948361051e565b92508282039050818111156106ac576106ab6105b0565b5b92915050565b600081519050919050565b600082825260208201905092915050565b60005b838110156106ec5780820151818401526020810190506106d1565b60008484015250505050565b6000601f19601f8301169050919050565b6000610714826106b2565b61071e81856106bd565b935061072e8185602086016106ce565b610737816106f8565b840191505092915050565b6000604082019050818103600083015261075c8185610709565b905061076b6020830184610528565b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052605160045260246000fdfea264697066735822122006c7d91368c8390cb4f21f6314ccd362b6d56cb17994af7009b53e7fb92411a864736f6c63430008180033",
"linkReferences": {},
"deployedLinkReferences": {}
}
Testing the Smart Contract
Writing an automated test script while building your smart contract is crucial and highly recommended. It acts like a two-factor authentication (2FA), ensuring that your smart contract performs as expected before deploying it to the live network.
\
Under the test
folder create a new file, and name it Coffee.
cjs. Inside the file, paste this code below :
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");
});
});
});
This code tests the functionality of the Coffee smart contract. It includes tests for deployment, buying coffee, and handling direct Ether transfers to the contract.
\ Here is a breakdown:
Fixture Function: 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 };
}
- Deploys the Coffee contract: Creates a new contract instance and deploys it using the deployer's account.
- Estimates gas: Estimates the gas required for deployment.
- Returns: The deployed contract instance, deployer, and buyer accounts.
Deployment Tests
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");
});
});
- Checks initial values: Ensures that
totalCoffeesSold
andtotalEtherReceived
are set to zero after deployment.
Buying Coffee Tests
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);
});
});
- Purchasing coffee and emitting an event: Tests that buying coffee updates the state and emits the
CoffeePurchased
event. - Reverting on zero quantity: Ensures the transaction reverts if the quantity is zero.
- Updating state correctly: Verifies that
totalCoffeesSold
andtotalEtherReceived
are updated correctly after a purchase.
Fallback Function Test
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");
});
});
- Reverting on direct Ether transfer: Ensures that sending Ether directly to the contract (without calling a function) reverts the transaction.
Testing the Smart Contract
After you have written the test script, you will:
- When running your contracts and tests on Hardhat Network, you can print logging messages and contract variables calling
console.log()
from your Solidity code. To use it, you have to importhardhat/console.sol
in your contract code like this:
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;
import "hardhat/console.sol";
contract Coffee {
//...
}
- To test the contract, run the following command in your terminal:
npx hardhat test
\
- You should have an output like this below:
This shows that your smart contract functions the way it is expected.
If you run
npx hardhat test
it automatically compile and test the smart contract. You can try it out and let me know in the comment section.
Deploying the Smart Contract
Here, you will be deploying your smart contract to Sepolia Testnet. Testnet allows you to test your smart contract in an environment that mimics the Ethereum mainnet without incurring significant costs. If you are good with the function of the dApp, you can then redeploy to Ethereum Mainnet.
\
- Install the dotenv package and these dependencies.
npm install dotenv
npm install --save-dev @nomicfoundation/hardhat-web3-v4 'web3@4'
This will add Web3.Js and Dotenv to your project by including it in the 'node_modules' folder.
\
- import them into your
hardhat.config.cjs
file
require('dotenv').config();
require("@nomicfoundation/hardhat-toolbox");
require("@nomicfoundation/hardhat-web3-v4");
const HardhatUserConfig = require("hardhat/config");
module.exports = {
solidity: "0.8.24",
}
};
Create an
.env
file in your root folder.\
Get your account private key from your MetaMask wallet and dRPC API key.
\
Store them in your
.env
file.
DRPC_API_KEY=your_drpc_api_key
PRIVATE_KEY=your_wallet_private_key
\
- Update the
hardhat.config.cjs
file to include the Sepolia Testnet Configuration:
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}`],
}
}
};
- Create a new script file under the
ignition/module
folder, and name itdeploy.cjs
. Add the following code to deploy your smart contract:
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");
const CoffeeModule = buildModule("CoffeeModule", (m) => {
const coffee = m.contract("Coffee");
return { coffee };
});
module.exports = CoffeeModule;
- Deploy the smart contract by running the following command in your terminal:
npx hardhat ignition deploy ./ignition/modules/deploy.cjs --network sepolia
\
After running the command prompt, you will be asked to Confirm deploy to network sepolia (11155111)? (y/n)
, type in y
. You should see the address of your deployed smart contract in the terminal upon successful deployment.
![](https://cdn.hackernoon.com/images/h9zh5laZcuVhjnaK4lBLPxYH14f2-2024-09-11T21:19:22.321Z-z91qwgqtph3grtvm5c8ittg1)
You can also access the contract address in the deployed_addresses.json
file.
![Screenshot of a Vite-Project file explorer and a deployed_addresses.json file opened. ](https://cdn.hackernoon.com/images/h9zh5laZcuVhjnaK4lBLPxYH14f2-2024-09-11T21:19:22.296Z-ucw8oinon8gx51r964gwyai4)*Congratulations, you have successfully deployed your smart contract to Sepolia Testnet. 🎉*
Conclusion
This article has taught you how to write payment smart contract, test, compile and deploy smart contract using the hardhat CLI.
\ In the next article, you will learn to build the front end for this dApp. This UI will consist of:
- Input field for the number of Coffees bought.
- A button that triggers a payment transaction and deducts it from your account.
- Display Total coffee bought and the amount received in ether and USD
- The price of Coffee itself both in ether and USD.
Reference
Beyond Default Messages: Mastering Custom Errors in Solidity
Mastering Custom Errors in Solidity: Elevate Your Smart Contracts Beyond Default Messages
\ ← Previous Article Next Article →
This content originally appeared on HackerNoon and was authored by Ileolami
Ileolami | Sciencx (2024-09-11T21:19:23+00:00) How to Deploy a Smart Contract to Ethereum Network Using dRPC API Key and Endpoint. Retrieved from https://www.scien.cx/2024/09/11/how-to-deploy-a-smart-contract-to-ethereum-network-using-drpc-api-key-and-endpoint/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.