diff --git a/apps/cow-amm-deployer/src/app/amm/(components)/CreateAmmForm.tsx b/apps/cow-amm-deployer/src/app/amm/(components)/CreateAmmForm.tsx index f6921846f..c1c21098a 100644 --- a/apps/cow-amm-deployer/src/app/amm/(components)/CreateAmmForm.tsx +++ b/apps/cow-amm-deployer/src/app/amm/(components)/CreateAmmForm.tsx @@ -1,7 +1,9 @@ import { useSafeAppsSDK } from "@gnosis.pm/safe-apps-react-sdk"; +import { TokenBalance } from "@gnosis.pm/safe-apps-sdk"; import { zodResolver } from "@hookform/resolvers/zod"; import { slateDarkA } from "@radix-ui/colors"; import { InfoCircledIcon } from "@radix-ui/react-icons"; +import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; import { useForm, UseFormReturn } from "react-hook-form"; @@ -10,44 +12,45 @@ import { Input } from "#/components/Input"; import { Select, SelectItem } from "#/components/Select"; import { Spinner } from "#/components/Spinner"; import { Tooltip } from "#/components/Tooltip"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "#/components/ui/accordion"; import { Form, FormMessage } from "#/components/ui/form"; import { Label } from "#/components/ui/label"; import { useFallbackState } from "#/hooks/useFallbackState"; import { useRawTxData } from "#/hooks/useRawTxData"; +import { useSafeBalances } from "#/hooks/useSafeBalances"; import { createAmmSchema } from "#/lib/schema"; import { createAMMArgs } from "#/lib/transactionFactory"; import { FALLBACK_STATES, IToken, PRICE_ORACLES } from "#/lib/types"; -import { ChainId } from "#/utils/chainsPublicClients"; -import { cowTokenList } from "#/utils/cowTokenList"; import { FallbackAndDomainWarning } from "./FallbackAndDomainWarning"; import { TokenSelect } from "./TokenSelect"; -const getDefaultData = (chainId: ChainId) => { - const token0 = cowTokenList.find( - (token) => token.chainId === chainId && token.symbol === "COW", +const getNewMinTradeToken0 = (newToken0: IToken, assets: TokenBalance[]) => { + const asset0 = assets.find( + (asset) => + asset.tokenInfo.address.toLowerCase() === newToken0.address.toLowerCase(), ); - const token1 = cowTokenList.find( - (token) => token.chainId === chainId && token.symbol === "wstETH", - ); - return { - token0, - token1, - chainId, - minTradedToken0: 0.2, - priceOracle: PRICE_ORACLES.BALANCER, - balancerPoolId: - "0x4cdabe9e07ca393943acfb9286bbbd0d0a310ff600020000000000000000005c", - }; + return 10 / Number(asset0?.fiatConversion); }; export function CreateAmmForm() { const { - safe: { chainId, safeAddress }, + safe: { safeAddress, chainId }, } = useSafeAppsSDK(); + const router = useRouter(); + const { assets } = useSafeBalances(); + const form = useForm({ resolver: zodResolver(createAmmSchema), - defaultValues: getDefaultData(chainId as ChainId), + defaultValues: { + chainId, + safeAddress: safeAddress, + }, }); const { @@ -66,6 +69,7 @@ export function CreateAmmForm() { await createAMMArgs(data).then((txArgs) => { sendTransactions(txArgs); }); + router.push("/amm"); }; useEffect(() => { @@ -94,6 +98,7 @@ export function CreateAmmForm() { address: token.address, symbol: token.symbol, }); + setValue("minTradedToken0", getNewMinTradeToken0(token, assets)); }} label="First Token" selectedToken={token0 ?? undefined} @@ -123,13 +128,22 @@ export function CreateAmmForm() { )} - + + + Advanced Options + + + + + + {fallbackState !== FALLBACK_STATES.HAS_DOMAIN_VERIFIER && ( ; + } + + if (!loaded) { + return ; + } + + if (safe.chainId !== mainnet.id && safe.chainId !== gnosis.id) { + return ( +
+
+ This app isn't available on this network +
+
+ Please change to {gnosis.name} or {mainnet.name} +
+
+ ); + } + + if (!isAmmRunning || !cowAmm) { + return ( +
+
+
+
+

+ CoW AMM (Automatic Market Maker) +

+ + There isn't any AMM running. Create it to provide liquidity + without suffering with LVR. + +
+
+ + + Create AMM + + } + /> +
+
+
+
+ ); + } + + const priceOracleLink = + cowAmm.priceOracle === PRICE_ORACLES.BALANCER + ? getBalancerPoolUrl( + safe.chainId as ChainId, + cowAmm.priceOracleData?.balancerPoolId, + ) + : getUniV2PairUrl( + safe.chainId as ChainId, + cowAmm.priceOracleData?.uniswapV2PairAddress, + ); + + return ( +
+
+
+
+

+ CoW AMM (Automatic Market Maker) +

+
+ Using price information from {cowAmm.priceOracle} + {priceOracleLink && ( + + + + )} +
+
+
+ + +
+
+
+ + AMM Composition + +
+ +
+
+ ); +} diff --git a/apps/cow-amm-deployer/src/components/ui/accordion.tsx b/apps/cow-amm-deployer/src/components/ui/accordion.tsx new file mode 100644 index 000000000..8900299e5 --- /dev/null +++ b/apps/cow-amm-deployer/src/components/ui/accordion.tsx @@ -0,0 +1,57 @@ +"use client"; + +import * as AccordionPrimitive from "@radix-ui/react-accordion"; +import { ChevronDownIcon } from "@radix-ui/react-icons"; +import * as React from "react"; + +import { cn } from "#/lib/utils"; + +const Accordion = AccordionPrimitive.Root; + +const AccordionItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AccordionItem.displayName = "AccordionItem"; + +const AccordionTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + svg]:rotate-180", + className, + )} + {...props} + > + {children} + + + +)); +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName; + +const AccordionContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + +
{children}
+
+)); +AccordionContent.displayName = AccordionPrimitive.Content.displayName; + +export { Accordion, AccordionContent, AccordionItem, AccordionTrigger }; diff --git a/apps/cow-amm-deployer/src/lib/gql/sdk.ts b/apps/cow-amm-deployer/src/lib/gql/sdk.ts index 3740aac44..ddcad8245 100644 --- a/apps/cow-amm-deployer/src/lib/gql/sdk.ts +++ b/apps/cow-amm-deployer/src/lib/gql/sdk.ts @@ -2,7 +2,7 @@ import { GraphQLClient } from "graphql-request"; import { getSdk } from "#/lib/gql/generated"; -export const ENDPOINT = "http://localhost:42069"; +export const ENDPOINT = "https://composable-cow-api.up.railway.app"; const client = new GraphQLClient(ENDPOINT); diff --git a/apps/cow-amm-deployer/src/lib/transactionFactory.ts b/apps/cow-amm-deployer/src/lib/transactionFactory.ts index eb39edfd7..eba02a6d1 100644 --- a/apps/cow-amm-deployer/src/lib/transactionFactory.ts +++ b/apps/cow-amm-deployer/src/lib/transactionFactory.ts @@ -3,7 +3,7 @@ import { BaseTransaction } from "@gnosis.pm/safe-apps-sdk"; import { Address, encodeFunctionData, parseUnits } from "viem"; import { FALLBACK_STATES, PRICE_ORACLES } from "#/lib/types"; -import { ChainId } from "#/utils/chainsPublicClients"; +import { ChainId, publicClientsFromIds } from "#/utils/chainsPublicClients"; import { cowAmmModuleAbi } from "./abis/cowAmmModule"; import { gnosisSafeV12 } from "./abis/gnosisSafeV12"; @@ -212,6 +212,23 @@ export async function createAMMArgs(data: typeof createAmmSchema._type) { } })(); + const publicClient = publicClientsFromIds[data.chainId as ChainId]; + + const isCoWAmmModuleEnabled = await publicClient.readContract({ + address: data.safeAddress as Address, + abi: gnosisSafeV12, + functionName: "isModuleEnabled", + args: [COW_AMM_MODULE_ADDRESS], + }); + const enableCoWAmmTxs = isCoWAmmModuleEnabled + ? [] + : [ + { + type: TRANSACTION_TYPES.ENABLE_COW_AMM_MODULE, + safeAddress: data.safeAddress, + } as enableCowAmmModuleArgs, + ]; + const metadataApi = new MetadataApi(); const appDataDoc = await metadataApi.generateAppDataDoc({ @@ -228,10 +245,7 @@ export async function createAMMArgs(data: typeof createAmmSchema._type) { return [ ...fallbackTxs, - { - type: TRANSACTION_TYPES.ENABLE_COW_AMM_MODULE, - safeAddress: data.safeAddress, - } as enableCowAmmModuleArgs, + ...enableCoWAmmTxs, { type: TRANSACTION_TYPES.CREATE_COW_AMM, token0: data.token0.address,