This project is a simple Tip Jar smart contract built with Solidity, Hardhat, OpenZeppelin, and Ethers v6. It was developed as part of my blockchain study roadmap to understand smart contract patterns, especially the Ownable access control model.
Build a small but realistic Solidity contract that:
- Accepts ETH tips from any user
- Stores tip messages + metadata
- Allows only the owner to withdraw funds
- Demonstrates the pull-payment pattern
- Uses OpenZeppelin’s Ownable for access control
- Is fully tested using Hardhat + Ethers v6
-
The contract deployer becomes the default owner.
-
The
onlyOwnermodifier protects sensitive functions likewithdraw(). -
Newer OZ versions use custom errors, not string reverts:
OwnableUnauthorizedAccount(address) -
Tests must use:
.to.be.revertedWithCustomError(contract, "OwnableUnauthorizedAccount")
Instead of sending funds during tipping, the contract holds ETH until the owner calls:
function withdraw() external onlyOwner { ... }This avoids re-entrancy issues and matches Web3 best practices.
-
Learned how to deploy contracts with Hardhat.
-
Wrote event tests (
TipReceived,Withdraw). -
Fixed multiple issues involving incorrect signer usage and undefined variables.
-
Learned why testing wallet balance changes can fail due to gas costs.
-
Replaced balance-difference tests with the correct pattern:
- Check contract balance before withdrawal
- Check contract balance after withdrawal
- Verify event emission
Ethers v6 returns BigInt, not BigNumber. I learned how to avoid:
TypeError: Cannot mix BigInt and other types
by ensuring all math stays strictly in BigInt.
-
sendTip(message)— send ETH + message -
receive()/fallback()— accept direct ETH transfers -
getAllTips()— retrieve full tip history -
withdraw()— owner-only ETH withdrawal -
Events:
TipReceivedWithdraw
All tests pass:
14 passing
0 failing
contracts/
TipJarOwnable.sol
scripts/
deploy.js
test/
tipjar.test.js
hardhat.config.js
npx hardhat nodenpx hardhat test --network localhostnpx hardhat run scripts/deploy.js --network localhost