Skip to content

Commit

Permalink
Refactor steps to be more deterministic
Browse files Browse the repository at this point in the history
  • Loading branch information
MattPereira committed Aug 12, 2024
1 parent 3f7909e commit 45ed2a7
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 145 deletions.
246 changes: 131 additions & 115 deletions packages/nextjs/app/cow/_components/PoolCreation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Alert, ExternalLinkButton, TextField, TokenField, TransactionButton } f
import {
type PoolCreationState,
getPoolUrl,
useBindPool,
useBindToken,
useCreatePool,
useFinalizePool,
useNewPoolEvents,
Expand Down Expand Up @@ -41,137 +41,68 @@ export const PoolCreation = ({ state, clearState }: ManagePoolCreationProps) =>
state.token2.address,
pool?.address,
);
const refetchAllowances = () => {
refetchAllowance1();
refetchAllowance2();
};

const { mutate: createPool, isPending: isCreatePending, error: createPoolError } = useCreatePool();
const { mutateAsync: approve, isPending: isApprovePending, error: approveError } = useApproveToken(refetchAllowances);
const { mutateAsync: bind, isPending: isBindPending, error: bindError } = useBindPool(() => refetchPool());
const { mutate: approve1, isPending: isApprove1Pending, error: approve1Error } = useApproveToken();
const { mutate: approve2, isPending: isApprove2Pending, error: approve2Error } = useApproveToken();
const { mutate: bind1, isPending: isBind1Pending, error: bind1Error } = useBindToken();
const { mutate: bind2, isPending: isBind2Pending, error: bind2Error } = useBindToken();
const { mutate: setSwapFee, isPending: isSetSwapFeePending, error: setSwapFeeError } = useSetSwapFee();
const { mutate: finalizePool, isPending: isFinalizePending, error: finalizeError } = useFinalizePool();
const txError = createPoolError || approveError || bindError || setSwapFeeError || finalizeError;
const txError =
createPoolError || approve1Error || approve2Error || bind1Error || bind2Error || setSwapFeeError || finalizeError;

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

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

const handleApproveTokens = async () => {
const txs = [];
if (token1RawAmount > allowance1) {
txs.push(
approve({
token: state.token1.address,
spender: pool?.address,
rawAmount: token1RawAmount,
}),
);
}
if (token2RawAmount > allowance2)
txs.push(
approve({
token: state.token2.address,
spender: pool?.address,
rawAmount: token2RawAmount,
}),
);
const results = await Promise.all(txs);
if (results.every(result => result === "success")) setPersistedState({ ...state, step: 3 });
};

const handleBindTokens = async () => {
const txs = [];
// 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,
token: state.token1.address,
rawAmount: token1RawAmount,
}),
);
}
if (!poolTokens?.includes(state.token2.address.toLowerCase())) {
txs.push(
bind({
pool: pool?.address,
token: state.token2.address,
rawAmount: token2RawAmount,
}),
);
}
const results = await Promise.all(txs);
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: () => setPersistedState({ ...state, step: 5 }) },
);
};

const handleFinalize = async () => {
finalizePool(pool?.address, {
onSuccess: () => setPersistedState({ ...state, step: 6 }),
});
};

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

const etherscanURL = pool && getBlockExplorerAddressLink(targetNetwork, pool.address);

return (
<>
<div className="bg-base-200 p-7 rounded-xl w-full sm:w-[555px] flex flex-grow shadow-lg">
<div className="flex flex-col items-center gap-3 w-full">
<h5 className="text-xl md:text-2xl font-bold text-center">Preview your pool</h5>
<div className="flex flex-wrap justify-center gap-5 md:relative">
<div className="bg-base-200 p-6 rounded-xl w-full flex flex-grow shadow-lg md:w-[555px]">
<div className="flex flex-col items-center gap-3 w-full">
<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 pool tokens:</div>
<div className="w-full flex flex-col gap-3">
<TokenField value={state.token1Amount} selectedToken={state.token1} isDisabled={true} />
<TokenField value={state.token2Amount} selectedToken={state.token2} isDisabled={true} />
<div className="w-full">
<div className="ml-1 mb-1">Selected pool tokens:</div>
<div className="w-full flex flex-col gap-3">
<TokenField value={state.token1Amount} selectedToken={state.token1} isDisabled={true} />
<TokenField value={state.token2Amount} selectedToken={state.token2} isDisabled={true} />
</div>
</div>
<TextField label="Pool name:" value={state.poolName} isDisabled={true} />
<TextField label="Pool symbol:" value={state.poolSymbol} isDisabled={true} />
</div>
<TextField label="Pool name:" value={state.poolName} isDisabled={true} />
<TextField label="Pool symbol:" value={state.poolSymbol} isDisabled={true} />
</div>
<div className="flex md:absolute md:top-0 md:-right-[215px]">
<StepsDisplay state={state} />
</div>
</div>
{state.step < 6 && <StepsDisplay currentStep={state.step} />}

{pool && state.step === 6 && (
{pool && state.step === 8 && (
<>
<div className="w-full flex flex-col gap-3">
<Alert type="success">Your CoW AMM pool was successfully created!</Alert>
Expand Down Expand Up @@ -211,47 +142,132 @@ export const PoolCreation = ({ state, clearState }: ManagePoolCreationProps) =>
title="Create Pool"
isPending={isCreatePending}
isDisabled={isCreatePending || isWrongNetwork}
onClick={handleCreatePool}
onClick={() => {
createPool(
{ name: state.poolName, symbol: state.poolSymbol },
{
onSuccess: newPoolAddress => {
setUserPoolAddress(newPoolAddress);
setPersistedState({ ...state, step: 2 });
},
},
);
}}
/>
</>
);
case 2:
return (
<TransactionButton
title="Approve"
isPending={isApprovePending}
isDisabled={isApprovePending || isWrongNetwork}
onClick={handleApproveTokens}
title={`Approve ${state.token1.symbol}`}
isPending={isApprove1Pending}
isDisabled={isApprove1Pending || isWrongNetwork}
onClick={() => {
approve1(
{ token: state.token1.address, spender: pool?.address, rawAmount: token1RawAmount },
{
onSuccess: () => {
refetchAllowance1();
if (allowance1 >= token1RawAmount) setPersistedState({ ...state, step: 3 });
},
},
);
}}
/>
);
case 3:
return (
<TransactionButton
title="Add Liquidity"
isPending={isBindPending}
isDisabled={isBindPending || isWrongNetwork}
onClick={handleBindTokens}
title={`Approve ${state.token2.symbol}`}
isPending={isApprove2Pending}
isDisabled={isApprove2Pending || isWrongNetwork}
onClick={() => {
approve2(
{ token: state.token2.address, spender: pool?.address, rawAmount: token2RawAmount },
{
onSuccess: () => {
refetchAllowance2();
if (allowance2 >= token2RawAmount) setPersistedState({ ...state, step: 4 });
},
},
);
}}
/>
);
case 4:
return (
<TransactionButton
title={`Add ${state.token1.symbol}`}
isPending={isBind1Pending}
isDisabled={isBind1Pending || isWrongNetwork}
onClick={() => {
bind1(
{
pool: pool?.address,
token: state.token1.address,
rawAmount: token1RawAmount,
},
{
onSuccess: () => {
refetchPool();
setPersistedState({ ...state, step: 5 });
},
},
);
}}
/>
);
case 5:
return (
<TransactionButton
title={`Add ${state.token2.symbol}`}
isPending={isBind2Pending}
isDisabled={isBind2Pending || isWrongNetwork}
onClick={() => {
bind2(
{
pool: pool?.address,
token: state.token2.address,
rawAmount: token2RawAmount,
},
{
onSuccess: () => {
refetchPool();
setPersistedState({ ...state, step: 6 });
},
},
);
}}
/>
);
case 6:
return (
<TransactionButton
title="Set Swap Fee"
onClick={handleSetSwapFee}
isPending={isSetSwapFeePending}
isDisabled={isSetSwapFeePending || isWrongNetwork}
onClick={() => {
setSwapFee(
{ pool: pool?.address, rawAmount: pool?.MAX_FEE },
{ onSuccess: () => setPersistedState({ ...state, step: 7 }) },
);
}}
/>
);
case 5:
case 7:
return (
<TransactionButton
title="Finalize"
onClick={handleFinalize}
isPending={isFinalizePending}
isDisabled={isFinalizePending || isWrongNetwork}
onClick={() => {
finalizePool(pool?.address, {
onSuccess: () => setPersistedState({ ...state, step: 8 }),
});
}}
/>
);
case 6:
case 8:
return (
<TransactionButton
title="Create Another Pool"
Expand All @@ -265,7 +281,7 @@ export const PoolCreation = ({ state, clearState }: ManagePoolCreationProps) =>
}
})()}

{state.step < 6 && (
{state.step < 8 && (
<div className=" link flex items-center gap-2" onClick={() => setIsResetModalOpen(true)}>
Start Over
</div>
Expand Down
24 changes: 13 additions & 11 deletions packages/nextjs/app/cow/_components/StepsDisplay.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
/**
* Accepts pool configuration as props
*/
export const StepsDisplay = ({ currentStep }: { currentStep: number }) => {
export const StepsDisplay = ({ state }: { state: any }) => {
return (
<ul className="steps steps-vertical sm:steps-horizontal sm:w-[555px] bg-base-200 py-4 rounded-xl shadow-md">
<li className="px-5 sm:px-0 step step-accent">Create </li>
<li className={`px-5 sm:px-0 step ${currentStep > 1 && "step-accent"}`}>Approve </li>
<li className={`px-5 sm:px-0 step ${currentStep > 2 && "step-accent"}`}>Add Liquidity</li>
<li className={`px-5 sm:px-0 step ${currentStep > 3 && "step-accent"}`}>Set Fee</li>
<li className={`px-5 sm:px-0 step ${currentStep > 4 && "step-accent"}`}>Finalize</li>
</ul>
<div className="bg-base-200 px-5 pt-5 rounded-xl shadow-md">
<h5 className="text-xl font-bold text-center">Steps</h5>
<ul className="steps steps-vertical">
<li className="step step-accent">Create </li>
<li className={`step ${state.step > 1 && "step-accent"}`}>Approve {state.token1.symbol}</li>
<li className={`step ${state.step > 2 && "step-accent"}`}>Approve {state.token2.symbol}</li>
<li className={`step ${state.step > 3 && "step-accent"}`}>Add {state.token1.symbol}</li>
<li className={`step ${state.step > 4 && "step-accent"}`}>Add {state.token2.symbol}</li>
<li className={`step ${state.step > 5 && "step-accent"}`}>Set Fee</li>
<li className={`step ${state.step > 6 && "step-accent"}`}>Finalize</li>
</ul>
</div>
);
};
4 changes: 2 additions & 2 deletions packages/nextjs/app/cow/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const CowAmm: NextPage = () => {
return (
<div className="flex-grow bg-base-300">
<div className="flex justify-center px-5">
<div className="w-full sm:w-[555px]">
<div className="w-full md: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>
{!isMounted ? (
Expand All @@ -44,7 +44,7 @@ const CowLoadingSkeleton = () => {
<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="w-full sm:w-[325px] h-[50px]">
<div className="animate-pulse bg-base-200 rounded-xl w-full h-full"></div>
</div>
</>
Expand Down
2 changes: 1 addition & 1 deletion packages/nextjs/components/common/ExternalLinkButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const ExternalLinkButton = ({ href, text }: { href: string; text: string
href={href}
>
{text}
<ArrowTopRightOnSquareIcon className="w-4 h-4 mb-0.5" />
<ArrowTopRightOnSquareIcon className="w-4 h-4" />
</Link>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const WrongNetworkDropdown = () => {

return (
<div className="dropdown dropdown-end mr-2">
<label tabIndex={0} className="btn btn-error btn-sm dropdown-toggle gap-1">
<label tabIndex={0} className="btn btn-error rounded-xl dropdown-toggle gap-1">
<span>Wrong network</span>
<ChevronDownIcon className="h-6 w-4 ml-2 sm:ml-0" />
</label>
Expand Down
Loading

0 comments on commit 45ed2a7

Please sign in to comment.