Thematic Bounty — Wallet Integration Tutorial

Roy.
16 min readAug 13, 2023

--

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:

  1. 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.
  2. 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.
  3. 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 :

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
Create app with thirdweb

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:

Wrapping <Component /> with 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

Adding <ConnectWallet /> component to homepage

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:

Default button preview

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:

Default wallets

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.

thirdweb supported wallets

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.

Code preview to add more wallets

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

Connect Button modal preview with added 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

Code preview to add localWallet support
Connect Button modal with Continue as guest option

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.

Local Wallet Creation

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.

Local wallet re log-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.

Backup wallet preview

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_ADDRESSwe’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.

Defining a state variable

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

Defining handleChange function

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:

App preview

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
Finished app preview

You can view the final finished app here

You can view the Github repo for the app here

You can view thirdweb docs here

--

--