From 5b75e9416a85b12ab9ab1b7415452d9d4974cca7 Mon Sep 17 00:00:00 2001 From: Pedro Yves Fracari Date: Mon, 10 Jun 2024 14:00:15 -0300 Subject: [PATCH 1/5] Add usd value and price impact warning on deposit form --- .../src/components/DepositForm.tsx | 24 +++++++++++++++++-- apps/cow-amm-deployer/src/lib/priceImpact.ts | 24 +++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 apps/cow-amm-deployer/src/lib/priceImpact.ts diff --git a/apps/cow-amm-deployer/src/components/DepositForm.tsx b/apps/cow-amm-deployer/src/components/DepositForm.tsx index 35aa47432..b1e0d891a 100644 --- a/apps/cow-amm-deployer/src/components/DepositForm.tsx +++ b/apps/cow-amm-deployer/src/components/DepositForm.tsx @@ -1,6 +1,6 @@ "use client"; -import { toast } from "@bleu/ui"; +import { formatNumber, toast } from "@bleu/ui"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm, useWatch } from "react-hook-form"; import { z } from "zod"; @@ -10,9 +10,11 @@ import { TokenInfo } from "#/components/TokenInfo"; import { Form, FormMessage } from "#/components/ui/form"; import { useManagedTransaction } from "#/hooks/tx-manager/useManagedTransaction"; import { ICowAmm } from "#/lib/fetchAmmData"; +import { calculatePriceImpact } from "#/lib/priceImpact"; import { getDepositSchema } from "#/lib/schema"; import { buildDepositAmmArgs } from "#/lib/transactionFactory"; +import { AlertCard } from "./AlertCard"; import { TokenAmountInput } from "./TokenAmountInput"; export function DepositForm({ @@ -46,6 +48,15 @@ export function DepositForm({ control, name: ["amount0", "amount1"], }); + const depositUsdValue = + cowAmmData.token0.usdPrice * amount0 + cowAmmData.token1.usdPrice * amount1; + + const priceImpact = calculatePriceImpact({ + balance0: Number(cowAmmData.token0.balance), + balance1: Number(cowAmmData.token1.balance), + amount0: Number(amount0), + amount1: Number(amount1), + }); const onSubmit = async (data: z.output) => { const txArgs = buildDepositAmmArgs({ @@ -109,6 +120,15 @@ export function DepositForm({ ) } + {depositUsdValue > 5000 && priceImpact > 0.1 && ( + +

+ The price impact of this deposit is{" "} + {formatNumber(priceImpact * 100, 2)}%. Deposits with high price + impact may result in lost funds. +

+
+ )} ); diff --git a/apps/cow-amm-deployer/src/lib/priceImpact.ts b/apps/cow-amm-deployer/src/lib/priceImpact.ts new file mode 100644 index 000000000..724b0c56b --- /dev/null +++ b/apps/cow-amm-deployer/src/lib/priceImpact.ts @@ -0,0 +1,24 @@ +export function calculatePriceImpact({ + balance0, + balance1, + amount0, + amount1, +}: { + balance0: number; + balance1: number; + amount0: number; + amount1: number; +}) { + const token0Ratio = amount0 / balance0; + const token1Ratio = amount1 / balance1; + const ratio0BiggerThan1 = token0Ratio > token1Ratio; + + const currentSpotPrice = ratio0BiggerThan1 + ? balance1 / balance0 + : balance0 / balance1; + const newSpotPrice = ratio0BiggerThan1 + ? (balance1 + amount1) / (balance0 + amount0) + : (balance0 + amount0) / (balance1 + amount1); + + return Math.abs(newSpotPrice - currentSpotPrice) / currentSpotPrice; +} From 0b52527810f05dc0955c016a06e6e2886fb9fb1a Mon Sep 17 00:00:00 2001 From: Pedro Yves Fracari Date: Tue, 11 Jun 2024 09:43:16 -0300 Subject: [PATCH 2/5] add create amm unbalanced amm warning --- .../new/(components)/CreateAMMForm.tsx | 57 ++++++++++++++++++- .../src/components/DepositForm.tsx | 12 ++-- .../cow-amm-deployer/src/hooks/useDebounce.ts | 17 ++++++ 3 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 apps/cow-amm-deployer/src/hooks/useDebounce.ts diff --git a/apps/cow-amm-deployer/src/app/[userId]/new/(components)/CreateAMMForm.tsx b/apps/cow-amm-deployer/src/app/[userId]/new/(components)/CreateAMMForm.tsx index 9df0cc354..825ef3c2d 100644 --- a/apps/cow-amm-deployer/src/app/[userId]/new/(components)/CreateAMMForm.tsx +++ b/apps/cow-amm-deployer/src/app/[userId]/new/(components)/CreateAMMForm.tsx @@ -1,13 +1,14 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; import { useRouter } from "next/navigation"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { useForm, useWatch } from "react-hook-form"; import { Address } from "viem"; import { useAccount } from "wagmi"; import { z } from "zod"; import { Button } from "#/components"; +import { AlertCard } from "#/components/AlertCard"; import { Input } from "#/components/Input"; import { PriceOracleForm } from "#/components/PriceOracleForm"; import { TokenAmountInput } from "#/components/TokenAmountInput"; @@ -20,11 +21,12 @@ import { } from "#/components/ui/accordion"; import { Form } from "#/components/ui/form"; import { useManagedTransaction } from "#/hooks/tx-manager/useManagedTransaction"; +import { useDebounce } from "#/hooks/useDebounce"; import { ConstantProductFactoryABI } from "#/lib/abis/ConstantProductFactory"; import { COW_CONSTANT_PRODUCT_FACTORY } from "#/lib/contracts"; import { IToken } from "#/lib/fetchAmmData"; import { ammFormSchema } from "#/lib/schema"; -import { getNewMinTradeToken0 } from "#/lib/tokenUtils"; +import { fetchTokenUsdPrice, getNewMinTradeToken0 } from "#/lib/tokenUtils"; import { buildTxCreateAMMArgs } from "#/lib/transactionFactory"; import { cn } from "#/lib/utils"; import { ChainId, publicClientsFromIds } from "#/utils/chainsPublicClients"; @@ -75,6 +77,13 @@ export function CreateAMMForm({ userId }: { userId: string }) { writeContract(buildTxCreateAMMArgs({ data })); } }; + const [token0UsdPrice, setToken0UsdPrice] = useState(); + const [token1UsdPrice, setToken1UsdPrice] = useState(); + const [amountUsdDiff, setAmountUsdDiff] = useState(); + const debouncedAmountUsdDiff = useDebounce( + amountUsdDiff, + 300 + ); useEffect(() => { setValue("safeAddress", safeAddress as string); @@ -99,6 +108,38 @@ export function CreateAMMForm({ userId }: { userId: string }) { onTxStatusFinal(); } }, [status]); + async function updateTokenUsdPrice( + token: IToken, + setAmountUsd: (value: number) => void + ) { + const amountUsd = await fetchTokenUsdPrice({ + chainId: chainId as ChainId, + tokenDecimals: token.decimals, + tokenAddress: token.address as Address, + }); + setAmountUsd(amountUsd); + } + + useEffect(() => { + if (token0) { + updateTokenUsdPrice(token0, setToken0UsdPrice); + } + }, [token0]); + + useEffect(() => { + if (token1) { + updateTokenUsdPrice(token1, setToken1UsdPrice); + } + }, [token1]); + + useEffect(() => { + if (token0?.address == token1?.address) return; + if (token0UsdPrice && token1UsdPrice && amount0 && amount1) { + const amount0Usd = amount0 * token0UsdPrice; + const amount1Usd = amount1 * token1UsdPrice; + setAmountUsdDiff(Math.abs(amount0Usd - amount1Usd)); + } + }, [amount0, amount1, token0UsdPrice, token1UsdPrice]); return ( // @ts-ignore @@ -183,6 +224,18 @@ export function CreateAMMForm({ userId }: { userId: string }) { + + {amountUsdDiff} {debouncedAmountUsdDiff} + + {(debouncedAmountUsdDiff || 0) > 5000 && ( + +

+ The difference between the USD value of the two token amounts is + greater than $5000. This may lead to an unbalanced AMM and result in + loss of funds. +

+
+ )}
); diff --git a/apps/cow-amm-deployer/src/hooks/useDebounce.ts b/apps/cow-amm-deployer/src/hooks/useDebounce.ts new file mode 100644 index 000000000..8669d175a --- /dev/null +++ b/apps/cow-amm-deployer/src/hooks/useDebounce.ts @@ -0,0 +1,17 @@ +import { useEffect, useState } from "react"; + +export function useDebounce(value: T, delay: number) { + const [debouncedValue, setDebouncedValue] = useState(value); + + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedValue(value); + }, delay); + + return () => { + clearTimeout(handler); + }; + }, [value, delay]); + + return debouncedValue; +} From 82dc658c92a66d40cf1d804370c2f2a2b77a1f87 Mon Sep 17 00:00:00 2001 From: Pedro Yves Fracari Date: Tue, 11 Jun 2024 14:18:49 -0300 Subject: [PATCH 3/5] refactor: separete thresholds into constants file --- .../new/(components)/CreateAMMForm.tsx | 3 ++- .../src/components/DepositForm.tsx | 23 +++++++++++-------- apps/cow-amm-deployer/src/lib/constants.ts | 6 +++++ 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/apps/cow-amm-deployer/src/app/[userId]/new/(components)/CreateAMMForm.tsx b/apps/cow-amm-deployer/src/app/[userId]/new/(components)/CreateAMMForm.tsx index 825ef3c2d..dd8524f6f 100644 --- a/apps/cow-amm-deployer/src/app/[userId]/new/(components)/CreateAMMForm.tsx +++ b/apps/cow-amm-deployer/src/app/[userId]/new/(components)/CreateAMMForm.tsx @@ -23,6 +23,7 @@ import { Form } from "#/components/ui/form"; import { useManagedTransaction } from "#/hooks/tx-manager/useManagedTransaction"; import { useDebounce } from "#/hooks/useDebounce"; import { ConstantProductFactoryABI } from "#/lib/abis/ConstantProductFactory"; +import { UNBALANCED_USD_DIFF_THRESHOLD } from "#/lib/constants"; import { COW_CONSTANT_PRODUCT_FACTORY } from "#/lib/contracts"; import { IToken } from "#/lib/fetchAmmData"; import { ammFormSchema } from "#/lib/schema"; @@ -227,7 +228,7 @@ export function CreateAMMForm({ userId }: { userId: string }) { {amountUsdDiff} {debouncedAmountUsdDiff} - {(debouncedAmountUsdDiff || 0) > 5000 && ( + {(debouncedAmountUsdDiff || 0) > UNBALANCED_USD_DIFF_THRESHOLD && (

The difference between the USD value of the two token amounts is diff --git a/apps/cow-amm-deployer/src/components/DepositForm.tsx b/apps/cow-amm-deployer/src/components/DepositForm.tsx index ad885d64e..193b6e833 100644 --- a/apps/cow-amm-deployer/src/components/DepositForm.tsx +++ b/apps/cow-amm-deployer/src/components/DepositForm.tsx @@ -10,6 +10,10 @@ import { TokenInfo } from "#/components/TokenInfo"; import { Form, FormMessage } from "#/components/ui/form"; import { useManagedTransaction } from "#/hooks/tx-manager/useManagedTransaction"; import { useDebounce } from "#/hooks/useDebounce"; +import { + PRICE_IMPACT_THRESHOLD, + USD_VALUE_FOR_PRICE_IMPACT_WARNING, +} from "#/lib/constants"; import { ICowAmm } from "#/lib/fetchAmmData"; import { calculatePriceImpact } from "#/lib/priceImpact"; import { getDepositSchema } from "#/lib/schema"; @@ -124,15 +128,16 @@ export function DepositForm({ ) } - {debouncedDepositUsdValue > 5000 && debouncedPriceImpact > 0.1 && ( - -

- The price impact of this deposit is{" "} - {formatNumber(debouncedPriceImpact * 100, 2)}%. Deposits with high - price impact may result in lost funds. -

-
- )} + {debouncedDepositUsdValue > USD_VALUE_FOR_PRICE_IMPACT_WARNING && + debouncedPriceImpact > PRICE_IMPACT_THRESHOLD && ( + +

+ The price impact of this deposit is{" "} + {formatNumber(debouncedPriceImpact * 100, 2)}%. Deposits with high + price impact may result in lost funds. +

+
+ )}