Blockchain Smart Contracts Tutorial: A Step-by-Step Guide with Examples
- Ali Tuna
- Oct 31
- 3 min read
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