Skip to content

Commit

Permalink
Merge pull request #6 from balancer/persist-pool-creation
Browse files Browse the repository at this point in the history
Persist pool creation
  • Loading branch information
danielmkm authored Aug 9, 2024
2 parents 5719234 + 0e39544 commit 2f4466d
Show file tree
Hide file tree
Showing 13 changed files with 136 additions and 93 deletions.
1 change: 1 addition & 0 deletions packages/nextjs/app/cow/_components/PoolConfiguration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ export const PoolConfiguration = () => {
token2Amount,
poolName,
poolSymbol,
step: 1,
});
}}
/>
Expand Down
114 changes: 77 additions & 37 deletions packages/nextjs/app/cow/_components/PoolCreation.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import { useState } from "react";
import { useEffect, useState } from "react";
import { StepsDisplay } from "./StepsDisplay";
import { Address, parseUnits } from "viem";
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
import { Alert, ExternalLinkButton, TextField, TokenField, TransactionButton } from "~~/components/common/";
import { useBindPool, useCreatePool, useFinalizePool, useReadPool, useSetSwapFee } from "~~/hooks/cow/";
import {
useBindPool,
useCreatePool,
useFinalizePool,
useNewPoolEvents,
useReadPool,
useSetSwapFee,
} from "~~/hooks/cow/";
import { getPoolUrl } from "~~/hooks/cow/getPoolUrl";
import { PoolCreationState } from "~~/hooks/cow/usePoolCreationState";
import { usePoolCreationPersistedState } from "~~/hooks/cow/usePoolCreationState";
import { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork";
import { useApproveToken, useReadToken } from "~~/hooks/token";
import { getBlockExplorerAddressLink } from "~~/utils/scaffold-eth";
Expand All @@ -18,9 +26,10 @@ interface ManagePoolCreationProps {
export const PoolCreation = ({ state, clearState }: ManagePoolCreationProps) => {
const token1RawAmount = parseUnits(state.token1Amount, state.token1.decimals);
const token2RawAmount = parseUnits(state.token2Amount, state.token2.decimals);
const [currentStep, setCurrentStep] = useState(1);

const [userPoolAddress, setUserPoolAddress] = useState<Address>();

useNewPoolEvents(setUserPoolAddress);
const { targetNetwork } = useTargetNetwork();
const isWrongNetwork = targetNetwork.id !== state.chainId;
const { data: pool, refetch: refetchPool } = useReadPool(userPoolAddress);
Expand All @@ -44,24 +53,27 @@ export const PoolCreation = ({ state, clearState }: ManagePoolCreationProps) =>
const { mutate: finalizePool, isPending: isFinalizePending, error: finalizeError } = useFinalizePool();
const txError = createPoolError || approveError || bindError || setSwapFeeError || finalizeError;

const setPersistedState = usePoolCreationPersistedState(state => state.setPersistedState);

const handleCreatePool = () => {
const payload = { name: state.poolName, symbol: state.poolSymbol };
createPool(payload, {
onSuccess: newPoolAddress => {
setUserPoolAddress(newPoolAddress);
setCurrentStep(2);
createPool(
{ name: state.poolName, symbol: state.poolSymbol },
{
onSuccess: newPoolAddress => {
setUserPoolAddress(newPoolAddress);
setPersistedState({ ...state, step: 2 });
},
},
});
);
};

const handleApproveTokens = async () => {
if (!pool) throw new Error("Pool address is required to approve tokens");
const txs = [];
if (token1RawAmount > allowance1) {
txs.push(
approve({
token: state.token1.address,
spender: pool.address,
spender: pool?.address,
rawAmount: token1RawAmount,
}),
);
Expand All @@ -70,53 +82,73 @@ export const PoolCreation = ({ state, clearState }: ManagePoolCreationProps) =>
txs.push(
approve({
token: state.token2.address,
spender: pool.address,
spender: pool?.address,
rawAmount: token2RawAmount,
}),
);
const results = await Promise.all(txs);
if (results.every(result => result === "success")) setCurrentStep(3);
if (results.every(result => result === "success")) setPersistedState({ ...state, step: 3 });
};

const handleBindTokens = async () => {
if (!pool) throw new Error("Required value is undefined in handleBindTokens");
const poolTokens = pool.getCurrentTokens.map(token => token.toLowerCase());
// If not already bound, bind the token
const txs = [];
if (!poolTokens.includes(state.token1.address.toLowerCase())) {
// If not already bound, bind the token
const poolTokens = pool?.currentTokens.map(token => token.toLowerCase());
if (!poolTokens?.includes(state.token1.address.toLowerCase())) {
txs.push(
bind({
pool: pool.address,
pool: pool?.address,
token: state.token1.address,
rawAmount: token1RawAmount,
}),
);
}
if (!poolTokens.includes(state.token2.address.toLowerCase())) {
if (!poolTokens?.includes(state.token2.address.toLowerCase())) {
txs.push(
bind({
pool: pool.address,
pool: pool?.address,
token: state.token2.address,
rawAmount: token2RawAmount,
}),
);
}
const results = await Promise.all(txs);
if (results.every(result => result === "success")) setCurrentStep(4);
if (results.every(result => result === "success")) setPersistedState({ ...state, step: 4 });
};

const handleSetSwapFee = async () => {
if (!pool) throw new Error("Pool is undefined in handleSetSwapFee");
setSwapFee({ pool: pool.address, rawAmount: pool.MAX_FEE }, { onSuccess: () => setCurrentStep(5) });
setSwapFee(
{ pool: pool.address, rawAmount: pool.MAX_FEE },
{ onSuccess: () => setPersistedState({ ...state, step: 5 }) },
);
};

const handleFinalize = async () => {
if (!pool) throw new Error("Pool is undefined in handleFinalize");
finalizePool(pool.address, {
onSuccess: () => setCurrentStep(6),
finalizePool(pool?.address, {
onSuccess: () => setPersistedState({ ...state, step: 6 }),
});
};

useEffect(() => {
if (pool && pool.numTokens < 2n) {
if (allowance1 < token1RawAmount || allowance2 < token2RawAmount) {
setPersistedState({ ...state, step: 2 });
} else {
setPersistedState({ ...state, step: 3 });
}
}
if (pool && pool.numTokens === 2n && !pool.isFinalized) {
if (pool.swapFee !== pool.MAX_FEE) {
setPersistedState({ ...state, step: 4 });
} else {
setPersistedState({ ...state, step: 5 });
}
}
if (pool && pool.isFinalized && state.step !== 1) setPersistedState({ ...state, step: 6 });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pool, allowance1, allowance2, token1RawAmount, token2RawAmount]);

return (
<>
<div className="bg-base-200 p-7 rounded-xl w-full sm:w-[555px] flex flex-grow shadow-lg">
Expand All @@ -133,15 +165,18 @@ export const PoolCreation = ({ state, clearState }: ManagePoolCreationProps) =>
<TextField label="Pool symbol:" value={state.poolSymbol} isDisabled={true} />
</div>
</div>
{currentStep < 6 && <StepsDisplay currentStep={currentStep} />}
{state.step < 6 && <StepsDisplay currentStep={state.step} />}

{pool && currentStep === 6 && (
{pool && state.step === 6 && (
<>
<div className="bg-base-200 w-full py-4 rounded-xl shadow-md text-center sm:text-lg overflow-hidden">
{pool.address}
<div className="bg-base-100 w-full py-4 rounded-xl shadow-md flex justify-center">
<div className="font-semibold sm:text-lg overflow-hidden text-transparent bg-clip-text bg-gradient-to-r from-violet-500 via-violet-300 via-40% to-orange-400">
{pool.address}
</div>
</div>

<Alert type="success">
You CoW AMM pool was successfully created! Because of caching, it may take a few minutes for the pool to
Your CoW AMM pool was successfully created! Because of caching, it may take a few minutes for the pool to
appear in the Balancer app
</Alert>

Expand All @@ -158,15 +193,20 @@ export const PoolCreation = ({ state, clearState }: ManagePoolCreationProps) =>
{isWrongNetwork && <Alert type="error">You&apos;re connected to the wrong network</Alert>}

{(() => {
switch (currentStep) {
switch (state.step) {
case 1:
return (
<TransactionButton
title="Create Pool"
isPending={isCreatePending}
isDisabled={isCreatePending || isWrongNetwork}
onClick={handleCreatePool}
/>
<>
<TransactionButton
title="Create Pool"
isPending={isCreatePending}
isDisabled={isCreatePending || isWrongNetwork}
onClick={handleCreatePool}
/>
<div className="link flex items-center gap-2" onClick={clearState}>
Back to Configure
</div>
</>
);
case 2:
return (
Expand Down
29 changes: 27 additions & 2 deletions packages/nextjs/app/cow/page.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,34 @@
"use client";

import { useEffect, useState } from "react";
import { PoolCreation } from "./_components";
import type { NextPage } from "next";
import { PoolConfiguration } from "~~/app/cow/_components/PoolConfiguration";
import { usePoolCreationPersistedState } from "~~/hooks/cow/usePoolCreationState";

const CowAmm: NextPage = () => {
const [isMounted, setIsMounted] = useState(false);

const persistedState = usePoolCreationPersistedState(state => state.state);
const clearPersistedState = usePoolCreationPersistedState(state => state.clearPersistedState);

useEffect(() => {
setIsMounted(true);
}, []);

return (
<div className="flex-grow bg-base-300">
<div className="flex justify-center px-5">
<div className="w-full sm:w-[555px]">
<div className="flex items-center flex-col flex-grow py-10 gap-6">
<h1 className="text-2xl md:text-4xl font-bold">Create a CoW AMM Pool</h1>
{!persistedState && <PoolConfiguration />}
{persistedState && <PoolCreation state={persistedState} clearState={clearPersistedState} />}
{!isMounted ? (
<CowLoadingSkeleton />
) : !persistedState ? (
<PoolConfiguration />
) : (
persistedState && <PoolCreation state={persistedState} clearState={clearPersistedState} />
)}
</div>
</div>
</div>
Expand All @@ -25,3 +37,16 @@ const CowAmm: NextPage = () => {
};

export default CowAmm;

const CowLoadingSkeleton = () => {
return (
<>
<div className="w-full h-[496px]">
<div className="animate-pulse bg-base-200 rounded-xl w-full h-full"></div>
</div>
<div className="w-full h-[104px]">
<div className="animate-pulse bg-base-200 rounded-xl w-full h-full"></div>
</div>
</>
);
};
2 changes: 1 addition & 1 deletion packages/nextjs/components/common/TextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const TextField: React.FC<TextFieldProps> = ({ label, placeholder, value,
value={value}
onChange={onChange}
disabled={isDisabled}
className="shadow-md rounded-xl w-full input bg-base-300 disabled:text-base-content disabled:bg-base-300 px-5 h-[55px] text-lg"
className="shadow-md border-0 rounded-xl w-full input bg-base-300 disabled:text-base-content disabled:bg-base-300 px-5 h-[55px] text-lg"
/>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion packages/nextjs/components/common/TokenField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export const TokenField: React.FC<TokenFieldProps> = ({
min="0"
placeholder="0.0"
value={value}
className={`${sufficientAmount !== undefined && (amountGreaterThanBalance || !sufficientAmount) && "ring-1 ring-red-400"} h-[77px] pb-5 text-right text-2xl w-full input rounded-xl bg-base-300 disabled:bg-base-300 disabled:text-base-content`}
className={`${sufficientAmount !== undefined && (amountGreaterThanBalance || !sufficientAmount) && "ring-1 ring-red-400"} border-0 h-[77px] pb-5 text-right text-2xl w-full input rounded-xl bg-base-300 disabled:bg-base-300 disabled:text-base-content`}
/>
<div className="absolute top-0 left-0 ">
<div className="p-2.5">
Expand Down
1 change: 0 additions & 1 deletion packages/nextjs/hooks/common/index.ts

This file was deleted.

33 changes: 0 additions & 33 deletions packages/nextjs/hooks/common/useLocalStorage.ts

This file was deleted.

6 changes: 3 additions & 3 deletions packages/nextjs/hooks/cow/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { type Address } from "viem";
export type BCowPool = {
address: Address;
isFinalized: boolean;
getNumTokens: bigint;
getCurrentTokens: Address[];
getSwapFee: bigint;
numTokens: bigint;
currentTokens: Address[];
swapFee: bigint;
MAX_FEE: bigint;
};

Expand Down
2 changes: 1 addition & 1 deletion packages/nextjs/hooks/cow/useBindPool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { abis } from "~~/contracts/abis";
import { useTransactor } from "~~/hooks/scaffold-eth";

type BindPayload = {
pool: Address;
pool: Address | undefined;
token: Address;
rawAmount: bigint;
};
Expand Down
5 changes: 3 additions & 2 deletions packages/nextjs/hooks/cow/useFinalizePool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ export const useFinalizePool = () => {
const publicClient = usePublicClient();
const writeTx = useTransactor(); // scaffold hook for tx status toast notifications

const finalize = async (pool: Address) => {
const finalize = async (pool: Address | undefined) => {
if (!publicClient) throw new Error("No public client found!");
if (!walletClient) throw new Error("No wallet client found!");
if (!pool) throw new Error("No pool address found!");

const { request: finalizePool } = await publicClient.simulateContract({
abi: abis.CoW.BCoWPool,
Expand All @@ -28,5 +29,5 @@ export const useFinalizePool = () => {
});
};

return useMutation({ mutationFn: (pool: Address) => finalize(pool) });
return useMutation({ mutationFn: (pool: Address | undefined) => finalize(pool) });
};
Loading

0 comments on commit 2f4466d

Please sign in to comment.