Watch step-by-step guides to get started with blockchain development
Learn how to create and set up your MetaMask wallet for blockchain transactions
Step-by-step guide to deploying your smart contract using MetaMask on Sepolia testnet
Master Etherscan to track transactions and verify smart contracts
Go to https://remix.ethereum.org in your browser. This is the official Solidity IDE.
In the File Explorer panel on left, click the file icon with plus sign. Name your file (e.g., ConfCert.sol, Voting.sol, etc.). The .sol extension is required.
Copy the Solidity code from this documentation and paste it into your new file in Remix. The code will be automatically syntax highlighted.
Click the Solidity Compiler icon (second icon in left sidebar). Select compiler version 0.8.31 or higher. Click the blue Compile button. Wait for successful compilation (green checkmark will appear).
Install MetaMask browser extension if not already installed. Create or import wallet. Switch network to Sepolia Testnet. Get free Sepolia ETH from faucet (search 'Sepolia faucet' or use https://sepoliafaucet.com).
Click Deploy & Run Transactions icon (third icon in left sidebar). In ENVIRONMENT dropdown, select 'Injected Provider - MetaMask'. MetaMask will popup - click Connect. Your Sepolia account should appear. Click orange Deploy button. MetaMask will popup for transaction confirmation. Confirm transaction and wait for deployment.
After deployment, contract will appear under Deployed Contracts section. Click copy icon next to contract name to copy address. This address is what you paste in the frontend .env file.
In Remix, expand deployed contract to see all functions. Functions in orange are payable. Functions in red change state (require gas). Functions in blue are view functions (free to call). Test each function to ensure contract works correctly.
A blockchain-based certificate issuance and verification system that stores certificate data on IPFS and metadata on blockchain.
Contract Address
0x8b94D4dB48ECAec78875e9D58e132EC389Bbe5AD
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.31;
contract ConfCert {
uint256 public nextId = 1000;
struct Certificate {
uint256 id;
string studentName;
string ipfsCID;
address issuer;
}
mapping(uint256 => Certificate) public certificates;
event CertificateIssued(uint256 id, string studentName, string ipfsCID);
// Single certificate
function issueCertificate(
string memory _studentName,
string memory _ipfsCID
) public {
nextId++;
certificates[nextId] = Certificate(
nextId,
_studentName,
_ipfsCID,
msg.sender
);
emit CertificateIssued(nextId, _studentName, _ipfsCID);
}
// BULK CERTIFICATE UPLOAD
function issueCertificatesBatch(
string[] memory _studentNames,
string[] memory _ipfsCIDs
) public {
require(
_studentNames.length == _ipfsCIDs.length,
"Input array length mismatch"
);
for (uint256 i = 0; i < _studentNames.length; i++) {
nextId++;
certificates[nextId] = Certificate(
nextId,
_studentNames[i],
_ipfsCIDs[i],
msg.sender
);
emit CertificateIssued(nextId, _studentNames[i], _ipfsCIDs[i]);
}
}
function getCertificate(uint256 _id)
public
view
returns (string memory, string memory, address)
{
require(_id > 0 && _id <= nextId, "Certificate does not exist");
Certificate memory cert = certificates[_id];
return (cert.studentName, cert.ipfsCID, cert.issuer);
}
}nextId starts at 1000 and increments for each certificate. The certificates mapping stores all certificate data indexed by ID.
Contains id (unique identifier), studentName (recipient name), ipfsCID (IPFS hash of certificate file), and issuer (wallet that created it).
Creates a single certificate. Takes student name and IPFS CID as parameters, increments nextId, stores certificate data, and emits an event.
Allows bulk issuance of certificates. Takes arrays of names and CIDs, validates they have the same length, and creates multiple certificates in one transaction.
View function to retrieve certificate details by ID. Returns student name, IPFS CID, and issuer address.
A transparent and tamper-proof voting system where admin can add candidates and users can vote once.
Contract Address
0x1514EFb55d58A80C11b11F0D4b63990E21299f78
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.31;
contract Voting {
struct Candidate {
uint id;
string name;
uint voteCount;
}
mapping(uint => Candidate) public candidates;
mapping(address => bool) public hasVoted;
uint public candidatesCount;
address public admin;
constructor() {
admin = msg.sender;
addCandidate("Alice");
addCandidate("Bob");
}
modifier onlyAdmin() {
require(msg.sender == admin, "Only admin allowed");
_;
}
function addCandidate(string memory _name) public onlyAdmin {
candidatesCount++;
candidates[candidatesCount] = Candidate(
candidatesCount,
_name,
0
);
}
function vote(uint _candidateId) public {
require(!hasVoted[msg.sender], "Already voted");
require(_candidateId > 0 && _candidateId <= candidatesCount, "Invalid candidate");
hasVoted[msg.sender] = true;
candidates[_candidateId].voteCount++;
}
function getCandidate(uint _id) public view returns (string memory, uint) {
Candidate memory c = candidates[_id];
return (c.name, c.voteCount);
}
}Contains id (unique number), name (candidate name), and voteCount (number of votes received).
candidates mapping stores all candidates by ID. hasVoted mapping tracks which addresses have voted. candidatesCount keeps total number of candidates. admin stores the deployer's address.
Sets msg.sender as admin and pre-adds two sample candidates (Alice and Bob) for demonstration.
Restricts certain functions to only be callable by the admin address.
Admin-only function to add new candidates. Increments candidatesCount, creates new Candidate struct with vote count starting at 0.
Allows any address to vote once for a valid candidate. Checks if address hasn't voted yet, validates candidate ID, marks address as voted, and increments candidate's vote count.
View function to retrieve candidate name and current vote count by ID.
A transparent donation platform where admin approves charities, users donate, and approved charities can withdraw funds.
Contract Address
0x5Ae3a0d0432e6Fb355CD278Aea1914D791d2cB97
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.31;
contract CharityDonation {
address public admin;
mapping(address => uint256) public donations;
mapping(address => bool) public approvedCharities;
uint256 public totalDonations;
event DonationReceived(address indexed donor, uint256 amount);
event CharityAdded(address charity);
event CharityRemoved(address charity);
event FundsWithdrawn(address indexed to, uint256 amount);
modifier onlyAdmin() {
require(msg.sender == admin, "Only admin allowed");
_;
}
modifier onlyCharity() {
require(approvedCharities[msg.sender], "Not an approved charity");
_;
}
constructor() {
admin = msg.sender;
}
// Donate ETH
function donate() external payable {
require(msg.value > 0, "Donation must be greater than 0");
donations[msg.sender] += msg.value;
totalDonations += msg.value;
emit DonationReceived(msg.sender, msg.value);
}
// Admin approves charity wallet
function addCharity(address _charity) external onlyAdmin {
require(_charity != address(0), "Invalid address");
approvedCharities[_charity] = true;
emit CharityAdded(_charity);
}
// Admin removes charity
function removeCharity(address _charity) external onlyAdmin {
approvedCharities[_charity] = false;
emit CharityRemoved(_charity);
}
// Charity withdraws funds TO ANY ADDRESS
function withdraw(address _to, uint256 amount) external onlyCharity {
require(_to != address(0), "Invalid recipient");
require(amount <= address(this).balance, "Insufficient balance");
(bool success, ) = payable(_to).call{value: amount}("");
require(success, "Transfer failed");
emit FundsWithdrawn(_to, amount);
}
function contractBalance() public view returns (uint256) {
return address(this).balance;
}
}admin stores deployer address. donations mapping tracks how much each address donated. approvedCharities mapping tracks which addresses are approved charities. totalDonations tracks cumulative donations.
onlyAdmin restricts functions to admin only. onlyCharity restricts functions to approved charity addresses only.
Payable function allowing anyone to send ETH. Validates donation is greater than 0, adds to sender's donation record, increases totalDonations, and emits event.
Admin-only function to approve a charity address. Validates address is not zero address, sets approvedCharities mapping to true, emits event.
Admin-only function to revoke charity approval. Sets approvedCharities mapping to false for the address.
Charity-only function to withdraw funds to any address. Validates recipient is not zero address, checks contract has sufficient balance, transfers ETH using call method, and emits event.
View function returning current ETH balance held by the contract.
A hash-based certificate verification system where university stores certificate hashes and anyone can verify authenticity.
Contract Address
0xFA112C9447a28Ba10d00Dcfc1B9381cBFFcF0116
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.31;
contract CertificateHashVerifier {
address public university;
// hash => exists
mapping(bytes32 => bool) private certificateHashes;
event CertificateHashStored(bytes32 hash);
constructor() {
university = msg.sender;
}
modifier onlyUniversity() {
require(msg.sender == university, "Only university allowed");
_;
}
// Store certificate hash
function storeCertificateHash(bytes32 _hash) external onlyUniversity {
require(!certificateHashes[_hash], "Hash already exists");
certificateHashes[_hash] = true;
emit CertificateHashStored(_hash);
}
// Public verification
function verifyCertificate(bytes32 _hash) external view returns (bool) {
return certificateHashes[_hash];
}
}university stores the deployer's address (the university). certificateHashes is a private mapping storing whether a hash exists (bytes32 hash to bool).
Sets msg.sender (deployer) as the university address.
Restricts certain functions to only be callable by the university address.
University-only function to store a certificate hash. Takes bytes32 hash as parameter, validates it doesn't already exist, sets mapping to true, emits event.
Public view function anyone can call. Takes bytes32 hash as parameter, returns true if hash exists in mapping (certificate is authentic), false otherwise.
The frontend generates SHA-256 hash from certificate file content using browser's crypto API. Only the hash (32 bytes) is stored on blockchain, not the actual file. To verify, re-hash the file and check if that hash exists.
A decentralized rental agreement platform where landlords create agreements, tenants deposit funds, landlords claim monthly rent, and security deposits are automatically managed.
Contract Address
CONTRACT_ADDRESS_HERE
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.31;
contract SmartRentalAgreement {
struct Agreement {
address landlord;
address tenant;
uint256 monthlyRent;
uint256 securityDeposit;
uint256 leaseStart;
uint256 leaseEnd;
uint256 lastRentClaimed;
bool depositReleased;
bool active;
}
uint256 public agreementCount;
mapping(uint256 => Agreement) public agreements;
uint256 constant RENT_INTERVAL = 30 days;
uint256 constant CLAIM_WINDOW = 5 days;
event AgreementCreated(
uint256 agreementId,
address landlord,
address tenant
);
event RentDeposited(uint256 agreementId, uint256 amount);
event RentClaimed(uint256 agreementId, uint256 amount);
event DepositReleased(uint256 agreementId, uint256 amount);
constructor() {}
function createAgreement(
address _tenant,
uint256 _monthlyRent,
uint256 _securityDeposit,
uint256 _leaseDuration
) external {
require(_tenant != msg.sender, "Tenant cannot be landlord");
require(_monthlyRent > 0, "Invalid rent");
agreementCount++;
agreements[agreementCount] = Agreement({
landlord: msg.sender,
tenant: _tenant,
monthlyRent: _monthlyRent,
securityDeposit: _securityDeposit,
leaseStart: block.timestamp,
leaseEnd: block.timestamp + _leaseDuration,
lastRentClaimed: block.timestamp,
depositReleased: false,
active: true
});
emit AgreementCreated(agreementCount, msg.sender, _tenant);
}
function depositFunds(uint256 _agreementId) external payable {
Agreement storage a = agreements[_agreementId];
require(a.active, "Inactive agreement");
require(msg.sender == a.tenant, "Only tenant");
require(
msg.value == a.monthlyRent + a.securityDeposit,
"Incorrect amount"
);
emit RentDeposited(_agreementId, msg.value);
}
function claimMonthlyRent(uint256 _agreementId) external {
Agreement storage a = agreements[_agreementId];
require(msg.sender == a.landlord, "Only landlord");
require(block.timestamp <= a.leaseEnd, "Lease ended");
require(
block.timestamp >= a.lastRentClaimed + RENT_INTERVAL,
"Rent not due"
);
require(
block.timestamp <= a.lastRentClaimed + RENT_INTERVAL + CLAIM_WINDOW,
"Claim window missed"
);
a.lastRentClaimed = block.timestamp;
(bool sent, ) = a.landlord.call{value: a.monthlyRent}("");
require(sent, "Rent transfer failed");
emit RentClaimed(_agreementId, a.monthlyRent);
}
function releaseDeposit(uint256 _agreementId) external {
Agreement storage a = agreements[_agreementId];
require(msg.sender == a.landlord, "Only landlord");
require(block.timestamp >= a.leaseEnd, "Lease active");
require(!a.depositReleased, "Already released");
a.depositReleased = true;
a.active = false;
(bool sent, ) = a.tenant.call{value: a.securityDeposit}("");
require(sent, "Deposit transfer failed");
emit DepositReleased(_agreementId, a.securityDeposit);
}
function contractBalance() external view returns (uint256) {
return address(this).balance;
}
}Contains all rental agreement details: landlord address, tenant address, monthlyRent (in wei), securityDeposit (in wei), leaseStart timestamp, leaseEnd timestamp, lastRentClaimed timestamp, depositReleased boolean, and active boolean.
agreementCount tracks total number of agreements created. agreements mapping stores all agreements by ID. RENT_INTERVAL is fixed at 30 days. CLAIM_WINDOW gives landlords 5 days to claim rent after it becomes due.
Landlord creates rental agreement by specifying tenant address, monthly rent amount, security deposit amount, and lease duration in seconds. Validates tenant is not same as landlord, rent is greater than 0. Increments agreementCount, creates new Agreement struct with current timestamp as leaseStart, calculates leaseEnd, and emits event.
Tenant deposits first month's rent plus security deposit in a single transaction. Validates agreement is active, caller is the tenant, and exact amount (monthlyRent + securityDeposit) is sent. Emits RentDeposited event. Funds are held by contract.
Landlord claims monthly rent payment. Validates caller is landlord, lease hasn't ended, at least 30 days have passed since last claim, and claim is within 5-day window. Updates lastRentClaimed timestamp, transfers monthlyRent amount to landlord using call, and emits event. If landlord misses the 5-day window, that month's rent cannot be claimed.
Landlord releases security deposit back to tenant after lease ends. Validates caller is landlord, lease has ended (current time >= leaseEnd), and deposit hasn't been released yet. Sets depositReleased to true, marks agreement as inactive, transfers securityDeposit to tenant, and emits event.
Contract uses block.timestamp for all time calculations. RENT_INTERVAL (30 days) determines when next rent is due. CLAIM_WINDOW (5 days) gives landlord grace period to claim rent. If landlord doesn't claim within window, they forfeit that month's rent. Tenant benefits from landlord's missed claims.
Role-based access control (only landlord or tenant can call specific functions). Prevents double claiming of rent or deposit. Validates exact payment amounts. Uses low-level call for ETH transfers with proper error handling. All funds held securely by contract until claimed or released.
View function returning total ETH balance held by the contract. Useful for verifying funds are properly deposited and tracking overall contract state.
A blockchain-based assignment submission system that creates immutable timestamps for student work, stores files on IPFS, and allows assignment of work to specific recipients.
Contract Address
CONTRACT_ADDRESS_HERE
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.31;
contract AssignmentVault {
struct Submission {
address student;
address assignedTo;
string cid;
uint256 timestamp;
}
uint256 public submissionCount;
mapping(uint256 => Submission) public submissions;
event AssignmentStamped(
uint256 indexed id,
address indexed student,
address indexed assignedTo,
string cid,
uint256 timestamp
);
function submitAssignment(
address _assignedTo,
string memory _cid
) public {
require(_assignedTo != address(0), "Invalid recipient");
require(bytes(_cid).length > 0, "CID required");
submissionCount++;
submissions[submissionCount] = Submission(
msg.sender,
_assignedTo,
_cid,
block.timestamp
);
emit AssignmentStamped(
submissionCount,
msg.sender,
_assignedTo,
_cid,
block.timestamp
);
}
function getSubmission(uint256 _id)
public
view
returns (
address student,
address assignedTo,
string memory cid,
uint256 timestamp
)
{
Submission memory s = submissions[_id];
return (s.student, s.assignedTo, s.cid, s.timestamp);
}
}Contains all assignment submission details: student address (who submitted), assignedTo address (recipient/teacher), cid (IPFS content identifier for the file), and timestamp (when submitted on blockchain).
submissionCount tracks total number of assignments submitted, starting at 0 and incrementing with each new submission. submissions mapping stores all submission data indexed by submission ID.
Emitted whenever a new assignment is submitted. Contains indexed parameters for efficient filtering: submission id, student address, and assignedTo address. Also includes CID and timestamp for complete tracking.
Student submits an assignment by providing recipient address and IPFS CID. Validates recipient is not zero address and CID is not empty. Increments submissionCount, creates new Submission struct with msg.sender as student and current block.timestamp, stores in mapping, and emits event. Returns nothing but submission ID can be derived from event logs.
Public view function to retrieve complete submission details by ID. Takes submission ID as parameter and returns tuple of (student address, assignedTo address, IPFS CID, timestamp). Anyone can call this function to verify any submission. Returns zero values if submission doesn't exist.
Uses block.timestamp to create immutable proof of submission time. Once recorded on blockchain, timestamp cannot be altered, providing definitive evidence of when assignment was submitted. This prevents disputes about late submissions or tampering with submission times.
Contract stores only the IPFS CID (Content Identifier), not the actual file. The file is uploaded to IPFS separately by the frontend, which ensures decentralized storage. CID acts as a cryptographic fingerprint - if file content changes, CID changes. This ensures file integrity and permanent availability.
Each submission is assigned to a specific address (teacher, evaluator, or institution). This creates a clear record of who the work was submitted to. The assignedTo field is immutable after submission, providing accountability and proper assignment routing.
Perfect for academic institutions to track student submissions with tamper-proof timestamps. Useful for freelancers proving work delivery times. Can be used in competitions or contests to verify entry submission times. Provides legal proof of intellectual property creation dates.