🏦 Day 14 of #30DaysOfSolidity — Building a Smart Bank with Modular Deposit Boxes (Interface + Foundry)
Saurav Kumar

Saurav Kumar @sauravkumar8178

About: I'm a Software Developer diving into Software Development, passionate about learning and innovating to create cutting-edge solutions.

Location:
Greater Noida, India
Joined:
Jul 12, 2022

🏦 Day 14 of #30DaysOfSolidity — Building a Smart Bank with Modular Deposit Boxes (Interface + Foundry)

Publish Date: Oct 14
1 0

🌟 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
Enter fullscreen mode Exit fullscreen mode

⚙️ 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
Enter fullscreen mode Exit fullscreen mode

💾 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);
    }
}
Enter fullscreen mode Exit fullscreen mode

🧪 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();
    }
}
Enter fullscreen mode Exit fullscreen mode

Run your tests:

forge test -vv
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

🔐 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.

Comments 0 total

    Add comment