π 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.
Top comments (0)