DEV Community

Cover image for Build a Web3 Payments Interface with React, WAGMI, RainbowKit, Alchemy & Chakra UI
Alejandro Hernández
Alejandro Hernández

Posted on • Edited on

Build a Web3 Payments Interface with React, WAGMI, RainbowKit, Alchemy & Chakra UI

A how-to guide for developers on implementing Web3 payments in a React application.

Web3 Payments Interface Demo

This article will show you how to build a Web3 Payments Interface using React (Nextjs), WAGMI (Ethers.js), RainbowKit & Chakra UI. We will explore how to interact with a smart contract through the client to allow the user to send tokens (USDC) or Ether on any EVM compatible blockchain to a given address.

TLDR; Check this CodeSandBox

Prerequisites

This article assumes that you have at least basic knowledge of React and have built React Components.

To develop and test the application, you'll need an Ethereum wallet. The tool we'll be using for wallet connection supports multiple types of wallets; nevertheless, if you are unfamiliar with Web3 Wallets, I'd recommend using a browser wallet, specifically Metamask, which is the most popular and is supported by all of the Web3 tools. You can download the Metamask browser extension here and learn how to use it here.

You'll need test tokens to use the application. For this tutorial, we'll use the Görli Ethereum Testnet; you can get free Görli ETH by using the Alchemy Goerli Faucet. The code used for this project works on any EVM-compatible blockchain, so if you don't like Ethereum/Görli, feel free to use the network of your preference.

Also, you'll need an Alchemy API Kit for the RainbowKit configuration. Get one at https://dashboard.alchemyapi.io with the account you've created to get the Görli ETH.

Setup & Installation

You can fork the starter code repo and skip this section.

RainbowKit, the library we're going to use for wallet connection has the option to scaffold a RainbowKit + wagmi + Next.js app with one command. It'll generate a new directory containing a boilerplate project, and install all required dependencies.

Open your terminal and execute the following command:

npm init @rainbow-me/rainbowkit@latest
Enter fullscreen mode Exit fullscreen mode

Let's also install Chakra UI right away using:

npm i @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^6 @chakra-ui/icons

Enter fullscreen mode Exit fullscreen mode

Once the installation finishes, we have to make a few changes to the RainbowKit configuration.

Create a .env file and add your Alchemy API Key with the following format:

NEXT_PUBLIC_ALCHEMY_KEY = "YOUR API KEY"
Enter fullscreen mode Exit fullscreen mode

Now let's adapt the RainbowKit config and add the Chakra provider in pages/_app.tsx. Replace it with the following code:

import "../styles/globals.css";
import "@rainbow-me/rainbowkit/styles.css";
import type { AppProps } from "next/app";
import { RainbowKitProvider, getDefaultWallets } from "@rainbow-me/rainbowkit";
import { chain, configureChains, createClient, WagmiConfig } from "wagmi";
import { alchemyProvider } from "wagmi/providers/alchemy";
import { ChakraProvider } from "@chakra-ui/react";

const { chains, provider, webSocketProvider } = configureChains(
  [chain.goerli],
  [
    alchemyProvider({
      // This is Alchemy's default API key.
      // You can get your own at https://dashboard.alchemyapi.io
      apiKey: process.env.NEXT_PUBLIC_ALCHEMY_KEY,
    }),
  ]
);

const { connectors } = getDefaultWallets({
  appName: "RainbowKit App",
  chains,
});

const wagmiClient = createClient({
  connectors,
  provider,
  webSocketProvider,
});

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <ChakraProvider>
      <WagmiConfig client={wagmiClient}>
        <RainbowKitProvider
          chains={chains}
          coolMode
        >
          <Component {...pageProps} />
        </RainbowKitProvider>
      </WagmiConfig>
    </ChakraProvider>
  );
}

export default MyApp;
Enter fullscreen mode Exit fullscreen mode

What we've done there? We've just removed the chains we're not going to use, replaced the default Alchemy API Key with our own, and set up the Chakra Provider.

Next, let's replace the boilerplate code in pages/index.ts. Remove everything inside the <main className={styles.main}></main> tag except the <ConnectButton /> component. You can also replace the info in the header and footer with your own or remove them.

Building the Interface Part 1 - Sending ETH Payments

Because ETH is going to be one of the payment options along with a dollar stablecoin, we'll have to make an ETH/USD conversion, so we need to know the ETH price. Let's do a fetch on the server to get ETH's price inside our pages/index.ts file using the Coingecko API and getServerSideProps from Next:

import type { GetServerSideProps } from "next";

export const getServerSideProps: GetServerSideProps = async () => {
  const response = await fetch(
    "http://api.coingecko.com/api/v3/coins/ethereum"
  );
  const ethPrice = await response.json();
  return {
    props: {
      ethPrice: ethPrice.market_data.current_price.usd,
    },
  };
};
Enter fullscreen mode Exit fullscreen mode

Now it's time to start building our interface. Let's create a components folder and an Interface.tsx file inside it.

Once the file is created, we need to import the Chakra UI components to mimic the Uniswap Interface UI, the WAGMI Hooks we're going to use to send ETH transactions and create our component. WAGMI is a Hooks Library built on top of ethers.js, the most popular Ethereum library to make smart contract interactions in Javascript.

import { Box, Button, Flex, FormControl, Input, Text } from "@chakra-ui/react";
import {
  usePrepareSendTransaction,
  useSendTransaction,
  useWaitForTransaction,
} from "wagmi";

import { parseEther} from "ethers/lib/utils";

export type Props = {
  ethPrice: number;
};

const Interface = ({ ethPrice }: Props) => {
  const address = REPLACE_WITH_YOUR_WALLET_ADDRESS; //wallet address that will receive the funds
  const subAmount = "5"; //amount to be paid in USD, here we're simulating it's for a subscription and hardcoding it but you can bring it from props.

  const priceInEth = (Number(subAmount) / ethPrice).toFixed(6).toString(); //converting the amount in USD to ETH

//Pay with ether https://wagmi.sh/docs/prepare-hooks/usePrepareSendTransaction
  const { config } = usePrepareSendTransaction({
    request: {
      to: address,
      value: parseEther(priceInEth), // parse the ETH amount to make it readable for the Ethereum Virtual Machine --- https://docs.ethers.io/v4/api-utils.html
    },
  });

// https://wagmi.sh/docs/hooks/useSendTransaction
const { data, sendTransaction } = useSendTransaction(config); 

  //Wait for payment to be completed https://wagmi.sh/docs/hooks/useWaitForTransaction
  const { isLoading, isSuccess } = useWaitForTransaction({
    hash: data?.hash, //transaction hash
  });


  return (
    <Box
      w="30rem"
      mx="auto"
      mt="1.25rem"
      boxShadow="rgb(0 0 0 / 8%) 0rem 0.37rem 0.62rem"
      borderRadius="1.37rem"
      bg="#ffffff"
      pb={5}
      pl={1}
    >
      <Flex
        alignItems="center"
        p="1rem 1.25rem 0.5rem"
        color="rgb(86, 90, 105)"
        justifyContent="space-between"
        borderRadius="1.37rem 1.37rem 0 0"
      >
        <Flex color="black" fontWeight="500">
          <Text ml={1}>1 ETH = USD {ethPrice}</Text>
        </Flex>
      </Flex>
      <Box p="0.5rem" borderRadius="0 0 1.37rem 1.37rem">
        <Flex>
          <Box>
            <form
              onSubmit={(e) => {
                e.preventDefault();
                sendTransaction?.();
              }}
            >
              <FormControl>
                <Flex
                  alignItems="center"
                  justifyContent="space-between"
                  bg="#f7f8fa"
                  pos="relative"
                  p="1rem 1rem 1.7rem"
                  borderRadius="1.25rem"
                  border="0.06rem solid rgb(237, 238, 242)"
                  _hover={{ border: "0.06rem solid rgb(211,211,211)" }}
                >
                  <Text color="black">To:</Text>
                  <Input
                    color="black"
                    aria-label="Recipient"
                    value={address}
                    fontSize="1.1rem"
                    fontWeight="500"
                    width="100%"
                    size="1rem"
                    textAlign="right"
                    outline="none"
                    border="none"
                    focusBorderColor="none"
                  />
                </Flex>
                <Flex
                  alignItems="center"
                  justifyContent="space-between"
                  bg="#f7f8fa"
                  pos="relative"
                  p="1rem 1rem 1.7rem"
                  borderRadius="1.25rem"
                  mt="0.25rem"
                  border="0.06rem solid rgb(237, 238, 242)"
                  _hover={{ border: "0.06rem solid rgb(211,211,211)" }}
                >
                  <Input
                    fontSize="36px"
                    width="100%"
                    size="19rem"
                    textAlign="left"
                    outline="none"
                    border="none"
                    focusBorderColor="none"
                    color="black"
                    aria-label="Amount"
                    value={priceInEth}
                  />
                </Flex>
                <Box mt="0.5rem" padding={1} maxH="3rem">
                  <Button
                    disabled={isLoading || isSuccess}
                    type={"submit"}
                    color="white"
                    bg="#bca7ca"
                    width="100%"
                    p="1.62rem"
                    borderRadius="1.25rem"
                    _hover={{
                      bg: "white",
                      color: "#bca7ca",
                      border: "2px",
                      borderColor: "#bca7ca",
                    }}
                    fontSize="1.5rem"
                  >
                    {isLoading ? "Sending Payment..." : "Send Payment"}
                  </Button>
                </Box>
              </FormControl>
            </form>
          </Box>
        </Flex>
        {isSuccess && (
          <Flex color="black" mt={7} mb={7} textAlign="center">
            Successfully paid {priceInEth} ETH to {address}
          </Flex>
        )}
      </Box>
    </Box>
  );
};

export default Interface;

Enter fullscreen mode Exit fullscreen mode

Once we've built the first part of our Interface component, we can show it on our page. Let's go back to pages/index.ts to add it. We want our interface to appear only after the user wallet is connected, so we'll need to import {useAccount } from WAGMI. Also, we'll have to add the props to our Home component to pass it to our interface.

import { ConnectButton } from "@rainbow-me/rainbowkit";
import styles from "../styles/Home.module.css";
import { Props } from "../components/Interface";
import Interface from "../components/Interface";
import { useAccount } from "wagmi";

const Home = (props: Props) => {
  const { isConnected } = useAccount();
  return (
    <div className={styles.container}>
      <main className={styles.main}>
        <ConnectButton />
        {isConnected && <Interface ethPrice={props.ethPrice} />}
      </main>
    </div>
  );
};

export default Home;

Enter fullscreen mode Exit fullscreen mode

Our UI should look like this after the wallet is connected, and we should be able to send ETH payments at this point:

Web3 Payments Interface UI - Part 1

I'd recommend checking the links commented in the code to know more about the hooks we've used and how they work.

Building the Interface Part 2.0 - Smart contract interactions

Sending transactions with the native currency of the blockchain is not the same as interacting with a custom smart contract; in this case, an ERC-20 smart contract.

Every smart contract has a set of methods and events defined by the developer(s) of the SC, and it's through those methods that we interact with them while using the contract's ABI (similar to an API but not the same).

Nevertheless, the industry has set a technical standard for fungible and non-fungible tokens (ERC-20, ERC-721, ERC-1155). These standards contain a list of methods included in every contract. To send USDC (ERC-20) transactions, we'll have to use the transfer method, which is part of the standard.

It's also noteworthy that meeting the convention doesn't mean not having custom properties/methods/events. For example, the USDC token has six decimals, while other coins have nine, or eighteen decimals, and we have to know it beforehand to parse the amounts correctly.

Building the Interface Part 2.1 - Select between different tokens

If we want to pay with different tokens, we need a way to choose between them first, so let's build a Select that allows us to choose between multiple options and makes it possible to put each token icon alongside the label.

To do that we're going to use react-select. Here I'm using the chakra-adapted version and you can install it with the following command:

npm i chakra-react-select
Enter fullscreen mode Exit fullscreen mode

Before creating the select component, let's create a utils folder and put it inside the ETH and USDC logos. Also, create an index.ts file inside the utils folder to set the token options; it will include the smart contract address for Görli USDC (not necessary for ETH, in this case, the value will be undefined), the labels and the icons for ETH & USDC.

import { StaticImageData } from "next/image";
import EthIcon from "./etherLogo.png";
import UsdcIcon from "./usd-coin-usdc-logo.png";
export type TokenOptions = {
  label: string;
  icon: StaticImageData;
  value?: string;
};
export const tokenOptions: TokenOptions[] = [
  { value: undefined, label: "ETH", icon: EthIcon },
  {
    value: "0x07865c6E87B9F70255377e024ace6630C1Eaa37F", //Görli USDC Smart Contracts address
    label: "USDC",
    icon: UsdcIcon,
  },
];
Enter fullscreen mode Exit fullscreen mode

You can add any additional ERC-20 token of your preference to that array of objects using its smart contract address.

Now, let's implement the react-select by creating a new file inside our components folder called TokenSelect.tsx and adding the following code:


import { ChevronDownIcon } from "@chakra-ui/icons";
import { Flex, Text } from "@chakra-ui/react";
import { Select as ReactSelect, components } from "chakra-react-select";
import Image from "next/image";

const TokenSelect = ({
  selectedToken,
  handleTokenChange,
  tokenOptions,
}: any) => {
  const DropdownIndicator = (props: any) => {
    return (
      <components.DropdownIndicator {...props}>
        <ChevronDownIcon color={"black"} />
      </components.DropdownIndicator>
    );
  };
  const Option = (props: any) => (
    <components.Option {...props}>
      <Flex color="black">
        <Image
          src={props.data.icon}
          alt="logo"
          width="30"
          height="25"
          style={{ borderRadius: 10, overflow: "hidden" }}
        />
        <Text ml={2}>{props.data.label}</Text>
      </Flex>
    </components.Option>
  );

  const SingleValue = (props: any) => (
    <components.SingleValue {...props}>
      <Flex position="absolute" top="15%">
        <Image
          src={selectedToken.icon}
          alt="selected-logo"
          width="30"
          height="25"
          style={{ borderRadius: 10, overflow: "hidden" }}
        />
        <Text ml={1} mr={1} fontSize="lg" color="white">
          {props.data.label}
        </Text>
      </Flex>
    </components.SingleValue>
  );
  return (
    <ReactSelect
      value={selectedToken}
      options={tokenOptions}
      onChange={handleTokenChange}
      components={{
        Option,
        SingleValue,
        DropdownIndicator,
      }}
    />
  );
};

export default TokenSelect;

Enter fullscreen mode Exit fullscreen mode

To finish the Select implementation, we need to go back to our Interface.tsx component, add the necessary imports, a State, a function to change the value of the amount input, and add the TokenSelect component to our UI inside the <Flex></Flex> tag from the amount input.


(...)
import { useState } from "react";
import { TokenOptions, tokenOptions } from "../utils";
import TokenSelect from "./TokenSelect";

const Interface = ({ ethPrice }: Props) => {
(...)
const [selectedToken, setSelectedToken] = useState(tokenOptions[0]);
const handleTokenChange = (value: TokenOptions) => {
setSelectedToken(value); //change selected token
  };

     return (
             (...)
             <Flex
                  alignItems="center"
                  justifyContent="space-between"
                  bg="#f7f8fa"
                  pos="relative"
                  p="1rem 1rem 1.7rem"
                  borderRadius="1.25rem"
                  mt="0.25rem"
                  border="0.06rem solid rgb(237, 238, 242)"
                  _hover={{ border: "0.06rem solid rgb(211,211,211)" }}
                >
                  <Input
                    fontSize="36px"
                    width="100%"
                    size="19rem"
                    textAlign="left"
                    outline="none"
                    border="none"
                    focusBorderColor="none"
                    color="black"
                    aria-label="Amount"
                    value={selectedToken.value ? subAmount : priceInEth}
                  />
                  <Box
                    w={215}
                    boxShadow="rgb(0 0 0 / 8%) 0rem 0.37rem 0.62rem"
                    borderRadius="1.37rem"
                    bg="#bca7ca"
                  >
                    <TokenSelect
                      selectedToken={selectedToken}
                      tokenOptions={tokenOptions}
                      handleTokenChange={handleTokenChange}
                    />
                  </Box>
                </Flex>
                (...)
)}
Enter fullscreen mode Exit fullscreen mode

Building the Interface Part 2.2 - Sending USDC payments

It's time to enable our Interface to send USDC payments. Because of the reasons explained in section 2.0 about custom token vs. native token transactions, we have to use Hooks for SC interactions instead of the Hooks used for ETH payments.

As mentioned at the end of the part 1 section, I strongly suggest checking the comments in the code to gain more context about what's happening there. Having said that, add the following code, including the imports, to your Interface.tsx file.

import { parseUnits } from "ethers/lib/utils";
import {
  erc20ABI,
  useContractWrite,
  usePrepareContractWrite,
} from "wagmi";

const Interface = ({ ethPrice }: Props) => {
  (...)

  //Pay with custom token (USDC) https://wagmi.sh/docs/hooks/useContractWrite
  const { config: configWrite } = usePrepareContractWrite({
    address: selectedToken.value, //Goerli USDC contract address
    abi: erc20ABI, //Standard ERC-20 ABI https://www.quicknode.com/guides/smart-contract-development/what-is-an-abi
    functionName: "transfer", //We're going to use the tranfer method provided in the ABI, here's an example of a standard transfer method https://docs.ethers.io/v5/single-page/#/v5/api/contract/example/
    args: [address, parseUnits(subAmount, 6)], //[receiver, amount] Note that the units to parse are six because that's the number of decimals set for USDC in its contract. In order to add another token with a different amount of decimals its necessary to add additional logic here for it to work.
  });
  const { data: dataWrite, write } = useContractWrite(configWrite);

}
Enter fullscreen mode Exit fullscreen mode

Add the hash from useContractWrite to useWaitForTransaction:

const { isLoading, isSuccess } = useWaitForTransaction({
    hash: data?.hash || dataWrite?.hash, //transaction hash
  });
Enter fullscreen mode Exit fullscreen mode

Add a condition in the onSubmit to send the transaction: if the value of the selected token exists, execute the write function; if not, call sendTransaction.

onSubmit={(e) => {
 e.preventDefault();
 selectedToken.value ? write?.() : sendTransaction?.();
}}
Enter fullscreen mode Exit fullscreen mode

Finally, let's update the success message to reflect if the payment was with ETH or USDC.

{isSuccess && (
 <Flex color="black" mt={7} mb={7} textAlign="center">
  Successfully paid {selectedToken.value ? subAmount : priceInEth}
{" "} {selectedToken.label} to {address}</Flex>)}
Enter fullscreen mode Exit fullscreen mode

The final version of Interface.tsx should look like this:

import { Box, Button, Flex, FormControl, Input, Text } from "@chakra-ui/react";
import { parseEther, parseUnits } from "ethers/lib/utils";
import { useState } from "react";
import {
  erc20ABI,
  useContractWrite,
  usePrepareContractWrite,
  usePrepareSendTransaction,
  useSendTransaction,
  useWaitForTransaction,
} from "wagmi";
import { Props } from "../pages";
import { TokenOptions, tokenOptions } from "../utils";
import TokenSelect from "./TokenSelect";

const Interface = ({ ethPrice }: Props) => {
  const [selectedToken, setSelectedToken] = useState(tokenOptions[0]);
  const address = "0xc839b7c702C88e88dd31a29A942fB0dB59a00B06"; //wallet address that will receive the funds
  const subAmount = "5"; //amount to be paid in USD, here we're simulating it's for a subscription and hardcoding it but you can bring it from props.

  const priceInEth = (Number(subAmount) / ethPrice).toFixed(6).toString(); //converting the amount in USD to ETH

  const handleTokenChange = (value: TokenOptions) => {
    setSelectedToken(value); //change selected token
  };

  //Pay with custom token (USDC) https://wagmi.sh/docs/hooks/useContractWrite
  const { config: configWrite } = usePrepareContractWrite({
    address: selectedToken.value, //Goerli USDC contract address
    abi: erc20ABI, //Standard ERC-20 ABI https://www.quicknode.com/guides/smart-contract-development/what-is-an-abi
    functionName: "transfer", //We're going to use the tranfer method provided in the ABI, here's an example of a standard transfer method https://docs.ethers.io/v5/single-page/#/v5/api/contract/example/
    args: [address, parseUnits(subAmount, 6)], //[receiver, amount] Note that the units to parse are six because that's the number of decimals set for USDC in its contract. In order to add another token with a different amount of decimals its necessary to add additional logic here for it to work.
  });
  const { data: dataWrite, write } = useContractWrite(configWrite);

  //Pay with ether https://wagmi.sh/docs/prepare-hooks/usePrepareSendTransaction
  const { config } = usePrepareSendTransaction({
    request: {
      to: address,
      value: parseEther(priceInEth), // parse the ETH amount to make it readable for the Ethereum Virtual Machine --- https://docs.ethers.io/v4/api-utils.html
    },
  });

  //https://wagmi.sh/docs/hooks/useSendTransaction
  const { data, sendTransaction } = useSendTransaction(config);

  //Wait for payment to be completed https://wagmi.sh/docs/hooks/useWaitForTransaction
  const { isLoading, isSuccess } = useWaitForTransaction({
    hash: data?.hash || dataWrite?.hash, //transaction hash
  });

  return (
    <Box
      w="30rem"
      mx="auto"
      mt="1.25rem"
      boxShadow="rgb(0 0 0 / 8%) 0rem 0.37rem 0.62rem"
      borderRadius="1.37rem"
      bg="#ffffff"
      pb={5}
      pl={1}
    >
      <Flex
        alignItems="center"
        p="1rem 1.25rem 0.5rem"
        color="rgb(86, 90, 105)"
        justifyContent="space-between"
        borderRadius="1.37rem 1.37rem 0 0"
      >
        <Flex color="black" fontWeight="500">
          <Text ml={1}>1 ETH = USD {ethPrice}</Text>
        </Flex>
      </Flex>
      <Box p="0.5rem" borderRadius="0 0 1.37rem 1.37rem">
        <Flex>
          <Box>
            <form
              onSubmit={(e) => {
                e.preventDefault();
                selectedToken.value ? write?.() : sendTransaction?.();
              }}
            >
              <FormControl>
                <Flex
                  alignItems="center"
                  justifyContent="space-between"
                  bg="#f7f8fa"
                  pos="relative"
                  p="1rem 1rem 1.7rem"
                  borderRadius="1.25rem"
                  border="0.06rem solid rgb(237, 238, 242)"
                  _hover={{ border: "0.06rem solid rgb(211,211,211)" }}
                >
                  <Text color="black">To:</Text>
                  <Input
                    color="black"
                    aria-label="Recipient"
                    value={address}
                    fontSize="1.1rem"
                    fontWeight="500"
                    width="100%"
                    size="1rem"
                    textAlign="right"
                    outline="none"
                    border="none"
                    focusBorderColor="none"
                  />
                </Flex>
                <Flex
                  alignItems="center"
                  justifyContent="space-between"
                  bg="#f7f8fa"
                  pos="relative"
                  p="1rem 1rem 1.7rem"
                  borderRadius="1.25rem"
                  mt="0.25rem"
                  border="0.06rem solid rgb(237, 238, 242)"
                  _hover={{ border: "0.06rem solid rgb(211,211,211)" }}
                >
                  <Input
                    fontSize="36px"
                    width="100%"
                    size="19rem"
                    textAlign="left"
                    outline="none"
                    border="none"
                    focusBorderColor="none"
                    color="black"
                    aria-label="Amount"
                    value={selectedToken.value ? subAmount : priceInEth}
                  />
                  <Box
                    w={215}
                    boxShadow="rgb(0 0 0 / 8%) 0rem 0.37rem 0.62rem"
                    borderRadius="1.37rem"
                    bg="#bca7ca"
                  >
                    <TokenSelect
                      selectedToken={selectedToken}
                      tokenOptions={tokenOptions}
                      handleTokenChange={handleTokenChange}
                    />
                  </Box>
                </Flex>
                <Box mt="0.5rem" padding={1} maxH="3rem">
                  <Button
                    disabled={isLoading || isSuccess}
                    type={"submit"}
                    color="white"
                    bg="#bca7ca"
                    width="100%"
                    p="1.62rem"
                    borderRadius="1.25rem"
                    _hover={{
                      bg: "white",
                      color: "#bca7ca",
                      border: "2px",
                      borderColor: "#bca7ca",
                    }}
                    fontSize="1.5rem"
                  >
                    {isLoading ? "Sending Payment..." : "Send Payment"}
                  </Button>
                </Box>
              </FormControl>
            </form>
          </Box>
        </Flex>
        {isSuccess && (
          <Flex color="black" mt={7} mb={7} textAlign="center">
            Successfully paid {selectedToken.value ? subAmount : priceInEth}{" "}
            {selectedToken.label} to {address}
          </Flex>
        )}
      </Box>
    </Box>
  );
};

export default Interface;

Enter fullscreen mode Exit fullscreen mode

We should be able now to send USDC payments, and our UI should look like this:

Final version Web3 Payments Interface

Here's a demo to play with: https://web3payments.vercel.app/
Final version repo: https://github.com/ephcrat/web3payments/
CodeSandBox: https://codesandbox.io/p/github/ephcrat/web3payments/main

If something doesn't work as expected or you have any doubts, please let me know in the comments!

Top comments (0)