From 13f2dfe4e4d972872644ee1200eddb2c0249b238 Mon Sep 17 00:00:00 2001 From: vkulinich Date: Wed, 2 Oct 2024 11:12:30 +0200 Subject: [PATCH] Add liquidity limit --- src/api/pools.ts | 8 +- src/components/Input/Input.tsx | 56 ++++++------- src/i18n/locales/en/translations.json | 3 + .../modals/AddLiquidity/AddLiquidity.tsx | 16 ++++ .../modals/AddLiquidity/AddLiquidity.utils.ts | 10 ++- .../modals/AddLiquidity/AddLiquidityForm.tsx | 50 +++++++++-- .../LimitModal/LimitModal.styled.ts | 15 ++++ .../components/LimitModal/LimitModal.tsx | 82 +++++++++++++++++++ .../stablepool/transfer/TransferModal.tsx | 15 +++- src/sections/pools/table/PoolsTable.utils.tsx | 50 ++++++----- src/state/liquidityLimit.ts | 19 +++++ 11 files changed, 263 insertions(+), 61 deletions(-) create mode 100644 src/sections/pools/modals/AddLiquidity/components/LimitModal/LimitModal.styled.ts create mode 100644 src/sections/pools/modals/AddLiquidity/components/LimitModal/LimitModal.tsx create mode 100644 src/state/liquidityLimit.ts diff --git a/src/api/pools.ts b/src/api/pools.ts index f8661fb64..04c9cbc04 100644 --- a/src/api/pools.ts +++ b/src/api/pools.ts @@ -7,6 +7,7 @@ import { QUERY_KEYS } from "utils/queryKeys" import { useAccountBalances } from "./accountBalances" import { ApiPromise } from "@polkadot/api" import type { u32 } from "@polkadot/types" +import { BN_NAN } from "utils/constants" export const useShareOfPools = (assets: string[]) => { const { account } = useAccount() @@ -64,11 +65,12 @@ export const useSDKPools = () => { const getDynamicAssetFees = (api: ApiPromise, assetId: string | u32) => async () => { const res = await api.query.dynamicFees.assetFee(assetId) - const data = res.unwrap() + + const data = res.unwrapOr(null) return { - protocolFee: data.protocolFee.toBigNumber().div(10_000), - assetFee: data.assetFee.toBigNumber().div(10_000), + protocolFee: data?.protocolFee.toBigNumber().div(10_000) ?? BN_NAN, + assetFee: data?.assetFee.toBigNumber().div(10_000) ?? BN_NAN, } } diff --git a/src/components/Input/Input.tsx b/src/components/Input/Input.tsx index 09a42d483..a68338426 100644 --- a/src/components/Input/Input.tsx +++ b/src/components/Input/Input.tsx @@ -39,35 +39,33 @@ export const Input = forwardRef( ref, ) => { return ( - <> - - + ) }, ) diff --git a/src/i18n/locales/en/translations.json b/src/i18n/locales/en/translations.json index c03656fb7..f767b9d62 100644 --- a/src/i18n/locales/en/translations.json +++ b/src/i18n/locales/en/translations.json @@ -14,6 +14,7 @@ "details": "Details", "manage": "Manage", "close": "Close", + "edit": "Edit", "submit": "Submit", "transfer": "Transfer", "24Volume": "24h volume", @@ -270,6 +271,8 @@ "liquidity.asset.capacity.full": "<0>Asset cap reached: <1>{{ filled, compact }} / {{ capacity, compact }} {{ symbol }}", "liquidity.add.modal.button.joinFarms": "Add liquidity & Join Farms", "liquidity.add.modal.title": "Add liquidity", + "liquidity.add.modal.limit.title": "Add Liquidity Limit", + "liquidity.add.modal.limit.validation.max": "Max value is {{value}}", "liquidity.add.modal.provideLiquidity": "Provide Liquidity", "liquidity.add.modal.provideLiquidity.loading": "Providing liquidity", "liquidity.add.modal.row.transactionCostValue": "≈ {{ amount, bignumber }} {{ symbol }}", diff --git a/src/sections/pools/modals/AddLiquidity/AddLiquidity.tsx b/src/sections/pools/modals/AddLiquidity/AddLiquidity.tsx index a55bf649c..2a5af169c 100644 --- a/src/sections/pools/modals/AddLiquidity/AddLiquidity.tsx +++ b/src/sections/pools/modals/AddLiquidity/AddLiquidity.tsx @@ -20,10 +20,12 @@ import { isEvmAccount } from "utils/evm" import { useAccount } from "sections/web3-connect/Web3Connect.utils" import { scaleHuman } from "utils/balance" import { usePoolData } from "sections/pools/pool/Pool" +import { LimitModal } from "./components/LimitModal/LimitModal" export enum Page { ADD_LIQUIDITY, ASSET_SELECTOR, + LIMIT_LIQUIDITY, WAIT, } @@ -182,6 +184,11 @@ export const AddLiquidity = ({ isOpen, onClose, farms }: Props) => { page={page} direction={direction} onClose={onClose} + onBack={ + page === Page.LIMIT_LIQUIDITY + ? () => paginateTo(Page.ADD_LIQUIDITY) + : undefined + } contents={[ { title: t("liquidity.add.modal.title"), @@ -203,6 +210,7 @@ export const AddLiquidity = ({ isOpen, onClose, farms }: Props) => { onSuccess={onSuccess} isJoinFarms={isJoinFarms} setIsJoinFarms={setIsJoinFarms} + setLiquidityLimit={() => paginateTo(Page.LIMIT_LIQUIDITY)} /> ), }, @@ -220,6 +228,14 @@ export const AddLiquidity = ({ isOpen, onClose, farms }: Props) => { /> ), }, + { + title: t("liquidity.add.modal.limit.title"), + noPadding: true, + headerVariant: "GeistMono", + content: ( + paginateTo(Page.ADD_LIQUIDITY)} /> + ), + }, { title: steps[currentStep].label, headerVariant: "gradient", diff --git a/src/sections/pools/modals/AddLiquidity/AddLiquidity.utils.ts b/src/sections/pools/modals/AddLiquidity/AddLiquidity.utils.ts index c18e348de..e6bec4545 100644 --- a/src/sections/pools/modals/AddLiquidity/AddLiquidity.utils.ts +++ b/src/sections/pools/modals/AddLiquidity/AddLiquidity.utils.ts @@ -62,7 +62,7 @@ export const getAddToOmnipoolFee = (api: ApiPromise, farms: Farm[]) => { return txs } -const getSharesToGet = ( +export const getSharesToGet = ( omnipoolAsset: TOmnipoolAssetsData[number], amount: string, ) => { @@ -98,7 +98,7 @@ export const useAddLiquidity = (assetId: string, assetValue?: string) => { const { account } = useAccount() const { data: assetBalance } = useTokenBalance(assetId, account?.address) - const poolShare = useMemo(() => { + const { poolShare, sharesToGet } = useMemo(() => { if (ommipoolAsset && assetValue) { const sharesToGet = getSharesToGet( ommipoolAsset, @@ -108,16 +108,20 @@ export const useAddLiquidity = (assetId: string, assetValue?: string) => { const totalShares = BigNumber(ommipoolAsset.shares).plus(sharesToGet) const poolShare = BigNumber(sharesToGet).div(totalShares).times(100) - return poolShare + return { poolShare, sharesToGet } } + + return { poolShare: undefined, sharesToGet: undefined } }, [assetValue, ommipoolAsset, pool.meta.decimals]) return { poolShare, + sharesToGet, spotPrice, omnipoolFee, assetMeta: pool.meta, assetBalance, + ommipoolAsset, } } diff --git a/src/sections/pools/modals/AddLiquidity/AddLiquidityForm.tsx b/src/sections/pools/modals/AddLiquidity/AddLiquidityForm.tsx index 2c5675237..4945ba91b 100644 --- a/src/sections/pools/modals/AddLiquidity/AddLiquidityForm.tsx +++ b/src/sections/pools/modals/AddLiquidity/AddLiquidityForm.tsx @@ -1,6 +1,6 @@ import { Controller, FieldErrors, useForm } from "react-hook-form" import BigNumber from "bignumber.js" -import { BN_0 } from "utils/constants" +import { BN_0, BN_100 } from "utils/constants" import { WalletTransferAssetSelect } from "sections/wallet/transfer/WalletTransferAssetSelect" import { SummaryRow } from "components/Summary/SummaryRow" import { Spacer } from "components/Spacer/Spacer" @@ -10,11 +10,12 @@ import { Trans, useTranslation } from "react-i18next" import { DisplayValue } from "components/DisplayValue/DisplayValue" import { PoolAddLiquidityInformationCard } from "./AddLiquidityInfoCard" import { Separator } from "components/Separator/Separator" -import { Button } from "components/Button/Button" +import { Button, ButtonTransparent } from "components/Button/Button" import { FormValues } from "utils/helpers" import { scale } from "utils/balance" import { getAddToOmnipoolFee, + getSharesToGet, useAddLiquidity, useAddToOmnipoolZod, } from "./AddLiquidity.utils" @@ -31,6 +32,7 @@ import { useEffect } from "react" import { useAssets } from "providers/assets" import { Switch } from "components/Switch/Switch" import { FarmDetailsRow } from "sections/pools/farms/components/detailsCard/FarmDetailsRow" +import { useLiquidityLimit } from "state/liquidityLimit" type Props = { assetId: string @@ -42,6 +44,7 @@ type Props = { farms: Farm[] isJoinFarms: boolean setIsJoinFarms: (value: boolean) => void + setLiquidityLimit: () => void } export const AddLiquidityForm = ({ @@ -54,11 +57,13 @@ export const AddLiquidityForm = ({ farms, isJoinFarms, setIsJoinFarms, + setLiquidityLimit, }: Props) => { const { t } = useTranslation() const { api } = useRpcProvider() const { native } = useAssets() const { createTransaction } = useStore() + const { addLiquidityLimit } = useLiquidityLimit() const zodSchema = useAddToOmnipoolZod(assetId, farms) const form = useForm<{ @@ -73,8 +78,14 @@ export const AddLiquidityForm = ({ const [debouncedAmount] = useDebouncedValue(watch("amount"), 300) - const { poolShare, spotPrice, omnipoolFee, assetMeta, assetBalance } = - useAddLiquidity(assetId, debouncedAmount) + const { + poolShare, + spotPrice, + omnipoolFee, + assetMeta, + assetBalance, + ommipoolAsset, + } = useAddLiquidity(assetId, debouncedAmount) const estimatedFees = useEstimatedFees(getAddToOmnipoolFee(api, farms)) @@ -91,8 +102,19 @@ export const AddLiquidityForm = ({ const amount = scale(values.amount, assetMeta.decimals).toString() + const tx = + BigNumber(addLiquidityLimit).gt(0) && ommipoolAsset + ? api.tx.omnipool.addLiquidityWithLimit( + assetId, + amount, + getSharesToGet(ommipoolAsset, amount) + .times(BN_100.minus(addLiquidityLimit).div(BN_100)) + .toFixed(0), + ) + : api.tx.omnipool.addLiquidity(assetId, amount) + return await createTransaction( - { tx: api.tx.omnipool.addLiquidity(assetId, amount) }, + { tx }, { onSuccess: (result) => { onSuccess(result, amount) @@ -186,6 +208,24 @@ export const AddLiquidityForm = ({ )} /> + + {t("value.percentage", { value: addLiquidityLimit })} + setLiquidityLimit()}> + {t("edit")} + + + } + /> + void }) => { + const { t } = useTranslation() + const { addLiquidityLimit, udpate } = useLiquidityLimit() + + const form = useForm<{ + value: string + }>({ + mode: "onChange", + defaultValues: { value: addLiquidityLimit }, + resolver: zodResolver( + z.object({ + value: required.refine((value) => BN(value).lte(100), { + message: t("liquidity.add.modal.limit.validation.max", { + value: 100, + }), + }), + }), + ), + }) + + return ( +
{ + udpate(value) + onConfirm() + })} + sx={{ flex: "column", justify: "space-between", height: 300, p: 24 }} + autoComplete="off" + > + ( + + onChange(value.toString())} + /> +
+ + {error && {error.message}} +
+
+ )} + /> + + + + ) +} diff --git a/src/sections/pools/stablepool/transfer/TransferModal.tsx b/src/sections/pools/stablepool/transfer/TransferModal.tsx index c7209511c..76c7b86db 100644 --- a/src/sections/pools/stablepool/transfer/TransferModal.tsx +++ b/src/sections/pools/stablepool/transfer/TransferModal.tsx @@ -28,6 +28,7 @@ import { scaleHuman } from "utils/balance" import { isEvmAccount } from "utils/evm" import { useAssets } from "providers/assets" import { usePoolData } from "sections/pools/pool/Pool" +import { LimitModal } from "sections/pools/modals/AddLiquidity/components/LimitModal/LimitModal" export enum Page { OPTIONS, @@ -35,6 +36,7 @@ export enum Page { WAIT, MOVE_TO_OMNIPOOL, ASSETS, + LIMIT_LIQUIDITY, } type Props = { @@ -282,7 +284,9 @@ export const TransferModal = ({ onClose, defaultPage, farms }: Props) => { onBack={ !defaultPage && ![Page.OPTIONS, Page.WAIT].includes(page) ? goBack - : undefined + : page === Page.LIMIT_LIQUIDITY + ? () => paginateTo(Page.MOVE_TO_OMNIPOOL) + : undefined } contents={[ { @@ -367,6 +371,7 @@ export const TransferModal = ({ onClose, defaultPage, farms }: Props) => { onSubmitted={() => paginateTo(Page.WAIT)} isJoinFarms={isJoinFarms} setIsJoinFarms={setIsJoinFarms} + setLiquidityLimit={() => paginateTo(Page.LIMIT_LIQUIDITY)} /> ), }, @@ -386,6 +391,14 @@ export const TransferModal = ({ onClose, defaultPage, farms }: Props) => { /> ), }, + { + title: t("liquidity.add.modal.limit.title"), + noPadding: true, + headerVariant: "GeistMono", + content: ( + paginateTo(Page.MOVE_TO_OMNIPOOL)} /> + ), + }, ]} /> diff --git a/src/sections/pools/table/PoolsTable.utils.tsx b/src/sections/pools/table/PoolsTable.utils.tsx index babc107ef..e43002a09 100644 --- a/src/sections/pools/table/PoolsTable.utils.tsx +++ b/src/sections/pools/table/PoolsTable.utils.tsx @@ -48,6 +48,7 @@ import { TransferModal, } from "sections/pools/stablepool/transfer/TransferModal" import { useDynamicAssetFees } from "api/pools" +import { AddLiquidity } from "sections/pools/modals/AddLiquidity/AddLiquidity" const NonClickableContainer = ({ children, @@ -74,10 +75,6 @@ const NonClickableContainer = ({ const AssetTableName = ({ pool }: { pool: TPool | TXYKPool }) => { const asset = pool.meta - - const farms = useFarms([pool.id]) - const dynamicFees = useDynamicAssetFees(pool.meta.id) - const isDesktop = useMedia(theme.viewport.gte.md) return ( @@ -126,15 +123,7 @@ const AssetTableName = ({ pool }: { pool: TPool | TXYKPool }) => { {asset.name} )} - {farms.data?.length > 0 && !isDesktop && ( - - )} + {!isDesktop && } ) @@ -217,6 +206,7 @@ const LiquidityModalWrapper: React.FC<{ ) if (!pool) return null + return ( - + {pool.meta.isStableSwap ? ( + + ) : ( + + )} ) } @@ -314,6 +306,24 @@ const ManageLiquidityButton: React.FC<{ ) } +const CompactAPY = ({ assetId }: { assetId: string }) => { + const farms = useFarms([assetId]) + const dynamicFees = useDynamicAssetFees(assetId) + + if (farms.data?.length) + return ( + + ) + + return null +} + const APY = ({ assetId, fee, diff --git a/src/state/liquidityLimit.ts b/src/state/liquidityLimit.ts new file mode 100644 index 000000000..750603698 --- /dev/null +++ b/src/state/liquidityLimit.ts @@ -0,0 +1,19 @@ +import { create } from "zustand" +import { persist } from "zustand/middleware" + +type LimitStore = { + addLiquidityLimit: string + udpate: (value: string) => void +} + +export const useLiquidityLimit = create()( + persist( + (set) => ({ + addLiquidityLimit: "1", + udpate: (value) => set(() => ({ addLiquidityLimit: value })), + }), + { + name: "liquidity-limit", + }, + ), +)