How to Build a Web3 "Buy Me a Coffee" dApp with thirdweb
In this guide, we'll walk through how to create a decentralized application (dapp) that allows users to connect their wallet, send tips, and leave messages.
By the end of this guide, you will have a fully functioning web3 "Buy Me a Coffee" clone built with Next.js, thirdweb, and Solidity. Users will be able to:
- Connect their wallet using various web3 providers
- Send ETH tips and leave messages
- View total tips received and recent messages
You can check out a video version of this tutorial here:
Checkout the GitHub repo:
Prerequisites
Before we begin, make sure you have the following:
- A thirdweb account
- A wallet like MetaMask to connect to thirdweb
- Some test funds on a testnet of choice
Step 1: Create the Smart Contract
First, let's create the smart contract that will handle the on-chain logic for our app. We'll be creating this contract from scratch with Solidity.
- Create a new contract by running
npx thirdweb create contract
in your command line and selectHardhat
orForge
as your framework. - Go to the Solidity file created.
contracts
folder. If using Forge this will be located in thesrc
folder.- Add the following code:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
contract BuyMeACoffee {
struct Coffee {
address sender;
string message;
uint256 timestamp;
}
uint256 totalCoffee;
address payable owner;
event NewCoffee(address indexed sender, uint256 timestamp, string message);
constructor() {
owner = payable(msg.sender);
}
function buyMeACoffee (
string memory _message
) payable public {
require(msg.value > 0 ether, "You need to send some ether");
totalCoffee += 1;
payable(owner).transfer(msg.value);
emit NewCoffee(msg.sender, block.timestamp, _message);
}
function getTotalCoffee() public view returns (uint256) {
return totalCoffee;
}
}
Now that we have our smart contract created to handle our onchain functionality of our accountability app we can deploy it to any EVM blockchain.
Step 2: Deploy the Smart Contract
Next, we need to deploy the smart contract that will power our accountability app.
- Run the command
npx thirdweb deploy
to deploy your smart contract - Select the chain you want to deploy your contract to and click 'Deploy Now'
Once deployed, save the contract address somewhere as we'll need it later.
Step 3: Set Up the Next.js Project and install Connect SDK
Next, let's set up our Next.js project:
- Open a terminal and run:
npx thirdweb create app
- Select
Next.js
for the framework - Open project in code editor
- Edit the
.env
file and add youclientID
- Define your
chain
. In this example we'll be using Base Sepolia Testnet
import { defineChain } from "thirdweb";
import { baseSepolia } from "thirdweb/chains";
export const chain = defineChain( baseSepolia );
Step 4: Add a way to connect a wallet to app
Next, let's create a way for a user to connect a web3 wallet to our accountability app.
- In
page.tsx
we can add aConnectEmbed
component from Connect SDK
<div>
<ConnectEmbed
client={client}
chain={chain}
/>
</div>
- We can then check to see if a wallet is connected and if there is we can show a connected
ConnectButton
const account = useActiveAccount();
if(account) {
return (
<div>
<ConnectButton
client={client}
chain={chain}
/>
</div>
)
}
Now we have a way for our user to connect and disconnect a wallet from our accountability app.
Step 5: Build accountability app
Get Contract
Now, lets get some data from our smart contract and create our accountability app to interact with.
- Create a new file
contract.ts
in autils
folder - Get our accountability smart contract using
getContract
const contractAddress = "<Contract_Address>";
export const contract = getContract({
client: client,
chain: chain,
address: contractAddress,
abi: contractABI
});
- Create a new file for the accountability smart contract ABI
export const contractABI = ["Contract_ABI"] as const;
First, create a new file called _app.tsx
in the pages
directory and add the following code:
import type { AppProps } from 'next/app';
import { ChainId, ThirdwebProvider } from '@thirdweb-dev/react';
import '../styles/globals.css';
// This is the chainId your dApp will work on.
const activeChainId = ChainId.Mumbai;
function MyApp({ Component, pageProps }: AppProps) {
return (
<ThirdwebProvider desiredChainId={activeChainId}>
<Component {...pageProps} />
</ThirdwebProvider>
);
}
export default MyApp;
Creating the Buy Me a Coffee Component
Now that we have our contract set up, let's build the main component for our dapp - the "Buy Me a Coffee" form.
- Create state variables for the input fields a user will have to fill out for the tip amount and the message they want to leave
const [buyAmount, setBuyAmount] = useState(0);
const [message, setMessage] = useState("");
- Create input for user to specify the tip amount and the message they want to leave
<div>
<label>Tip amount</label>
<p>*Must be greater than 0.</p>
<input
type="number"
value={buyAmount}
onChange={(e) => setBuyAmount(Number(e.target.value))}
step={0.01}
/>
<label>Message</label>
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Enter a message..."
/>
</div>
- Create a
TransactionButton
only if a user has entered a tip amount greater than 0 and a message. After the transaction is complete it should reset the values for the tip amount and message and refetch the information from the contract.
{message && buyAmount > 0 && (
<TransactionButton
transaction={() => (
prepareContractCall({
contract: contract,
method: "buyMeACoffee",
params: [message],
value: BigInt(toWei(buyAmount.toString())),
})
)}
onTransactionConfirmed={() => {
alert("Thank you for the coffee!")
setBuyAmount(0);
setMessage("");
refetchTotalCoffees();
refetchContractEvents();
}}
>Buy Coffee</TransactionButton>
)}
- Lets display the total coffees (tips) that have been left. To do this we can call the
getTotalCoffee
function from the smart contract using theuseReadContract
hook
const {
data: totalCoffees,
refetch: refetchTotalCoffees
} = useReadContract({
contract: contract,
method: "getTotalCoffee",
});
- Next, lets get the the events from our smart contract. Out events should show to most recent tips and messages left. We can then display this data on our app.
const {
data: contractEvents,
refetch: refetchContractEvents
} = useContractEvents({
contract: contract,
});
- We can also add some additional functions to truncate our wallet addresses and convert the block timestamp to a readable date.
const truncateWalletAddress = (address: string) => {
return `${address.slice(0, 6)}...${address.slice(-4)}`;
}
const convertDate = (timestamp: bigint) => {
const timestampNumber = Number(timestamp);
return new Date(timestampNumber * 1000).toLocaleString();
};
- Finally, we'll display our total coffees and we can display the contract events of recent coffees in our app. When mapping though the array of events you can
reverse()
the array to display the most recent event at the top of your list.
<div>
<h3>Total Coffees: {totalCoffees?.toString()}</h3>
<p>Recent Coffees:</p>
{contractEvents && contractEvents.length > 0 && (
[...contractEvents].reverse().map((event, index) => (
<div key={index}>
<div>
<p>
{truncateWalletAddress(event.args.sender)}
</p>
<p>
{convertDate(event.args.timestamp)}
</p>
</div>
<p>
{event.args.message}
</p>
</div>
))
)}
</div>
Testing it Out
Start your Next.js app with yarn dev
and open http://localhost:3000
in your browser.
Connect your wallet using the ConnectEmbed
modal. You should then see the BuyMeACoffee
component with the form to send a tip and message.
Enter a tip amount and message, then click "Buy a Coffee". Approve the transaction in your wallet. Once confirmed, you should see an alert and the recent coffees list and total should update.
Try sending a few more tips from different wallets to see the recent coffees list grow.
And that's it! You've now built a fully functional web3 "Buy Me a Coffee" dapp using thirdweb. Users can connect their wallet, send tips with messages, and view all the recent tips.
Conclusion
And there you have it! We've built a web3 accountability app that allows users to:
- Connect their wallet
- Tip an amount and leave a message
- Display total coffees (tips) left
- Display the most recent coffees (tips) left
By using thirdweb's Connect SDK, we were able to easily interact with our smart contract to read data and make transactions.
You can build an app like this on any EVM-compatible blockchain, including popular L2s like Optimism, Base, Arbitrum, Avalanche and more.
The thirdweb Connect SDK provides a simple and powerful way to build web3 apps with a great developer experience.
I hope you enjoyed this tutorial and found it valuable!