NextJS app: how to send USDT tokens between wallets?

Mark
Coinmonks
10 min readMay 22, 2024

--

how to send USDT tokens between wallets within NextJS app

Explore how to retrieve ERC20 token balances and execute token transfers between wallets on a blockchain network. Learn to integrate cutting-edge technologies like NextJS, TypeScript, ReactJS, RainbowKit, and Wagmi through clear, step-by-step instructions. Read on to transform your NextJS app into a fully functional web3 powerhouse!

This article is a continuation of the work described in the previous article.

At that work we explored the process of connecting a web3 application frontend made with nextJS framework using RainbowKit and Wagmi to a test private blockchain network.

As of this material, we will learn how to retrieve wallet balances in ERC20 tokens and how to make token transfers between wallets.

Before we begin, let’s take a look at the building primitives we’re going to use.

This example will use the following technology stack.

TypeScript — is a free and open-source high-level programming language developed by Microsoft that adds static typing with optional type annotations to JavaScript. TypeScript is a language extension that adds features to ECMAScript 6. It helps developers to make code more readable and maintainable.

ReactJS — is an open-source front-end JavaScript library for building user interfaces based on components. It is maintained by Meta (formerly Facebook) and a community of individual developers and companies. Currently, according to the official documentation, it is not recommended to start new projects on pure React.js without React-powered frameworks.

NextJS — is an open-source a flexible framework on a top of ReactJS with server-side rendering and static website generation. Official React documentation mentions Next.js as the first among “Recommended frameworks”

Wagmi — is a library, consisting of a collection of 40+ React Hooks. It provides developers with intuitive building blocks to build their web3 ReactJS apps and significantly simplifies implementation of app logic related with typical operations with wallets, transactions, smart-contracts, etc.

Viem — is full-featured lightweight library for interacting with EVM chains. It is a TypeScript Interface that provides low-level stateless primitives for interacting with EVM-based blockchains. It is an alternative to ethers.js and web3.js with a focus on reliability, efficiency, and excellent developer experience.

RainbowKit — is a React library that makes it easy to add wallet connection to your dApp. Aside from handling the connection and disconnection of wallets, RainbowKit supports numerous wallets, swaps connection chains, resolves address to ENS, displays balance and much more!

Great! Let’s get started. First, let’s create a skeleton of our application based on NextJS. To do this you need to run the command:

npx create-next-app@latest

This starts installer.

nextjs installer

After creation of an empty application skeleton, let’s clean up a bit of the code that created the installation package for us.

To do this, go-ahead, following the recommendations on the main page to edit the file app/page.tsx. You should remove everything inside the Home function so that the Home function returns the following content:

export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
NextJS Web3 application
</main>
);
}

Build a project for just in case and check that these edits did not break anything…

Next we need to install the rainbowkit/wagmi/viem packages.

To do this, go to the RainbowKit website https://www.rainbowkit.com/docs/installation and in the Manual Installation section, copy the installation command:

npm install @rainbow-me/rainbowkit wagmi viem@2.x @tanstack/react-query

After successfully installing these libraries, you will also need to adjust next.config.js, following the recommendations from here https://github.com/rainbow-me/rainbowkit/blob/main/examples/with-next-app/next.config.js. Resulting content of next.config.js:

/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
webpack: config => {
config.externals.push('pino-pretty', 'lokijs', 'encoding');
return config;
},
};
export default nextConfig;

Now let’s create a file app/providers.tsx with the following content:

'use client';

import * as React from 'react';
import {
WalletList,
getDefaultConfig,
Chain,
} from '@rainbow-me/rainbowkit';

import { metaMaskWallet, trustWallet, uniswapWallet, walletConnectWallet } from '@rainbow-me/rainbowkit/wallets';
import { WagmiProvider } from 'wagmi';
import {
polygon,
} from 'wagmi/chains';

import { QueryClientProvider, QueryClient, } from "@tanstack/react-query";

const queryClient = new QueryClient();

const _walletList: WalletList = [
{
groupName: 'Recommended',
wallets: [metaMaskWallet, trustWallet, uniswapWallet, walletConnectWallet],
},
];

const _chains: readonly [Chain, ...Chain[]] = [polygon];

const config = getDefaultConfig({
appName: 'nextjsweb3paymentapp',
projectId: 'YOUR_PROJECT_ID',
wallets: _walletList,
chains: _chains,
ssr: false,
});
export function Providers({ children }: { children: React.ReactNode }) {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
</WagmiProvider>
);
}

This file is a global configuration for connecting Wagmi to the application. And note that the provider for RainbowKit is not included here. This was done intentionally to show that it is quite possible to make the code responsible for the crypto wallet connection only the local part of the application.

Then you need to make this configuration visible to the entire application. To do this, change the contents of the app/layout.tsx file as follows:

import type { Metadata } from "next";

import { Inter } from "next/font/google";
import { Providers } from './providers';

import "./globals.css";
import '@rainbow-me/rainbowkit/styles.css';

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
title: "NextJS web3 App",
description: "",
};

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={inter.className}>
<Providers>
{children}
</Providers>
</body>
</html>
);
}

Check that the changes did not break the project and, if all is well, let’s continue to make changes.

Now we need to make our own private blockchain network and add this custom network to the configuration. How to create a private blockchain network for testing and debugging is written in the article:

Just like the last time, let’s create a private blockchain network based on Polygon. After successfully creating a private network for our test application, we need to get the configuration to connect this network to our application (this is done using the “</>Snippets” button in BuildBear Dashboard) and add this configuration in the app/providers.tsx file:

const bbtestnet = {
id: 17524,
name: "yucky-daredevil-xxxxxxx",
nativeCurrency: {
decimals: 18,
name: "Native Token",
symbol: "Native Token",
},
rpcUrls: {
public: { http: ["https://rpc.buildbear.io/yucky-daredevil-xxxxxxx"] },
default: { http: ["https://rpc.buildbear.io/yucky-daredevil-xxxxxx"] },
},
blockExplorers: {
etherscan: {
name: "BBExplorer",
url: "https://explorer.buildbear.io/yucky-daredevil-xxxxxx",
},
default: {
name: "BBExplorer",
url: "https://explorer.buildbear.io/yucky-daredevil-xxxxxxx",
},
},
} as const satisfies Chain;

Also, we need to add this network to the list of networks with which we will work:

const _chains: readonly [Chain, ...Chain[]] = [polygon, bbtestnet];

Now let’s add a button to connect a crypto wallet using RainbowKit to the main page.

Make the following changes to the app/page.tsx file:

'use client'

import {
ConnectButton,
RainbowKitProvider,
} from '@rainbow-me/rainbowkit';

export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center justify-center p-24">
<RainbowKitProvider>
<h1 className='text-center text-3xl font-extrabold'>NextJS Web3 application</h1><br />
<div className='flex flex-row justify-center items-center'>
<ConnectButton />
</div>
</RainbowKitProvider>

</main>
);
}

Now, the main page should have the following appearance with the ability to select a blockchain network from two options:

Now let’s add components to transfer USDT ERC20 tokens to a given crypto wallet, and also implement the UI logic. You need to write code for UI that ensures the following logic:

  • display the wallet address where the transfer is made,
  • display the address of the wallet from which the transfer is made,
  • display a field to specify the transfer amount,
  • display the button to execute transfer,
  • message to display transaction status,
  • message that a Receipt of inclusion to a block is expected from the blockchain,
  • message that a Receipt has been received displaying transaction hash.

The final page layout will look something like this:

I don’t provide the entire code here, since these are standard React/NextJS components and are not related to the topic of our article. The full app/page.tsx code will be available for self-study on GitHub.

Then you need to top up the test wallet from which payments will be made. This is done through the BuildBear Faucet. We will not describe this in detail here either. It is assumed that the reader is already familiar with this. If necessary, you can see the instructions in the previous article.

The key question of our work today is how to get a wallet balance for a given ERC20 token, and based on the event of clicking on the “Go!” button initiate a transfer from one wallet to another?

To get a wallet balance for a ERC20 token, let’s define the following Wagmi hook in the app/page.tsx file:

const useBalanceResult = useBalance({
address: address as Address,
token: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F' as Address, /// USDT contract address
})

Now you can obtain balance whenever corresponding hook triggers, as something like this:

useEffect(() => {
if (useBalanceResult) {
console.log(useBalanceResult.data);
if (useBalanceResult.data?.value)
setcurrentUSDTbalance(Number(formatUnits(useBalanceResult.data?.value!, 6)));
}
}, [useBalanceResult]);

The next point is to make crypto transfer. To do this, in the app/page.tsx file we define the function const FunctionSend = async () => {…}

Within this function,

a) define ABI:

const abi = [
{
type: 'function',
name: 'approve',
stateMutability: 'nonpayable',
inputs: [
{ name: 'spender', type: 'address' },
{ name: 'amount', type: 'uint256' },
],
outputs: [{ type: 'bool' }],
},
{
type: 'function',
name: 'transferFrom',
stateMutability: 'nonpayable',
inputs: [
{ name: '_from', type: 'address' },
{ name: '_to', type: 'address' },
{ name: 'amount', type: 'uint256' },
],
outputs: [{ type: 'bool' }],
},
]

This is a description of the input parameters and names of functions of the smart-contract of ERC20 token. These functions we will call.

b) Call the ‘approve’ function of smart-contract of ERC20 token to obtain the consent of the wallet owner to make a transaction for the specified amount:

let trxId = await writeContractAsync({
abi,
address: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F', /// USDT contract address
functionName: 'approve',
args: [
address,
parseUnits(selectedAmount, 6),],
chainId: chainId,
});

The point is that you cannot immediately call the transfer function. Preliminary, you need to obtain explicit approval from the sender to debit a given number of tokens. Without receiving such an approval to debit tokens, the transaction will fail with an error.

c) Call the function ‘transferFrom’ of smart-contract of ERC20 token to make the transfer

trxId = await writeContractAsync({
abi,
address: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F', /// USDT contract address
functionName: 'transferFrom',
args: [
address as Address, // => address FROM,
process.env.RECEPIENT_ADDRESS, // => address TO,
parseUnits(selectedAmount, 6),],
chainId: chainId,
});

d) And after that, we need to wait until the transaction is successfully included in the next block. To do this, we use two hooks:

const TransactionReceipt = useWaitForTransactionReceipt({
hash: paymentTrxnId as Address,
})

This hook waits for a receipt, which will be received only when the transaction is included in the block. Or when an error occurs that prevents this event from happening. The logic runs every time the state of the paymentTrxnId variable changes. This variable stores the ID of the transaction that was initiated by the ‘transferFrom’ function call. In other words, when we receive the payment transaction ID as a result of calling the ‘transferFrom’ function.

It will take some time, after changing the payment transaction ID, before the “useWaitForTransactionReceipt” hook returns a response. Therefore, another hook is required to track when the response is returned and execute the actions we need on this event (for example, display the transaction result to the user):

// This useEffect triggers only when TransactionReceipt changes value
useEffect(() => {
console.log('TransactionReceipt is');
console.log(TransactionReceipt);

if (TransactionReceipt) {
if (TransactionReceipt.data && (TransactionReceipt.status === 'success')) {
if (TransactionReceipt.data.transactionHash === (paymentTrxnId as Address)) {
console.log('Transaction ' + paymentTrxnId + ' was successfull');

// clear triggering transaction_id.
setpaymentTrxnId('');

}
}
}
}, [TransactionReceipt])

That’s literally all. This logic will allow you to programmatically transfer a given amount of ERC20 tokens from one crypto wallet to another using the Wagmi API.

The code for this project is available here on GitHub:

Conclusion

In conclusion, this article extends the prior discussion on integrating a web3 application frontend built with NextJS, RainbowKit, and Wagmi with a test private blockchain network. It provides a comprehensive guide on how to retrieve wallet balances in ERC20 tokens and facilitate token transfers between wallets.

Starting with an introduction to the essential technologies, the article walks through setting up a basic NextJS application and configuring it with RainbowKit, Wagmi, and Viem for blockchain interactions. Detailed steps include installing necessary packages, setting up a global provider for configuration, and creating a private blockchain network using BuildBear.

The article proceeds to implement a user interface for connecting a crypto wallet, displaying wallet information, and initiating ERC20 token transfers. It emphasizes using Wagmi hooks for managing wallet balances and transaction states, showcasing the practical application of TypeScript and React components to handle asynchronous blockchain operations.

By following this guide, developers can build a functional web3 application capable of managing ERC20 tokens, highlighting the seamless integration of modern web development frameworks with blockchain technology.

The project code is available on GitHub for reference and further exploration.

If you are interested in this topic, let’s keep exploring together.

Please, subscribe me.

Follow my X

--

--