Repository: sweetmantech/IN_PROCESS
Pull request #318: feat: usdc purhcase prototype
Author: login: techeng322
Created: 2025-04-22T18:19:09Z | Merged: 2025-04-22T19:13:21Z



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced a new purchase flow allowing users to buy tokens using Uniswap V3 and an ERC20 minter contract.
  - Users can enter a comment and complete the purchase in a single transaction, including swap and mint steps.
  - The interface displays collection details, token ID, price, and provides real-time feedback on transaction status and errors.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/app/swap/purchase/page.tsx b/app/swap/purchase/page.tsx
new file mode 100644
index 00000000..ac0ddf4c
--- /dev/null
+++ b/app/swap/purchase/page.tsx
@@ -0,0 +1,97 @@
+"use client";
+
+import useBalance from "@/hooks/useBalance";
+import { Address, parseEther, parseUnits } from "viem";
+import { CHAIN, CHAIN_ID, MULTICALL3_ADDRESS } from "@/lib/consts";
+import { getPublicClient } from "@/lib/viem/publicClient";
+import useSignedAddress from "@/hooks/useSignedAddress";
+import useSignTransaction from "@/hooks/useSignTransaction";
+import { useState } from "react";
+import { USDC_TOKEN } from "@/lib/tokens";
+import { multicall3ABI } from "@/lib/abis/multicall3ABI";
+import { toast } from "sonner";
+import getSwapAndMintMulticallCalls from "@/lib/calls/getSwapAndMintMuticallCalls";
+import getPoolInfo from "@/lib/uniswap/getPoolInfo";
+
+const Swap = () => {
+  const balances = useBalance();
+  const signedAddress = useSignedAddress();
+  const { signTransaction } = useSignTransaction();
+  const amount = "1";
+  const [loading, setLoading] = useState(false);
+  const [comment, setComment] = useState("");
+  const [transactionLink, setTransactionLink] = useState("");
+
+  const onPurchase = async () => {
+    try {
+      setLoading(true);
+      const collectionAddress = "0x1590252D59F9DB3af56860b864CB49C97eF35ba9";
+      const usdcPrice = parseUnits(amount, USDC_TOKEN.decimals);
+      const { amountInMaximum, liquidity, gasEstimate } = await getPoolInfo(
+        signedAddress as Address,
+        usdcPrice,
+      );
+      const ethBalance = parseEther(balances.ethBalance.toString());
+      if (ethBalance < amountInMaximum) {
+        toast.error("insufficient amount");
+        setLoading(false);
+        return;
+      }
+      const calls = getSwapAndMintMulticallCalls(
+        signedAddress as Address,
+        collectionAddress,
+        BigInt(1),
+        comment,
+        usdcPrice,
+        amountInMaximum,
+        liquidity,
+      );
+      const hash = await signTransaction({
+        address: MULTICALL3_ADDRESS,
+        abi: multicall3ABI as any,
+        functionName: "aggregate3Value",
+        args: [calls],
+        account: signedAddress as Address,
+        value: amountInMaximum,
+        chain: CHAIN,
+        maxFeePerGas: gasEstimate,
+        maxPriorityFeePerGas: gasEstimate,
+      });
+      const publicClient = getPublicClient(CHAIN_ID);
+      await publicClient.waitForTransactionReceipt({ hash });
+      setTransactionLink(`https://basescan.org/tx/${hash}`);
+      setLoading(false);
+    } catch (error) {
+      console.error(error);
+      setLoading(false);
+    }
+  };
+
+  return (
+    <div className="grow flex flex-col justify-center items-center gap-2">
+      <p className="font-archivo text-lg">chain: Base - 8453</p>
+      <p className="font-archivo text-lg">
+        collection address: 0x1590252D59F9DB3af56860b864CB49C97eF35ba9
+      </p>
+      <p className="font-archivo text-lg">token id: 1</p>
+      <p className="font-archivo text-lg">price: 1 USDC</p>
+      {transactionLink && <p>Transaction: {transactionLink}</p>}
+      <input
+        type="text"
+        value={comment}
+        className="font-archivo p-2 rounded-md"
+        onChange={(e) => setComment(e.target.value)}
+        placeholder="enter comment"
+      />
+      <button
+        className="bg-black font-archivo text-white py-2 px-6 rounded-md disabled:bg-grey-moss-300 disabled:cursor-not-allowed"
+        onClick={onPurchase}
+        disabled={loading}
+      >
+        {loading ? "Purchasing..." : "Purchase"}
+      </button>
+    </div>
+  );
+};
+
+export default Swap;
diff --git a/lib/calls/getERC20MintCall.ts b/lib/calls/getERC20MintCall.ts
new file mode 100644
index 00000000..92bdab7b
--- /dev/null
+++ b/lib/calls/getERC20MintCall.ts
@@ -0,0 +1,31 @@
+import { erc20MinterABI } from "@zoralabs/protocol-deployments";
+import { Address, encodeFunctionData } from "viem";
+
+const getERC20MintCall = (
+  mintTo: Address,
+  tokenId: bigint,
+  collectionAddress: Address,
+  price: bigint,
+  erc20Address: Address,
+  referral: Address,
+  comment: string,
+) => {
+  const ercMintCall = encodeFunctionData({
+    abi: erc20MinterABI,
+    functionName: "mint",
+    args: [
+      mintTo,
+      BigInt(1),
+      collectionAddress,
+      tokenId,
+      price,
+      erc20Address,
+      referral,
+      comment,
+    ],
+  });
+
+  return ercMintCall;
+};
+
+export default getERC20MintCall;
diff --git a/lib/calls/getSwapAndMintMuticallCalls.ts b/lib/calls/getSwapAndMintMuticallCalls.ts
new file mode 100644
index 00000000..2da77b26
--- /dev/null
+++ b/lib/calls/getSwapAndMintMuticallCalls.ts
@@ -0,0 +1,73 @@
+import { Address } from "viem";
+import {
+  CHAIN_ID,
+  MULTICALL3_ADDRESS,
+  UNISWAP_ROUTER_ADDRESS,
+} from "../consts";
+import { erc20MinterAddresses } from "../protocolSdk/constants";
+import { USDC_TOKEN, WETH_TOKEN } from "../tokens";
+import getERC20MintCall from "./getERC20MintCall";
+import getSwapCall from "./getSwapCall";
+import getUsdcApprovalCall from "./getUsdcApprovalCall";
+import getWETHApprovalCall from "./getWETHApprovalCall";
+
+const getSwapAndMintMulticallCalls = (
+  signedAddress: Address,
+  collection: Address,
+  tokenId: bigint,
+  comment: string,
+  price: bigint,
+  amountInMaximum: bigint,
+  sqrtPriceLimitX96: bigint,
+) => {
+  const wethApprovalCall = getWETHApprovalCall(amountInMaximum);
+  const swapCall = getSwapCall(
+    WETH_TOKEN.address as Address,
+    USDC_TOKEN.address as Address,
+    MULTICALL3_ADDRESS,
+    price,
+    amountInMaximum,
+    sqrtPriceLimitX96,
+  );
+  const usdcApproveCall = getUsdcApprovalCall(erc20MinterAddresses[CHAIN_ID]);
+  const erc20MintCall = getERC20MintCall(
+    signedAddress as Address,
+    tokenId,
+    collection,
+    price,
+    USDC_TOKEN.address as Address,
+    signedAddress as Address,
+    comment,
+  );
+
+  const calls = [
+    {
+      target: WETH_TOKEN.address as Address,
+      value: BigInt(0),
+      callData: wethApprovalCall,
+      allowFailure: false,
+    },
+    {
+      target: UNISWAP_ROUTER_ADDRESS,
+      value: amountInMaximum,
+      callData: swapCall,
+      allowFailure: false,
+    },
+    {
+      target: USDC_TOKEN.address,
+      value: BigInt(0),
+      callData: usdcApproveCall,
+      allowFailure: false,
+    },
+    {
+      target: erc20MinterAddresses[CHAIN_ID] as Address,
+      value: BigInt(0),
+      callData: erc20MintCall,
+      allowFailure: false,
+    },
+  ];
+
+  return calls;
+};
+
+export default getSwapAndMintMulticallCalls;
diff --git a/lib/calls/getSwapCall.ts b/lib/calls/getSwapCall.ts
new file mode 100644
index 00000000..4d4e749a
--- /dev/null
+++ b/lib/calls/getSwapCall.ts
@@ -0,0 +1,33 @@
+import { Address, encodeFunctionData } from "viem";
+import { swapRouter02ABI } from "../abis/uniswap/swapRouter02ABI";
+import { FeeAmount } from "@uniswap/v3-sdk";
+
+const getSwapCall = (
+  tokenIn: Address,
+  tokenOut: Address,
+  recipient: Address,
+  amountOut: bigint,
+  amountInMaximum: bigint,
+  sqrtPriceLimitX96: bigint,
+) => {
+  const swapCall = encodeFunctionData({
+    functionName: "exactOutputSingle",
+    args: [
+      {
+        tokenIn,
+        tokenOut,
+        fee: FeeAmount.LOW,
+        recipient,
+        deadline: Math.floor(Date.now() / 1000) + 60 * 20,
+        amountOut,
+        amountInMaximum,
+        sqrtPriceLimitX96,
+      },
+    ],
+    abi: swapRouter02ABI as any,
+  });
+
+  return swapCall;
+};
+
+export default getSwapCall;
diff --git a/lib/calls/getUsdcApprovalCall.ts b/lib/calls/getUsdcApprovalCall.ts
new file mode 100644
index 00000000..1975b31f
--- /dev/null
+++ b/lib/calls/getUsdcApprovalCall.ts
@@ -0,0 +1,13 @@
+import { Address, encodeFunctionData, erc20Abi, maxUint256 } from "viem";
+
+const getUsdcApprovalCall = (spendar: Address) => {
+  const usdcApprovalCall = encodeFunctionData({
+    functionName: "approve",
+    args: [spendar, maxUint256],
+    abi: erc20Abi as any,
+  });
+
+  return usdcApprovalCall;
+};
+
+export default getUsdcApprovalCall;
diff --git a/lib/calls/getWETHApprovalCall.ts b/lib/calls/getWETHApprovalCall.ts
new file mode 100644
index 00000000..82dedbdb
--- /dev/null
+++ b/lib/calls/getWETHApprovalCall.ts
@@ -0,0 +1,15 @@
+import { encodeFunctionData } from "viem";
+import { UNISWAP_ROUTER_ADDRESS } from "../consts";
+import { wethABI } from "../abis/wethABI";
+
+const getWETHApprovalCall = (amountInMaximum: bigint) => {
+  const wethApprovalCall = encodeFunctionData({
+    functionName: "approve",
+    args: [UNISWAP_ROUTER_ADDRESS, amountInMaximum],
+    abi: wethABI as any,
+  });
+
+  return wethApprovalCall;
+};
+
+export default getWETHApprovalCall;
diff --git a/lib/uniswap/getPoolInfo.ts b/lib/uniswap/getPoolInfo.ts
new file mode 100644
index 00000000..c2f0e8d1
--- /dev/null
+++ b/lib/uniswap/getPoolInfo.ts
@@ -0,0 +1,71 @@
+import { QUOTER_ADDRESSES, V3_CORE_FACTORY_ADDRESSES } from "@uniswap/sdk-core";
+import { computePoolAddress, FeeAmount } from "@uniswap/v3-sdk";
+import { CHAIN_ID } from "../consts";
+import { USDC_TOKEN, WETH_TOKEN } from "../tokens";
+import { getPublicClient } from "../viem/publicClient";
+import { v3UniswapPoolABI } from "../abis/uniswap/v2UniswapPoolABI";
+import { Address } from "viem";
+import { quoteV2ABI } from "../abis/uniswap/quoteV2ABI";
+
+const getPoolInfo = async (account: Address, swapAmount: bigint) => {
+  const poolAddress = computePoolAddress({
+    factoryAddress: V3_CORE_FACTORY_ADDRESSES[CHAIN_ID],
+    tokenA: WETH_TOKEN,
+    tokenB: USDC_TOKEN,
+    fee: FeeAmount.LOW,
+  });
+  const publicClient = getPublicClient(CHAIN_ID);
+
+  const poolCalls = [
+    "token0",
+    "token1",
+    "fee",
+    "tickSpacing",
+    "liquidity",
+    "slot0",
+  ].map((functionName: string) => ({
+    address: poolAddress,
+    abi: v3UniswapPoolABI,
+    functionName,
+  }));
+
+  const returnValues: any = await publicClient.multicall({
+    contracts: poolCalls as any,
+  });
+
+  const tokenIn = returnValues[0].result;
+  const tokenOut = returnValues[1].result;
+  const fee = returnValues[2].result;
+  const liquidity = returnValues[4].result;
+
+  const { result } = await publicClient.simulateContract({
+    address: QUOTER_ADDRESSES[CHAIN_ID] as Address,
+    abi: quoteV2ABI,
+    functionName: "quoteExactOutputSingle",
+    args: [
+      {
+        tokenIn,
+        tokenOut,
+        amount: swapAmount,
+        fee: fee.toString(),
+        sqrtPriceLimitX96: liquidity.toString(),
+      },
+    ],
+    account,
+  });
+
+  const gasEstimate = result[3] as bigint;
+
+  const amountInMaximum = (result[0] as bigint) + gasEstimate;
+
+  return {
+    tokenIn,
+    tokenOut,
+    fee,
+    liquidity,
+    amountInMaximum,
+    gasEstimate,
+  };
+};
+
+export default getPoolInfo;


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 4**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>app/swap/purchase/page.tsx (1)</summary><blockquote>

`120-123`: **Unnecessary WETH approval introduces extra calldata & gas**

`exactOutputSingle` is funded with raw ETH via `value: amountInMaximum`.  
The router wraps ETH internally, so approving WETH is redundant and will fail if the user holds no WETH balance.

Consider removing this call to streamline the multicall and avoid edge‑case failures.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between b4fb008fba34e305436082cbc895ec654b08bc7c and e718bf6d5a09ea6f0ca2acae15a1da5b99bc9563.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `app/swap/purchase/page.tsx` (1 hunks)

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>app/swap/purchase/page.tsx (1)</summary>

`113-118`: **Balance check uses `ethBalance` string → `parseEther` risk**

If `balances.ethBalance` is already a human‑readable string (e.g. `"0.0345"`), `parseEther` works; if it’s a BigInt or wei string, the conversion will throw.  
Confirm the type returned by `useBalance()` or normalise it beforehand to avoid runtime errors.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T18:22:14Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>♻️ Duplicate comments (4)</summary><blockquote>

<details>
<summary>lib/uniswap/getPoolInfo.ts (3)</summary><blockquote>

`1-1`: **Wrong constants package – compile‑time failure**

---

`6-6`: **ABI filename says V2 but used for V3 pool**

---

`50-52`: **`sqrtPriceLimitX96` misuse (passes liquidity)**

</blockquote></details>
<details>
<summary>app/swap/purchase/page.tsx (1)</summary><blockquote>

`30-38`: _⚠️ Potential issue_

**Passing pool liquidity to swap helper repeats the price‑limit bug**  

`liquidity` from `getPoolInfo` is forwarded as `sqrtPriceLimitX96`, causing the downstream swap to revert. Pass `0n` or a computed limit instead.

```diff
-        liquidity,
+        0n, // no price limit
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>lib/calls/getWETHApprovalCall.ts (1)</summary><blockquote>

`5-13`: **Avoid `any` in strongly‑typed call encoding**

`encodeFunctionData` already validates the ABI shape – casting to `any` removes that protection and may let a wrong ABI slip through compile‑time checks.  Import the type generated for the WETH ABI instead of downgrading it:

```diff
-import { wethABI } from "../abis/wethABI";
+import { type Abi } from "viem";
+import { wethABI } from "../abis/wethABI";
 …
-    abi: wethABI as any,
+    abi: wethABI as Abi,
```

This keeps the call fully type‑safe.

</blockquote></details>
<details>
<summary>lib/calls/getSwapAndMintMuticallCalls.ts (1)</summary><blockquote>

`1-1`: **Spelling: file/function is `Multicall`, not `Muticall`**

The missing “l” leaks into import paths (`getSwapAndMintMuticallCalls`), making them easy to mistype and hard to grep for later.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between e718bf6d5a09ea6f0ca2acae15a1da5b99bc9563 and 570514cdc01dbf65d5d083ba0b45c56ffe7ff8b2.

</details>

<details>
<summary>📒 Files selected for processing (7)</summary>

* `app/swap/purchase/page.tsx` (1 hunks)
* `lib/calls/getERC20MintCall.ts` (1 hunks)
* `lib/calls/getSwapAndMintMuticallCalls.ts` (1 hunks)
* `lib/calls/getSwapCall.ts` (1 hunks)
* `lib/calls/getUsdcApprovalCall.ts` (1 hunks)
* `lib/calls/getWETHApprovalCall.ts` (1 hunks)
* `lib/uniswap/getPoolInfo.ts` (1 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (3)</summary>

* lib/calls/getSwapCall.ts
* lib/calls/getUsdcApprovalCall.ts
* lib/calls/getERC20MintCall.ts

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (2)</summary>

<details>
<summary>lib/calls/getSwapAndMintMuticallCalls.ts (3)</summary><blockquote>

<details>
<summary>lib/tokens.ts (2)</summary>

* `WETH_TOKEN` (2-8)
* `USDC_TOKEN` (10-16)

</details>
<details>
<summary>lib/consts.ts (3)</summary>

* `MULTICALL3_ADDRESS` (33-33)
* `CHAIN_ID` (23-23)
* `UNISWAP_ROUTER_ADDRESS` (31-32)

</details>
<details>
<summary>lib/protocolSdk/constants.ts (1)</summary>

* `erc20MinterAddresses` (70-83)

</details>

</blockquote></details>
<details>
<summary>app/swap/purchase/page.tsx (4)</summary><blockquote>

<details>
<summary>lib/tokens.ts (1)</summary>

* `USDC_TOKEN` (10-16)

</details>
<details>
<summary>lib/consts.ts (3)</summary>

* `MULTICALL3_ADDRESS` (33-33)
* `CHAIN` (22-22)
* `CHAIN_ID` (23-23)

</details>
<details>
<summary>lib/abis/multicall3ABI.ts (1)</summary>

* `multicall3ABI` (1-440)

</details>
<details>
<summary>lib/viem/publicClient.ts (1)</summary>

* `publicClient` (14-14)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>app/swap/purchase/page.tsx (1)</summary>

`50-56`: **Adjust transaction `value` if ETH is no longer sent to router**

After setting the router call `value` to `0`, the outer Multicall3 must supply the sum of inner `value`s (which will now be `0` as well):

```diff
-        value: amountInMaximum,
+        value: 0n,
```

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T19:08:00Z

Review comments:
diff_hunk: @@ -0,0 +1,226 @@
+"use client";
+
+import useBalance from "@/hooks/useBalance";
+import { QUOTER_ADDRESSES, V3_CORE_FACTORY_ADDRESSES } from "@uniswap/sdk-core";
+import { computePoolAddress, FeeAmount } from "@uniswap/v3-sdk";
+import {
+  Address,
+  encodeFunctionData,
+  erc20Abi,
+  maxUint256,
+  parseEther,
+  parseUnits,
+} from "viem";
+import {
+  CHAIN,
+  CHAIN_ID,
+  MULTICALL3_ADDRESS,
+  UNISWAP_ROUTER_ADDRESS,
+} from "@/lib/consts";
+import { getPublicClient } from "@/lib/viem/publicClient";
+import useSignedAddress from "@/hooks/useSignedAddress";
+import useSignTransaction from "@/hooks/useSignTransaction";
+import { useState } from "react";
+import { USDC_TOKEN, WETH_TOKEN } from "@/lib/tokens";
+import { quoteV2ABI } from "@/lib/abis/uniswap/quoteV2ABI";
+import { wethABI } from "@/lib/abis/wethABI";
+import { swapRouter02ABI } from "@/lib/abis/uniswap/swapRouter02ABI";
+import { v3UniswapPoolABI } from "@/lib/abis/uniswap/v2UniswapPoolABI";
+import { multicall3ABI } from "@/lib/abis/multicall3ABI";
path: app/swap/purchase/page.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**ABI path implies V2 but is used for a V3 pool**

The import points to `v2UniswapPoolABI`, yet the contract we interact with is a V3 pool.  
Either rename the file to reflect V3 or point to the correct ABI file – mis‑matched ABIs will make `publicClient.multicall` revert.

```diff
-import { v3UniswapPoolABI } from "@/lib/abis/uniswap/v2UniswapPoolABI";
+import { v3UniswapPoolABI } from "@/lib/abis/uniswap/v3UniswapPoolABI";
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
import { swapRouter02ABI } from "@/lib/abis/uniswap/swapRouter02ABI";
-import { v3UniswapPoolABI } from "@/lib/abis/uniswap/v2UniswapPoolABI";
+import { v3UniswapPoolABI } from "@/lib/abis/uniswap/v3UniswapPoolABI";
import { multicall3ABI } from "@/lib/abis/multicall3ABI";
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-22T18:22:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,226 @@
+"use client";
+
+import useBalance from "@/hooks/useBalance";
+import { QUOTER_ADDRESSES, V3_CORE_FACTORY_ADDRESSES } from "@uniswap/sdk-core";
+import { computePoolAddress, FeeAmount } from "@uniswap/v3-sdk";
+import {
path: app/swap/purchase/page.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Wrong Uniswap constants package – will break the build**

`QUOTER_ADDRESSES` and `V3_CORE_FACTORY_ADDRESSES` live in `@uniswap/v3-sdk`, not `@uniswap/sdk-core`. The current import path will cause a compile‑time error and the component will never render.

```diff
-import { QUOTER_ADDRESSES, V3_CORE_FACTORY_ADDRESSES } from "@uniswap/sdk-core";
+import { QUOTER_ADDRESSES, V3_CORE_FACTORY_ADDRESSES } from "@uniswap/v3-sdk";
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
import { QUOTER_ADDRESSES, V3_CORE_FACTORY_ADDRESSES } from "@uniswap/v3-sdk";
import { computePoolAddress, FeeAmount } from "@uniswap/v3-sdk";
import {
  // …
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 570514c
created_at: 2025-04-22T18:22:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,226 @@
+"use client";
+
+import useBalance from "@/hooks/useBalance";
+import { QUOTER_ADDRESSES, V3_CORE_FACTORY_ADDRESSES } from "@uniswap/sdk-core";
+import { computePoolAddress, FeeAmount } from "@uniswap/v3-sdk";
+import {
+  Address,
+  encodeFunctionData,
+  erc20Abi,
+  maxUint256,
+  parseEther,
+  parseUnits,
+} from "viem";
+import {
+  CHAIN,
+  CHAIN_ID,
+  MULTICALL3_ADDRESS,
+  UNISWAP_ROUTER_ADDRESS,
+} from "@/lib/consts";
+import { getPublicClient } from "@/lib/viem/publicClient";
+import useSignedAddress from "@/hooks/useSignedAddress";
+import useSignTransaction from "@/hooks/useSignTransaction";
+import { useState } from "react";
+import { USDC_TOKEN, WETH_TOKEN } from "@/lib/tokens";
+import { quoteV2ABI } from "@/lib/abis/uniswap/quoteV2ABI";
+import { wethABI } from "@/lib/abis/wethABI";
+import { swapRouter02ABI } from "@/lib/abis/uniswap/swapRouter02ABI";
+import { v3UniswapPoolABI } from "@/lib/abis/uniswap/v2UniswapPoolABI";
+import { multicall3ABI } from "@/lib/abis/multicall3ABI";
+import { toast } from "sonner";
+import { erc20MinterAddresses } from "@/lib/protocolSdk/constants";
+import { erc20MinterABI } from "@zoralabs/protocol-deployments";
+
+const Swap = () => {
+  const balances = useBalance();
+  const signedAddress = useSignedAddress();
+  const { signTransaction } = useSignTransaction();
+  const amount = "1";
+  const [loading, setLoading] = useState(false);
+  const [comment, setComment] = useState("");
+
+  const onPurchase = async () => {
+    try {
+      setLoading(true);
+      const swapAmount = parseUnits(amount, USDC_TOKEN.decimals);
+      const poolAddress = computePoolAddress({
+        factoryAddress: V3_CORE_FACTORY_ADDRESSES[CHAIN.id],
+        tokenA: WETH_TOKEN,
+        tokenB: USDC_TOKEN,
+        fee: FeeAmount.LOW,
+      });
+      const publicClient = getPublicClient(CHAIN.id);
+
+      const poolCalls = [
+        {
+          address: poolAddress,
+          abi: v3UniswapPoolABI,
+          functionName: "token0",
+        },
+        {
+          address: poolAddress,
+          abi: v3UniswapPoolABI,
+          functionName: "token1",
+        },
+        {
+          address: poolAddress,
+          abi: v3UniswapPoolABI,
+          functionName: "fee",
+        },
+        {
+          address: poolAddress,
+          abi: v3UniswapPoolABI,
+          functionName: "tickSpacing",
+        },
+        {
+          address: poolAddress,
+          abi: v3UniswapPoolABI,
+          functionName: "liquidity",
+        },
+        {
+          address: poolAddress,
+          abi: v3UniswapPoolABI,
+          functionName: "slot0",
+        },
+      ];
+
+      const returnValues: any = await publicClient.multicall({
+        contracts: poolCalls as any,
+      });
+
+      const tokenIn = returnValues[0].result;
+      const tokenOut = returnValues[1].result;
+      const fee = returnValues[2].result;
+      const liquidity = returnValues[4].result;
+
+      const { result } = await publicClient.simulateContract({
+        address: QUOTER_ADDRESSES[CHAIN.id] as Address,
+        abi: quoteV2ABI,
+        functionName: "quoteExactOutputSingle",
+        args: [
+          {
+            tokenIn,
+            tokenOut,
+            amount: swapAmount,
+            fee: fee.toString(),
+            sqrtPriceLimitX96: liquidity.toString(),
+          },
+        ],
+        account: signedAddress as Address,
+      });
+
+      const amountInMaximum = (result[0] as bigint) + (result[3] as bigint); // 1st: exact amount in 2nd: estimated gas fee
+      const ethBalance = parseEther(balances.ethBalance.toString());
+      if (ethBalance < amountInMaximum) {
+        toast.error("insufficient amount");
+        setLoading(false);
+        return;
+      }
+      const wethApprovalCall = encodeFunctionData({
+        functionName: "approve",
+        args: [UNISWAP_ROUTER_ADDRESS, amountInMaximum],
+        abi: wethABI as any,
+      });
+      const swapCall = encodeFunctionData({
+        functionName: "exactOutputSingle",
+        args: [
+          {
+            tokenIn: WETH_TOKEN.address,
+            tokenOut: USDC_TOKEN.address,
+            fee: 500,
+            recipient: MULTICALL3_ADDRESS,
+            deadline: Math.floor(Date.now() / 1000) + 60 * 20,
+            amountOut: swapAmount,
+            amountInMaximum: amountInMaximum,
+            sqrtPriceLimitX96: liquidity,
+          },
+        ],
path: app/swap/purchase/page.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Same `sqrtPriceLimitX96` misuse in the actual swap call**

The swap will revert for the same reason as the quote. Set the parameter to `0n` (or a proper limit) instead of liquidity.

```diff
-            sqrtPriceLimitX96: liquidity,
+            sqrtPriceLimitX96: 0n,
```

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-22T18:22:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,226 @@
+"use client";
+
+import useBalance from "@/hooks/useBalance";
+import { QUOTER_ADDRESSES, V3_CORE_FACTORY_ADDRESSES } from "@uniswap/sdk-core";
+import { computePoolAddress, FeeAmount } from "@uniswap/v3-sdk";
+import {
+  Address,
+  encodeFunctionData,
+  erc20Abi,
+  maxUint256,
+  parseEther,
+  parseUnits,
+} from "viem";
+import {
+  CHAIN,
+  CHAIN_ID,
+  MULTICALL3_ADDRESS,
+  UNISWAP_ROUTER_ADDRESS,
+} from "@/lib/consts";
+import { getPublicClient } from "@/lib/viem/publicClient";
+import useSignedAddress from "@/hooks/useSignedAddress";
+import useSignTransaction from "@/hooks/useSignTransaction";
+import { useState } from "react";
+import { USDC_TOKEN, WETH_TOKEN } from "@/lib/tokens";
+import { quoteV2ABI } from "@/lib/abis/uniswap/quoteV2ABI";
+import { wethABI } from "@/lib/abis/wethABI";
+import { swapRouter02ABI } from "@/lib/abis/uniswap/swapRouter02ABI";
+import { v3UniswapPoolABI } from "@/lib/abis/uniswap/v2UniswapPoolABI";
+import { multicall3ABI } from "@/lib/abis/multicall3ABI";
+import { toast } from "sonner";
+import { erc20MinterAddresses } from "@/lib/protocolSdk/constants";
+import { erc20MinterABI } from "@zoralabs/protocol-deployments";
+
+const Swap = () => {
+  const balances = useBalance();
+  const signedAddress = useSignedAddress();
+  const { signTransaction } = useSignTransaction();
+  const amount = "1";
+  const [loading, setLoading] = useState(false);
+  const [comment, setComment] = useState("");
+
+  const onPurchase = async () => {
+    try {
+      setLoading(true);
+      const swapAmount = parseUnits(amount, USDC_TOKEN.decimals);
+      const poolAddress = computePoolAddress({
+        factoryAddress: V3_CORE_FACTORY_ADDRESSES[CHAIN.id],
+        tokenA: WETH_TOKEN,
+        tokenB: USDC_TOKEN,
+        fee: FeeAmount.LOW,
+      });
+      const publicClient = getPublicClient(CHAIN.id);
+
+      const poolCalls = [
+        {
+          address: poolAddress,
+          abi: v3UniswapPoolABI,
+          functionName: "token0",
+        },
+        {
+          address: poolAddress,
+          abi: v3UniswapPoolABI,
+          functionName: "token1",
+        },
+        {
+          address: poolAddress,
+          abi: v3UniswapPoolABI,
+          functionName: "fee",
+        },
+        {
+          address: poolAddress,
+          abi: v3UniswapPoolABI,
+          functionName: "tickSpacing",
+        },
+        {
+          address: poolAddress,
+          abi: v3UniswapPoolABI,
+          functionName: "liquidity",
+        },
+        {
+          address: poolAddress,
+          abi: v3UniswapPoolABI,
+          functionName: "slot0",
+        },
+      ];
+
+      const returnValues: any = await publicClient.multicall({
+        contracts: poolCalls as any,
+      });
+
+      const tokenIn = returnValues[0].result;
+      const tokenOut = returnValues[1].result;
+      const fee = returnValues[2].result;
+      const liquidity = returnValues[4].result;
+
+      const { result } = await publicClient.simulateContract({
+        address: QUOTER_ADDRESSES[CHAIN.id] as Address,
+        abi: quoteV2ABI,
+        functionName: "quoteExactOutputSingle",
+        args: [
+          {
+            tokenIn,
+            tokenOut,
+            amount: swapAmount,
+            fee: fee.toString(),
+            sqrtPriceLimitX96: liquidity.toString(),
+          },
+        ],
+        account: signedAddress as Address,
path: app/swap/purchase/page.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`sqrtPriceLimitX96` is set to pool liquidity – this is an invalid parameter**

`quoteExactOutputSingle` expects a price limit (uint160 sqrtPriceX96) but receives the pool’s `liquidity` (uint128).  
Passing an out‑of‑range value causes the quoter call to revert on mainnet.

```diff
-            sqrtPriceLimitX96: liquidity.toString(),
+            // 0 means “no price limit”
+            sqrtPriceLimitX96: 0n,
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
      const { result } = await publicClient.simulateContract({
        address: QUOTER_ADDRESSES[CHAIN.id] as Address,
        abi: quoteV2ABI,
        functionName: "quoteExactOutputSingle",
        args: [
          {
            tokenIn,
            tokenOut,
            amount: swapAmount,
            fee: fee.toString(),
            // 0 means “no price limit”
            sqrtPriceLimitX96: 0n,
          },
        ],
        account: signedAddress as Address,
      });
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-22T18:22:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,73 @@
+import { Address } from "viem";
+import {
+  CHAIN_ID,
+  MULTICALL3_ADDRESS,
+  UNISWAP_ROUTER_ADDRESS,
+} from "../consts";
+import { erc20MinterAddresses } from "../protocolSdk/constants";
+import { USDC_TOKEN, WETH_TOKEN } from "../tokens";
+import getERC20MintCall from "./getERC20MintCall";
+import getSwapCall from "./getSwapCall";
+import getUsdcApprovalCall from "./getUsdcApprovalCall";
+import getWETHApprovalCall from "./getWETHApprovalCall";
+
+const getSwapAndMintMulticallCalls = (
+  signedAddress: Address,
+  collection: Address,
+  tokenId: bigint,
+  comment: string,
+  price: bigint,
+  amountInMaximum: bigint,
+  sqrtPriceLimitX96: bigint,
+) => {
+  const wethApprovalCall = getWETHApprovalCall(amountInMaximum);
+  const swapCall = getSwapCall(
+    WETH_TOKEN.address as Address,
+    USDC_TOKEN.address as Address,
+    MULTICALL3_ADDRESS,
+    price,
+    amountInMaximum,
+    sqrtPriceLimitX96,
+  );
path: lib/calls/getSwapAndMintMuticallCalls.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`sqrtPriceLimitX96` is not a liquidity value – swap will revert**  

You forward `liquidity` from `getPoolInfo` as the price limit.  `sqrtPriceLimitX96` must be either `0n` (no limit) or a valid uint160 price threshold. Passing liquidity (uint128) will overflow the parameter and the router will revert.

```diff
-  const swapCall = getSwapCall(
-    WETH_TOKEN.address as Address,
-    USDC_TOKEN.address as Address,
-    MULTICALL3_ADDRESS,
-    price,
-    amountInMaximum,
-    sqrtPriceLimitX96,
-  );
+  const swapCall = getSwapCall(
+    WETH_TOKEN.address as Address,
+    USDC_TOKEN.address as Address,
+    MULTICALL3_ADDRESS,
+    price,
+    amountInMaximum,
+    0n, // no price limit
+  );
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
  const swapCall = getSwapCall(
    WETH_TOKEN.address as Address,
    USDC_TOKEN.address as Address,
    MULTICALL3_ADDRESS,
    price,
    amountInMaximum,
    0n, // no price limit
  );
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-22T19:07:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,73 @@
+import { Address } from "viem";
+import {
+  CHAIN_ID,
+  MULTICALL3_ADDRESS,
+  UNISWAP_ROUTER_ADDRESS,
+} from "../consts";
+import { erc20MinterAddresses } from "../protocolSdk/constants";
+import { USDC_TOKEN, WETH_TOKEN } from "../tokens";
+import getERC20MintCall from "./getERC20MintCall";
+import getSwapCall from "./getSwapCall";
+import getUsdcApprovalCall from "./getUsdcApprovalCall";
+import getWETHApprovalCall from "./getWETHApprovalCall";
+
+const getSwapAndMintMulticallCalls = (
+  signedAddress: Address,
+  collection: Address,
+  tokenId: bigint,
+  comment: string,
+  price: bigint,
+  amountInMaximum: bigint,
+  sqrtPriceLimitX96: bigint,
+) => {
+  const wethApprovalCall = getWETHApprovalCall(amountInMaximum);
+  const swapCall = getSwapCall(
+    WETH_TOKEN.address as Address,
+    USDC_TOKEN.address as Address,
+    MULTICALL3_ADDRESS,
+    price,
+    amountInMaximum,
+    sqrtPriceLimitX96,
+  );
+  const usdcApproveCall = getUsdcApprovalCall(erc20MinterAddresses[CHAIN_ID]);
+  const erc20MintCall = getERC20MintCall(
+    signedAddress as Address,
+    tokenId,
+    collection,
+    price,
+    USDC_TOKEN.address as Address,
+    signedAddress as Address,
+    comment,
+  );
+
+  const calls = [
+    {
+      target: WETH_TOKEN.address as Address,
+      value: BigInt(0),
+      callData: wethApprovalCall,
+      allowFailure: false,
+    },
+    {
+      target: UNISWAP_ROUTER_ADDRESS,
+      value: amountInMaximum,
+      callData: swapCall,
+      allowFailure: false,
+    },
path: lib/calls/getSwapAndMintMuticallCalls.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Uniswap V3 router call should be value‑less (WETH is an ERC‑20)**  

You approve WETH first, so the router will pull tokens from the caller.  
Sending `amountInMaximum` as `value` turns the call into an ETH swap and will
revert.

```diff
-    {
-      target: UNISWAP_ROUTER_ADDRESS,
-      value: amountInMaximum,
+    {
+      target: UNISWAP_ROUTER_ADDRESS,
+      value: 0n,
```

Remember to adjust the outer Multicall3 transaction `value` accordingly.  



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    {
      target: UNISWAP_ROUTER_ADDRESS,
      value: 0n,
      callData: swapCall,
      allowFailure: false,
    },
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-22T19:07:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,97 @@
+"use client";
+
+import useBalance from "@/hooks/useBalance";
+import { Address, parseEther, parseUnits } from "viem";
+import { CHAIN, CHAIN_ID, MULTICALL3_ADDRESS } from "@/lib/consts";
+import { getPublicClient } from "@/lib/viem/publicClient";
+import useSignedAddress from "@/hooks/useSignedAddress";
+import useSignTransaction from "@/hooks/useSignTransaction";
+import { useState } from "react";
+import { USDC_TOKEN } from "@/lib/tokens";
+import { multicall3ABI } from "@/lib/abis/multicall3ABI";
+import { toast } from "sonner";
+import getSwapAndMintMulticallCalls from "@/lib/calls/getSwapAndMintMuticallCalls";
+import getPoolInfo from "@/lib/uniswap/getPoolInfo";
+
+const Swap = () => {
+  const balances = useBalance();
+  const signedAddress = useSignedAddress();
+  const { signTransaction } = useSignTransaction();
+  const amount = "1";
+  const [loading, setLoading] = useState(false);
+  const [comment, setComment] = useState("");
+  const [transactionLink, setTransactionLink] = useState("");
+
+  const onPurchase = async () => {
+    try {
+      setLoading(true);
+      const collectionAddress = "0x1590252D59F9DB3af56860b864CB49C97eF35ba9";
+      const usdcPrice = parseUnits(amount, USDC_TOKEN.decimals);
+      const { amountInMaximum, liquidity, gasEstimate } = await getPoolInfo(
+        signedAddress as Address,
+        usdcPrice,
+      );
+      const ethBalance = parseEther(balances.ethBalance.toString());
+      if (ethBalance < amountInMaximum) {
+        toast.error("insufficient amount");
+        setLoading(false);
+        return;
+      }
+      const calls = getSwapAndMintMulticallCalls(
+        signedAddress as Address,
+        collectionAddress,
+        BigInt(1),
+        comment,
+        usdcPrice,
+        amountInMaximum,
+        liquidity,
+      );
+      const hash = await signTransaction({
+        address: MULTICALL3_ADDRESS,
+        abi: multicall3ABI as any,
+        functionName: "aggregate3Value",
+        args: [calls],
+        account: signedAddress as Address,
+        value: amountInMaximum,
+        chain: CHAIN,
+        maxFeePerGas: gasEstimate,
+        maxPriorityFeePerGas: gasEstimate,
+      });
path: app/swap/purchase/page.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`gasEstimate` is a gas *units* figure, not a gas *price***  

`quoteExactOutputSingle` returns an intrinsic gas estimate (≈ 21 000, 100 000 …).  
Feeding this into `maxFeePerGas` / `maxPriorityFeePerGas` will under‑price the
transaction (< 1 gwei) and leave it stuck in the mempool.

Retrieve the current base fee + tip from the client instead:

```diff
-        maxFeePerGas: gasEstimate,
-        maxPriorityFeePerGas: gasEstimate,
+        maxFeePerGas: await publicClient.getGasPrice(),
+        maxPriorityFeePerGas: parseGwei("1"), // or a user‑selected tip
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
      const hash = await signTransaction({
        address: MULTICALL3_ADDRESS,
        abi: multicall3ABI as any,
        functionName: "aggregate3Value",
        args: [calls],
        account: signedAddress as Address,
        value: amountInMaximum,
        chain: CHAIN,
-       maxFeePerGas: gasEstimate,
-       maxPriorityFeePerGas: gasEstimate,
+       maxFeePerGas: await publicClient.getGasPrice(),
+       maxPriorityFeePerGas: parseGwei("1"), // or a user‑selected tip
      });
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-22T19:08:00Z
author_association: CONTRIBUTOR

PR comments:
user: login: vercel[bot]
created_at: 2025-04-22T18:19:13Z
body: [vc]: #U6U80KM/Y4NPFC+KSA4Y/HEp6xkVr/9hdbPY2MqvAWo=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJpbi1wcm9jZXNzIiwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3N3ZWV0bWFudGVjaHMtcHJvamVjdHMvaW4tcHJvY2Vzcy9Ec3laTHhBdHlhSlNwVFdncXF0RGJ1RzNjS2Y2IiwicHJldmlld1VybCI6ImluLXByb2Nlc3MtZ2l0LXRlY2hlbmdtZS1teWMtMTYzNS0yM2JhZjAtc3dlZXRtYW50ZWNocy1wcm9qZWN0cy52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IkRFUExPWUVEIiwicm9vdERpcmVjdG9yeSI6bnVsbH1dfQ==
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Updated (UTC) |
| :--- | :----- | :------ | :------ |
| **in-process** | ✅ Ready ([Inspect](https://vercel.com/sweetmantechs-projects/in-process/DsyZLxAtyaJSpTWgqqtDbuG3cKf6)) | [Visit Preview](https://in-process-git-techengme-myc-1635-23baf0-sweetmantechs-projects.vercel.app) | Apr 22, 2025 7:13pm |


author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-04-22T18:19:20Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

A new React client component named `Swap` has been added to implement a token purchase flow on the Base chain. This component orchestrates a multi-step process involving Uniswap V3 pool data retrieval, swap quote simulation, user balance checks, and encoding multiple contract calls (WETH approval, swap, USDC approval, mint) into a single multicall3 transaction. It manages user input for a comment and UI state for loading and transaction feedback. The feature enables users to purchase a specific NFT token for a fixed price of 1 USDC, with all required on-chain actions executed atomically in one transaction.

## Changes

| File(s)                                    | Change Summary                                                                                                         |
|--------------------------------------------|------------------------------------------------------------------------------------------------------------------------|
| app/swap/purchase/page.tsx                  | Added new `Swap` React component implementing token purchase flow with Uniswap V3 and ERC20 minter integration using multicall3. |
| lib/calls/getERC20MintCall.ts                | Added `getERC20MintCall` to encode mint function call data for ERC20 minter contract with parameters including comment.  |
| lib/calls/getSwapAndMintMuticallCalls.ts    | Added `getSwapAndMintMulticallCalls` to assemble multicall3 calls for WETH approval, swap, USDC approval, and minting.  |
| lib/calls/getSwapCall.ts                      | Added `getSwapCall` to encode Uniswap V3 router `exactOutputSingle` swap call with specified parameters.                |
| lib/calls/getUsdcApprovalCall.ts              | Added `getUsdcApprovalCall` to encode ERC20 `approve` call data for unlimited USDC allowance to a spender.              |
| lib/calls/getWETHApprovalCall.ts              | Added `getWETHApprovalCall` to encode WETH `approve` call data for maximum allowance to Uniswap router.                 |
| lib/uniswap/getPoolInfo.ts                     | Added async `getPoolInfo` to fetch Uniswap V3 pool details and simulate swap quote including gas estimate and max input.|

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant SwapComponent
    participant Wallet
    participant UniswapV3Pool
    participant Quoter
    participant ERC20Minter
    participant Multicall3

    User->>SwapComponent: Click "Purchase"
    SwapComponent->>UniswapV3Pool: Fetch pool info (multicall)
    SwapComponent->>Quoter: Simulate exact output swap quote
    SwapComponent->>SwapComponent: Check ETH balance
    alt Insufficient ETH
        SwapComponent->>User: Show error toast
    else Sufficient ETH
        SwapComponent->>SwapComponent: Encode calls (WETH approve, swap, USDC approve, mint)
        SwapComponent->>Wallet: Sign and send multicall3 transaction
        Multicall3->>UniswapV3Pool: Execute swap
        Multicall3->>ERC20Minter: Mint token
        Multicall3->>Wallet: Confirm transaction
        SwapComponent->>User: Display transaction link and update UI
    end
```

## Possibly related PRs

- sweetmantech/IN_PROCESS#316: Extends swap functionality by integrating a token purchase flow with Uniswap V3 and ERC20 minter, building upon multicall batching logic.

## Poem

> 🐇 Hop, hop, the tokens flow,  
> One USDC, the price to show.  
> Approvals set, swaps in line,  
> Minting magic, all combined.  
> A multicall dance on chain so bright,  
> The rabbit’s code leaps into the light! ✨💸🐰

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- DwQgtGAEAqAWCWBnSTIEMB26CuAXA9mAOYCmGJATmriQCaQDG+Ats2bgFyQAOFk+AIwBWJBrngA3EsgEBPRvlqU0AgfFwA6NPEgQAfACgjoCEejqANiS4AzEtS7ZEtBj2wVYDNIhI8K+AlxZbhIDADlsZgFKLgBmAEYADgMAVR8KLhoGWDIiWIAmfKN9Y3AoMnp8GxwCYjJlGnomVnYuXn5hUXEpGXkmJSpVdS0dEpMoOFRUTBrCUnIqRoUWjE5IKgB3SERI5jQKeTkFAZU1TW1dMENS0wM0bm4AekQN+8fud2zvEne0Ug1cIgAB4cAwAIghBgAxFDIABBACSdQW1Do212+3kVUYsEwpEQRjhkHIWwASvYxIwLPB2MtuPhyKtiWg2PQAAYAZVe3DZKGQ8FW/lo2AYaIIkBsaAY8GpuFR6EgBAA1mQ3BQvj4JRZ8FsGYqcpAAELfHHaLAACi+AsgCIAIpBEgAWACssQAlCgMBJ8BYJAKiAqbPAgWj4pAUhzbQBhPzwUUS/B8NDbELSoOuMIAMWgCgsVjE8D1mHoytVdo0NtwkCc0kgsHw+CVyHFUtFiGQNb4AjQFkwbYANNt4ERyPRXnmSFW0LRaBRpIhB8XFVQMIgpeI9Yhhxh/RLsBgCwye+pZBW4L5mvTGVW9gK5QLkNqvBZtnKaAm+Nrp7vEG+nIvq3SZY2CZAUPlwRcMHoZNcBXNdDywakMCVDQzANJg50gbUiDjT19V8NkGQABU+XEfF5bxZAPPcDw3LAQgoGxE2YZsDSYvMdR/GhuEQUEDCgDlJ0ffBvwwANf3lJclCDchWIvH18zo9AZzndt0CgtUNV8Fl8H3TR+MgIj9h8OTNLI7TmF00DVnwcNIxjUssH3dREFQqAox7CxkDZUhcCIhsLARDAmN5DZ1FgfChxHNFp1ned1PoCNo1jeNxTnWCaSkSK9iBeBmEiT1wPQSy9P4aoAFFoAACXWEgAEdsHgOdaEHekfSw+AGvgWgT0g6DICIbxIGkcQ9hoNzICjHIGCbFBqlwA1OwAcmQSqau7XsD18VAdhsdMaSZcUmCkPgFt8HK8oKsC8GKqzcAAbjm4kAkHdRtnrDZkBmSh/FOkTfwS9ABETQEJqjBlfwoEVAUgfKLHEZ8LFiRhPOQJi+Bee5uF3JdmDvXcjgFb0lV3HzJy5e44SggBZO9qeweG408jy80QULwtqqwJEwKtuH2FlJ0ofkDwsbAerEyKt2i6CVPnQcmAnBDlLi9tB0cm1bXllgQIguzkt4OMSEHC78uYQqbp0vS+o6rqeqCCaOW3L6NJ8KCvthhmEc85HYMweClPFM7IGplIABloARKM4VD0PYgAfThW1bVJcqOQ5BRBXXSAwoWyK/iIOdBqWRGFxTUR4BsWRd0GtGSF8PmqDYGgKFcgyAHVtBh9HIt91d10LLA51FeBuCnDTsG4WhUVM3v/YHjrkNfVEJqq4srGQH7E2d+hVKErCRPF8S320hgMMPixTzQ3wUgRWqoKFpeEc9dGxvnlRdKrIOrQwLXFaU2LVKl3Vnaa2BtRSLm1BLHOEUZjXSrEGEgFh6Dd2TM0HWgNkwfHVOZSAAg8AECwAtagy5hykBbpFLBWktQ6grAiaoME4L9z1EhJUfJ0DcxlCoKwr0qyoB6ogbgvZZB0FQgYSYyBLwMlpHeEgBdp4e0ZoI3wDIwDf09M3Jhq5PQMFFofHgAVn62XShQTKPY1aNlVBtPsF5ppNmNp7JmeYfaMKVkwVcsFoYD0HFLHcYlrYkBDAwPAXj0B5n4Ngkaiw0TWmTFuMSVh0AEDxq4Wemj+CEINMaTU39RFGBhPCeGDQB7NlskHJQOj+Z0WQNiAJ9IKBLETG4AQ1JXDsHUDSAkBkwgkDJBSKskjrzDSBHUpYQ1pJoE9lwTk3JeTWjZFjZ43J3ikW+L8f4gIgRsnBJCfitxqQCEeCXR4vlyqkijPkAADLTVYLMLAAl4tssE0JYSImRA0NEOxWCYjKqaMS0hCTEh6dWcQsp5A2H3ErMmuBTnnKuXeW5szhawUUCKMUuJ+kQw8WIZ2w0DyKDROC2i89EYflhvjCWMwYWXMVBYpycSAxBzZHjVYvIm71mQf4M2jLKAMEudc5ucJDQIjZGeNiEL/6nxIKPZADcBbN2FjosWu4g5DxHgdKcss1LmiZXeaA+A2RunMSqLAdpIDascgiWgBrf6KTfpq0uYCjaQCpRcmlxrlaAMHHOOwFAqAvgAXLDBwFaSQ39LQqsZB+i1jOpqfYRBIjsCRbZVBnlIBTzlDwNAsgvz0CcMqg0bJI34szOKgetpqBoFZZOdlEpOWRTZH6EgzBeT7KoAcbxqYK5VwpRKYMaJmVVgajzE8Pz4iit8EWpQTRU3puTKgM6g9JzuFHKK1AhKlaSmlLKeRvApX7HzedO8y4/ZpJJbO0l4L6k5D4DWPCbjfaUhkVQBCrcjDPIKRoqpNLIrlN7IsYpPzakgzRI0j4zTcJtPEP8gyJaiVFhnHQKZJyzl8vhZ5c0A69VcDhPao1ZBLVcDUDhVYNqugDxwyrXi8JcMpWsLg4cd5Bw8suRRwB2GaPep+j2djlGtYrDWKGsSHoAC8ehXwmLErMrAbJ9mHNRscycLr+W3PuVsiETzdlgCMLJo5vkKbcCprQfl9MvZ5lua5QEoJ1PvtefMd59BPl7AOD8r4fzOlEhJDRSFenuSGeMw4xG5nEXqKFKiks6KM6Q2hji/YVAsTVDhqZl8JLBAiGxaS2J9VsCRuUdUZp+AZpqPwAxagAGiY+j9BLdWWN/Dcy8t47kpdcbkqIOGxUaAVQyv5k3B+ApFV6M7B6wNCtbXwd426ssmsFRoJDRlPxtHjZoFyqbc2U4SokaDY6jqeNebdcFhQcdXmlJPowJ5eQdmolo10nwM9FbM3ZoPr0T0xNdw5AsAxI7xSuBSIyw8Wru425rQm1gARFR/SDh+93TGDwAdA/FElGMfpkzJhSDuaH6wP6UAh+QX7vB8CVYDAj4HZcoIE3kMmAdlAM4Pt1kuSHjSB04ywC64n0Cy5phpPQWVPWW7juyajdAmEF3oHbE25p0SbLqXCQMGKvqs0udTalropcKQRVB2mbtDKDRygoL5anz6NXjaDnV7LPy4e2Vdi1SKk60S3blNbZMNhewBn4Z5TiEtJQyncCQQ7sX5eoHSsugljTb0xIUUl6nATNAwDFXBrAqAgP1JiqZcZnshkjJ+UHSyworC5Js4U/9ENv1lNEH+0rReanDOA5UPgYGWm4vEFBzpUBYNK1iohyAUL9N+bpgF5mqNzQ+LoKx+cPHPW5lGxgMfgaLW0EIwxjbM3VhcEE0QVqJjRTz+I7T9buAgrUyW5dZgW/GPbDqvUoiG+SChzyuoAAGgATgAGwn9WCJsTcI5eyCk53nT8mfOUw0y95JbmaqZmqp7wzp4gxuiPLFB7LwAHK6bkzcgqaWawH5K2b1BRIOYYjObYiub4gAqebrpKRd4oGeTBZ3ihaijhbEJuJRbpbfR4pTqfZ6gkrdxByo5IDciQAABqyM/geAlAK0neAS64AA8ngOBI7PEiQLyOjmyooG1nKJ1iLqtj3LSkNqLldnwHAoDB/OBGrAaKqtjLSAGqrD+tIE1CBlIRbLvkYedIfitnoZbEyG7hsHQA7mftgPsL4P4AELRttm9NzvtodiQfPDbnJJqCEfKuosmijGEuenzA9tOIBAeqIcwSQK3nROWnKLyMEqCtnBzJwWjjwfwRjkIXwIKgiGEXXIqDSLocgLiBQC4PiiWKUgaNqFsHYBZHdLWiwJFFwejpGAANKHZKDThIS+AcEGjo47STjfrUrMpCFox1pfzuBziHR5Q+4x4TqZHTqJF3YB5LoUCjikqh5YAMInoIR54YEF7l5aKBwGi/qVIAaV4jIga17YDgatKrDtLQYt6lpjYd5kH3C3LmgWpT7UZG60qSFrAj4WEmHqrT4WGuG4Bwmv4753T75OGRCYneLn5+RX4347aP4v70bb7v7ib+g/4yYIFyaswKa4D6aoFsywGabab0lIG4BpAuBwh/b449islWaQgYFIgXaog4FfJ4HVAEHQYeZAoFEjrhF6hQq8kMD8l451YIpsJUEoo0H6jEISk0Abz7EJFIJ3YoLM5nJgDUrzIClSC8gqlYCIzKEda1ixL+gJIxFU5sig5QT7C8jYhBAhCd4IlsyAyB6nGmQ270DnotgOm+BZ64lmz0jtgIEJLVYcSvBbRmpMpLYpB3j5DOhP4GqkpBzq4Vyc4k4DBaGuS7GsF0rRoFo27ZFloVpVoLSKBFG5wVlygBktHOo2nUrVF5nMYXJwhqBlnig271o1b45yHmmrqmlRoHEvjnpeBYDRCARigW7fE7YKipJKxEJVhyKrAdgYDUg7ZohE7qz+l6ImJECwAwxPG+A4RSAg4hD3xJj2phFAnx4bxV5J7QQp4kCShp6J5VjBkGjZ4Mw+5vr56foAavlpql6vEV7VCQWfFNL16QYdLFCQBtnAlz6d6+Tqmam1ZCnob3n7AomIBugr5zZEC0l/6MlkXOAakOlUV5iqbskQBaYGCsVeRMmA7VQUWCkWDCnoEvLilYGSnojSnxa/KEEGAKlbCwUJJQqiVVTiXameRgFTCCj6lorEKQXuz0oJLOmkWTjaW6XcUWAiox5rr/ntaqGemyGZqNz7ad5ok4nLaRBBnzTBAEREZ3gUQaRRlaKxmNnmlpqWmNKMrzmOmwzVrdnQW+DaX67ri+5cUvioBnlLAoVDE8GCHNx1k9kRQVmdoIL0AmwFRol/lx47mmSFqZFEUYC5GVrAoyjKlrEFqNrNodQCBtqyBepgWUCRrpGZX3oG7whCooDMAfEcoDHJhPg9hzW3xBi54NnRV24ppbm+A1jtHogCAHkzBJXrVHlKQqI5IIV3FIVF4oUvGF5aLvHV7hI4UQZ/FN4EXtXKwgm+S2W5Xgm+UYAH7+XH4Ul3gMXUmSZ4R0mIH/42VrR2WSX6WAhqY7L8WckHLOTQ5Mn+Q+hBRMT3IilPJilvLYEKVOZKVynuaApbCUQHiwD+AYC6RowuVQqE2BTBT6q6lGXCgGnih2C4DZCAyXhCGoX3hWD0ACgvwPFAwfwKjFX3B8HIxtQvgoIDSSCqith9FLjo4NWVi9otz9I9hBK9gmkUIGLmEdj0rhilGq3lGjHdWygdIVV+B0BgUCi7nGrIDmiZVLgI6GoSh1FQYUDWxqLljG3C41jux164T5aFa4jWg6LqrfoMQvwKiJaOIvh6hBwa1ZWUhpSTgmIkBZQ51KL6LtQ+nkJbiJaYAkDs0XyZC0oXJ4YYDxCDg9FqxxhKgch8zSjzbUi2y9SAyIDai4AXIVhwg2BlVR4G67hzhcw8yQAm61gQm0pBQd1wnd11yDgj2NR2yyAeg2B9XnR95hKqSeylxvT10MzyKoIMg06xVFWO3cCQAACK2AAQVOM1WcM5v4eU8oQcYhlIBhN06ODUv95Zsx1V1Zhtu+h299lt88kVyAldCS198MpcZ9AxGwCAYtQcNcw0QDY02kGkyZEN6haJtUXUzUguvgAwOttAFYmYAoZ2DhMV6DUuSulI96Zo6RhdjkuD+9NsR9Y9VDR+NDu+2iui1c3g1sxDQ0I0wD40DZVlCeQFoyoF4FkBkFmeMFKKued1Ly9xX6T1aFL11SmF2j2FCdvxje+FMGLl7eJF8yiAVErgVlvk3NxN+A5oetekdFDWlMu+mJMNl+LASAJAwAAA3pCSE8DhidCePj0VwBgJENEBHeI91CePibdHpH5UfgUzXOVGQ6iJiQAL56AsX0l41LK+MBT+NgHmgQERraMwHWa7LXDjC4qVD0J4BzByXFzaytDrBoBbCObfJHBRqDBnAjCXCGB9NoLqBxzdSIBxxziNoeG0BxwST1JLM3DlAADsSQAgNgT+tAzoaAFyD+9gT+NgFyXg+QUoaAJA8QNz8QU8zoAgD+D+AgDAD+JZyMYwZQkAzoJzFyzo8QjoDALgFy3zFzT+zo1ztAFyiQsQ3YFyAgLoDAJZe0JAJze0iQAg+QRzBgKz2sazGzWz5dNIuzccFQSzQAA= -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=sweetmantech/IN_PROCESS&utm_content=318):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR