Skip to content

Commit

Permalink
done swap pages
Browse files Browse the repository at this point in the history
  • Loading branch information
0x-stan committed Oct 20, 2022
1 parent c457cd7 commit c8ed4fc
Show file tree
Hide file tree
Showing 8 changed files with 1,008 additions and 93 deletions.
250 changes: 248 additions & 2 deletions basic/59-wagmi-and-nextjs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ We will try to make a simple website of UniswapV2 by wagmi and nextjs.
- Next.js (server-side rendering (SSR))
- rainbowkit (based on wagmi)
- styled-components (react css package)
- react-toastify (toast plugin)

### wagmi

Expand All @@ -33,6 +34,14 @@ Next.js aims to have best-in-class developer experience and many built-in featur

RainbowKit is a React library that makes it easy to add wallet connection to your dapp. It's intuitive, responsive and customizable.

## Quick Start

```sh
cd uniswap-v2-interface-wagmi
yarn install
yarn dev
```

## Step-by-Step

### installation
Expand All @@ -50,10 +59,10 @@ cd uniswap-v2-interface-wagmi/
yarn dev
```

Add wagmi, ethers, rainbowkit
Add wagmi, ethers, rainbowkit, react-toastify

```sh
yarn add wagmi ethers @rainbow-me/rainbowkit
yarn add wagmi ethers @rainbow-me/rainbowkit react-toastify
```

### Config Wagmi and Rainbowkit
Expand All @@ -77,7 +86,9 @@ import {
} from 'wagmi';
import { alchemyProvider } from 'wagmi/providers/alchemy';
import { publicProvider } from 'wagmi/providers/public';
import { ToastContainer } from 'react-toastify';
import '@rainbow-me/rainbowkit/styles.css';
import 'react-toastify/dist/ReactToastify.css';

```

Expand Down Expand Up @@ -115,6 +126,7 @@ Add `<WagmiConfig>`, `<RainbowKitProvider>` to contain our page content in react
<WagmiConfig client={wagmiClient}>
<RainbowKitProvider chains={chains}>
<GlobalStyle />
<ToastContainer />
<Component {...pageProps} />
</RainbowKitProvider>
</WagmiConfig>
Expand Down Expand Up @@ -182,10 +194,244 @@ import { ConnectButton } from '@rainbow-me/rainbowkit';
...
```

copy abi.json file from etherscan, put them in `abi/` folder, create `config/contractConfig.ts`, set some configuration of contracts.

```ts
// config/contractConfig.ts
// goerli testnet

import UniswapRouter2ABI from "../abi/UniswapRouter2ABI.json";
import WETHABI from "../abi/WETHABI.json";
import DaiABI from "../abi/DaiABI.json";

export const ROUTER2_ADDRESS = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D';
export const WETH_ADDRESS = '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6';
export const DAI_ADDRESS = '0xf2edF1c091f683E3fb452497d9a98A49cBA84666'

export const router2ContractConfig = {
address: ROUTER2_ADDRESS,
abi: UniswapRouter2ABI,
};
export const wethContractConfig = {
address: WETH_ADDRESS,
abi: WETHABI,
}
export const daiContractConfig = {
address: DAI_ADDRESS,
abi: DaiABI,
}
```

Add some dom in swap.tsx (select token list and amount inputbox), give them some styles with setyled-components.

```tsx
// pages/swap.tsx
const tokens = [
{ name: 'WETH', config: wethContractConfig },
{ name: 'Dai', config: daiContractConfig },
];
export default function Swap() {
const [inputIndex, setInputIndex] = useState(0);
const [outputIndex, setOutputIndex] = useState(1);
const [inputAmount, setInputAmount] = useState<BigNumber>(BigNumber.from("0"));
const [outputAmount, setOutputAmount] = useState<BigNumber>(BigNumber.from("0"));

return (
...
<SwapBox>
<TokenList>
<h3>input token:</h3>
{tokens.map((item, index) => (
<label key={index}>
<input name="inputToken" type="radio" checked={index == inputIndex} onChange={(e: any) => {
setInputIndex(index)
if (outputIndex == index) {
setOutputIndex((index+1) % tokens.length)
}
}} />
{item.name}
</label>
))}
<input type="text" value={formatEther(inputAmount)} onChange={(e:any) => {
const value = e.target.value;
if (value && value !== '') {
setInputAmount(parseEther(value));
} else {
setInputAmount(BigNumber.from("0"))
}
}} />
</TokenList>
<TokenList>
<h3>output token:</h3>
{tokens.map((item, index) => (
<label key={index}>
<input name="outputToken" type="radio" checked={index == outputIndex} onChange={(e: any) => {
setOutputIndex(index)
if (inputIndex == index) {
setInputIndex((index+1) % tokens.length)
}
}} />
{item.name}
</label>
))}
<input type="text" value={formatEther(outputAmount)} disabled/>
</TokenList>
</SwapBox>
...
);
}
```

UniswapV2 `Router2.getAmountsOut()` function will help us calculate outputToken amount. we could use wagmi hook `useContractRead` to fetch contract function. The hook will return a `refetch` function which will be put in `useEffect` react hook. When a select token index or input amount changes, it will fetch a new result of getAmountsOut function.

```tsx
// pages/swap.tsx

const { isLoading: getAmountsOutLoading, refetch: getAmountsOutRefetch } = useContractRead({
// fetch router2.getAmountsOut get output amount
// function getAmountsOut(uint amountIn, address[] memory path) public view returns (uint[] memory amounts);
...router2ContractConfig,
functionName: "getAmountsOut",
args: [
inputAmount,
[
tokens[inputIndex].config.address,
tokens[outputIndex].config.address,
]
],
// trigger condition: input and output are different token && inputAmount > 0
enabled: inputIndex !== outputIndex && inputAmount.gt(BigNumber.from("0")),
onSuccess(data: BigNumber) {
if (data && data[1]) setOutputAmount(data[1])
},
onError(err) {
// console.error(err)
toast.error(JSON.stringify(err))
}
});
```

same way add fetch allowance logstic. wagmi hook `useAccount` will return user's wallet addres and connecting status.

```tsx
// get user address from wagmi hook useAccount
const { address, isConnected } = useAccount();
const [isApproved, setIsApproved] = useState(false);

const { isLoading: allowanceLoading, refetch: allowanceRefetch } = useContractRead({
// fetch outputToken.allowance
// function allowance(address account, address spender) public view returns (uint);
address: tokens[inputIndex].config.address,
abi: tokens[inputIndex].config.abi,
functionName: "allowance",
enabled: isConnected,
args: [address, router2ContractConfig.address],
onSuccess(data: BigNumber) {
if (data.gt(BigNumber.from("0"))) {
setIsApproved(data.gte(outputAmount))
} else {
setIsApproved(false);
}
},
onError(err) {
toast.error(JSON.stringify(err))
}
});
```

Refetch result when select index or input amount changes.

```tsx
// fetch getAmountsOut when select index or input amount changed
useEffect(() => {
if (!getAmountsOutLoading && inputIndex !== outputIndex && inputAmount.gt(BigNumber.from("0"))) {
getAmountsOutRefetch()
allowanceRefetch()
}
}, [inputIndex, inputAmount])
```

wagmi hook `usePrepareContractWrite` help us to eagerly fetches the parameters required for sending a contract write transaction such as the gas estimate. rainbowkit hook `useAddRecentTransaction` will add tx hash in rainbowkit when we send transcation.

```tsx
const toastId = useRef<Id | null>(null);
// addRecentTransaction will add tx hash in rainbowkit when send transcation
const addRecentTransaction = useAddRecentTransaction();

const { config: approveConfig } = usePrepareContractWrite({
address: tokens[inputIndex].config.address,
abi: tokens[inputIndex].config.abi,
enabled: isConnected,
functionName: "approve",
args: [
router2ContractConfig.address,
constants.MaxUint256
],
onError(err: any) {
toast.error(JSON.stringify(err));
},
});
```

hook `useContractWrite` is the acctually send transaction method, use prepare hook's result be its input params.

```tsx
const { write: approveWrite } = useContractWrite({
...approveConfig,
onMutate(data) {
toastId.current = toast("Please wait...", { isLoading: true });
},
onSuccess(data) {
console.warn("writen contract approve:\n", data);
// add pending tx hash in rainbowkit
addRecentTransaction({
hash: data.hash,
description: `approve`,
confirmations: 1,
});
// wati tx confirmed 1 block
data
.wait(1)
.then((res) => {
console.warn("transaction confirmed", res);
toast.update(toastId.current as Id, {
render: "approve successfully",
type: toast.TYPE.SUCCESS,
isLoading: false,
autoClose: 3_000,
});
setIsApproved(true);
})
.catch((err) => {
console.error(err);
toast.update(toastId.current as Id, {
render: err,
type: toast.TYPE.ERROR,
isLoading: false,
});
});
},
onError(err: any) {
toast.update(toastId.current as Id, {
render: JSON.stringify(err),
type: toast.TYPE.ERROR,
isLoading: false,
autoClose: 5_000,
});
},
});
```

The same way to add swap logstic. And add tow buttons in the dom, we've done!

You can find the final code in [pages/swap.tsx](./uniswap-v2-interface-wagmipages/swap.tsx).


## Reference

- wagmi <https://wagmi.sh/>
- Next.js <https://nextjs.org/docs/getting-started>
- rainbowkit <https://www.rainbowkit.com/>
- styled-components <https://github.com/styled-components/styled-components>
- [Client-side vs. server-side rendering: why it’s not all black and white](https://www.freecodecamp.org/news/what-exactly-is-client-side-rendering-and-hows-it-different-from-server-side-rendering-bd5c786b340d/)
- UniswapV2 docs <https://docs.uniswap.org/protocol/V2/reference/smart-contracts/router-02>
86 changes: 0 additions & 86 deletions basic/59-wagmi-and-nextjs/uniswap-v2-interface-wagmi/README.md

This file was deleted.

Loading

0 comments on commit c8ed4fc

Please sign in to comment.