I built Solana Tip Jar for the Solana Hackathon (Nov 6-8, 2026), hosted by Major League Hacking. It was my first hackathon and my first project on Solana, so I picked something small enough to finish in a weekend but real enough to use the core ideas properly: wallets, signed transactions, fees, and reading data from the chain.
Live demo: https://the-solana-tip-jar.netlify.app
Source code: https://github.com/Brage1025/Tipjar
What it does
You connect a Solana wallet, send SOL to an address, and attach a short message (up to 140 characters). The app then shows:
- A live Recent tips feed, newest first, with a link to Solana Explorer for each tip
- A Top tippers leaderboard with the five biggest senders
There is no database and no backend. The blockchain is the data source. The app runs on devnet, Solana's practice network, so it uses free test SOL and no real money.
How it works
The part that surprised me most is that a Solana transaction is a bundle of instructions. My tip is one transaction with two of them:
- A transfer of SOL from the sender to the jar address
- A Memo instruction that stores the message text
const tx = new Transaction().add(
SystemProgram.transfer({ fromPubkey: publicKey, toPubkey: to, lamports })
);
tx.add(
new TransactionInstruction({
keys: [],
programId: MEMO_PROGRAM_ID, // Solana's built-in Memo program
data: new TextEncoder().encode(message),
})
);
const sig = await sendTransaction(tx, connection);
Because both instructions are in the same transaction, they succeed or fail together. The wallet signs it, so the app never sees the private key.
To show the feed, the app asks the network for recent transactions involving the jar address, then reads the transfer amount, the sender, and the memo from each one. The leaderboard is just those tips added up by sender.
Problems I ran into
The feed kept reshuffling. My first version replaced the whole list on every 10-second refresh, and slow responses could arrive out of order. The fix was to merge new results into the existing list, keyed by transaction signature, and always sort by slot (when the transaction was confirmed):
setTips((prev) => {
const all = new Map(prev.map((t) => [t.signature, t]));
found.forEach((t) => all.set(t.signature, t));
return [...all.values()].sort((a, b) => b.slot - a.slot);
});
It should also ignore any response that has been replaced by a newer request.
A wallet that never answers. Once, Phantom never showed its approval prompt, and my button stayed on "Sending..." forever. Now the app waits 45 seconds for the wallet and then shows a clear message and re-enables the button:
const timeout = new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error("WALLET_TIMEOUT")), 45_000)
);
const sig = await Promise.race([walletCall, timeout]);
Wallet detection. I assumed the wallet list was fixed. It actually depends on what is installed in the browser, so I added explicit entries for Phantom, Solflare, Coinbase Wallet and Trust Wallet, which show an install link if they are missing.
What I learned
- A wallet holds the keys and signs transactions, and the app only asks it to sign
- Devnet and mainnet are separate networks, and a faucet gives free test SOL
- Every transaction pays a small network fee, even when you tip yourself
- Anyone can verify a transaction on Solana Explorer
- Live data needs care: refreshes can arrive out of order, and wallets can fail silently
How I built it
I built this project from the ground up, taking ownership of the direction, feature selection, design decisions, testing, and deployment. During the planning process, a Solana tip jar with on-chain messages was suggested by one of my friends, and I chose to pursue it.
I set up my Phantom wallet on Devnet, obtained test SOL from the faucet, ran the application locally, and tested real transactions. Throughout development, I identified and noted down bugs, including a wallet request that got stuck and a tip feed that kept reshuffling. I deployed the application to Netlify, published the source code on GitHub, recorded the demo video, before I wrote this post.
I used React and TypeScript in this project, made the transaction logic for sending SOL with an on-chain memo, and added the feed and leaderboard functionality. I also implemented fixes based on the issues I identified, including the feed reshuffling problem and a 45-second wallet timeout.
I started this hackathon with no prior Solana experience. Working through the implementation, testing transactions, and troubleshooting problems gave me practical experience with Solana wallets, transactions, memos, and fees. I now understand how the application works and can explain the technical decisions behind it.
What's next
- Tips in USDC and other SPL tokens
- A custom on-chain program (with Anchor) to store totals and tip goals
-
.solnames instead of shortened addresses - Badges for top tippers, and multiple jars with their own pages
Try it yourself
Use a browser with a Solana wallet such as Phantom, switch it to Devnet (in Phantom: Settings, Developer Settings, Testnet Mode), and get free test SOL at faucet.solana.com. Then connect and send a tip.
The code is open source under the MIT License.
The Solana logo is from solana.com/branding and belongs to the Solana Foundation. This is an independent project and is not affiliated with or endorsed by Solana.
Top comments (1)
Good job. Reminds me on my first Web3 days