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:
Let’s get your hands dirty.
Create a Folder under your root directory, and name it contracts
.
Create a File under the contracts
folder, and name it coffee.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;
}
}
//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). uint256 public constant coffeePrice = 0.0002 ether;
uint256 public totalCoffeesSold;
uint256 public totalEtherReceived;
coffeePrice
: Set as a constant value of 0.0002 ether
.totalCoffeesSold
: Tracks the number of coffees sold.totalEtherReceived
: Tracks the total Ether received by the contract.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 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.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 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:
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.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.
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
dependencies
You 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
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": {}
}
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:
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 };
}
describe("Deployment", function () {
it("Should set the initial values correctly", async function () {
const { coffee } = await loadFixture(deployCoffeeFixture);
const totalCoffeesSold = await coffee.methods.totalCoffeesSold().call();
const totalEtherReceived = await coffee.methods.totalEtherReceived().call();
expect(totalCoffeesSold).to.equal("0");
expect(totalEtherReceived).to.equal("0");
});
});
totalCoffeesSold
and totalEtherReceived
are set to zero after deployment.describe("Buying Coffee", function () {
it("Should purchase coffee and emit an event", async function () {
const { coffee, buyer } = await loadFixture(deployCoffeeFixture);
const quantity = 3;
const totalCost = web3.utils.toWei("0.0006", "ether");
const receipt = await coffee.methods.buyCoffee(quantity).send({ from: buyer, value: totalCost });
const event = receipt.events.CoffeePurchased;
expect(event).to.exist;
expect(event.returnValues.buyer).to.equal(buyer);
expect(event.returnValues.quantity).to.equal(String(quantity));
expect(event.returnValues.totalCost).to.equal(totalCost);
});
it("Should revert if the quantity is zero", async function () {
const { coffee, buyer } = await loadFixture(deployCoffeeFixture);
expect(
coffee.methods.buyCoffee(0).send({ from: buyer, value: web3.utils.toWei("0.0002", "ether") })
).to.be.revertedWith("QuantityMustBeGreaterThanZero");
});
it("Should update totalCoffeesSold and totalEtherReceived correctly", async function () {
const { coffee, buyer } = await loadFixture(deployCoffeeFixture);
const quantity = 5;
const totalCost = web3.utils.toWei("0.001", "ether");
await coffee.methods.buyCoffee(quantity).send({ from: buyer, value: totalCost });
const totalCoffeesSold = await coffee.methods.totalCoffeesSold().call();
const totalEtherReceived = await coffee.methods.totalEtherReceived().call();
expect(totalCoffeesSold).to.equal(String(quantity));
expect(totalEtherReceived).to.equal(totalCost);
});
});
CoffeePurchased
event.totalCoffeesSold
and totalEtherReceived
are updated correctly after a purchase.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");
});
});
After you have written the test script, you will:
console.log()
from your Solidity code. To use it, you have to import hardhat/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.
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 it deploy.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.
You can also access the contract address in the deployed_addresses.json
file.
Congratulations, you have successfully deployed your smart contract to Sepolia Testnet. 🎉
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:
Beyond Default Messages: Mastering Custom Errors in Solidity
Mastering Custom Errors in Solidity: Elevate Your Smart Contracts Beyond Default Messages