Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pool creation for v3 #15

Draft
wants to merge 41 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
6cbab77
sketch out structure for v3 pool creation
MattPereira Oct 4, 2024
38f4cc5
Select pool typeUI
MattPereira Oct 4, 2024
d513eae
outline token selection ui
MattPereira Oct 5, 2024
cf79cca
progress towards choosing pool tokens
MattPereira Oct 7, 2024
8fe4a94
outline component structure for choosing pool parameters and info
MattPereira Oct 8, 2024
aab3fd3
sketch out pool configuration flow
MattPereira Oct 8, 2024
d2ad2bb
Merge branch 'main' into v3-pool-creation
MattPereira Oct 9, 2024
f3ef0f9
Merge branch 'main' into v3-pool-creation
MattPereira Oct 9, 2024
ab1a25c
refactor with zustand ftw
MattPereira Oct 10, 2024
42ba3c0
basic skeleton for all pool creation inputs
MattPereira Oct 10, 2024
983e6bc
begin adapting for both stable and weighted pool creation
MattPereira Oct 10, 2024
8cc4a09
can create stable pools!!!
MattPereira Oct 11, 2024
e0d4d8d
outline flow for v3 pool creation modal
MattPereira Oct 12, 2024
c1fd404
add steps for permit2 and pool initialization
MattPereira Oct 12, 2024
7a6679b
successfully created stable pool
MattPereira Oct 13, 2024
f4ebd25
address initial feedback items
MattPereira Oct 14, 2024
0a0b0df
clean up default state and input validations
MattPereira Oct 15, 2024
9407b17
refactor pool creation store with improved update pattern
MattPereira Oct 16, 2024
afc7e09
refactor pool creation process
MattPereira Oct 17, 2024
33b66a4
improve pool creation steps progression
MattPereira Oct 17, 2024
332a59b
improve input validations
MattPereira Oct 21, 2024
f173b4e
improve state update reliability
MattPereira Oct 21, 2024
4d8862d
add reset feature and prevent modal from closing during pool creation…
MattPereira Oct 21, 2024
19c2a23
rough draft for UX to choose boosted variant
MattPereira Oct 23, 2024
dc8d67e
Merge branch 'main' into v3-pool-creation
MattPereira Oct 23, 2024
790a721
fix boost toggle UX
MattPereira Oct 24, 2024
c12502a
refactor pool creation steps structure ftw
MattPereira Oct 24, 2024
534b135
Add extra steps for boosted pool creation
MattPereira Oct 26, 2024
ef6274f
first draft batch swap hook
MattPereira Oct 26, 2024
c79d9e6
begin pivot to multi swap w/o SDK
MattPereira Oct 28, 2024
609c65d
outline multi swap hook
MattPereira Oct 28, 2024
d77e252
functional mvp for creating boosted pools!!!
MattPereira Oct 29, 2024
f270672
Merge branch 'main' into v3-pool-creation
MattPereira Oct 30, 2024
f71227b
refactor pool initialization setp to include permit sigs
MattPereira Oct 30, 2024
2bac8a3
refactor pool creation manager component
MattPereira Oct 30, 2024
bf344e2
polish before second review
MattPereira Oct 30, 2024
2a2b878
store pool state in local storage
MattPereira Nov 1, 2024
2b04620
refactor organization of common components
MattPereira Nov 4, 2024
c97cc2d
polish styles and weight calculations
MattPereira Nov 4, 2024
5ac874d
Add maximum pool name length
MattPereira Nov 6, 2024
32f5f63
fetch boost whitelist from api
MattPereira Nov 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -195,7 +195,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
26 changes: 22 additions & 4 deletions packages/nextjs/app/cow/_components/PoolCreation.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { useEffect, useState } from "react";
import { PoolCreated, PoolResetModal, StepsDisplay } from "./";
import { PoolCreated } from "./";
import { Address, parseUnits } from "viem";
import { useSwitchChain } from "wagmi";
import { Alert, TextField, TokenField, TransactionButton } from "~~/components/common/";
import {
Alert,
PoolStateResetModal,
PoolStepsDisplay,
TextField,
TokenField,
TransactionButton,
} from "~~/components/common/";
import { CHAIN_NAMES } from "~~/hooks/balancer/";
import {
type PoolCreationState,
Expand Down Expand Up @@ -259,7 +266,18 @@ export const PoolCreation = ({ state, clearState }: ManagePoolCreationProps) =>
</div>
</div>
<div className="flex lg:absolute lg:top-0 lg:-right-[225px]">
<StepsDisplay state={state} />
<PoolStepsDisplay
currentStepNumber={state.step}
steps={[
{ label: "Create Pool" },
{ label: `Approve ${state.token1.symbol}` },
{ label: `Approve ${state.token2.symbol}` },
{ label: `Add ${state.token1.symbol}` },
{ label: `Add ${state.token2.symbol}` },
{ label: "Set Swap Fee" },
{ label: "Finalize Pool" },
]}
/>
</div>
</div>

Expand Down Expand Up @@ -298,7 +316,7 @@ export const PoolCreation = ({ state, clearState }: ManagePoolCreationProps) =>
</div>
)}
{isResetModalOpen && (
<PoolResetModal
<PoolStateResetModal
setIsModalOpen={setIsResetModalOpen}
etherscanURL={pool && !pool.isFinalized ? etherscanURL : undefined}
clearState={() => clearState()}
Expand Down
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";
26 changes: 16 additions & 10 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"
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>
);
};
62 changes: 62 additions & 0 deletions packages/nextjs/app/v3/_components/ChooseInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React, { useEffect } from "react";
import { PoolType } from "@balancer/sdk";
import { TextField } from "~~/components/common";
import { useFetchBoostableMap, usePoolCreationStore } from "~~/hooks/v3";
import { MAX_POOL_NAME_LENGTH } from "~~/utils/constants";

/**
* @dev Gauge creation reverts if the name is longer than 32 characters
* https://github.com/balancer/pool-creator/issues/17#issuecomment-2430158673
*/
export const ChooseInfo = () => {
const { name, symbol, tokenConfigs, poolType, updatePool } = usePoolCreationStore();

const { data: boostableWhitelist } = useFetchBoostableMap();

useEffect(() => {
if (poolType) {
const symbol = tokenConfigs
.map(token => {
const { useBoostedVariant, tokenInfo, weight } = token;
const boostedVariant = boostableWhitelist?.[token.address];
const symbol = useBoostedVariant ? boostedVariant?.symbol : tokenInfo?.symbol;
if (poolType === PoolType.Weighted && weight) {
return `${token.weight.toFixed(0)}${symbol}`;
}
return symbol;
})
.join("-");

updatePool({
name: symbol.split("-").join(" "),
symbol,
});
}
}, [tokenConfigs, poolType, updatePool, boostableWhitelist]);

return (
<div>
<div className="text-xl mb-5">Choose pool information:</div>
<div className="mb-5 flex flex-col gap-4">
<div className="bg-base-100 p-5 rounded-xl">
<TextField
label="Pool name"
placeholder="Enter pool name"
value={name}
maxLength={MAX_POOL_NAME_LENGTH}
onChange={e => updatePool({ name: e.target.value })}
/>
</div>

<div className="bg-base-100 p-5 rounded-xl">
<TextField
label="Pool symbol"
placeholder="Enter pool symbol"
value={symbol}
onChange={e => updatePool({ symbol: e.target.value })}
/>
</div>
</div>
</div>
);
};
Loading
Loading