DEV Community

Cover image for πŸ’° Day 8 β€” Building a Multi-Currency Digital Tip Jar 🌍
Saurav Kumar
Saurav Kumar

Posted on

πŸ’° Day 8 β€” Building a Multi-Currency Digital Tip Jar 🌍

Welcome to Day 8 of my #30DaysOfSolidity challenge!

Today’s build: a Multi-Currency Digital Tip Jar β€” where users can send Ether directly or simulate tips in other currencies like USD or EUR.

Think of it like a decentralized β€œBuy Me a Coffee” button β€” global, borderless, and blockchain-powered. β˜•πŸ’Ž


πŸͺ™ Project Overview

The TipJar contract allows:

  • πŸ’° Real ETH tips using payable.
  • 🌍 Simulated USD/EUR tips (based on conversion rates).
  • 🧾 Tracking of every user’s total contributions.
  • πŸ”’ Owner-only withdrawals.

It’s a simple concept with real-world applications β€” from global donations to decentralized tipping platforms.


🧠 Key Concepts Learned

  • Handling Ether transfers using msg.value and payable.
  • Using mappings to track contributions per user.
  • Implementing enums to support multiple currencies.
  • Managing conversion rates with owner control.
  • Emitting events for transparency and analytics.

βš™οΈ Smart Contract β€” TipJar.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

/// @title TipJar - Multi-currency digital tip jar (ETH + simulated USD/EUR)
/// @author Saurav
/// @notice Accepts ETH tips and simulated foreign currency tips
/// @dev Owner manages conversion rates; all contributions stored in wei-equivalent
contract TipJar {
    address public owner;

    enum Currency { USD, EUR }

    // Conversion: wei-per-cent (1 cent of USD/EUR = X wei)
    mapping(Currency => uint256) public weiPerCent;

    // Track real ETH and simulated (converted) contributions
    mapping(address => uint256) public ethContributed;
    mapping(address => uint256) public simulatedWeiContributed;

    uint256 public totalEthReceived;
    uint256 public totalSimulatedWei;

    // Events
    event EtherTipped(address indexed from, uint256 amountWei, string message);
    event CurrencyTipped(address indexed from, Currency currency, uint256 amountCents, uint256 weiEquivalent, string message);
    event RateUpdated(Currency currency, uint256 weiPerCent);
    event Withdraw(address indexed to, uint256 amountWei);

    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }

    constructor(uint256 usdWeiPerCent, uint256 eurWeiPerCent) {
        owner = msg.sender;
        weiPerCent[Currency.USD] = usdWeiPerCent;
        weiPerCent[Currency.EUR] = eurWeiPerCent;
    }

    /// @notice Tip with Ether
    function tipEther(string calldata message) external payable {
        require(msg.value > 0, "Send some ETH");
        ethContributed[msg.sender] += msg.value;
        totalEthReceived += msg.value;
        emit EtherTipped(msg.sender, msg.value, message);
    }

    /// @notice Simulate USD/EUR tip
    function tipCurrency(Currency currency, uint256 amountCents, string calldata message) external {
        require(amountCents > 0, "Amount must be > 0");
        uint256 rate = weiPerCent[currency];
        require(rate > 0, "Rate not set");

        uint256 weiEquivalent = amountCents * rate;
        simulatedWeiContributed[msg.sender] += weiEquivalent;
        totalSimulatedWei += weiEquivalent;

        emit CurrencyTipped(msg.sender, currency, amountCents, weiEquivalent, message);
    }

    /// @notice Update conversion rate (owner only)
    function setRate(Currency currency, uint256 newWeiPerCent) external onlyOwner {
        require(newWeiPerCent > 0, "Rate must be > 0");
        weiPerCent[currency] = newWeiPerCent;
        emit RateUpdated(currency, newWeiPerCent);
    }

    /// @notice Withdraw ETH (owner only)
    function withdraw(address payable to, uint256 amountWei) external onlyOwner {
        require(amountWei <= address(this).balance, "Not enough balance");
        (bool ok, ) = to.call{value: amountWei}("");
        require(ok, "Transfer failed");
        emit Withdraw(to, amountWei);
    }

    /// @notice Get total (ETH + simulated) wei contributed by user
    function totalWeiBy(address user) external view returns (uint256) {
        return ethContributed[user] + simulatedWeiContributed[user];
    }

    receive() external payable {
        ethContributed[msg.sender] += msg.value;
        totalEthReceived += msg.value;
        emit EtherTipped(msg.sender, msg.value, "");
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ’‘ How It Works

  1. Users can tip using Ether directly (real payment).
  2. Users can simulate tips in USD or EUR, converted to wei based on the owner’s set rate.
  3. The contract stores both real and simulated contributions for each address.
  4. The owner manages exchange rates and can withdraw ETH from the contract.

πŸ”’ Example Conversion

If 1 ETH = $2,000 β†’
1 cent = 1e18 / 200,000 = 500000000000000 wei

So, to set the USD rate:
setRate(Currency.USD, 500000000000000)

If 1 ETH = €1,800 β†’
1 cent = 1e18 / 180,000 = 5555555555555 wei
Use that for EUR rate.


🧩 Real-World Applications

  • A global tip jar for developers and creators.
  • A donation system supporting multiple currencies.
  • A foundation for multi-currency DeFi and dApp payment systems.

πŸš€ Future Upgrades

  • πŸ”— Integrate Chainlink oracles for live currency rates.
  • πŸ’΅ Support more fiat and crypto currencies.
  • 🧠 Add a frontend dashboard for live supporter stats.
  • 🧰 Implement multi-sig ownership for secure fund control.

🧾 GitHub Source Code

πŸ”— View the complete code on GitHub


πŸ’¬ Reflection

This project helped me understand how financial logic, conversion systems, and smart contracts can come together to power global applications.

The blockchain doesn’t care about borders β€” and neither should appreciation. 🌍


βœ… Summary

A simple idea β€” tipping β€” turned into a global decentralized system.
With Solidity, we can reimagine how appreciation and contributions flow β€” without borders, intermediaries, or limits.

Top comments (0)