Skip to content

Commit

Permalink
Merge pull request #31 from celo-org/30-transfer-usdc-and-usdt
Browse files Browse the repository at this point in the history
feat: transfer ERC20 tokens
  • Loading branch information
therealharpaljadeja authored May 7, 2024
2 parents 8275e31 + f8e1c79 commit 0810915
Show file tree
Hide file tree
Showing 6 changed files with 3,395 additions and 1,256 deletions.
141 changes: 37 additions & 104 deletions packages/react-app/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,114 +1,47 @@
import { Disclosure } from "@headlessui/react";
import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/outline";
import { ConnectButton } from "@rainbow-me/rainbowkit";
import Image from "next/image";
import { useEffect, useState } from "react";
import { useConnect } from "wagmi";
import { InjectedConnector } from "wagmi/connectors/injected";
import { injected } from "wagmi/connectors";

export default function Header() {
const [hideConnectBtn, setHideConnectBtn] = useState(false);
const { connect } = useConnect({
connector: new InjectedConnector(),
});
const [hideConnectBtn, setHideConnectBtn] = useState(false);
const { connect } = useConnect();

useEffect(() => {
if (window.ethereum && window.ethereum.isMiniPay) {
setHideConnectBtn(true);
connect();
}
}, []);

return (
<Disclosure as="nav" className="bg-prosperity border-b border-black">
{({ open }) => (
<>
<div className="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8">
<div className="relative flex h-16 justify-between">
<div className="absolute inset-y-0 left-0 flex items-center sm:hidden">
{/* Mobile menu button */}
<Disclosure.Button className="inline-flex items-center justify-center rounded-md p-2 text-black focus:outline-none focus:ring-1 focus:ring-inset focus:rounded-none focus:ring-black">
<span className="sr-only">
Open main menu
</span>
{open ? (
<XMarkIcon
className="block h-6 w-6"
aria-hidden="true"
/>
) : (
<Bars3Icon
className="block h-6 w-6"
aria-hidden="true"
/>
)}
</Disclosure.Button>
</div>
<div className="flex flex-1 items-center justify-center sm:items-stretch sm:justify-start">
<div className="flex flex-shrink-0 items-center">
<Image
className="block h-8 w-auto sm:block lg:block"
src="/logo.svg"
width="24"
height="24"
alt="Celo Logo"
/>
</div>
<div className="hidden sm:ml-6 sm:flex sm:space-x-8">
<a
href="/"
className="inline-flex items-center border-b-2 border-black px-1 pt-1 text-sm font-medium text-gray-900"
>
Send cUSD
</a>
<a
href="/noncusd"
className="inline-flex items-center border-black px-1 pt-1 text-sm font-medium text-gray-900"
>
nonCUSD
</a>
</div>
</div>
<div className="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
{!hideConnectBtn && (
<ConnectButton
showBalance={{
smallScreen: true,
largeScreen: false,
}}
/>
)}
</div>
</div>
</div>
useEffect(() => {
if (window.ethereum && window.ethereum.isMiniPay) {
setHideConnectBtn(true);
connect({ connector: injected({ target: "metaMask" }) });
}
}, []);

<Disclosure.Panel className="sm:hidden">
<div className="space-y-1 pt-2 pb-4">
<Disclosure.Button
as="a"
href="#"
className="block border-l-4 border-black py-2 pl-3 pr-4 text-base font-medium text-black"
>
Send cUSD
</Disclosure.Button>
<Disclosure.Button
as="a"
href="/noncusd"
className="block border-l-4 border-black py-2 pl-3 pr-4 text-base font-medium text-black"
>
nonCUSD
</Disclosure.Button>
{/* Add here your custom menu elements */}
</div>
</Disclosure.Panel>
</>
return (
<div className="bg-prosperity border-b border-black">
<div className="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8">
<div className="relative flex h-16 justify-between">
<div className="flex flex-1 items-center justify-center sm:items-stretch sm:justify-start">
<div className="flex flex-shrink-0 items-center">
<Image
className="block h-8 w-auto sm:block lg:block"
src="/logo.svg"
width="24"
height="24"
alt="Celo Logo"
/>
</div>
</div>
<div className="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
{!hideConnectBtn && (
<ConnectButton
showBalance={{
smallScreen: true,
largeScreen: false,
}}
/>
)}
</Disclosure>
);
}

declare global {
interface Window {
ethereum: any;
}
</div>
</div>
</div>
</div>
);
}
66 changes: 66 additions & 0 deletions packages/react-app/hooks/useSendErc20.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { useCallback } from "react";
import { encodeFunctionData, parseUnits } from "viem";
import { usePublicClient, useWalletClient } from "wagmi";
import toast from "react-hot-toast";
import stableTokenAbi from "../abi/StableToken";
import { celo, celoAlfajores } from "viem/chains";

export default function useSendErc20() {
const { data: walletClient } = useWalletClient();

const publicClient = usePublicClient();

const sendErc20 = useCallback(
async function (
tokenAddress: `0x${string}`,
receiverAddress: string,
transferValue: string,
tokenDecimals: number
) {
let hash = await walletClient?.sendTransaction({
to: tokenAddress,
data: encodeFunctionData({
abi: stableTokenAbi,
functionName: "transfer",
args: [
receiverAddress,
// Different tokens can have different decimals, cUSD (18), USDC (6)
parseUnits(`${Number(transferValue)}`, tokenDecimals),
],
}),
// If the wallet is connected to a different network then you get an error.
chain: celoAlfajores,
// chain: celo,
});
console.log(tokenAddress);

if (publicClient && hash) {
let txToast = toast.loading(
"Transaction sent waiting for confirmation",
{
duration: 6000,
}
);

const transaction = await publicClient.waitForTransactionReceipt({
hash,
});

if (transaction.status === "success") {
toast.success("Transaction successful", {
id: txToast,
duration: 2000,
});
} else {
toast.error("Something went wrong", {
id: txToast,
duration: 2000,
});
}
}
},
[walletClient]
);

return sendErc20;
}
13 changes: 9 additions & 4 deletions packages/react-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,22 @@
},
"dependencies": {
"@celo/identity": "^5.1.1",
"@celo/rainbowkit-celo": "^1.0.1",
"@chakra-ui/next-js": "^2.2.0",
"@chakra-ui/react": "^2.8.2",
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
"@headlessui/react": "^1.7.14",
"@heroicons/react": "^2.0.18",
"@rainbow-me/rainbowkit": "^1.0.2",
"@rainbow-me/rainbowkit": "2.0.2",
"@tanstack/react-query": "5.28.6",
"ethers": "^5.7.2",
"framer-motion": "^11.1.8",
"next": "13.4.2",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hot-toast": "^2.4.1",
"viem": "^1.19.11",
"wagmi": "^1.2"
"viem": "2.8.18",
"wagmi": "2.5.12"
},
"devDependencies": {
"@types/node": "^20.2.1",
Expand Down
87 changes: 47 additions & 40 deletions packages/react-app/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,60 @@
import { Alfajores, Celo } from "@celo/rainbowkit-celo/chains";
import celoGroups from "@celo/rainbowkit-celo/lists";
import { RainbowKitProvider } from "@rainbow-me/rainbowkit";
import "@rainbow-me/rainbowkit/styles.css";
import "../styles/globals.css";
import {
RainbowKitProvider,
connectorsForWallets,
} from "@rainbow-me/rainbowkit";
import type { AppProps } from "next/app";
import { WagmiConfig, configureChains, createConfig } from "wagmi";
import { publicProvider } from "wagmi/providers/public";
import { WagmiProvider, createConfig, http } from "wagmi";
import Layout from "../components/Layout";
import "../styles/globals.css";

import { injectedWallet } from "@rainbow-me/rainbowkit/wallets";
import { celo, celoAlfajores } from "wagmi/chains";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Toaster } from "react-hot-toast";

const projectId = process.env.NEXT_PUBLIC_WC_PROJECT_ID as string; // get one at https://cloud.walletconnect.com/app

const { chains, publicClient } = configureChains(
[Celo, Alfajores],
[publicProvider()]
import { ChakraProvider } from "@chakra-ui/react";

const connectors = connectorsForWallets(
[
{
groupName: "Recommended",
wallets: [injectedWallet],
},
],
{
appName: "Celo Composer",
projectId: "044601f65212332475a09bc14ceb3c34",
}
);

const connectors = celoGroups({
chains,
projectId,
appName:
(typeof document === "object" && document.title) || "Your App Name",
const config = createConfig({
connectors,
chains: [
// celo,
celoAlfajores,
],
transports: {
// [celo.id]: http(),
[celoAlfajores.id]: http(),
},
});

const appInfo = {
appName: "Celo Composer",
};

const wagmiConfig = createConfig({
connectors,
publicClient: publicClient,
});
const queryClient = new QueryClient();

function App({ Component, pageProps }: AppProps) {
return (
<WagmiConfig config={wagmiConfig}>
<RainbowKitProvider
chains={chains}
appInfo={appInfo}
coolMode={true}
>
<Layout>
<Toaster />
<Component {...pageProps} />
</Layout>
</RainbowKitProvider>
</WagmiConfig>
);
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider>
<ChakraProvider>
<Layout>
<Toaster />
<Component {...pageProps} />
</Layout>
</ChakraProvider>
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
);
}

export default App;
Loading

0 comments on commit 0810915

Please sign in to comment.