Project Documentation

Complete guide to smart contracts, code explanations, and deployment

Video Tutorials

Watch step-by-step guides to get started with blockchain development

1

Creating a MetaMask Wallet

Learn how to create and set up your MetaMask wallet for blockchain transactions

2

Deploy Smart Contract on Sepolia ETH Testnet (Remix)

Step-by-step guide to deploying your smart contract using MetaMask on Sepolia testnet

3

How to Operate Etherscan

Master Etherscan to track transactions and verify smart contracts

Quick Tips

  • Watch the MetaMask tutorial first to set up your wallet
  • Follow the deployment guide to deploy your first smart contract
  • Use Etherscan to verify and interact with deployed contracts

How to Deploy Smart Contracts on Remix IDE

1

Open Remix IDE

Go to https://remix.ethereum.org in your browser. This is the official Solidity IDE.

2

Create New File

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.

3

Paste Contract Code

Copy the Solidity code from this documentation and paste it into your new file in Remix. The code will be automatically syntax highlighted.

4

Compile Contract

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).

5

Setup MetaMask

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).

6

Deploy Contract

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.

7

Get Contract Address

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.

8

Test Contract

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.

Important Notes

  • Always test contracts on Sepolia testnet before mainnet deployment
  • Keep your contract address safe after deployment
  • The deployer address becomes admin/university for access control
  • Verify compiler version matches (0.8.31 or compatible)

ConfCert - Certificate Management System

A blockchain-based certificate issuance and verification system that stores certificate data on IPFS and metadata on blockchain.

Contract Address

0x8b94D4dB48ECAec78875e9D58e132EC389Bbe5AD

Key Features

  • Issue individual certificates with student name and IPFS CID
  • Bulk certificate issuance for multiple students at once
  • Certificate verification using unique ID
  • IPFS integration for storing certificate files
  • Immutable record keeping on blockchain

Complete Solidity Code

// 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);
    }
}

Code Explanation

State Variables

nextId starts at 1000 and increments for each certificate. The certificates mapping stores all certificate data indexed by ID.

Certificate Struct

Contains id (unique identifier), studentName (recipient name), ipfsCID (IPFS hash of certificate file), and issuer (wallet that created it).

issueCertificate Function

Creates a single certificate. Takes student name and IPFS CID as parameters, increments nextId, stores certificate data, and emits an event.

issueCertificatesBatch Function

Allows bulk issuance of certificates. Takes arrays of names and CIDs, validates they have the same length, and creates multiple certificates in one transaction.

getCertificate Function

View function to retrieve certificate details by ID. Returns student name, IPFS CID, and issuer address.

Voting System - Democratic Voting

A transparent and tamper-proof voting system where admin can add candidates and users can vote once.

Contract Address

0x1514EFb55d58A80C11b11F0D4b63990E21299f78

Key Features

  • Admin-controlled candidate management
  • One vote per address enforcement
  • Public vote count visibility
  • Transparent results on blockchain
  • Pre-loaded with sample candidates (Alice and Bob)

Complete Solidity Code

// 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);
    }
}

Code Explanation

Candidate Struct

Contains id (unique number), name (candidate name), and voteCount (number of votes received).

State Variables

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.

Constructor

Sets msg.sender as admin and pre-adds two sample candidates (Alice and Bob) for demonstration.

onlyAdmin Modifier

Restricts certain functions to only be callable by the admin address.

addCandidate Function

Admin-only function to add new candidates. Increments candidatesCount, creates new Candidate struct with vote count starting at 0.

vote Function

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.

getCandidate Function

View function to retrieve candidate name and current vote count by ID.

Charity Donation System

A transparent donation platform where admin approves charities, users donate, and approved charities can withdraw funds.

Contract Address

0x5Ae3a0d0432e6Fb355CD278Aea1914D791d2cB97

Key Features

  • Public donation function (anyone can donate)
  • Admin-controlled charity approval system
  • Approved charities can withdraw to any address
  • Track total donations and individual contributions
  • Transparent fund management on blockchain

Complete Solidity Code

// 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;
    }
}

Code Explanation

State Variables

admin stores deployer address. donations mapping tracks how much each address donated. approvedCharities mapping tracks which addresses are approved charities. totalDonations tracks cumulative donations.

Modifiers

onlyAdmin restricts functions to admin only. onlyCharity restricts functions to approved charity addresses only.

donate Function

Payable function allowing anyone to send ETH. Validates donation is greater than 0, adds to sender's donation record, increases totalDonations, and emits event.

addCharity Function

Admin-only function to approve a charity address. Validates address is not zero address, sets approvedCharities mapping to true, emits event.

removeCharity Function

Admin-only function to revoke charity approval. Sets approvedCharities mapping to false for the address.

withdraw Function

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.

contractBalance Function

View function returning current ETH balance held by the contract.

University Certificate Hash Verifier

A hash-based certificate verification system where university stores certificate hashes and anyone can verify authenticity.

Contract Address

0xFA112C9447a28Ba10d00Dcfc1B9381cBFFcF0116

Key Features

  • Store SHA-256 hash of certificates on blockchain
  • University-only hash storage
  • Public verification (anyone can check)
  • Tamper-proof verification
  • No file storage - only cryptographic fingerprints

Complete Solidity Code

// 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];
    }
}

Code Explanation

State Variables

university stores the deployer's address (the university). certificateHashes is a private mapping storing whether a hash exists (bytes32 hash to bool).

Constructor

Sets msg.sender (deployer) as the university address.

onlyUniversity Modifier

Restricts certain functions to only be callable by the university address.

storeCertificateHash Function

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.

verifyCertificate Function

Public view function anyone can call. Takes bytes32 hash as parameter, returns true if hash exists in mapping (certificate is authentic), false otherwise.

How Hashing Works

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.

Smart Rental Agreement System

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

Key Features

  • Create rental agreements with customizable terms
  • Automatic rent payment scheduling (30-day intervals)
  • Security deposit management with automatic release
  • 5-day claim window for monthly rent collection
  • Transparent lease start/end tracking
  • Immutable agreement records on blockchain

Complete Solidity Code

// 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;
    }
}

Code Explanation

Agreement Struct

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.

State Variables and Constants

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.

createAgreement Function

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.

depositFunds Function

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.

claimMonthlyRent Function

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.

releaseDeposit Function

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.

Time-Based Logic

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.

Security Features

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.

contractBalance Function

View function returning total ETH balance held by the contract. Useful for verifying funds are properly deposited and tracking overall contract state.

Assignment Vault - Timestamped Assignment Submission

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

Key Features

  • Submit assignments with IPFS file storage
  • Assign submissions to specific recipient addresses
  • Blockchain timestamp proves submission time
  • Unique submission ID for each assignment
  • Public verification of any submission
  • Permanent and tamper-proof record keeping

Complete Solidity Code

// 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);
    }
}

Code Explanation

Submission Struct

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).

State Variables

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.

AssignmentStamped Event

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.

submitAssignment Function

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.

getSubmission Function

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.

Timestamp Proof

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.

IPFS Integration

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.

Recipient Assignment

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.

Use Cases

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.

Ready to Deploy?

Follow the deployment guide above and use the contract codes provided. All contracts are tested and production-ready.