Skip to content

Commit

Permalink
Persist pool creation state in local storage
Browse files Browse the repository at this point in the history
  • Loading branch information
MattPereira committed Aug 8, 2024
1 parent 5719234 commit 8c0c6de
Show file tree
Hide file tree
Showing 10 changed files with 125 additions and 75 deletions.
41 changes: 22 additions & 19 deletions packages/nextjs/app/cow/_components/PoolConfiguration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { parseUnits } from "viem";
import { useAccount } from "wagmi";
import { Alert, TransactionButton } from "~~/components/common";
import { TextField, TokenField } from "~~/components/common/";
import { useStore } from "~~/hooks/common";
import { useCheckIfPoolExists } from "~~/hooks/cow";
import { getPoolUrl } from "~~/hooks/cow/getPoolUrl";
import { usePoolCreationPersistedState } from "~~/hooks/cow/usePoolCreationState";
Expand All @@ -23,7 +24,7 @@ export const PoolConfiguration = () => {
const [hasAgreedToWarning, setAgreedToWarning] = useState<boolean>(false);
const [poolName, setPoolName] = useState<string>("");
const [poolSymbol, setPoolSymbol] = useState<string>("");
const setPersistedState = usePoolCreationPersistedState(state => state.setPersistedState);
const setPersistedState = useStore(usePoolCreationPersistedState, state => state.setPersistedState);

const { data } = useFetchTokenList();
const tokenList = data || [];
Expand Down Expand Up @@ -167,24 +168,26 @@ export const PoolConfiguration = () => {
)}

<div className="min-w-96 px-5">
<TransactionButton
title="Preview"
isPending={false}
isDisabled={!canProceedToCreate}
onClick={() => {
setPersistedState({
chainId: chain?.id || 0,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
token1: token1!,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
token2: token2!,
token1Amount,
token2Amount,
poolName,
poolSymbol,
});
}}
/>
{setPersistedState && (
<TransactionButton
title="Preview"
isPending={false}
isDisabled={!canProceedToCreate}
onClick={() => {
setPersistedState({
chainId: chain?.id || 0,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
token1: token1!,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
token2: token2!,
token1Amount,
token2Amount,
poolName,
poolSymbol,
});
}}
/>
)}
</div>
</>
);
Expand Down
39 changes: 34 additions & 5 deletions packages/nextjs/app/cow/_components/PoolCreation.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
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 { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork";
Expand All @@ -21,6 +28,7 @@ export const PoolCreation = ({ state, clearState }: ManagePoolCreationProps) =>
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 Down Expand Up @@ -80,7 +88,7 @@ export const PoolCreation = ({ state, clearState }: ManagePoolCreationProps) =>

const handleBindTokens = async () => {
if (!pool) throw new Error("Required value is undefined in handleBindTokens");
const poolTokens = pool.getCurrentTokens.map(token => token.toLowerCase());
const poolTokens = pool.currentTokens.map(token => token.toLowerCase());
// If not already bound, bind the token
const txs = [];
if (!poolTokens.includes(state.token1.address.toLowerCase())) {
Expand Down Expand Up @@ -117,6 +125,24 @@ export const PoolCreation = ({ state, clearState }: ManagePoolCreationProps) =>
});
};

useEffect(() => {
if (pool && pool.numTokens < 2n) {
if (allowance1 < token1RawAmount || allowance2 < token2RawAmount) {
setCurrentStep(2);
} else {
setCurrentStep(3);
}
}
if (pool && pool.numTokens === 2n && !pool.isFinalized) {
if (pool.swapFee !== pool.MAX_FEE) {
setCurrentStep(4);
} else {
setCurrentStep(5);
}
}
if (pool && pool.isFinalized) setCurrentStep(6);
}, [pool]);

return (
<>
<div className="bg-base-200 p-7 rounded-xl w-full sm:w-[555px] flex flex-grow shadow-lg">
Expand All @@ -137,9 +163,12 @@ export const PoolCreation = ({ state, clearState }: ManagePoolCreationProps) =>

{pool && currentStep === 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
appear in the Balancer app
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/hooks/common/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from "./useLocalStorage";
export * from "./useStore";
33 changes: 0 additions & 33 deletions packages/nextjs/hooks/common/useLocalStorage.ts

This file was deleted.

17 changes: 17 additions & 0 deletions packages/nextjs/hooks/common/useStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// useStore.ts
import { useEffect, useState } from "react";

/**
* A custom hook for using Zustand store in Next.js.
* https://docs.pmnd.rs/zustand/integrations/persisting-store-data#usage-in-next.js
*/
export const useStore = <T, F>(store: (callback: (state: T) => unknown) => unknown, callback: (state: T) => F) => {
const result = store(callback) as F;
const [data, setData] = useState<F>();

useEffect(() => {
setData(result);
}, [result]);

return data;
};
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
27 changes: 18 additions & 9 deletions packages/nextjs/hooks/cow/usePoolCreationState.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
import { Token } from "~~/hooks/token";

export interface PoolCreationState {
Expand All @@ -11,12 +12,20 @@ export interface PoolCreationState {
poolSymbol: string;
}

export const usePoolCreationPersistedState = create<{
state: PoolCreationState | null;
setPersistedState: (state: PoolCreationState) => void;
clearPersistedState: () => void;
}>(set => ({
state: null,
setPersistedState: (state: PoolCreationState) => set({ state }),
clearPersistedState: () => set({ state: null }),
}));
export const usePoolCreationPersistedState = create(
persist<{
state: PoolCreationState | null;
setPersistedState: (state: PoolCreationState) => void;
clearPersistedState: () => void;
}>(
set => ({
state: null,
setPersistedState: (state: PoolCreationState) => set({ state }),
clearPersistedState: () => set({ state: null }),
}),
{
name: "pool-creation-state",
getStorage: () => localStorage,
},
),
);
4 changes: 2 additions & 2 deletions packages/nextjs/hooks/cow/useReadPool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const useReadPool = (address: Address | undefined) => {
if (!client) throw new Error("Wagmi public client is undefined");
if (!address) throw new Error("Pool address is undefined");

const [isFinalized, getNumTokens, getCurrentTokens, getSwapFee, MAX_FEE] = await Promise.all([
const [isFinalized, numTokens, currentTokens, swapFee, MAX_FEE] = await Promise.all([
client.readContract({
abi,
address,
Expand Down Expand Up @@ -43,7 +43,7 @@ export const useReadPool = (address: Address | undefined) => {
}),
]);

return { address, isFinalized, getNumTokens, getCurrentTokens, getSwapFee, MAX_FEE };
return { address, isFinalized, numTokens, currentTokens, swapFee, MAX_FEE };
},
enabled: !!address,
});
Expand Down
2 changes: 1 addition & 1 deletion packages/nextjs/scaffold.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export type ScaffoldConfig = {

const scaffoldConfig = {
// The networks on which your DApp is live
targetNetworks: [chains.sepolia, chains.mainnet, chains.gnosis],
targetNetworks: [chains.foundry, chains.sepolia, chains.mainnet, chains.gnosis],

// If using chains.foundry as your targetNetwork, you must specify a network to fork
targetFork: chains.sepolia,
Expand Down

0 comments on commit 8c0c6de

Please sign in to comment.