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.valueandpayable. - 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, "");
}
}
💡 How It Works
- Users can tip using Ether directly (real payment).
- Users can simulate tips in USD or EUR, converted to wei based on the owner’s set rate.
- The contract stores both real and simulated contributions for each address.
- 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.

