top of page

Blockchain Smart Contracts Tutorial: A Step-by-Step Guide with Examples

Updated: Nov 1

A smart contract is a self-executing contract with the terms of the agreement directly written into code. It operates on blockchain technology, allowing for secure, transparent, and automated transactions without the need for intermediaries. Smart contracts automatically enforce and execute the terms when predetermined conditions are met, reducing the risk of fraud and increasing efficiency.

If you wanna know more, you can check out my own above the link. Thank you supporting us.

1. Data Types

Data types define what kind of information you can store in a smart contract.

Example: Basic Data Types

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract DataTypesExample {
    // String - stores text
    string public name = "Alice";

    // Uint - stores positive numbers (unsigned integer)
    uint public age = 25;

    // Address - stores blockchain wallet addresses
    address public owner = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;

    // Bool - stores true or false
    bool public isActive = true;
}

What this shows:

  • string → text like names

  • uint → numbers like age

  • address → wallet addresses

  • bool → yes/no (true/false) values

2. Structs

Structs let you group related data together — like creating your own custom data type.

Example: Person Struct

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract StructExample {
    struct Person {
        string name;
        uint age;
    }

    Person public person1;
	//We are using java style setter and getter.	
    function setPerson(string memory _name, uint _age) public {
        person1 = Person(_name, _age);
    }

    function getPerson() public view returns (string memory, uint) {
        return (person1.name, person1.age);
    }
}

What this shows:

  • struct Person groups name and age.

  • Create a person: Person("Bob", 30).

  • Access data: person1.name or person1.age.

3. Mappings

Mappings act like dictionaries — they link one set of data to another.

In blockchain, mappings are used to associate addresses with information (like balances or personal data).

Example: Simple Mapping

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract MappingExample {
    struct Person {
        uint age;
        string name;
    }
	//we can create space in this way. 
    Person[5] public persons;

    // Mapping: address → age
    mapping(address => uint) public ages;
    mapping(address => string) public names;
}

What this shows:

  • mapping(address => uint) means: for each address, store a number.

  • msg.sender is the address of the person calling the function.

  • Quick lookup: give an address → get age instantly.

4. Events

Events are blockchain notifications — like public announcements when something happens.

Example: Event Logging

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract EventExample {
    event AgeUpdated(address person, uint newAge);

    mapping(address => uint) public ages;

    function setAge(uint _age) public {
        ages[msg.sender] = _age;

        emit AgeUpdated(msg.sender, _age);
    }
}

What this shows:

  • event AgeUpdated defines what info is broadcast.

  • emit AgeUpdated(...) sends the notification.

  • External apps can listen for these events to track updates.

Final Example: Complete Person Registry

Now let’s combine everything into one practical smart contract.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract PersonRegistry {

    struct Person {
        string name;
        uint age;
        bool exists;
    }

    mapping(address => Person) public persons;

    event PersonRegistered(address indexed person, string name, uint age);
    event PersonUpdated(address indexed person, string name, uint age);

    function register(string memory _name, uint _age) public {
        require(!persons[msg.sender].exists, "Already registered!");
        require(_age > 0 && _age < 150, "Invalid age!");

        persons[msg.sender] = Person(_name, _age, true);

        emit PersonRegistered(msg.sender, _name, _age);
    }

    function updateInfo(string memory _name, uint _age) public {
        require(persons[msg.sender].exists, "Not registered!");
        require(_age > 0 && _age < 150, "Invalid age!");

        persons[msg.sender].name = _name;
        persons[msg.sender].age = _age;

        emit PersonUpdated(msg.sender, _name, _age);
    }

    function getMyInfo() public view returns (string memory, uint) {
        require(persons[msg.sender].exists, "Not registered!");
        Person memory p = persons[msg.sender];
        return (p.name, p.age);
    }

    function getPerson(address _person) public view returns (string memory, uint) {
        require(persons[_person].exists, "Person not found!");
        Person memory p = persons[_person];
        return (p.name, p.age);
    }
}

Step-by-Step Explanation

1. Struct Person

Defines a grouped data type:

  • name — string

  • age — uint

  • exists — bool (to check registration)

2. Mapping

mapping(address => Person) public persons; stores each user’s details.

  • public automatically generates a getter function: persons(address).

3. Events

  • PersonRegistered and PersonUpdated notify when data changes.

  • indexed person allows filtering by address.

4. Functions

register(...)

  • Prevents duplicate registration.

  • Validates age.

  • Creates new record → emits PersonRegistered.

updateInfo(...)

  • Requires user to exist.

  • Updates info → emits PersonUpdated.

getMyInfo() and getPerson(address)

  • view functions (read-only, no gas off-chain).

  • Return name and age.

Key Solidity Concepts Explained

Return Types

Functions can specify outputs with returns(...).Example: returns (string memory, uint) means the function outputs two values.

  • string memory → temporary text data.

  • uint → number.

When called off-chain, view functions don’t cost gas.

Setter vs Getter

  • Setter → modifies state (costs gas): register(), updateInfo().

  • Getter → reads state (free off-chain): getMyInfo(), getPerson().

msg.sender

Represents the caller’s address.Used for user-specific data and access control.

public

A visibility modifier. Makes a function or variable accessible externally. For variables, Solidity auto-creates getter functions.

Naming with Underscores

name and age are input parameters. The underscore prevents conflict with storage variables.

Comments


bottom of page