From 79c1f2d7269410760953eafd36bec20a23689b2d Mon Sep 17 00:00:00 2001 From: WitherBlock Date: Fri, 30 Aug 2024 19:57:40 +0400 Subject: [PATCH] feat: working project --- README.md | 82 +- apps/web/.env.sample | 1 + apps/web/components/Send.tsx | 109 +- apps/web/components/TokenBalance.tsx | 8 +- apps/web/components/TokenInfo.tsx | 38 +- apps/web/components/TransferInfo.tsx | 46 +- apps/web/constants/env.ts | 2 + apps/web/package.json | 20 +- apps/web/pages/_app.tsx | 58 +- apps/web/pages/index.tsx | 67 +- apps/web/utils/index.ts | 19 + protocol/core/.env.sample | 2 + protocol/core/deploy/disperse/index.ts | 17 +- protocol/core/hardhat.config.ts | 6 +- yarn.lock | 9126 ++++++++++++++---------- 15 files changed, 5467 insertions(+), 4134 deletions(-) create mode 100644 apps/web/.env.sample create mode 100644 apps/web/constants/env.ts create mode 100644 apps/web/utils/index.ts create mode 100644 protocol/core/.env.sample diff --git a/README.md b/README.md index bc389a0..bb0a8ca 100644 --- a/README.md +++ b/README.md @@ -1,81 +1,19 @@ -# Turborepo starter +# Disperse -This is an official starter Turborepo. +One transaction to disperse ERC20 tokens to multiple addresses. -## Using this example +## Development -Run the following command: +Run the following commands: -```sh -npx create-turbo@latest -``` - -## What's inside? - -This Turborepo includes the following packages/apps: - -### Apps and Packages - -- `docs`: a [Next.js](https://nextjs.org/) app -- `web`: another [Next.js](https://nextjs.org/) app -- `ui`: a stub React component library shared by both `web` and `docs` applications -- `eslint-config-custom`: `eslint` configurations (includes `eslint-config-next` and `eslint-config-prettier`) -- `tsconfig`: `tsconfig.json`s used throughout the monorepo - -Each package/app is 100% [TypeScript](https://www.typescriptlang.org/). - -### Utilities - -This Turborepo has some additional tools already setup for you: - -- [TypeScript](https://www.typescriptlang.org/) for static type checking -- [ESLint](https://eslint.org/) for code linting -- [Prettier](https://prettier.io) for code formatting - -### Build - -To build all apps and packages, run the following command: - -``` -cd my-turborepo -pnpm build -``` - -### Develop +To install dependencies: -To develop all apps and packages, run the following command: - -``` -cd my-turborepo -pnpm dev -``` - -### Remote Caching - -Turborepo can use a technique known as [Remote Caching](https://turbo.build/repo/docs/core-concepts/remote-caching) to share cache artifacts across machines, enabling you to share build caches with your team and CI/CD pipelines. - -By default, Turborepo will cache locally. To enable Remote Caching you will need an account with Vercel. If you don't have an account you can [create one](https://vercel.com/signup), then enter the following commands: - -``` -cd my-turborepo -npx turbo login +```sh +yarn ``` -This will authenticate the Turborepo CLI with your [Vercel account](https://vercel.com/docs/concepts/personal-accounts/overview). - -Next, you can link your Turborepo to your Remote Cache by running the following command from the root of your Turborepo: +To run the local node and web app: +```sh +yarn dev ``` -npx turbo link -``` - -## Useful Links - -Learn more about the power of Turborepo: - -- [Tasks](https://turbo.build/repo/docs/core-concepts/monorepos/running-tasks) -- [Caching](https://turbo.build/repo/docs/core-concepts/caching) -- [Remote Caching](https://turbo.build/repo/docs/core-concepts/remote-caching) -- [Filtering](https://turbo.build/repo/docs/core-concepts/monorepos/filtering) -- [Configuration Options](https://turbo.build/repo/docs/reference/configuration) -- [CLI Usage](https://turbo.build/repo/docs/reference/command-line-reference) diff --git a/apps/web/.env.sample b/apps/web/.env.sample new file mode 100644 index 0000000..4c1b141 --- /dev/null +++ b/apps/web/.env.sample @@ -0,0 +1 @@ +NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID= \ No newline at end of file diff --git a/apps/web/components/Send.tsx b/apps/web/components/Send.tsx index 6f2ad80..00345b9 100644 --- a/apps/web/components/Send.tsx +++ b/apps/web/components/Send.tsx @@ -1,7 +1,12 @@ -import { useEffect, useState } from "react"; -import { erc20ABI, usePublicClient, useWalletClient } from "wagmi"; +import { useCallback, useEffect, useState } from "react"; +import { + useAccount, + usePublicClient, + useReadContract, + useWalletClient, +} from "wagmi"; import { Button } from "@nextui-org/react"; -import { getContract, Address } from "viem"; +import { getContract, Address, erc20Abi, parseUnits } from "viem"; import { PERMIT2_ADDRESS, @@ -14,9 +19,7 @@ import { import disperseAbi from "../constants/abis/disperse"; -// const TOKEN_ADDRESS = "0xda5bb55c0eA3f77321A888CA202cb84aE30C6AF5"; const DISPERSE_ADDRESS = "0xC5ac98C06391981A4802A31ca5C62e6c3EfdA48d"; - interface PermitSingle { details: { token: `0x${string}`; @@ -31,34 +34,60 @@ interface PermitSingle { const Send = ({ tokenAddress, transferDetails, - // isNative, totalValue, }: { - isNative: boolean; tokenAddress: `0x${string}`; transferDetails: { recipients: Address[]; values: string[]; }; - totalValue: bigint; + totalValue: number; }) => { const walletClient = useWalletClient(); const publicClient = usePublicClient(); + const result = useReadContract({ + abi: erc20Abi, + address: tokenAddress, + functionName: "decimals", + }); + + const account = useAccount(); + const [allowanceData, setAllowanceData] = useState( null ); - const handleClick = async () => { - if (!allowanceData || !walletClient.data) return; + const handleClick = useCallback(async () => { + if ( + !allowanceData || + !walletClient.data || + !result.data || + !account.address || + !publicClient + ) + return; + + const disperse = getContract({ + abi: disperseAbi, + address: DISPERSE_ADDRESS, + client: { wallet: walletClient.data }, + }); const token = getContract({ address: tokenAddress, - abi: erc20ABI, - walletClient: walletClient.data, + abi: erc20Abi, + client: { wallet: walletClient.data }, }); - if (allowanceData.amount <= totalValue) { + const data = await publicClient?.readContract({ + abi: erc20Abi, + address: tokenAddress, + functionName: "allowance", + args: [account.address, PERMIT2_ADDRESS], + }); + + if (data <= parseUnits(String(totalValue), result.data)) { try { await token.write.approve([PERMIT2_ADDRESS as Address, MaxUint256]); } catch { @@ -66,16 +95,10 @@ const Send = ({ } } - const disperse = getContract({ - abi: disperseAbi, - address: DISPERSE_ADDRESS, - walletClient: walletClient.data, - }); - const permit: PermitSingle = { details: { token: tokenAddress, - amount: 1n, + amount: parseUnits(String(totalValue), result.data), expiration: Number(MaxUint48), nonce: allowanceData.nonce, }, @@ -92,37 +115,49 @@ const Send = ({ let signature = await walletClient.data.signTypedData({ domain, types, - //@ts-ignore - values, + primaryType: "PermitSingle", + message: { ...values }, }); await disperse.write.disperseSingleWithPermit2([ tokenAddress, transferDetails.recipients, - transferDetails.values.map((v) => BigInt(v)), + transferDetails.values.map((v) => parseUnits(v, result.data)), permit, signature, ]); - }; + }, [ + account.address, + allowanceData, + publicClient, + result.data, + tokenAddress, + totalValue, + transferDetails.recipients, + transferDetails.values, + walletClient.data, + ]); useEffect(() => { async function update() { - const allowanceProvider = new AllowanceProvider( - publicClient, - PERMIT2_ADDRESS - ); - - const allowanceData = await allowanceProvider.getAllowanceData( - "0xda5bb55c0eA3f77321A888CA202cb84aE30C6AF5", - "0xDe485812E28824e542B9c2270B6b8eD9232B7D0b", - "0xC5ac98C06391981A4802A31ca5C62e6c3EfdA48d" - ); - - setAllowanceData(allowanceData); + if (publicClient && account.address) { + const allowanceProvider = new AllowanceProvider( + publicClient, + PERMIT2_ADDRESS + ); + + const allowanceData = await allowanceProvider.getAllowanceData( + tokenAddress, + account.address, + DISPERSE_ADDRESS + ); + + setAllowanceData(allowanceData); + } } update(); - }, [publicClient]); + }, [publicClient, account.address, tokenAddress]); return ( { items={tokens} > {(item) => ( - - {(item as any).name} - + {item.name} )} @@ -129,18 +157,19 @@ const Web = () => {