Create a directory called ‘RentAgreement’.
Launch the VS Code editor and access the terminal. Then, navigate to the RentAgreement directory.
Run the below command on terminal.
npx hardhat init

If you encounter a permission error during installation, attempt to run the command again using sudo.
sudo npx hardhat init
- Smart Contract
Once the project files have been created, navigate to the ‘contracts’ folder and change the name of the ‘Lock.sol’ file to ‘RentAgreement.sol’.
Paste the below code into the ‘RentAgreement.sol’ file.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
contract RentAgreement {
address payable public landlord;
address public tenant;
uint public rentAmount;
uint public startTimestamp;
uint public durationMonths;
uint public rentPaidUntil;
bool public isTerminated;
uint public constant penaltyRate = 10;
uint public constant gracePeriodEndDay = 7;
bool public terminationRequested = false;
address public terminationRequester;
event RentPaid(address tenant, uint amount, uint paidUntil, uint penalty);
event TerminationRequested(address requester);
event AgreementTerminated(address terminator);
modifier onlyLandlord() {
require(
msg.sender == landlord,
"Only the landlord can perform this action"
);
_;
}
modifier onlyTenant() {
require(
msg.sender == tenant,
"Only the tenant can perform this action"
);
_;
}
modifier notTerminated() {
require(!isTerminated, "this contract is terminated");
_;
}
modifier notTerminationRequester() {
require(
msg.sender != terminationRequester,
"Requester cannot approve termination."
);
_;
}
constructor(address _landlord, uint _rentAmount, uint _durationMonths) {
require(_landlord != address(0), "Invalid landlord address");
landlord = payable(_landlord);
rentAmount = _rentAmount;
durationMonths = _durationMonths;
startTimestamp = block.timestamp;
}
function setTenant(address _tenant) external onlyLandlord notTerminated {
require(tenant == address(0), "tenant alreadt set");
tenant = _tenant;
}
function payRent() external payable onlyTenant notTerminated {
require(
!terminationRequested,
"Termination requested, contract is pending closure"
);
require(
block.timestamp < startTimestamp + (durationMonths * 30 days),
"Rent Agrement has expired"
);
uint currentMonth = (block.timestamp - startTimestamp) / 30 days;
uint paymentDueDate = startTimestamp +
(currentMonth * 30 days) +
(gracePeriodEndDay * 1 days);
uint penalty = 0;
if (block.timestamp > paymentDueDate) {
penalty = (rentAmount * penaltyRate) / 100;
}
require(
msg.value == rentAmount + penalty,
"Incorrect amount, penalty may apply."
);
rentPaidUntil = block.timestamp + 30 days;
emit RentPaid(msg.sender, msg.value, rentPaidUntil, penalty);
}
function withdrawRent() external onlyLandlord {
require(
!terminationRequested,
"Termination requested, contract is pending closure"
);
uint amount = address(this).balance;
require(amount > 0, "No funds available");
landlord.transfer(amount);
}
function requestTermination() external notTerminated {
require(
msg.sender == tenant || msg.sender == landlord,
"only tenant or the landlord can request termination"
);
require(!terminationRequested, "Termination already requested ");
terminationRequested = true;
terminationRequester = msg.sender;
emit TerminationRequested(msg.sender);
}
function approveTermination()
external
notTerminationRequester
notTerminated
{
require(terminationRequested, "No termination request to approve ");
isTerminated = true;
emit AgreementTerminated(msg.sender);
transferBalanceToLandlord();
}
function transferBalanceToLandlord() private {
uint balance = address(this).balance;
(bool sent, ) = landlord.call{value: balance}("");
require(sent, "failed to send Ether");
}
}
- Compile the Smart Contract
Use the below command on terminal to compile smart contract.
npx hardhat compile

- Testing with Hardhat
Navigate to the ‘test’ folder and change the name of the ‘Lock.js’ file to ‘test.js’.
Paste the below code into the ‘test.js’ file.
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("RentAgreement", function(){
let rentAgreement;
let owner;
let tenant;
before(async function(){
[owner, tenant] = await ethers.getSigners();
const RentAgreement = await ethers.getContractFactory("RentAgreement");
rentAgreement = await RentAgreement.deploy(owner.address, ethers.utils.parseEther("1"), 12);
await rentAgreement.deployed();
});
it("Should allow the landlord to set the tenant", async function(){
await rentAgreement.connect(owner).setTenant(tenant.address);
const setTenantAddress = await rentAgreement.tenant();
if (setTenantAddress !== tenant.address) {
throw new Error("Failed to set tenant address");
}
});
it("Should allow tenant to pay rent, including penalties if late", async function(){
const rentAmount = ethers.utils.parseEther("1");
const penaltyAmount = rentAmount.mul(10).div(100);
await ethers.provider.send("evm_increaseTime", [8 * 24 * 60 * 60 ]);
await ethers.provider.send("evm_mine");
await rentAgreement.connect(tenant).payRent({value: rentAmount.add(penaltyAmount)});
const rentPaidUntil = await rentAgreement.rentPaidUntil();
const expectedRentPaidUntil = (await ethers.provider.getBlock('latest')).timestamp + 30 * 24 * 60 * 60;
expect(rentPaidUntil).to.equal(expectedRentPaidUntil);
});
it("Should allow Landlord to withdraw rent", async function(){
const initialBalance= await owner.getBalance();
await rentAgreement.connect(owner).withdrawRent();
const newBalance = await owner.getBalance();
expect(newBalance).to.be.above(initialBalance);
});
it("should handle terminiation request and approvals correctly", async function(){
await rentAgreement.connect(tenant).requestTermination();
const isTerminationRequested = await rentAgreement.terminationRequested();
if(!isTerminationRequested){
throw new Error("Failed to request termination");
}
await rentAgreement.connect(owner).approveTermination();
const isTerminated = await rentAgreement.isTerminated();
if(!isTerminated){
throw new Error("failed to approve termination");
}
})
});
Open the ‘hardhat.config.js’ file in the project root folder and add the following code to the file.
require("@nomiclabs/hardhat-ethers");
To install the Ether library, open the terminal in VS Code and run the following command.
npm install ethers@^5.0.0 --save-dev

To initiate the testing with Hardhat, run the following command. You should see ‘4 passing’ tests as a result.
npx hardhat test

Note: If you encounter any issues due to the Ether library version, try running the command below first, and then initiate the test again.
npm install --save-dev @nomiclabs/hardhat-ethers --force
- Deploying the Smart Contract
Navigate to the ‘ignition/modules’ folder and change the name of the ‘Lock.js’ file to ‘deploy.js’.
Paste the below code into the ‘deploy.js’ file.
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");
const RENT_AMOUNT = 1_000_000_000_000_000_000n; //1 Ether
const DURATION_MONTHS = 12;
module.exports = buildModule("RentAgreementModule", (m) =>{
const landlordAddress = m.getParameter("landlordAddress", "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266");
const rentAmount = m.getParameter("rentAmount", RENT_AMOUNT);
const durationMonths = m.getParameter("durationMonths", DURATION_MONTHS);
const rentAgreement = m.contract("RentAgreement", [landlordAddress, rentAmount, durationMonths]);
return { rentAgreement };
})
To find the landlord address in your local network, run the following command and copy the address of the first account.
npx hardhat node
Replace the landlord address in the code below with the address you copied.
const landlordAddress = m.getParameter("landlordAddress", "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266");

To deploy the contract, run the following commands. You should see the deployed address as a result.
npx hardhat compile
npx hardhat ignition deploy ignition/modules/deploy.
js --network localhost

- Interacting with the Smart Contract
Similar to Remix, we can interact with the smart contract on Hardhat, but we need to create scripts to do this.
Create a ‘scripts’ folder in the project root directory and create three files named as follows:
1. setTenant.js
2. payRent.js
3. withdraw.js
Paste the following scripts into the files you created.
//setTenant.js
const hre = require("hardhat");
async function main() {
// The address of the deployed RentAgreement contract
const contractAddress = "0x5FbDB2315678afecb367f032d93F642f64180aa3";
// Get the signers - the first account for the landlord and the third account as the tenant
const [landlord, , tenant] = await hre.ethers.getSigners();
console.log("Using the following address as the tenant:", tenant.address);
// Get the contract instance, connecting it with the landlord account to perform operations
const RentAgreement = await hre.ethers.getContractFactory("RentAgreement");
const rentAgreement = RentAgreement.attach(contractAddress).connect(landlord);
// Execute the setTenant function to set the third account as the tenant
const setTenantTx = await rentAgreement.setTenant(tenant.address);
await setTenantTx.wait(); // Wait for the transaction to be mined
console.log(
`Tenant set successfully with address ${tenant.address}. Transaction Hash: ${setTenantTx.hash}`
);
}
main().catch((error) => {
console.error(error);
process.exit(1);
});
//payRent.js
const hre = require("hardhat");
async function main() {
// The address of the deployed RentAgreement contract
const contractAddress = "0x5FbDB2315678afecb367f032d93F642f64180aa3";
// Get the signers; the third account is the tenant
const [, , tenant] = await hre.ethers.getSigners();
// The amount of rent to be paid, adjust this according to your contract terms
const rentAmount = hre.ethers.utils.parseEther("1"); // Example: 1 ether
// Fetch and log the tenant's balance before the rent payment
let tenantBalance = await hre.ethers.provider.getBalance(tenant.address);
console.log(
`Tenant's balance before paying rent: ${hre.ethers.utils.formatEther(
tenantBalance
)} ETH`
);
// Get the contract instance, connecting it with the tenant account to perform the transaction
const RentAgreement = await hre.ethers.getContractFactory("RentAgreement");
const rentAgreement = new hre.ethers.Contract(
contractAddress,
RentAgreement.interface,
tenant
);
// Execute the payRent function to pay the rent
const payRentTx = await rentAgreement.payRent({ value: rentAmount });
const receipt = await payRentTx.wait(); // Wait for the transaction to be mined
// Fetch and log the tenant's balance after the rent payment
tenantBalance = await hre.ethers.provider.getBalance(tenant.address);
console.log(
`Tenant's balance after paying rent: ${hre.ethers.utils.formatEther(
tenantBalance
)} ETH`
);
console.log(
`Rent paid successfully. Transaction Hash: ${receipt.transactionHash}`
);
}
main().catch((error) => {
console.error(error);
process.exit(1);
});
//withdraw.js
const hre = require("hardhat");
async function main() {
// The address of the deployed RentAgreement contract
const contractAddress = "0x5FbDB2315678afecb367f032d93F642f64180aa3";
// Get the signers; assuming the first account is the landlord
const [landlord, , tenant] = await hre.ethers.getSigners();
// Fetch and log the landlord's and tenant's balance before the withdrawal
let landlordBalance = await hre.ethers.provider.getBalance(landlord.address);
console.log(
`Landlord's balance before withdrawal: ${hre.ethers.utils.formatEther(
landlordBalance
)} ETH`
);
let tenantBalance = await hre.ethers.provider.getBalance(tenant.address);
console.log(
`Tenant's balance before withdrawal: ${hre.ethers.utils.formatEther(
tenantBalance
)} ETH`
);
// Get the contract instance, connecting it with the landlord account to perform the transaction
const RentAgreement = await hre.ethers.getContractFactory("RentAgreement");
const rentAgreement = new hre.ethers.Contract(
contractAddress,
RentAgreement.interface,
landlord
);
// Execute the withdrawRent function
const withdrawRentTx = await rentAgreement.withdrawRent();
const receipt = await withdrawRentTx.wait(); // Wait for the transaction to be mined
// Fetch and log the landlord's balance after the withdrawal
landlordBalance = await hre.ethers.provider.getBalance(landlord.address);
console.log(
`Rent withdrawal successful. Transaction Hash: ${receipt.transactionHash}`
);
console.log(
`Landlord's balance after withdrawal: ${hre.ethers.utils.formatEther(
landlordBalance
)} ETH`
);
console.log(
`Tenant's balance after withdrawal: ${hre.ethers.utils.formatEther(
tenantBalance
)} ETH`
);
}
main().catch((error) => {
console.error(error);
process.exit(1);
});
Make sure to replace the contract address variable with your deployed contract address in all three files.
const contractAddress = "0x5FbDB2315678afecb367f032d93F642f64180aa3";
- Running the Scripts
Run the following command to set the tenant. According to the script, the third account from ‘npx hardhat node’ will be set as the tenant.
npx hardhat run scripts/setTenant.js --network localhost

Run the following command to pay the rent.
npx hardhat run scripts/payRent.js --network localhost

Run the following command to withdraw the rent amount.
npx hardhat run scripts/withdraw.js --network localhost
