What is a Smart Contract?
Smart Contracts 📝 are simple programs stored on a blockchain network.
You can say it's like an agreement between two people in the form of computer code. The transactions in a smart contract are processed by the blockchain and stored as a 42 character hex address with the prefix "0x"
). All of which means that they can be sent automatically without needing a third party.
🤔 Remember: They're stored in a public database. And once a smart contract is deployed, it cannot be changed.
What is Solidity?
Solidity is one of the most popular languages used for building smart contracts on Ethereum Blockchain. It's also an object-oriented programming language.
Build Your First Smart Contract
- Open
Remix
IDE from here. - Click on
Sure
and thenDone
. - Under
default_workshop
, click oncreate new file
. - Rename it as
Hostel.sol
.
Now you're ready to write your first Smart Contract. 🤩
Contract Code
- You have to provide the
solidity version
in the smart contract:
pragma solidity ^0.5.16;
- Now create the main contract named
Hostel
:
contract Hostel{
...
}
- Now, inside the
contract Hostel{...}
follow the steps below. - Create some variables where the smart contract will store the payable
address
(42 char hex string with prefix :"0x"
) of theLandlord
& theTenant
.
address payable tenant;
address payable landlord;
- Create some public variables where the smart contract will store some integer values. For this, there's a data type called
uint
(256-bit unsigned integer)
uint public no_of_rooms = 0;
uint public no_of_agreement = 0;
uint public no_of_rent = 0;
- Now, create a
structure
to store details of each Hostel room likeHostel no.
,Hostel name
,Hostel address
,No of total agreements
,Monthly rent
,One-time security deposit
,Last agreement sign time
,Vacancy
,Landlord address
, andCurrent Tenant Address
.
struct Room{
uint roomid;
uint agreementid;
string roomname;
string roomaddress;
uint rent_per_month;
uint securityDeposit;
uint timestamp;
bool vacant;
address payable landlord;
address payable currentTenant;
}
map
previousstructure
with auint
(named :roomid
).
mapping(uint => Room) public Room_by_No;
- Similar to the above, create a
structure
for eachRental Agreement
and map that with auint
(named:agreementid
). This will store details like:Hostel no.
,Agreement No
,Hostel name
,Hostel address
,Monthly rent
,One-time security deposit
,Lockin Period
,Agreement sign time
,Landlord address
, andTenant Address
.
struct RoomAgreement{
uint roomid;
uint agreementid;
string Roomname;
string RoomAddresss;
uint rent_per_month;
uint securityDeposit;
uint lockInPeriod;
uint timestamp;
address payable tenantAddress;
address payable landlordAddress;
}
mapping(uint => RoomAgreement) public RoomAgreement_by_No;
- Now, create a
structure
for eachRent
payment and map that with auint
. This will store details like:Rent No.
,Hostel no.
,Agreement No
,Hostel name
,Hostel address
,Monthly rent
,Rent payment time
,Landlord address
, andTenant Address
.
struct Rent{
uint rentno;
uint roomid;
uint agreementid;
string Roomname;
string RoomAddresss;
uint rent_per_month;
uint timestamp;
address payable tenantAddress;
address payable landlordAddress;
}
mapping(uint => Rent) public Rent_by_No;
-
Create some modifiers that will help you verify a few things before running a function.
Here
require(...);
means that if the given condition is not satisfied, the function won't execute, and the given string will appear as an error code.
The following will check if the message sender is the landlord.
modifier onlyLandlord(uint _index) {
require(msg.sender == Room_by_No[_index].landlord, "Only landlord can access this");
_;
}
The following will check if the message sender is anyone except the landlord.
modifier notLandLord(uint _index) {
require(msg.sender != Room_by_No[_index].landlord, "Only Tenant can access this");
_;
}
The following will check whether the room is vacant or not.
modifier OnlyWhileVacant(uint _index){
require(Room_by_No[_index].vacant == true, "Room is currently Occupied.");
_;
}
The following will check whether the tenant has enough Ether
in his wallet to pay the rent.
modifier enoughRent(uint _index) {
require(msg.value >= uint(Room_by_No[_index].rent_per_month), "Not enough Ether in your wallet");
_;
}
The following will check whether the tenant has enough Ether
in his wallet to pay a one-time security deposit and one month's rent in advance.
modifier enoughAgreementfee(uint _index) {
require(msg.value >= uint(uint(Room_by_No[_index].rent_per_month) + uint(Room_by_No[_index].securityDeposit)), "Not enough Ether in your wallet");
_;
}
The following will check whether the tenant's address is the same as who has signed the previous rental agreement.
modifier sameTenant(uint _index) {
require(msg.sender == Room_by_No[_index].currentTenant, "No previous agreement found with you & landlord");
_;
}
The following will check whether any time is left for the agreement to end.
modifier AgreementTimesLeft(uint _index) {
uint _AgreementNo = Room_by_No[_index].agreementid;
uint time = RoomAgreement_by_No[_AgreementNo].timestamp + RoomAgreement_by_No[_AgreementNo].lockInPeriod;
require(now < time, "Agreement already Ended");
_;
}
The following will check whether 365 days have passed after the last agreement has been created.
modifier AgreementTimesUp(uint _index) {
uint _AgreementNo = Room_by_No[_index].agreementid;
uint time = RoomAgreement_by_No[_AgreementNo].timestamp + RoomAgreement_by_No[_AgreementNo].lockInPeriod;
require(now > time, "Time is left for contract to end");
_;
}
The following will check whether 30 days have passed after the last rent payment.
modifier RentTimesUp(uint _index) {
uint time = Room_by_No[_index].timestamp + 30 days;
require(now >= time, "Time left to pay Rent");
_;
}
- Now, create some functions
The following function will be used to add Rooms.
function addRoom(string memory _roomname, string memory _roomaddress, uint _rentcost, uint _securitydeposit) public {
require(msg.sender != address(0));
no_of_rooms ++;
bool _vacancy = true;
Room_by_No[no_of_rooms] = Room(no_of_rooms,0,_roomname,_roomaddress, _rentcost,_securitydeposit,0,_vacancy, msg.sender, address(0));
}
Now, create a function to sign the rental agreement for a hostel room between the landlord and a tenant.
Before creating the signAgreement
function, remember the following:
- The function will only execute if the user is
Tenant
, meaning that the user's address and the landlord's address don't match. - The function will only execute if the user has enough ether (payable 'ether') in their Ethereum wallet.(Enough ether means = one-time security deposit + 1st month's rent)
Let's use those modifiers here, so that:
- The function
signAgreement
will only execute only if the said room is vacant and the tenant has enough ether in their wallet.
Remember those modifiers in point no.10? Use those modifiers here to execute the following function.
function signAgreement(uint _index) public payable notLandLord(_index) enoughAgreementfee(_index) OnlyWhileVacant(_index) {
require(msg.sender != address(0));
address payable _landlord = Room_by_No[_index].landlord;
uint totalfee = Room_by_No[_index].rent_per_month + Room_by_No[_index].securityDeposit;
_landlord.transfer(totalfee);
no_of_agreement++;
Room_by_No[_index].currentTenant = msg.sender;
Room_by_No[_index].vacant = false;
Room_by_No[_index].timestamp = block.timestamp;
Room_by_No[_index].agreementid = no_of_agreement;
RoomAgreement_by_No[no_of_agreement]=RoomAgreement(_index,no_of_agreement,Room_by_No[_index].roomname,Room_by_No[_index].roomaddress,Room_by_No[_index].rent_per_month,Room_by_No[_index].securityDeposit,365 days,block.timestamp,msg.sender,_landlord);
no_of_rent++;
Rent_by_No[no_of_rent] = Rent(no_of_rent,_index,no_of_agreement,Room_by_No[_index].roomname,Room_by_No[_index].roomaddress,Room_by_No[_index].rent_per_month,now,msg.sender,_landlord);
}
Now, create a function that the tenant will use to pay the monthly rent to the landlord.
Before creating the payRent
function, remember the following:
- The function will only execute if the user's address and previous tenant's address both are the same, meaning that the user can only pay rent if he/she has signed an agreement with the landlord within the last 365 days.
- The function will only execute if the tenant had paid his/her previous rent more than a month ago.
- The function will only execute if the user has enough ether (payable 'ether') in his/her Ethereum wallet. (enough ether = enough room rent).
function payRent(uint _index) public payable sameTenant(_index) RentTimesUp(_index) enoughRent(_index){
require(msg.sender != address(0));
address payable _landlord = Room_by_No[_index].landlord;
uint _rent = Room_by_No[_index].rent_per_month;
_landlord.transfer(_rent);
Room_by_No[_index].currentTenant = msg.sender;
Room_by_No[_index].vacant = false;
no_of_rent++;
Rent_by_No[no_of_rent] = Rent(no_of_rent,_index,Room_by_No[_index].agreementid,Room_by_No[_index].roomname,Room_by_No[_index].roomaddress,_rent,now,msg.sender,Room_by_No[_index].landlord);
}
Let's create a function that the landlord will use to mark an agreement complete.
Before creating agreementCompleted
function, remember the following:
- The function will only execute if the user's address and the landlord's address are the same.
-
The function will only execute if the tenant had signed that agreement more than a year ago.
function agreementCompleted(uint _index) public payable onlyLandlord(_index) AgreementTimesUp(_index){ require(msg.sender != address(0)); require(Room_by_No[_index].vacant == false, "Room is currently Occupied."); Room_by_No[_index].vacant = true; address payable _Tenant = Room_by_No[_index].currentTenant; uint _securitydeposit = Room_by_No[_index].securityDeposit; _Tenant.transfer(_securitydeposit); }
Let's create a function that the landlord will use to terminate an agreement.
Before creating agreementTerminated
function, remember the following:
- The function will only execute if the user's address and the landlord's address are the same.
- The function will only execute if the tenant had signed that agreement less than a year ago.
function agreementTerminated(uint _index) public onlyLandlord(_index) AgreementTimesLeft(_index){
require(msg.sender != address(0));
Room_by_No[_index].vacant = true;
}
Compile
Now, click on the Solidity Compile
option in the left sidebar.
- Select compiler version
0.5.16+
- Then click on
Compile Hostel.sol
Similar to as follows:

Deploy
Click on the Deploy & Run Transactions
option in the left sidebar.
- Choose
Environment
>JavaScript VM (London)
- Now click on
Deploy

🎉 Congratulations, your smart contract has been deployed. 🎉
Sample Transactions
Remember that whenever a transaction is getting executed, it stores all the details in a unique hash
key.
Now, under Deployed Contract
click on > HOSTEL AT ..... (MEMORY)
- Click on the
V
icon (dropdown menu) ofaddRoom
function. - Fill up the details.
Similar to as follows:

Note: You're entering your details in
wei
not inether
(1 ether = 1000000000000000000 wei)
-
Then click on
transact
🎉 Congratulations, you've successfully added your 1st room in the contract. 🎉
(You can find the same in the terminal also.)
Now the landlord of the room is your 1st Ether Address. (The one with 99.99 test ether in wallet.)
- Change the
Account Address
from the dropdown menu. (Choose anyone except the one with 99.99 ether)

- Add the total amount you have previously chosen as (rent cost + security deposit)
- And then from the dropdown
wei
, chooseether

-
Scroll down and click on
signAgreement
, enter1
, and presssignAgreement
You can check the same by entering
RoomAgreementNo
:1

🎉 Congratulations, you've successfully signed your 1st agreement. 🎉
All your transactions are shown in the terminal
.

Now, you can cross verify this by checking your ether
account address.
Advantages of Smart Contracts
Now you may ask, "what's the use of smart contracts when there are several centralized methods?"
Let me explain some advantages of smart contracts over centralized systems:
- Here data cannot be changed or tampered with. So, it is almost impossible for malicious actors to manipulate data.
- It's completely decentralized.
- Unlike any centralized payment wallet, you don't have to pay any commission percentages to a middle man to transact.
Storage & Others
You may also ask "how are all the transactions recorded?"
You have to remember that smart contracts store data in a block of the blockchain, and all transactions are stored with a unique hash
key.
In Remix IDE, you can download the complete transactions history as a JSON file. For that, follow these steps:
- Click
Deploy & Run Transaction
- Then, expand the
Transactions Recorded (..) V
dropdown menu. - Then Click on the
Save
icon. - Press
ok
.
Gas Fee
You may have noticed that whenever a transaction is executed, a few wei
is getting deducted from your ether wallet.
It's called gas fee, which is the payment made by users to compensate for the computing energy required to process and validate transactions.
As more Ethereum miners come up in near future, the gas fee
will decrease in an inverse relation.
Future Possibilities
After this, if you want to build a fullstack website using React, you can use this smart contract as a backend.
For that you need to install/download:
Frontend:
Backend:
Testing:
Just follow the official documentation of Web3.js to connect your smart contract with your React app.
Conclusion
You've successfully understood what Solidity is and how smart contracts work. And you've successfully built and deployed a perfectly working smart contract (where a tenant can pay rent in ether (ETH) directly to the landlord's wallet without paying a single wei to a middle man).
To download the complete code used in this tutorial, click here.
Want to quickly add user login and signup functionality to your React apps? Use LoginRadius for free.