🌟 Introduction
Welcome back to Day 14 of our #30DaysOfSolidity challenge!
Today, we’re going beyond simple tokens and vaults — we’re building a Smart Bank where users can store secrets, transfer ownership, and interact through a VaultManager.
Imagine a digital locker system on blockchain — where each locker (deposit box) can be:
- Basic (free)
- Premium (paid)
- Time-Locked (retrievable only after a certain time)
This project will teach you interface design, contract modularity, and inter-contract communication — all essential for scalable dApps.
🧱 Concept Overview
We’ll build a modular banking system using Solidity where:
- Each Deposit Box (Basic, Premium, TimeLocked) follows a common interface.
- A VaultManager contract can deploy and interact with any box in a unified way.
- Each box supports ownership transfer — just like handing over your locker key.
🧠 Key Concepts You’ll Learn
- How to design and implement interfaces in Solidity.
- How to build modular and scalable smart contracts.
- Securely transfer ownership between users.
- Use ETH payments, time locks, and contract composition.
- Deploy and test contracts using Foundry.
🧩 Project Structure
day14-smart-bank/
│
├── src/
│ └── DepositBoxes.sol
│
├── script/
│ └── Deploy.s.sol
│
├── test/
│ └── DepositBoxes.t.sol
│
└── foundry.toml
⚙️ Foundry Setup
Foundry is a blazing-fast Ethereum development toolkit built in Rust.
If you haven’t set it up yet:
# 1️⃣ Install Foundryup
curl -L https://foundry.paradigm.xyz | bash
foundryup
# 2️⃣ Create new project
forge init day14-smart-bank
# 3️⃣ Enter project directory
cd day14-smart-bank
# 4️⃣ Build contracts
forge build
💾 Solidity Code — Deposit Boxes and Vault Manager
Below is the full code implementing three deposit boxes and a unified VaultManager.
Paste this into src/DepositBoxes.sol.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
/// @title Smart Bank — Modular Deposit Boxes + VaultManager
/// @notice Build flexible, secure, and interface-driven storage boxes on-chain.
/// @dev For learning: never store plaintext secrets on-chain.
abstract contract Ownable {
address private _owner;
event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);
constructor() { _transferOwnership(msg.sender); }
modifier onlyOwner() { require(msg.sender == _owner, "Not owner"); _; }
function owner() public view returns (address) { return _owner; }
function transferOwnership(address newOwner) public onlyOwner {
_transferOwnership(newOwner);
}
function _transferOwnership(address newOwner) internal {
_owner = newOwner;
emit OwnershipTransferred(msg.sender, newOwner);
}
}
interface IDepositBox {
function storeSecret(bytes calldata secret) external payable;
function retrieveSecret() external view returns (bytes memory);
function transferBoxOwnership(address newOwner) external;
function owner() external view returns (address);
}
contract BasicBox is Ownable, IDepositBox {
bytes private _secret;
event SecretStored(address indexed by, uint256 timestamp);
constructor(address _initOwner) { _transferOwnership(_initOwner); }
function storeSecret(bytes calldata secret) external payable {
_secret = secret; emit SecretStored(msg.sender, block.timestamp);
}
function retrieveSecret() external view onlyOwner returns (bytes memory) {
return _secret;
}
function transferBoxOwnership(address newOwner) external onlyOwner {
transferOwnership(newOwner);
}
}
contract PremiumBox is Ownable, IDepositBox {
bytes private _secret;
uint256 public storeFee;
uint256 public collectedFees;
event SecretStored(address indexed by, uint256 fee);
constructor(address _initOwner, uint256 _fee) {
_transferOwnership(_initOwner);
storeFee = _fee;
}
function storeSecret(bytes calldata secret) external payable {
require(msg.value >= storeFee, "Fee too low");
collectedFees += msg.value;
_secret = secret;
emit SecretStored(msg.sender, msg.value);
}
function retrieveSecret() external view onlyOwner returns (bytes memory) {
return _secret;
}
function transferBoxOwnership(address newOwner) external onlyOwner {
transferOwnership(newOwner);
}
function withdrawFees(address payable to) external onlyOwner {
uint256 amt = collectedFees; collectedFees = 0;
(bool ok,) = to.call{value: amt}("");
require(ok, "Transfer failed");
}
}
contract TimeLockedBox is Ownable, IDepositBox {
bytes private _secret;
uint256 public unlockTime;
event SecretStored(address indexed by, uint256 unlockTime);
constructor(address _initOwner, uint256 _unlockTime) {
_transferOwnership(_initOwner);
unlockTime = _unlockTime;
}
function storeSecret(bytes calldata secret) external payable {
_secret = secret; emit SecretStored(msg.sender, unlockTime);
}
function retrieveSecret() external view onlyOwner returns (bytes memory) {
require(block.timestamp >= unlockTime, "Locked");
return _secret;
}
function transferBoxOwnership(address newOwner) external onlyOwner {
transferOwnership(newOwner);
}
}
contract VaultManager {
struct Box { address box; string boxType; }
Box[] public boxes;
event BoxCreated(address box, string boxType);
event SecretStored(address box, address by);
function createBasicBox() external returns (address) {
BasicBox box = new BasicBox(msg.sender);
boxes.push(Box(address(box), "BasicBox"));
emit BoxCreated(address(box), "BasicBox");
return address(box);
}
function createPremiumBox(uint256 fee) external returns (address) {
PremiumBox box = new PremiumBox(msg.sender, fee);
boxes.push(Box(address(box), "PremiumBox"));
emit BoxCreated(address(box), "PremiumBox");
return address(box);
}
function createTimeLockedBox(uint256 unlockTime) external returns (address) {
TimeLockedBox box = new TimeLockedBox(msg.sender, unlockTime);
boxes.push(Box(address(box), "TimeLockedBox"));
emit BoxCreated(address(box), "TimeLockedBox");
return address(box);
}
function storeOnBox(address box, bytes calldata secret) external payable {
IDepositBox(box).storeSecret{value: msg.value}(secret);
emit SecretStored(box, msg.sender);
}
function retrieveFromBox(address box) external view returns (bytes memory) {
return IDepositBox(box).retrieveSecret();
}
function transferBoxOwnership(address box, address newOwner) external {
IDepositBox(box).transferBoxOwnership(newOwner);
}
}
🧪 Testing in Foundry
Create a test file at test/DepositBoxes.t.sol.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import "../src/DepositBoxes.sol";
contract DepositBoxesTest is Test {
VaultManager manager;
address user = address(0x1);
function setUp() public {
manager = new VaultManager();
vm.deal(user, 10 ether);
}
function testBasicBoxFlow() public {
vm.startPrank(user);
address box = manager.createBasicBox();
manager.storeOnBox(box, "hello");
bytes memory stored = manager.retrieveFromBox(box);
assertEq(keccak256(stored), keccak256("hello"));
vm.stopPrank();
}
}
Run your tests:
forge test -vv
You’ll see successful output validating storage and retrieval logic.
🚀 Deploying with Foundry Script
Create a script at script/Deploy.s.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "forge-std/Script.sol";
import "../src/DepositBoxes.sol";
contract DeployVaultManager is Script {
function run() external {
vm.startBroadcast();
VaultManager manager = new VaultManager();
console.log("VaultManager deployed at:", address(manager));
vm.stopBroadcast();
}
}
Deploy it to a testnet (e.g., Sepolia):
forge script script/Deploy.s.sol:DeployVaultManager \
--rpc-url $SEPOLIA_RPC_URL \
--private-key $PRIVATE_KEY \
--broadcast
🔐 Security Notes
⚠️ Do not store real secrets on-chain — all blockchain data is public.
✅ Store encrypted secrets or hashes.
✅ Use Reentrancy Guards if you expand payment features.
✅ Validate ownership on every transfer.
✅ Add pause or upgradeability features for production readiness.
💡 Real-World Use Cases
- Decentralized locker systems for documents or metadata.
- Escrow contracts that release information after a time.
- Tiered storage services (free vs. premium).
- Secure on-chain key handover systems.
🧠 Key Takeaways
- Interfaces enable plug-and-play contract architectures.
- Each contract can evolve independently under a common standard.
- Using Foundry makes development and testing fast, modular, and efficient.
With this foundation, you’re one step closer to mastering Solidity modular design patterns and building real-world dApps.
👨💻 Author
Saurav Kumar
Blockchain Developer | #30DaysOfSolidity | Smart Contract Engineer
Follow me on Dev.to for more Solidity tutorials.

