In the world of Web3, getting users into decentralized apps (dApps) is a big deal. But here’s the catch: onboarding can be tricky, especially for newbies to blockchain and crypto. Stuff like “seed phrases,” “signing transactions,” and “cryptographic keys” can be alien concepts. That’s why it’s essential to make the whole process user-friendly and easy, starting with wallets.
Challenges and problems with existing wallet:
Using current wallet solutions can be tough for folks. Here are some challenges they face:
- Getting Started Complexity: Newcomers to blockchain and crypto often find the process of setting up a wallet, making accounts, and handling cryptographic keys quite intimidating. This can make it hard for many potential users to explore the cool world of dApps.
- User-Friendly Features Missing: Many wallets lack easy-to-use features for handling accounts and transactions. Imagine losing your seed phrase; it’s like losing the key to your wallet. Not all wallets have simple ways to recover this, which can be a major headache for those not familiar with handling keys.
- Security Worries: People worry about their money and personal info. Some wallets have had security breaches, which could lead to losing funds or personal data. This adds to the anxiety of using these wallets.
Overview of thirdweb
thirdweb is a complete web3 development platform that offers a Wallet SDK to simplify wallet connection to your app, game, or backend using a universal wallet interface. The Wallet SDK allows you to build a fully-featured wallet solution or integrate an existing wallet provider with thirdweb’s Typescript, React, React Native, and Unity SDKs. These SDKs provide everything you need to create web3 apps with ease.
Key features and reasons behind thirdweb
thirdweb’s Wallet SDK brings the heat with a bunch of killer features that devs are gonna love when adding a wallet to their web3 app. Right off the bat, it supports most of the popular wallets, such as :
- custodial wallets like MetaMask, WalletConnect, and Coinbase Wallet
- non-custodial wallets like Magic and Paper
- multi-sig wallets like Safe (Gnosis)
- ERC-4337 compliant Smart Wallet
- Local Wallet that allows you to create a “continue as guest” experience.
thirdweb also stand out from other wallet solutions. For example, you can deploy smart contract wallets for your users, generate wallets for new users on the fly, and connect to multi-sig wallets via Safe. Additionally, the wallets library is open-source, so you can view the source code and contribute to it on GitHub.
thirdweb is chosen for this bounty for a great reason: their wallet integration is as smooth as butter. They’ve got a wide array of supported wallets and top-notch wallet features, which makes life easy for developers looking to add a wallet solution to their web3 app.
Differences between thirdweb and MetaMask
MetaMask and thirdweb are both big players in the world of Web3 wallets, but they’ve got some cool differences.
One biggie is the range of wallets they support. MetaMask is its own thing, while thirdweb’s Wallet SDK is like a super flexible toolbox. It supports lots of popular wallets, whether it’s custodial ones like MetaMask or non-custodial pals like Magic and Paper. They even have the multi-sig wizards like Safe (Gnosis), plus an ERC-4337 compliant Smart Wallet and a Local Wallet that allows users that are not used to wallets to “continue as guest” on your app. This diversity makes it super easy for developers to give users tons of wallet options.
Now, onto the fancy features. thirdweb’s Wallet SDK goes all out. You can set up smart contract wallets for your users, whip up new wallets on the spot, and even connect to those multi-sig wallets using Safe. It’s like a developer’s dream toolkit, giving you tons of control and flexibility for your wallet integration.
Last but not least, the openness factor. MetaMask keeps its code behind closed doors, but thirdweb? They’re all about sharing. Their wallets library is open-source, chillin’ on GitHub. This means developers can peek at the code, tweak it, and join the party. It’s like building trust and teamwork in the Web3 neighborhood.
Creating an app with thirdweb CLI
To create a thirdweb app, you can run the command below in your terminal.
npx thirdweb@latest create app
After running the command, you need to specify:
- Project Name e.g. walletapp
- Blockchain to use e.g. EVM
- Framework to use e.g. Next.js
- Language to use e.g. Typescript
And for this app, I’ll be implementing these 3 features:
- Connect Wallet
- New Wallet Creation using thirdweb
- Initiate a token transfer from the integrated wallet to an external wallet highlighting the signing process while executing the transfer transaction
Installing Chakra UI
For simplicity sake, we’ll also be using Chakra UI to help us build our UI. To use Chakra UI in our project, run one of the following commands in the terminal:
npm
npm i @chakra-ui/react @emotion/react @emotion/styled framer-motion
yarn
yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion
pnpm
pnpm add @chakra-ui/react @emotion/react @emotion/styled framer-motion
Chakra UI Provider Setup
After installing Chakra UI, you need to set up the ChakraProvider
at the root of your application.
In your code editor, go to pages/_app.tsx
and wrap the Component
with the ChakraProvider
:
Integrating the Connect Wallet button
Now let’s add a connect wallet button to our app, with thirdweb, all you need is one line of code to do this, go to your pages/index.tsx
file and delete all of the templated code and start from scratch, and all you need to do is add a ConnectWallet
component and we’ll add a little bit of styling to with Chakra UI to our code
Working code for pages/index.tsx
file:
import { ConnectWallet } from "@thirdweb-dev/react"
import styles from "../styles/Home.module.css"
import { NextPage } from "next"
import { Container, Flex } from "@chakra-ui/react"
const Home: NextPage = () => { // Defining a functional component called "Home" that returns a NextPage
return (
<div className={styles.container}>
<main className={styles.main}>
<Container maxW={"1080px"} py={4} mt={10}> {/* ChakraUI Container */}
<ConnectWallet /> {/* Rendering the ConnectWallet component */}
</Container>
</main>
</div>
)
}
export default Home // Exporting the Home component as the default export
After that, you can run npm run dev
and go to http://localhost:3000/
on your preferred browser to view your connect wallet button. by default, this is what your connect wallet button would look like:
If you want to add more customization to your button, you can try here.
And if you click on it, here are the default wallets that the connect wallet button comes with:
As you can see, by default the connect wallet button already comes with a variety of wallets, including Metamask, Coinbase, and even WalletConnect, which by itself supports a bunch of different wallets already, but thirdweb also has support for other wallets, here are the wallets that are currently supported by thirdweb.
But to add those wallets, you do have to add a ThirdwebProviderProps which is thesupportedWallets
inside the <ThirdwebProvider>
tag and give it an array for the wallets that you want to use on your app, now go to your pages/_app.tsx
file and add support for the wallet based on your need for this example, I’m going to add support to localWallet, which allows the user to sign in using guest mode and rainbowWallet.
Working code for pages/_app.tsx
file:
import type { AppProps } from "next/app"
import {
ThirdwebProvider,
coinbaseWallet,
localWallet,
metamaskWallet,
rainbowWallet,
walletConnect,
} from "@thirdweb-dev/react"
import "../styles/globals.css"
// This is the chain your dApp will work on.
// Change this to the chain your app is built for.
// You can also import additional chains from `@thirdweb-dev/chains` and pass them directly.
const activeChain = "mumbai"
function MyApp({ Component, pageProps }: AppProps) {
return (
<ThirdwebProvider
clientId={process.env.NEXT_PUBLIC_TEMPLATE_CLIENT_ID}
activeChain={activeChain}
// You can pass in an array of supported wallets here.
supportedWallets={[
metamaskWallet(),
coinbaseWallet(),
walletConnect(),
localWallet(),
rainbowWallet(),
]}
>
{/* Wrap app with Chakra Provider */}
<ChakraProvider>
<Component {...pageProps} />
</ChakraProvider>
</ThirdwebProvider>
)
}
export default MyApp
And here’s what it would look like after you add more wallets
As you can see, we added support for Rainbow Wallet and local Wallet which allows the user to sign in as a guest just like that, pretty easy right?
And for this example we will also be changing the activeChain from “ethereum” to “mumbai” because the smart contract we’ll be interacting with for this tutorial is deployed on mumbai, or if you have your own smart contract that is deployed on another blockchain, you can specify the activeChain to the one that your smart contract is deployed on. thirdweb supports ethereum, polygon, arbitrum, optimism, and testnets like goerli, mumbai, etc. Additionally, you can also import additional chains from @thirdweb-dev/chains
and pass them directly and search your preferred chainlist here.
Integrating Wallet Creation
New users to web3 might often find the process of setting up a wallet, making accounts, and handling cryptographic keys quite intimidating, thirdweb offers a seamless way to create a new wallet for a user that is not familiar with wallets to interact with your app using the local wallet. The local wallet can be added by passing localwallet()
as an array in the supportedWallets
prop which we had done earlier, it allows users to sign in as a guest by adding a new option on the connect wallet modal
When you click continue as guest, you can create a new wallet by entering a password, and it will automatically create a new wallet under the hood for the user to use.
When the page is refreshed, when the user tries to click to continue as guest again, their wallet will be saved and all they need to do is enter their password and they will be sign back in.
If someone wants to save this wallet, because this wallet only available on this app, when you click on your local wallet, there will be an option to Backup Wallet, so the user can export their wallet and it will give you a JSON file for the wallet.
And if the user want’s to use their wallet say on their new device, or using a new browser, they can simply click on continue as guest again, but at the very bottom, there’s an option to Import Wallet, all the user have to do is upload the JSON file for their wallet and enter in their password and wabam! They will be signed back in using their existing wallet, don’t forget to actually fund the wallet address to use any of the feature on your web because it requires gas.
Integrating Token Transfer Feature
For this tutorial, I will show you how you can integrate a simple and basic way for you to integrate a token transfer feature for your app. First of all, on your root directory, make 2 new folders, one called components
where we going to make some react components and the other one called const
.
make a file called addresses.ts
inside the const folder, this file will store our smart contract address, and the token address that we’re going to transfer, copy the below code to your addresses.ts
file
// Smart Contract Address
export const TRANSFER_CONTRACT_ADDRESS =
"0x34B4ac60125631Bb0E07F519F5c9FEDc7b22C239"
// Token Contract Address
export const TOKEN_CONTRACT_ADDRESS =
"0x9cd1A6FFe2Eb8e6f656a4817301D05B3920525D1"
for the TRANSFER_CONTRACT_ADDRESS
we’ll be using the address of my smart contract that i deployed using thirdweb, and for theTOKEN_CONTRACT_ADDRESS
we’ll be using a DropERC20 Token that i deployed on thirdweb, you can change both of those addresses if you want to use another smart contract or another token.
Next up. inside your components
folder, make 3 files: TransferCard.tsx
, TokenBalance.tsx
, and TxButton.tsx
Now for the first component, let’s make the TransferCard component, we’ll first need to make a state variable called formData
using the useState
hook, the formData
state variable is going to be an object that contains three properties: receiver
, amount
, and message
. These properties are initialized with empty strings.
Next, we’ll also define a function called handleChange
that takes in two parameters: an event object and a string representing the name of the property to update. The handleChange
function uses the setFormData
function to update the formData
state variable. It does this by creating a new object that copies the previous state of formData
using the spread operator, and then updating the property specified by the name
parameter with the value of the event target
Let’s move on to creating the form. First, we’ll start by adding the input field for the recipient’s address.
<Text mt={4}>Send To:</Text> {/* ChakraUI Text Component */}
<Input
placeholder="0x0000000" // A placeholder text for the input field
type="text" // The type of the input field
value={formData.receiver} // The value of the input field, which is set to the "receiver" property of the "formData" object
onChange={event => handleChange(event, "receiver")} // An onChange event handler that calls the "handleChange" function with the "event" object and the string "receiver" as arguments
/>
The code above defines an input field for the recipient address. The input field is associated with the receiver
property of the formData
state variable using the value
and onChange
props. The onChange
prop specifies a function to call whenever the user types something into the input field, and this function updates the receiver
property of the formData
state variable with the new value of the input field.
Next, we’ll have to do it again for the Amount, and Message Input field, while changing the placeholder, type, value, and onchange props to the associated value.
Code for Amount input field:
<Text mt={4} fontWeight={"bold"}>
Amount:
</Text>
<Input
placeholder="0.0" // A placeholder text for the input field
type="number" // set to "number" to ensure that the user enters a valid token amount.
value={formData.amount} // The value of the input field, which is set to the "amount" property of the "formData" object
onChange={event => handleChange(event, "amount")} // An onChange event handler that calls the "handleChange" function with the "event" object and the string "amount" as arguments
/>
Code for Message input field:
<Text mt={4} fontWeight={"bold"}>
Message:
</Text>
<Input
placeholder="Add message here." // A placeholder text for the input field
type="text" // set to "text" to allow the user to enter any kind of text in the message field.
value={formData.message} // The value of the input field, which is set to the "message" property of the "formData" object
onChange={event => handleChange(event, "message")} // An onChange event handler that calls the "handleChange" function with the "event" object and the string "message" as arguments
/>
Working code for TransferCard.tsx
:
import { useState } from "react"
import { Card, Input, Text, Heading } from "@chakra-ui/react"
export default function TransferCard() {
const [formData, setFormData] = useState({
receiver: "",
amount: "",
message: "",
})
const handleChange = (e: any, name: any) => {
setFormData(prevState => ({
...prevState,
[name]: e.target.value,
}))
}
return (
<Card w={"50%"} p={10}>
<Heading>Transfer Token</Heading>
<Text mt={4}>Send To:</Text>
<Input
placeholder="0x0000000"
type="text"
value={formData.receiver}
onChange={event => handleChange(event, "receiver")}
/>
<Text mt={4} fontWeight={"bold"}>
Amount:
</Text>
<Input
placeholder="0.0"
type="number"
value={formData.amount}
onChange={event => handleChange(event, "amount")}
/>
<Text mt={4} fontWeight={"bold"}>
Message:
</Text>
<Input
placeholder="Add message here."
type="text"
value={formData.message}
onChange={event => handleChange(event, "message")}
/>
</Card>
)
}
Next, we’ll create the component for the TxButton
, open up TxButton.tsx
and copy the code below:
import { Web3Button, useContract } from "@thirdweb-dev/react"
import { ethers } from "ethers"
import { useToast } from "@chakra-ui/react"
import { TRANSFER_CONTRACT_ADDRESS } from "../const/addresses"
type Props = {
tokenAddress: string
receiver: string
amount: string
message: string
}
const TxButton: React.FC<Props> = ({
tokenAddress,
receiver,
amount,
message,
}) => {
const { contract: tokenContract } = useContract(tokenAddress, "token")
const { contract: transferContract } = useContract(TRANSFER_CONTRACT_ADDRESS)
const toast = useToast()
return (
<Web3Button
contractAddress={TRANSFER_CONTRACT_ADDRESS}
action={async contract => {
await tokenContract?.setAllowance(
TRANSFER_CONTRACT_ADDRESS,
ethers.utils.parseEther(amount).toString()
)
await transferContract?.call("transfer", [
tokenAddress,
receiver,
ethers.utils.parseEther(amount),
message,
])
}}
onSuccess={() =>
toast({
title: "Transfer Success!",
description: "You have successfully transferred tokens! 😎",
status: "success",
duration: 9000,
isClosable: true,
})
}
>
Transfer
</Web3Button>
)
}
export default TxButton
Let’s break down the code, The TxButton
component takes in four props: tokenAddress
, receiver
, amount
, and message
. These props are used to set up the transfer of tokens from one address to another.
Inside the component, there are two useContract
hooks that are used to get the token contract and the transfer contract. The tokenContract
is obtained by passing the tokenAddress
prop and the string "token"
to the useContract
hook. The transferContract
is obtained by passing the TRANSFER_CONTRACT_ADDRESS
constant to the same hook.
The toast
hook is a hook from Chakra UI used to display a success message when the transfer is complete.
The Web3Button
component is used to render a button that initiates the token transfer. It takes in several props, including contractAddress
, which is set to the TRANSFER_CONTRACT_ADDRESS
constant, and action
, which is an asynchronous function that sets the allowance for the transfer contract and calls the transfer
function on the transfer contract with the appropriate arguments.
When the transfer is successful, the onSuccess
prop is called, which displays a success message using the toast
hook.
And for the final component, let’s make the TokenBalance component to show how much Token does the user have, copy the below working code:
import { Box, Text } from "@chakra-ui/react"
import { useAddress, useContract, useTokenBalance } from "@thirdweb-dev/react"
type Props = {
tokenAddress: string
}
const TokenBalance: React.FC<Props> = ({ tokenAddress }) => {
const address = useAddress()
const { contract } = useContract(tokenAddress)
const { data: balance, isLoading: isbalanceLoading } = useTokenBalance(
contract,
address
)
return (
<Box mt={4}>
{!isbalanceLoading && (
<Text fontWeight={"bold"}>
S$DROP Token Balance: {balance?.displayValue}
</Text>
)}
<Text>
Don't have any Tokens? Get some{" "}
<a href="https://walletintegration.vercel.app/claim" target="_blank">
here
</a>{" "}
</Text>
</Box>
)
}
export default TokenBalance
Now, let’s break it down, The TokenBalance
component takes in a single prop called tokenAddress
, which is used to get the token balance for a specific token.
Inside the component, the useAddress
hook is used to get the current address of the user. The useContract
hook is used to get the contract for the specified tokenAddress
. The useTokenBalance
hook is used to get the token balance for the user's address and the specified contract.
The TokenBalance
component renders a Box
component with a margin-top of 4. Inside the Box
, there is a Text
component with a bold font weight that displays the user's token balance for the specified token. This text is only displayed if the token balance is not loading.
Below the token balance text, there is another Text
component that displays a message to the user to get some tokens if they don't have any. And includes a link where to get the tokens.
Now that we got all of our components completed, let’s put it all together! Let’s import the TxButton
component to the TransferCard and wrap it in a Chakra UI Box component, place the component inside the Chakra UI Box, and there are several props that we need to assign for the TxButton, including: tokenAddress
, receiver
, amount
, and message
. These props are used to set up the transfer of tokens from one address to another.
<Box mt={8}>
<TxButton
tokenAddress={TOKEN_CONTRACT_ADDRESS}
receiver={formData.receiver}
amount={formData.amount.toString()}
message={formData.message}
/>
</Box>
Working code for TransferCard.tsx
import { useState } from "react"
import { Box, Card, Input, Text, Heading } from "@chakra-ui/react"
import TxButton from "./TxButton"
import { TOKEN_CONTRACT_ADDRESS } from "../const/addresses"
export default function TransferCard() {
const [formData, setFormData] = useState({
receiver: "",
amount: "",
message: "",
})
const handleChange = (e: any, name: any) => {
setFormData(prevState => ({
...prevState,
[name]: e.target.value,
}))
}
return (
<Card w={"50%"} p={10}>
<Heading>Transfer Token</Heading>
<Text mt={4}>Send To:</Text>
<Input
placeholder="0x0000000"
type="text"
value={formData.receiver}
onChange={event => handleChange(event, "receiver")}
/>
<Text mt={4} fontWeight={"bold"}>
Amount:
</Text>
<Input
placeholder="0.0"
type="number"
value={formData.amount}
onChange={event => handleChange(event, "amount")}
/>
<Text mt={4} fontWeight={"bold"}>
Message:
</Text>
<Input
placeholder="Add message here."
type="text"
value={formData.message}
onChange={event => handleChange(event, "message")}
/>
<Box mt={8}>
<TxButton
tokenAddress={TOKEN_CONTRACT_ADDRESS}
receiver={formData.receiver}
amount={formData.amount.toString()}
message={formData.message}
/>
</Box>
</Card>
)
}
Now let’s render the TransferCard
and the TokenBalance
component in the home page of our app with some styling from Chakra UI, but for theTokenBalance
component, we have to give it a prop of tokenAddress
which we will set to the TOKEN_CONTRACT_ADDRESS
constant to view the token balance for the user.
Working code for index.tsx
:
import { ConnectWallet } from "@thirdweb-dev/react"
import { NextPage } from "next"
import TransferCard from "../components/TransferCard"
import { Container, Flex } from "@chakra-ui/react"
import TokenBalance from "../components/TokenBalance"
import { TOKEN_CONTRACT_ADDRESS } from "../const/addresses"
const Home: NextPage = () => {
return (
<Container maxW={"1080px"} py={4}>
<ConnectWallet />
<TokenBalance tokenAddress={TOKEN_CONTRACT_ADDRESS} />
<Flex
flexDirection={"column"}
justifyContent={"center"}
alignItems={"center"}
mt={4}
>
<TransferCard />
</Flex>
</Container>
)
}
export default Home
And that’s it! We have successfully implemented the 3 feature for this walletapp! Here’s what your app would look like:
And it turns out pretty good if you ask me 😏
But I’ll be adding some more features for the final submission which are:
- Navbar
- Claim Drop Token Page
- Account Page
- Multiple choice of Token to transfer
- Recent Transfers
You can view the final finished app here
You can view the Github repo for the app here
You can view thirdweb docs here