Skip to content

Commit

Permalink
feat: working project
Browse files Browse the repository at this point in the history
  • Loading branch information
witherblock committed Aug 30, 2024
1 parent 5b87fcd commit 79c1f2d
Show file tree
Hide file tree
Showing 15 changed files with 5,467 additions and 4,134 deletions.
82 changes: 10 additions & 72 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)
1 change: 1 addition & 0 deletions apps/web/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=
109 changes: 72 additions & 37 deletions apps/web/components/Send.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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}`;
Expand All @@ -31,51 +34,71 @@ 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<AllowanceData | null>(
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 {
return;
}
}

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,
},
Expand All @@ -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 (
<Button color="default" onClick={handleClick}>
Expand Down
8 changes: 4 additions & 4 deletions apps/web/components/TokenBalance.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Address, formatUnits } from "viem";
import { erc20ABI, useContractRead } from "wagmi";
import { Address, erc20Abi, formatUnits } from "viem";
import { useReadContract } from "wagmi";

interface TokenBalanceProps {
contractAddress: Address;
Expand All @@ -12,8 +12,8 @@ const TokenBalance = ({
userAddress,
decimals,
}: TokenBalanceProps) => {
const balance = useContractRead({
abi: erc20ABI,
const balance = useReadContract({
abi: erc20Abi,
address: contractAddress,
functionName: "balanceOf",
args: [userAddress],
Expand Down
38 changes: 30 additions & 8 deletions apps/web/components/TokenInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,47 @@
import { useToken, useAccount } from "wagmi";
import { useAccount, useReadContracts } from "wagmi";
import TokenBalance from "./TokenBalance";
import { erc20Abi, zeroAddress } from "viem";

export default function TokenInfo({ address }: { address: `0x${string}` }) {
const { data, isError, isLoading } = useToken({
const token = {
address,
abi: erc20Abi,
};

const { data, isError, isLoading } = useReadContracts({
allowFailure: false,
contracts: [
{
...token,
functionName: "symbol",
},
{
...token,
functionName: "name",
},
{
...token,
functionName: "decimals",
},
],
});

const account = useAccount();

if (isLoading) return <div>Fetching token…</div>;
if (isError) return <div>Error fetching token</div>;
if (address === zeroAddress) return <></>;

if (isLoading) return <div className="mb-4">Fetching token…</div>;
if (isError || !data) return <div className="mb-4">Error fetching token</div>;

return (
<div>
<p>Symbol: {data?.symbol}</p>
<p>Name: {data?.name}</p>
<div className="mb-4">
<p>Symbol: {data[0]}</p>
<p>Name: {data[1]}</p>
{account.address ? (
<TokenBalance
userAddress={account.address}
contractAddress={address}
decimals={data?.decimals || 18}
decimals={data[2] || 18}
/>
) : null}
</div>
Expand Down
Loading

0 comments on commit 79c1f2d

Please sign in to comment.