Smart contracts aren’t just about tokens — they can also help users save and manage Ether securely.
Today, we’ll build a Digital Piggy Bank — a simple yet powerful example of how to store, withdraw, and track Ether safely on the blockchain.
This project demonstrates real-world Solidity skills, including secure fund handling, user-specific balances, reentrancy protection, and event logging.
🎯 What You’ll Learn
By completing this project, you’ll gain hands-on experience in:
- Managing user balances using mappings.
- Handling Ether deposits and withdrawals.
- Using
msg.sender
to identify users. - Emitting events for better traceability.
- Implementing security best practices like the Checks-Effects-Interactions pattern and reentrancy guards.
🧠 Concept Overview
A Digital Piggy Bank lets each user deposit and withdraw Ether independently.
When a user deposits Ether, it gets recorded under their address.
They can later withdraw their funds safely without affecting others.
This mimics a personalized savings system, showing how decentralized finance (DeFi) apps handle value securely.
💻 Smart Contract Code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @title DigitalPiggyBank
* @notice A secure and gas-efficient smart contract that allows users to deposit and withdraw Ether.
* Includes proper checks, events, and a safe withdrawal mechanism.
*/
contract DigitalPiggyBank {
/// @dev Mapping to store each user's Ether balance
mapping(address => uint256) private _balances;
/// @dev Emitted when a user deposits Ether
event Deposited(address indexed user, uint256 amount);
/// @dev Emitted when a user withdraws Ether
event Withdrawn(address indexed user, uint256 amount);
/// @dev Reentrancy guard to prevent reentrancy attacks
bool private _locked;
modifier nonReentrant() {
require(!_locked, "Reentrancy detected");
_locked = true;
_;
_locked = false;
}
/**
* @notice Deposit Ether into your digital piggy bank.
* @dev Ether sent is credited to the sender's balance.
*/
function deposit() external payable {
require(msg.value > 0, "Deposit amount must be greater than zero");
_balances[msg.sender] += msg.value;
emit Deposited(msg.sender, msg.value);
}
/**
* @notice Withdraw Ether from your balance.
* @param amount The amount of Ether to withdraw (in wei).
*/
function withdraw(uint256 amount) external nonReentrant {
uint256 userBalance = _balances[msg.sender];
require(amount > 0, "Withdrawal amount must be greater than zero");
require(userBalance >= amount, "Insufficient balance");
// Update balance before transferring (Checks-Effects-Interactions pattern)
_balances[msg.sender] = userBalance - amount;
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
emit Withdrawn(msg.sender, amount);
}
/**
* @notice View the balance of the caller.
* @return The Ether balance of the caller in wei.
*/
function getMyBalance() external view returns (uint256) {
return _balances[msg.sender];
}
/**
* @notice View the total Ether held in this contract.
* @return The total Ether in wei.
*/
function getContractBalance() external view returns (uint256) {
return address(this).balance;
}
}
🧩 Key Features
Feature | Description |
---|---|
💰 Deposit & Withdraw | Users can deposit and withdraw Ether anytime. |
🔒 Reentrancy Protection | Prevents reentrancy attacks using a custom guard. |
🧾 Event Logging | Transparent tracking of deposits and withdrawals. |
⚙️ Safe Transfers | Uses .call for modern, gas-efficient Ether transfers. |
🧱 Industry Best Practices | Implements Checks-Effects-Interactions and private state variables. |
🧪 How to Test
- Go to Remix IDE.
- Paste the code into a new Solidity file.
- Compile with Solidity 0.8.20 or above.
- Deploy using Injected Web3 (e.g., MetaMask).
- Try depositing and withdrawing Ether to test it out!
Example tests:
- Deposit 0.1 ETH → check your balance.
- Withdraw 0.05 ETH → verify balance update.
- Check contract balance to confirm total deposits.
🔐 Security Highlights
- Non-reentrant withdrawals: Prevents attacks that exploit recursive calls.
- Validated inputs: Ensures only valid deposit and withdrawal amounts.
-
Safe transfer pattern: Avoids gas limit issues common with
transfer()
orsend()
. - Immutable data flow: Each user can only control their own balance.
🌍 Real-World Use Cases
This project can evolve into:
- A DeFi savings vault where users lock funds for rewards.
- A donation wallet that records every contributor.
- A microbanking system for learning blockchain-based finance.
🔗 Source Code
Check out the full code and other Solidity projects in my repository 👇
👉 GitHub Repository
💬 Closing Thoughts
This Digital Piggy Bank is a major step toward understanding how real-world DeFi systems handle funds securely.
By mastering Ether management and safe coding patterns, you’re moving closer to becoming a professional blockchain developer.
Stay tuned for Day 7, where we’ll continue exploring more advanced Solidity concepts!
Top comments (0)