What's CrossFi?
CrossFi is a blockchain platform built to enable seamless cross-chain asset transfers and decentralized finance (DeFi) applications. With a focus on interoperability, security, and scalability, CrossFi provides developers and users with a powerful ecosystem for deploying decentralized applications (DApps) that can operate across multiple blockchain networks.
Prerequisites
- NodeJs
- Metamask
- Testnet ethers
- Alchemy API URL
Dev Tools
npm install -g yarn
Step 1: Creating and launching a new React project
npm create vite@latest rental-dapp --template react && cd rental-dapp
✍️ Choose React and Javascript from the prompt to set up your project.
Step 2: Installing Hardhat Plugin for development
yarn add hardhat
Step 3: Initializing Hardhat Ethereum development environment
npx hardhat init
✍️ To set up your project, select Create a JavaScript project and proceed with the prompt.
Step 4: Configuring Hardhat for development
- Open the
hardhat.config.cjs
file to set up the networks property
require("@nomicfoundation/hardhat-toolbox");
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.28",
networks: {
crossfiTestnet: {
chainId: 4157,
url: "https://crossfi-testnet.g.alchemy.com/v2/wAz9j4RJUgEBiaMljD1yGbi45YBRKXTK",
accounts: [
"8dba19966d85ea2137505039c47d1e6ba35ab560797e51924fuedb939d9d2146"
],
},
},
};
⚠️ Recommended: Follow this step-by-step guide to generate, configure and secure CrossFi API Key.
Step 5: Creating the smart contract
- Navigate to the
contracts
directory and create a new file namedRental.sol
- Populate the file with the following Solidity code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
contract Rental {
address owner;
constructor() {
owner = msg.sender;
}
// Add a new Renter
struct Renter {
address payable walletAddress;
string firstName;
string lastName;
bool canRent;
bool active;
uint balance;
uint amountDue;
uint start;
uint end;
}
// Mapping wallet addresses to each Renter
mapping (address => Renter) public renters;
// Function for adding a Renter to the list of "Renters"
function addRenter(address payable walletAddress,
string memory firstName,
string memory lastName,
bool canRent,
bool active,
uint balance,
uint amountDue,
uint start,
uint end) public {
renters[walletAddress] = Renter(payable (walletAddress), firstName, lastName, canRent, active, balance, amountDue, start, end);
}
// Checkout car ==> Rent a new car
function checkOut(address payable walletAddress) public {
require(renters[walletAddress].amountDue == 0, "You have a pending balance!");
require(renters[walletAddress].canRent == true, "Can't rent a car at the moment!");
renters[walletAddress].canRent = false;
renters[walletAddress].active = true;
renters[walletAddress].start = block.timestamp;
}
// Check-in car ==> Return rented car
function checkIn(address payable walletAddress) public {
require(renters[walletAddress].active == true, "Kindly checkout the car!");
renters[walletAddress].active = false;
renters[walletAddress].end = block.timestamp;
// Set amount due
setDueAmount(walletAddress);
}
// Calculate the Timespan
function renterTimespan(uint start, uint end) internal pure returns (uint){
return end - start;
}
// Get total duration of car use
function getTotalDuration(address payable walletAddress) public view returns(uint) {
require(renters[walletAddress].active == false, "The car is currently checked out!");
uint timespan = renterTimespan(renters[walletAddress].start, renters[walletAddress].end);
uint timespanInMinutes = timespan / 60;
return timespanInMinutes;
}
// Get contract balance
function balanceOf() public view returns (uint) {
return address(this).balance;
}
// Get Renter's balance
function balanceOfRenter(address payable walletAddress) public view returns(uint){
return renters[walletAddress].balance;
}
// Set Due amount
function setDueAmount(address payable walletAddress) internal {
uint timespanMinutes = getTotalDuration(walletAddress);
uint fiveMinutesIncrement = timespanMinutes / 5;
renters[walletAddress].amountDue = fiveMinutesIncrement * 5000000000000000;
}
// Reset renter's position after checking-in
function canRentCar(address payable walletAddress) public view returns(bool) {
return renters[walletAddress].canRent;
}
// Deposit fund
function deposit(address walletAddress) payable public {
renters[walletAddress].balance += msg.value;
}
// Make Payment after check-in
function makePayment(address walletAddress) payable public {
require(renters[walletAddress].amountDue > 0, "You do not have anything due at this time!");
require(renters[walletAddress].balance > 0, "You do not have enough fund to cover payment. Please make a deposit!");
renters[walletAddress].balance -= msg.value;
renters[walletAddress].canRent = true;
renters[walletAddress].active = false;
renters[walletAddress].amountDue = 0;
renters[walletAddress].start = 0;
renters[walletAddress].end = 0;
}
}
About the smart contract
This Solidity smart contract implements a rental system, likely for renting cars, where users can check out and check in a vehicle while managing their balances and payments. Below is a breakdown of its components:
Contract Overview
The contract is named Rental and enables renters to:
- Register themselves in the system.
- Check out a car if they meet the required conditions.
- Check in a car after use and calculate the rental cost.
- Deposit funds to their account.
- Make payments for the rental.
- Finally, the contract keeps track of renter details, including their balance, rental status, and amount due.
Key Components
1. State variables
address owner;
- Stores the owner's address (the deployer of the contract).
2. Constructor
constructor() {
owner = msg.sender;
}
- Assigns the deployer of the contract as the owner.
3. Renter Structure
struct Renter {
address payable walletAddress;
string firstName;
string lastName;
bool canRent;
bool active;
uint balance;
uint amountDue;
uint start;
uint end;
}
- Defines a
Renter
with: - Wallet address (to track user payments).
- Name (
firstName
andlastName
). - Rental eligibility (
canRent
). - Current rental status (
active
). - Financial details (
balance
andamountDue
). - Timestamps (
start
andend
) to track rental duration.
4. Mapping Renters
mapping (address => Renter) public renters;
- A mapping that links wallet addresses to their corresponding Renter records.
5. Adding a Renter
function addRenter(
address payable walletAddress,
string memory firstName,
string memory lastName,
bool canRent,
bool active,
uint balance,
uint amountDue,
uint start,
uint end) public {
renters[walletAddress] = Renter(
walletAddress, firstName, lastName, canRent, active, balance, amountDue, start, end);
}
- Allows adding a new renter with initial details.
6. Checking Out a Car
function checkOut(address payable walletAddress) public {
require(renters[walletAddress].amountDue == 0, "You have a pending balance!");
require(renters[walletAddress].canRent == true, "Can't rent a car at the moment!");
renters[walletAddress].canRent = false;
renters[walletAddress].active = true;
renters[walletAddress].start = block.timestamp;
}
- Function ensures:
- The renter does not have a pending balance.
- The renter is eligible to rent.
- Updates rental status and records the start time.
7. Checking In a Car
function checkIn(address payable walletAddress) public {
require(renters[walletAddress].active == true, "Kindly checkout the car!");
renters[walletAddress].active = false;
renters[walletAddress].end = block.timestamp;
// Calculate the due amount
setDueAmount(walletAddress);
}
- Function ensures:
- The renter has an active rental.
- Updates rental status and records the end time.
- Calls
setDueAmount()
function to calculate rental cost.
8. Calculating Rental Duration
function renterTimespan(uint start, uint end) internal pure returns (uint) {
return end - start;
}
- Computes the duration in seconds between
start
andend
.
9. Getting Total Rental Duration in Minutes
function getTotalDuration(address payable walletAddress) public view returns(uint) {
require(renters[walletAddress].active == false, "The car is currently checked out!");
uint timespan = renterTimespan(renters[walletAddress].start, renters[walletAddress].end);
uint timespanInMinutes = timespan / 60;
return timespanInMinutes;
}
- Converts the rental duration from seconds to minutes.
- Ensures the renter has checked in.
10. Checking Contract Balance
function balanceOf() public view returns (uint) {
return address(this).balance;
}
- Returns the total balance held by the contract.
11. Checking Renter's Balance
function balanceOfRenter(address payable walletAddress) public view returns(uint){
return renters[walletAddress].balance;
}
- Returns the funds available in a renter's balance.
12. Setting the Amount Due
function setDueAmount(address payable walletAddress) internal {
uint timespanMinutes = getTotalDuration(walletAddress);
uint fiveMinutesIncrement = timespanMinutes / 5;
renters[walletAddress].amountDue = fiveMinutesIncrement * 5000000000000000;
}
- Calculates the rental fee based on:
-
timespanMinutes / 5
→ Rounds down to 5-minute blocks. - Each block costs 0.005 ETH (
5000000000000000
wei).
13. Checking if a Renter Can Rent Again
function canRentCar(address payable walletAddress) public view returns(bool) {
return renters[walletAddress].canRent;
}
- Returns true if the renter is allowed to rent.
14. Depositing Funds
function deposit(address walletAddress) payable public {
renters[walletAddress].balance += msg.value;
}
- Allows renters to add funds to their balance.
15. Making Payment
function makePayment(address walletAddress) payable public {
require(renters[walletAddress].amountDue > 0, "You do not have anything due at this time!");
require(renters[walletAddress].balance > 0, "You do not have enough fund to cover payment. Please make a deposit!");
renters[walletAddress].balance -= msg.value;
renters[walletAddress].canRent = true;
renters[walletAddress].active = false;
renters[walletAddress].amountDue = 0;
renters[walletAddress].start = 0;
renters[walletAddress].end = 0;
}
- Function ensures:
- The renter has an outstanding balance.
- The renter has enough funds to cover the cost.
- Deducts the amount from the balance and resets rental status.
Summary
- Registration:
addRenter()
- Renting a car:
checkOut()
- Returning a car:
checkIn()
- Payment system:
-
setDueAmount()
calculates cost. -
deposit()
adds funds. -
makePayment()
clears debts. - Tracking rental duration:
getTotalDuration()
- Checking balances:
balanceOf()
,balanceOfRenter()
Step 6: Compiling the smart contract
- Add the directory where the automatically created ABI should be stored to the
hardhat.config.cjs
file
paths: {
artifacts: "./src/artifacts",
}
- The modified
hardhat.config.cjs
file should look as follows:
require("@nomicfoundation/hardhat-toolbox");
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.28",
paths: {
artifacts: "./src/artifacts",
},
networks: {
crossfiTestnet: {
chainId: 4157,
url: "https://crossfi-testnet.g.alchemy.com/v2/wAz9j4RJUgEBiaMljD1yGbi45YBRKXTK",
accounts: [
"8dba19966d85ea2137505039c47d1e6ba35ab560797e51924fuedb939d9d2146",
],
},
},
}
- Execute the following command to compile the contract
yarn hardhat compile
Step 7: Configuring the DApp for deployment
- Create a new folder for deployment in the root directory
mkdir deploy
Open the
deploy
folder and create a new file named00-deploy-rental.cjs
Install another Hardhat plugin as a package for deployment
yarn add hardhat-deploy --dev
- Import
hardhat-deploy
package to thehardhat.config.cjs
file
require("hardhat-deploy");
- Install
hardhat-deploy-ethers
to override the@nomiclabs/hardhat-ethers
package
yarn add --dev @nomiclabs/hardhat-ethers@npm:hardhat-deploy-ethers
- Configure a deployer account in the
hardhat.config.cjs
file
networks: {
// Code Here
},
namedAccounts: {
deployer: {
default: 0,
}
}
- Populate the
00-deploy-rental.cjs
file with the following code:
module.exports = async ({ getNamedAccounts, deployments }) => {
const { deploy } = deployments;
const { deployer } = await getNamedAccounts();
await deploy("Rental", {
contract: "Rental",
from: deployer,
args: [],
log: true,
});
};
module.exports.tags = ["Rental"];
Step 8: Deploying the contract to CrossFi Testnet
yarn hardhat deploy --network crossfiTestnet
- The following should be the deployment output if the deployment is successful:
Compiled 1 Solidity file successfully (evm target: paris).
deploying "Rental" (tx: 0x722012ff0b9036f3d56587de9e87549be2b95e3b10a76525b22d489e34f72ffb)...: deployed at 0x0847857BE3dce76060Fabe41648CcbFe7f6898Fc with 1585543 gas
✨ Done in 15.76s.
- The Deployed at information provide the contract address
...: deployed at 0x0847857BE3dce76060Fabe41648CcbFe7f6898Fc
Congratulations 🎉 You have successfully developed a decentralized application on the CrossFi test network.
Conclusion
In this tutorial, we explored how to develop a Rental DApp on CrossFi using Hardhat framework, covering smart contract creation and deployment. By leveraging CrossFi’s blockchain infrastructure, we created a decentralized rental system with seamless fund management.
As you continue refining your DApp, consider integrating frontend components with React, enhancing security with access control mechanisms, and optimizing gas fees. Experimenting with CrossFi’s features will further improve scalability and user experience.
Now that you have a solid foundation, what will you build next? 🚀
Top comments (0)