Skip to content

Commit

Permalink
Merge pull request #15 from balancer/v3-pool-creation
Browse files Browse the repository at this point in the history
Pool creation for v3
  • Loading branch information
MattPereira authored Dec 9, 2024
2 parents 3cb52eb + 9d6992d commit bf49b00
Show file tree
Hide file tree
Showing 67 changed files with 3,631 additions and 366 deletions.
79 changes: 79 additions & 0 deletions .cursorrules
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Key Principles and Best Practices

## Expertise Areas

- Solidity, TypeScript, Node.js, Next.js 14 App Router, React
- Viem v2, Wagmi v2, Shadcn UI, Radix UI, Tailwind Aria

## General Principles

- Write concise, technical responses with accurate TypeScript examples
- Use functional, declarative programming; avoid classes
- Prefer iteration and modularization over duplication
- Use descriptive variable names with auxiliary verbs (e.g., isLoading)
- Use lowercase with dashes for directories (e.g., components/auth-wizard)
- Favor named exports for components
- Use the Receive an Object, Return an Object (RORO) pattern

## JavaScript/TypeScript

- Use "function" keyword for pure functions; omit semicolons
- Use TypeScript for all code; prefer interfaces over types; avoid enums, use maps
- File structure: Exported component, subcomponents, helpers, static content, types
- Avoid unnecessary curly braces in conditional statements
- For single-line statements in conditionals, omit curly braces
- Use concise, one-line syntax for simple conditional statements (e.g., if (condition) doSomething())

## Error Handling and Validation

- Prioritize error handling and edge cases
- Handle errors and edge cases at the beginning of functions
- Use early returns for error conditions to avoid deeply nested if statements
- Place the happy path last in the function for improved readability
- Avoid unnecessary else statements; use if-return pattern instead
- Use guard clauses to handle preconditions and invalid states early
- Implement proper error logging and user-friendly error messages
- Consider using custom error types or error factories for consistent error handling

## React/Next.js

- Use functional components and TypeScript interfaces
- Use declarative JSX
- Use function, not const, for components
- Use Shadcn UI, Radix, and Tailwind Aria for components and styling
- Implement responsive design with Tailwind CSS
- Use mobile-first approach for responsive design
- Place static content and interfaces at file end
- Use content variables for static content outside render functions
- Minimize 'use client', 'useEffect', and 'setState'; favor RSC
- Use Zod for form validation
- Wrap client components in Suspense with fallback
- Use dynamic loading for non-critical components
- Optimize images: WebP format, size data, lazy loading
- Model expected errors as return values
- Use error boundaries for unexpected errors
- Use useActionState with react-hook-form for form validation
- Code in services/ dir always throw user-friendly errors that tanStackQuery can catch and show to the user

## Server Actions

- Use next-safe-action for all server actions
- Implement type-safe server actions with proper validation
- Utilize the action function from next-safe-action for creating actions
- Define input schemas using Zod for robust type checking and validation
- Handle errors gracefully and return appropriate responses
- Use import type { ActionResponse } from '@/types/actions'
- Ensure all server actions return the ActionResponse type
- Implement consistent error handling and success responses using ActionResponse

## Key Conventions

1. The import syntax for this project is `import {something} from "~~/path"
1. Rely on Next.js App Router for state changes
1. Prioritize Web Vitals (LCP, CLS, FID)
1. Minimize 'use client' usage:
- Prefer server components and Next.js SSR features
- Use 'use client' only for Web API access in small components
- Avoid using 'use client' for data fetching or state management

Refer to Next.js documentation for Data Fetching, Rendering, and Routing best practices: https://nextjs.org/docs
2 changes: 1 addition & 1 deletion packages/nextjs/app/cow/_components/PoolConfiguration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ export const PoolConfiguration = () => {
<label className="label cursor-pointer flex gap-4 m-0 p-0">
<input
type="checkbox"
className="checkbox rounded-lg"
className="checkbox rounded-lg border-neutral-700"
onChange={() => {
setAgreedToWarning(!hasAgreedToWarning);
}}
Expand Down
50 changes: 33 additions & 17 deletions packages/nextjs/app/cow/_components/PoolCreation.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { useEffect, useState } from "react";
import { useEffect } from "react";
import { useSearchParams } from "next/navigation";
import { PoolCreated, PoolResetModal, StepsDisplay } from "./";
import { PoolCreated } from "./";
import { parseUnits } from "viem";
import { useSwitchChain } from "wagmi";
import { Alert, TextField, TokenField, TransactionButton } from "~~/components/common/";
import {
Alert,
ContactSupportModal,
PoolStateResetModal,
PoolStepsDisplay,
TextField,
TokenField,
TransactionButton,
} from "~~/components/common/";
import { CHAIN_NAMES } from "~~/hooks/balancer/";
import {
type PoolCreationState,
Expand Down Expand Up @@ -42,8 +50,6 @@ export const PoolCreation = ({ poolCreation, updatePoolCreation, clearPoolCreati
const token2RawAmount = parseUnits(poolCreation.token2Amount, poolCreation.token2.decimals);
const { token1Weight, token2Weight } = getPerTokenWeights(poolCreation.tokenWeights);

const [isResetModalOpen, setIsResetModalOpen] = useState(false);

const { targetNetwork } = useTargetNetwork();
const { switchChain } = useSwitchChain();
const isWrongNetwork = targetNetwork.id !== poolCreation.chainId;
Expand Down Expand Up @@ -112,9 +118,9 @@ export const PoolCreation = ({ poolCreation, updatePoolCreation, clearPoolCreati
<div className="flex flex-wrap justify-center gap-5 lg:relative">
<div className="bg-base-200 p-6 rounded-xl w-full flex flex-grow shadow-xl md:w-[555px]">
<div className="flex flex-col items-center gap-5 w-full">
<h5 className="text-xl md:text-2xl font-bold text-center">Preview your poolCreation</h5>
<h5 className="text-xl md:text-2xl font-bold text-center">Preview your pool</h5>
<div className="w-full">
<div className="ml-1 mb-1">Selected poolCreation tokens:</div>
<div className="ml-1 mb-1">Selected pool tokens:</div>
<div className="w-full flex flex-col gap-3">
<TokenField
value={poolCreation.token1Amount}
Expand Down Expand Up @@ -294,7 +300,18 @@ export const PoolCreation = ({ poolCreation, updatePoolCreation, clearPoolCreati
</div>
</div>
<div className="flex lg:absolute lg:top-0 lg:-right-[225px]">
<StepsDisplay state={poolCreation} />
<PoolStepsDisplay
currentStepNumber={poolCreation.step}
steps={[
{ label: "Create Pool" },
{ label: `Approve ${poolCreation.token1.symbol}` },
{ label: `Approve ${poolCreation.token2.symbol}` },
{ label: `Add ${poolCreation.token1.symbol}` },
{ label: `Add ${poolCreation.token2.symbol}` },
{ label: "Set Swap Fee" },
{ label: "Finalize Pool" },
]}
/>
</div>
</div>

Expand Down Expand Up @@ -328,17 +345,16 @@ export const PoolCreation = ({ poolCreation, updatePoolCreation, clearPoolCreati
)}

{poolCreation.step < 8 && (
<div className=" link flex items-center gap-2" onClick={() => setIsResetModalOpen(true)}>
Start Over
<div className="flex justify-center mt-3 gap-2 items-center">
<ContactSupportModal />
<div className="text-xl">·</div>
<PoolStateResetModal
clearState={() => {
clearPoolCreation();
}}
/>
</div>
)}
{isResetModalOpen && (
<PoolResetModal
setIsModalOpen={setIsResetModalOpen}
etherscanURL={etherscanURL}
clearState={() => clearPoolCreation()}
/>
)}
</>
);
};
63 changes: 0 additions & 63 deletions packages/nextjs/app/cow/_components/PoolResetModal.tsx

This file was deleted.

30 changes: 0 additions & 30 deletions packages/nextjs/app/cow/_components/StepsDisplay.tsx

This file was deleted.

2 changes: 0 additions & 2 deletions packages/nextjs/app/cow/_components/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
export * from "./StepsDisplay";
export * from "./PoolCreation";
export * from "./PoolResetModal";
export * from "./PoolCreated";
export * from "./PoolConfiguration";
28 changes: 17 additions & 11 deletions packages/nextjs/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,45 @@
import Link from "next/link";
import type { NextPage } from "next";
import { BalancerLogo } from "~~/components/assets/BalancerLogo";
import { CowLogo } from "~~/components/assets/CowLogo";

const PAGES = [
{
emoji: <CowLogo />,
emoji: <CowLogo className="h-28" />,
title: "CoW AMMs",
href: "/cow",
description: "Deploy a CoW AMM pool",
},
{
emoji: <BalancerLogo className="h-28" />,
title: "Balancer v3",
href: "/v3",
description: "Deploy a CoW AMM pool",
},
];

const Home: NextPage = () => {
return (
<div className="flex-grow bg-base-300">
<div className="max-w-screen-2xl mx-auto">
<div className="max-w-screen-xl mx-auto">
<div className="flex items-center flex-col flex-grow py-10 px-5 lg:px-10">
<h1 className="text-5xl md:text-6xl font-bold my-5">Pool Creator</h1>
<p className="text-xl md:text-2xl mb-14 text-center">
<h1 className="text-5xl md:text-6xl font-bold mb-10">Pool Creator</h1>
<div className="text-xl md:text-2xl mb-14 text-center">
Create and initialize a variety of liquidity pool types with Balancer protocol
</p>
</div>

<div className="flex justify-center">
<div className="grid grid-cols-1 md:grid-cols-2 justify-center gap-10 w-full">
{PAGES.map(item => (
<Link
className="shadow-inner relative bg-base-100 hover:bg-base-200 text-2xl text-center p-8 rounded-3xl"
className="shadow-lg relative bg-base-100 hover:bg-base-200 text-2xl text-center p-8 rounded-3xl"
key={item.href}
href={item.href}
passHref
>
<h3 className="text-3xl md:text-4xl font-bold mb-5">{item.title}</h3>
<div className="flex justify-center my-7">
<div className="w-1/2">{item.emoji}</div>
<div className="flex justify-center mb-5">
<div>{item.emoji}</div>
</div>
<p className="text-xl mb-0">{item.description}</p>
<h3 className="text-3xl md:text-4xl font-bold mb-0">{item.title}</h3>
</Link>
))}
</div>
Expand Down
57 changes: 57 additions & 0 deletions packages/nextjs/app/v3/_components/ApproveOnTokenManager.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useEffect } from "react";
import { PERMIT2 } from "@balancer/sdk";
import { parseUnits } from "viem";
import { Address } from "viem";
import { Alert, TransactionButton } from "~~/components/common";
import { useTargetNetwork } from "~~/hooks/scaffold-eth";
import { useApproveToken, useReadToken } from "~~/hooks/token";
import { usePoolCreationStore } from "~~/hooks/v3";

type MinimalToken = { address: Address; amount: string; decimals: number; symbol: string };

export const ApproveOnTokenManager = ({ token }: { token: MinimalToken }) => {
const { targetNetwork } = useTargetNetwork();
const { step, updatePool } = usePoolCreationStore();
const { mutateAsync: approveOnToken, isPending: isApprovePending, error: approveError } = useApproveToken();

const rawAmount = parseUnits(token.amount, token.decimals);
const spender = PERMIT2[targetNetwork.id];
const { allowance, refetchAllowance } = useReadToken(token.address, spender);

const handleApprove = async () => {
await approveOnToken(
{ token: token.address, spender, rawAmount },
{
onSuccess: async () => {
const { data: allowance } = await refetchAllowance();
if (allowance && allowance >= rawAmount) {
updatePool({ step: step + 1 });
}
},
},
);
};

// auto move to next step if allowance is already enough
useEffect(() => {
if (allowance && allowance >= rawAmount) {
updatePool({ step: step + 1 });
}
}, [allowance, rawAmount, updatePool, step]);

return (
<div>
<TransactionButton
title={`Approve ${token.symbol}`}
isDisabled={isApprovePending}
isPending={isApprovePending}
onClick={handleApprove}
/>
{approveError && (
<div className="max-w-[500px] mt-4">
<Alert type="error">{approveError.message}</Alert>
</div>
)}
</div>
);
};
Loading

0 comments on commit bf49b00

Please sign in to comment.