
Coffee Chain — A Decentralized Tipping Platform on Ethereum
I built Coffee Chain, a decentralized Ethereum platform where supporters tip creators with ETH and attach messages — like a Web3 "Buy Me a Coffee". No platform holds the funds, no intermediary takes a cut. Every tip is a transparent, permanent on-chain transaction.
The Problem With Centralized Tipping
Platforms like Buy Me a Coffee and Patreon sit between creators and their supporters. They hold the money, set the rules, and take a fee. A creator's earnings are only ever as reliable as the platform running them.
On-chain tipping removes the middleman entirely. A supporter sends ETH straight to a smart contract, the tip lands in the creator's balance, and the message is recorded on-chain forever. There's no one to trust and nothing to censor.
The Contract
Coffee.sol is a single, self-contained contract. The core concept is a Memo — a tip with a message:
struct Memo {
address from;
uint256 timestamp;
string name;
string message;
}
Creator Registration
Anyone can register as a creator with a name and a short bio. The contract enforces a few rules:
- Names must be non-empty
- An address can only register once
- Names are unique — a
creatorByNamemapping (keyed by keccak hash) prevents impersonation
function registerCreator(string calldata _name, string calldata _about) external {
if (bytes(_name).length == 0) revert EmptyName();
if (creators[msg.sender].owner != address(0)) revert AlreadyRegistered();
...
}
Profiles can be updated later — including a rename — as long as the new name isn't taken.
Buying Coffee
Sending a tip is one transaction: the supporter names the creator, writes a message, and attaches ETH. The contract records the memo, bumps the creator's running total, and emits a NewCoffee event:
function buyCoffee(address payable _creator, string calldata _name, string calldata _message)
external payable
{
if (msg.value == 0) revert NoFundsSent();
if (creators[_creator].owner == address(0)) revert CreatorNotRegistered();
memosByCreator[_creator].push(
Memo({from: msg.sender, timestamp: block.timestamp, name: _name, message: _message})
);
creators[_creator].totalReceived += msg.value;
creatorBalances[_creator] += msg.value;
emit NewCoffee(_creator, msg.sender, msg.value, block.timestamp, _name, _message);
}
Withdrawals
Creators withdraw their own balance directly to their wallet. The withdrawal follows the Checks-Effects-Interactions pattern — the balance is zeroed before the ETH transfer, and restored if the transfer fails:
function withdraw() external {
if (creators[msg.sender].owner == address(0)) revert NotACreator();
uint256 amount = creatorBalances[msg.sender];
if (amount == 0) revert NoFundsToWithdraw();
creatorBalances[msg.sender] = 0; // effects before interaction
(bool success,) = msg.sender.call{value: amount}("");
if (!success) {
creatorBalances[msg.sender] = amount; // roll back on failure
revert WithdrawFailed();
}
emit FundsWithdrawn(msg.sender, amount);
}
Gas-Friendly Reads
Reading memos is designed for on-chain data. getMemosPaginated returns a bounded window with an offset, so a creator with thousands of tips can page through them without hitting the gas limit — a real concern with unbounded array returns.
Testing
The Foundry test suite covers the full surface: registration and uniqueness, name collisions, tipping flows, and withdrawal behavior:
forge test
Frontend
The frontend is a Next.js app with wallet connection via Reown AppKit (formerly WalletConnect). The routes map cleanly to the contract's features:
/— the marketing home page/[username]— a public creator profile with a support form and a live list of past memos/create— register as a creator/dashboard— the creator dashboard with a withdraw button and support history/docs— contract address and deployment info
Supporters attach a name and message with their tip; creators see their supporters and messages aggregated from on-chain state, not a database.
Deployment
The contract is deployed on Sepolia at 0x035efEe092383e2baFdAAAacF79167c55178fa59 and verified on Etherscan. The live app runs at coffee-chain-sepolia.vercel.app — Rakib's profile is at coffee-chain-sepolia.vercel.app/rakib.
Reflections
The design that matters most here is no custody. Funds never sit in a platform treasury — they're either with the supporter or the creator, and every transaction is public. That's the whole pitch of on-chain tipping, and the contract keeps it honest: no fee middleware, no admin that can freeze funds, just a creator, a supporter, and a message recorded on the blockchain.