Skip to content

Commit

Permalink
Create Withdraw page (#687)
Browse files Browse the repository at this point in the history
* Refactor interface of token info and token logo components

* Add withdraw relevant data to ICoWAMM

* Add withdraw page

* Remove unecessary imagesSrc states on token logo component
  • Loading branch information
yvesfracari authored May 29, 2024
1 parent a12646a commit 1bde62a
Show file tree
Hide file tree
Showing 11 changed files with 1,522 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ import { tomatoDark } from "@radix-ui/colors";
import { InfoCircledIcon } from "@radix-ui/react-icons";

import Table from "#/components/Table";
import { TokenInfo } from "#/components/TokenInfo";
import { Tooltip } from "#/components/Tooltip";
import { ICowAmm } from "#/lib/fetchAmmData";

import { TokenInfo } from "./TokenInfo";

export function PoolCompositionTable({ cowAmm }: { cowAmm: ICowAmm }) {
const anyTokenWithoutUsdPrice =
!cowAmm.token0.usdPrice || !cowAmm.token1.usdPrice;
Expand All @@ -30,17 +29,9 @@ export function PoolCompositionTable({ cowAmm }: { cowAmm: ICowAmm }) {
const valuePct =
(Number(token.usdValue) * 100) / cowAmm.totalUsdValue;
return (
// @ts-ignore
<Table.BodyRow key={token.address}>
<Table.BodyCell>
<TokenInfo
// @ts-ignore
symbol={token.symbol}
// @ts-ignore
id={token.address}
// @ts-ignore
logoUri={token.logoUri}
/>
<TokenInfo token={token} />
</Table.BodyCell>
<Table.BodyCell>{formatNumber(token.balance, 4)}</Table.BodyCell>
<Table.BodyCell>
Expand Down
12 changes: 6 additions & 6 deletions apps/cow-amm-deployer/src/app/amms/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,8 @@ import { ChainId } from "#/utils/chainsPublicClients";
import { PoolCompositionTable } from "./(components)/PoolCompositionTable";
import { PriceInformation } from "./(components)/PriceInformation";

async function getData({ params }: { params: { id: string } }) {
const ammData = await fetchAmmData(params.id);
return ammData;
}

export default async function Page({ params }: { params: { id: string } }) {
const ammData = await getData({ params });
const ammData = await fetchAmmData(params.id);

return (
<div className="flex w-full justify-center">
Expand Down Expand Up @@ -83,6 +78,11 @@ export default async function Page({ params }: { params: { id: string } }) {
<Pencil2Icon />
Edit CoW AMM LP parameters
</Button>
<Link href={`/amms/${params.id}/withdraw`}>
<Button className="flex items-center gap-1 py-3 px-6">
Withdraw
</Button>
</Link>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
"use client";

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 { ICowAmm } from "#/lib/fetchAmmData";
import { ammWithdrawSchema } from "#/lib/schema";
import {
TRANSACTION_TYPES,
withdrawCowAMMargs,
} from "#/lib/transactionFactory";
import { ChainId } from "#/utils/chainsPublicClients";

export function WithdrawForm({ cowAmm }: { cowAmm: ICowAmm }) {
const form = useForm<typeof ammWithdrawSchema._type>({
// @ts-ignore
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.order.owner,
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>
);
}
66 changes: 66 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,66 @@
"use client";

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

import { LinkComponent } from "#/components/Link";
import { Spinner } from "#/components/Spinner";
import { UnsuportedChain } from "#/components/UnsuportedChain";
import WalletNotConnected from "#/components/WalletNotConnected";
import { fetchAmmData, ICowAmm } from "#/lib/fetchAmmData";
import { supportedChainIds } from "#/utils/chainsPublicClients";

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

export default function Page({ params }: { params: { id: `0x${string}` } }) {
const [ammData, setAmmData] = useState<ICowAmm>();

async function loadAmmData() {
const data = await fetchAmmData(params.id);
setAmmData(data);
}
const { safe, connected } = useSafeAppsSDK();
useEffect(() => {
loadAmmData();
}, [params.id]);

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

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

if (!ammData) {
return <Spinner />;
}

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={ammData} />
</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
Original file line number Diff line number Diff line change
@@ -1,40 +1,31 @@
import { formatNumber } from "@bleu/utils/formatNumber";
import { formatNumber } from "@bleu/ui";
import { useSafeAppsSDK } from "@gnosis.pm/safe-apps-react-sdk";

import { TokenLogo } from "#/components/TokenLogo";
import { IToken, ITokenExtended } from "#/lib/types";
import { ChainId } from "#/utils/chainsPublicClients";
import { truncateAddress } from "#/utils/truncate";

export function TokenInfo({
symbol,
id,
amount,
logoUri,
}: {
logoUri: string;
symbol?: string | null;
id?: string;
amount?: number | string;
}) {
export function TokenInfo({ token }: { token: IToken | ITokenExtended }) {
const { safe } = useSafeAppsSDK();

return (
<div className="flex items-center gap-x-1">
<div className="flex items-center justify-center">
<div className="rounded-full">
<div className="rounded-full bg-white p-1">
<TokenLogo
src={logoUri}
tokenAddress={id}
tokenAddress={token.address}
chainId={safe.chainId as ChainId}
className="rounded-full"
alt="Token Logo"
height={28}
width={28}
height={22}
width={22}
quality={100}
/>
</div>
</div>
{symbol ? symbol : truncateAddress(id)}{" "}
{amount && `(${formatNumber(amount, 4, "decimal", "compact", 0.001)})`}
{token.symbol}{" "}
{"balance" in token &&
`(${formatNumber(token.balance, 4, "decimal", "compact", 0.001)})`}
</div>
);
}
Loading

0 comments on commit 1bde62a

Please sign in to comment.