Skip to content

Commit

Permalink
Add withdraw page
Browse files Browse the repository at this point in the history
  • Loading branch information
yvesfracari committed May 28, 2024
1 parent d360f84 commit e9129d1
Show file tree
Hide file tree
Showing 7 changed files with 262 additions and 14 deletions.
14 changes: 12 additions & 2 deletions apps/cow-amm-deployer/src/app/amms/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ResetIcon,
} from "@radix-ui/react-icons";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { Address } from "viem";

import { Button } from "#/components/Button";
Expand All @@ -23,6 +24,7 @@ import { PriceInformation } from "./(components)/PriceInformation";

export default function Page({ params }: { params: { id: `0x${string}` } }) {
const { data: cowAmm, loading } = useStandaloneAMM(params.id);
const router = useRouter();

const { safe, connected } = useSafeAppsSDK();

Expand Down Expand Up @@ -59,7 +61,7 @@ export default function Page({ params }: { params: { id: `0x${string}` } }) {
2,
"decimal",
"compact",
0.01
0.01,
)}
</span>
</div>
Expand All @@ -75,7 +77,7 @@ export default function Page({ params }: { params: { id: `0x${string}` } }) {
buildAccountCowExplorerUrl({
chainId: safe.chainId as ChainId,
address: safe.safeAddress as Address,
})
}),
)
}
rel="noreferrer noopener"
Expand All @@ -99,6 +101,14 @@ export default function Page({ params }: { params: { id: `0x${string}` } }) {
<Pencil2Icon />
Edit CoW AMM LP parameters
</Button>
<Button
className="flex items-center gap-1 py-3 px-6"
onClick={() => {
router.push(`/amms/${params.id}/withdraw`);
}}
>
Withdraw
</Button>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { formatNumber, toast } from "@bleu/ui";
import { useSafeAppsSDK } from "@gnosis.pm/safe-apps-react-sdk";
import { zodResolver } from "@hookform/resolvers/zod";
import * as Slider from "@radix-ui/react-slider";
import { useRouter } from "next/navigation";
import { Controller, useForm } from "react-hook-form";
import { parseUnits } from "viem";

import { Button } from "#/components/Button";
import Table from "#/components/Table";
import { TokenInfo } from "#/components/TokenInfo";
import { Form } from "#/components/ui/form";
import { useRawTxData } from "#/hooks/useRawTxData";
import { ammWithdrawSchema } from "#/lib/schema";
import {
TRANSACTION_TYPES,
withdrawCowAMMargs,
} from "#/lib/transactionFactory";
import { ICowAmm } from "#/lib/types";
import { ChainId } from "#/utils/chainsPublicClients";

export function WithdrawForm({ cowAmm }: { cowAmm: ICowAmm }) {
const form = useForm<typeof ammWithdrawSchema._type>({
resolver: zodResolver(ammWithdrawSchema),
defaultValues: {
withdrawPct: 50,
},
});
const router = useRouter();
const { sendTransactions } = useRawTxData();
const {
safe: { chainId },
} = useSafeAppsSDK();

const onSubmit = async (data: typeof ammWithdrawSchema._type) => {
const amount0 = parseUnits(
String((Number(cowAmm.token0.balance) * data.withdrawPct) / 100),
cowAmm.token0.decimals
);
const amount1 = parseUnits(
String((Number(cowAmm.token1.balance) * data.withdrawPct) / 100),
cowAmm.token1.decimals
);
const txArgs = {
type: TRANSACTION_TYPES.WITHDRAW,
amm: cowAmm.constantProductAddress,
amount0,
amount1,
chainId: chainId as ChainId,
} as withdrawCowAMMargs;
try {
await sendTransactions([txArgs]);
router.push(`/amms/${cowAmm.id}`);
} catch {
toast({
title: `Transaction failed`,
description: "An error occurred while processing the transaction.",
variant: "destructive",
});
}
};

const {
control,
formState: { isSubmitting },
} = form;

const { withdrawPct } = form.watch();

return (
<Form
{...form}
onSubmit={onSubmit}
className="flex flex-col gap-y-3 px-9 pb-9"
>
<div className="flex flex-col w-full">
<span className="mb-2 h-5 block">
Withdraw percentage: {withdrawPct}%
</span>
<Controller
name="withdrawPct"
control={control}
render={({ field }) => (
<Slider.Root
className="relative flex items-center select-none touch-none w-full h-5"
max={100}
step={0.1}
min={0.1}
onValueChange={field.onChange}
value={[field.value]}
>
<Slider.Track className="relative grow rounded-full h-[3px]">
<Slider.Range className="absolute bg-primary rounded-full h-full" />
</Slider.Track>
<Slider.Thumb className="block w-5 h-5 bg-primary rounded-[10px] hover:bg-primary/90 focus:outline-none" />
</Slider.Root>
)}
/>
</div>
<Table color="sand" classNames="overflow-y-auto">
<Table.HeaderRow>
<Table.HeaderCell>Balance</Table.HeaderCell>
<Table.HeaderCell>
Withdraw ($
{formatNumber((cowAmm.totalUsdValue * withdrawPct) / 100, 4)})
</Table.HeaderCell>
</Table.HeaderRow>
<Table.Body>
{[cowAmm.token0, cowAmm.token1].map((token) => {
return (
<Table.BodyRow key={token.address}>
<Table.BodyCell>
<TokenInfo token={token} />
</Table.BodyCell>
<Table.BodyCell>
<div className="flex flex-col gap-1 justify-end">
<span className="font-semibold">
{formatNumber(
(Number(token.balance) * withdrawPct) / 100,
4
)}{" "}
{token.symbol}
</span>
<span className="text-sm text-background/50">
$
{formatNumber(
(Number(token.usdValue) * withdrawPct) / 100,
4
)}
</span>
</div>
</Table.BodyCell>
</Table.BodyRow>
);
})}
</Table.Body>
</Table>
<Button
variant="highlight"
type="submit"
className="w-full"
disabled={isSubmitting}
>
Withdraw
</Button>
</Form>
);
}
58 changes: 58 additions & 0 deletions apps/cow-amm-deployer/src/app/amms/[id]/withdraw/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"use client";

import { useSafeAppsSDK } from "@gnosis.pm/safe-apps-react-sdk";
import { ArrowLeftIcon } from "@radix-ui/react-icons";

import { LinkComponent } from "#/components/Link";
import { Spinner } from "#/components/Spinner";
import { UnsuportedChain } from "#/components/UnsuportedChain";
import WalletNotConnected from "#/components/WalletNotConnected";
import { useStandaloneAMM } from "#/hooks/useStandaloneAmm";
import { supportedChainIds } from "#/utils/chainsPublicClients";

import { WithdrawForm } from "./(components)/WithdrawForm";

export default function Page({ params }: { params: { id: `0x${string}` } }) {
const { data: cowAmm, loading } = useStandaloneAMM(params.id);

const { safe, connected } = useSafeAppsSDK();

if (!connected) {
return <WalletNotConnected />;
}

if (!cowAmm || loading) {
return <Spinner />;
}

if (!supportedChainIds.includes(safe.chainId)) {
return <UnsuportedChain />;
}

return (
<div className="flex size-full items-center justify-center">
<div className="my-4 flex flex-col border-2 border-foreground bg-card border-card-foreground text-card-foreground">
<div className="relative flex size-full justify-center">
<LinkComponent
href={`/amms/${params.id}`}
content={
<div className="absolute left-8 flex h-full items-center">
<ArrowLeftIcon
height={16}
width={16}
className="text-background duration-200 hover:text-highlight"
/>{" "}
</div>
}
/>
<div className="flex w-[530px] flex-col items-center py-3">
<div className="text-xl">Proportional withdraw</div>
</div>
</div>
<div className="flex flex-col w-[530px] overflow-auto size-full max-h-[550px]">
<WithdrawForm cowAmm={cowAmm} />
</div>
</div>
</div>
);
}
14 changes: 7 additions & 7 deletions apps/cow-amm-deployer/src/components/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { createContext, useContext } from "react";
const TableContext = createContext({});

const predefinedClasses = {
gray: {
sand: {
solid: {
dark: { style: "bg-sand/90", border: "border-0" },
darkWithBorder: {
Expand All @@ -15,11 +15,11 @@ const predefinedClasses = {
},
},
},
brown: {
background: {
solid: {
dark: { style: "bg-primary/90", border: "border-0" },
dark: { style: "bg-background/90", border: "border-0" },
darkWithBorder: {
style: "bg-primary/90",
style: "bg-background/90",
border: "border border-brown6",
},
},
Expand All @@ -36,8 +36,8 @@ const predefinedClasses = {
} as const;

type TableColor = keyof typeof predefinedClasses;
type TableVariant = keyof typeof predefinedClasses.brown;
type TableShade = keyof typeof predefinedClasses.brown.solid;
type TableVariant = keyof typeof predefinedClasses.background;
type TableShade = keyof typeof predefinedClasses.background.solid;

function useTableContext() {
const context = useContext(TableContext);
Expand All @@ -53,7 +53,7 @@ function useTableContext() {

export default function Table({
children,
color = "gray",
color = "sand",
variant = "solid",
shade = "dark",
classNames,
Expand Down
4 changes: 4 additions & 0 deletions apps/cow-amm-deployer/src/lib/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,3 +233,7 @@ export const ammFormSchema = z
});
}
});

export const ammWithdrawSchema = z.object({
withdrawPct: z.coerce.number().positive().lte(100),
});
36 changes: 32 additions & 4 deletions apps/cow-amm-deployer/src/lib/transactionFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,21 @@ export enum TRANSACTION_TYPES {
CREATE_COW_AMM = "CREATE_COW_AMM",
EDIT_COW_AMM = "EDIT_COW_AMM",
STOP_COW_AMM = "STOP_COW_AMM",
WITHDRAW = "WITHDRAW_COW_AMM",
}

export interface BaseArgs {
type: TRANSACTION_TYPES;
}

export interface withdrawCowAMMargs extends BaseArgs {
type: TRANSACTION_TYPES.WITHDRAW;
amm: Address;
amount0: bigint;
amount1: bigint;
chainId: ChainId;
}

export interface stopCowAmmArgs extends BaseArgs {
type: TRANSACTION_TYPES.STOP_COW_AMM;
chainId: ChainId;
Expand Down Expand Up @@ -59,6 +68,25 @@ export interface ERC20ApproveArgs extends BaseArgs {
spender: Address;
amount: bigint;
}

class CoWAMMWithdrawRawTx implements ITransaction<withdrawCowAMMargs> {
createRawTx({
amm,
amount0,
amount1,
chainId,
}: withdrawCowAMMargs): BaseTransaction {
return {
to: COW_CONSTANT_PRODUCT_FACTORY[chainId],
value: "0",
data: encodeFunctionData({
abi: ConstantProductFactoryABI,
functionName: "withdraw",
args: [amm, amount0, amount1],
}),
};
}
}
class ERC20ApproveRawTx implements ITransaction<ERC20ApproveArgs> {
createRawTx({
tokenAddress,
Expand Down Expand Up @@ -154,11 +182,11 @@ class CowAmmStopTx implements ITransaction<stopCowAmmArgs> {
}
}
export interface TransactionBindings {
// [TRANSACTION_TYPES.ENABLE_COW_AMM_MODULE]: enableCowAmmModuleArgs;
[TRANSACTION_TYPES.ERC20_APPROVE]: ERC20ApproveArgs;
[TRANSACTION_TYPES.CREATE_COW_AMM]: createCowAmmArgs;
[TRANSACTION_TYPES.STOP_COW_AMM]: stopCowAmmArgs;
[TRANSACTION_TYPES.EDIT_COW_AMM]: editCowAmmArgs;
[TRANSACTION_TYPES.WITHDRAW]: withdrawCowAMMargs;
}

export type AllTransactionArgs = TransactionBindings[keyof TransactionBindings];
Expand All @@ -168,17 +196,17 @@ const TRANSACTION_CREATORS: {
TransactionBindings[key]
>;
} = {
// [TRANSACTION_TYPES.ENABLE_COW_AMM_MODULE]: CowAmmEnableModuleTx,
[TRANSACTION_TYPES.ERC20_APPROVE]: ERC20ApproveRawTx,
[TRANSACTION_TYPES.CREATE_COW_AMM]: CowAmmCreateTx,
[TRANSACTION_TYPES.STOP_COW_AMM]: CowAmmStopTx,
[TRANSACTION_TYPES.EDIT_COW_AMM]: CowAmmEditTx,
[TRANSACTION_TYPES.WITHDRAW]: CoWAMMWithdrawRawTx,
};

export class TransactionFactory {
static createRawTx<T extends TRANSACTION_TYPES>(
type: T,
args: TransactionBindings[T],
args: TransactionBindings[T]
): BaseTransaction {
const TransactionCreator = TRANSACTION_CREATORS[type];
const txCreator = new TransactionCreator();
Expand All @@ -197,7 +225,7 @@ export function buildTxAMMArgs({
}): AllTransactionArgs[] {
const priceOracleData = encodePriceOracleData(data as IEncodePriceOracleData);
const priceOracleAddress = getPriceOracleAddress(
data as IGetPriceOracleAddress,
data as IGetPriceOracleAddress
);

return [
Expand Down
Loading

0 comments on commit e9129d1

Please sign in to comment.