diff --git a/apps/main/src/components/DynamicFee/ExpandableDynamicFee.tsx b/apps/main/src/components/DynamicFee/ExpandableDynamicFee.tsx index 0124fbc4c5..3e257d6941 100644 --- a/apps/main/src/components/DynamicFee/ExpandableDynamicFee.tsx +++ b/apps/main/src/components/DynamicFee/ExpandableDynamicFee.tsx @@ -24,6 +24,7 @@ import { export type FeeBreakdown = { symbol: string value: string + id: string } type ExpandableDynamicFeeProps = { @@ -32,7 +33,7 @@ type ExpandableDynamicFeeProps = { readonly rangeLow: number readonly rangeHigh: number readonly tooltip?: string - readonly range: number[] + readonly range: readonly number[] readonly feesBreakdown: FeeBreakdown[] readonly label: string readonly description?: string diff --git a/apps/main/src/i18n/locales/en/liquidity.json b/apps/main/src/i18n/locales/en/liquidity.json index bf85ed8191..57133aa073 100644 --- a/apps/main/src/i18n/locales/en/liquidity.json +++ b/apps/main/src/i18n/locales/en/liquidity.json @@ -132,6 +132,8 @@ "liquidity.remove.modal.multiple.button": "Continue to summary", "liquidity.joinFarms.modal.submit.btn_one": "Join {{count, number}} Farm", "liquidity.joinFarms.modal.submit.btn_other": "Join {{count, number}} Farms", + "liquidity.remove.stablepool.modal.position.option.omnipool": "Remove to shares", + "liquidity.remove.stablepool.modal.position.option.stablepool": "Remove full", "liquidity.remove.stablepool.modal.proportionally": "Remove proportionally", "liquidity.remove.stablepool.modal.price": "1 {{poolSymbol}} = {{value, currency}}", "liquidity.remove.stablepool.modal.toast.submitted": "Withdrawing {{ value, number }} {{symbol}} from Stablepool...", diff --git a/apps/main/src/modules/liquidity/components/AddLiquidity/AddLiquidity.tsx b/apps/main/src/modules/liquidity/components/AddLiquidity/AddLiquidity.tsx index f6dba80249..c5cebd227f 100644 --- a/apps/main/src/modules/liquidity/components/AddLiquidity/AddLiquidity.tsx +++ b/apps/main/src/modules/liquidity/components/AddLiquidity/AddLiquidity.tsx @@ -67,7 +67,7 @@ export const AddLiquidity: FC = ({ onBack={onBack} />
- + label={t("liquidity.add.modal.selectAsset")} assetFieldName="asset" @@ -111,10 +111,8 @@ export const AddLiquidity: FC = ({ sx={{ my: "xxl" }} /> )} - - - + - + ) } diff --git a/apps/main/src/modules/liquidity/components/PositionsTable/OmnipoolPositions.tsx b/apps/main/src/modules/liquidity/components/PositionsTable/OmnipoolPositions.tsx index 4a385a087f..530c91f4ae 100644 --- a/apps/main/src/modules/liquidity/components/PositionsTable/OmnipoolPositions.tsx +++ b/apps/main/src/modules/liquidity/components/PositionsTable/OmnipoolPositions.tsx @@ -75,6 +75,7 @@ export const OmnipoolPositions = ({ }} search={{ selectable: true, + stableswapId: pool.stablepoolData?.id.toString(), }} > diff --git a/apps/main/src/modules/liquidity/components/PositionsTable/PositionsTable.columns.tsx b/apps/main/src/modules/liquidity/components/PositionsTable/PositionsTable.columns.tsx index e91e07fe03..ee51b91ec4 100644 --- a/apps/main/src/modules/liquidity/components/PositionsTable/PositionsTable.columns.tsx +++ b/apps/main/src/modules/liquidity/components/PositionsTable/PositionsTable.columns.tsx @@ -307,7 +307,9 @@ export const useOmnipoolPositionsTableColumns = () => { id: original.poolId, }} search={{ - positionId: isOmnipool ? original.positionId : "", + positionId: isOmnipool + ? original.positionId + : undefined, stableswapId: original.stableswapId, }} sx={{ textDecoration: "none" }} diff --git a/apps/main/src/modules/liquidity/components/RemoveLiquidity/ReceiveAssets.tsx b/apps/main/src/modules/liquidity/components/RemoveLiquidity/ReceiveAssets.tsx index 0a77e1a2fa..0bf3ddb448 100644 --- a/apps/main/src/modules/liquidity/components/RemoveLiquidity/ReceiveAssets.tsx +++ b/apps/main/src/modules/liquidity/components/RemoveLiquidity/ReceiveAssets.tsx @@ -21,9 +21,11 @@ export type TReceiveAsset = { export const ReceiveAssets = ({ assets, + title, positions, }: { assets: TReceiveAsset[] + title?: string positions?: Array }) => { const { t } = useTranslation(["common", "liquidity"]) @@ -33,7 +35,7 @@ export const ReceiveAssets = ({ return ( <> - {t("minimumReceived")} + {title ?? t("minimumReceived")} } ))} - {!!positions?.length && ( + {!!positions?.length && formattedRewards !== "0" && ( <> diff --git a/apps/main/src/modules/liquidity/components/RemoveLiquidity/RemoveLiquidity.tsx b/apps/main/src/modules/liquidity/components/RemoveLiquidity/RemoveLiquidity.tsx index 2731cff952..c3441793bd 100644 --- a/apps/main/src/modules/liquidity/components/RemoveLiquidity/RemoveLiquidity.tsx +++ b/apps/main/src/modules/liquidity/components/RemoveLiquidity/RemoveLiquidity.tsx @@ -3,6 +3,7 @@ import { Flex, ModalBody, ModalContentDivider, + ModalFooter, ModalHeader, Text, } from "@galacticcouncil/ui/components" @@ -18,6 +19,7 @@ import { TAssetData } from "@/api/assets" import { AssetLogo } from "@/components/AssetLogo" import { ExpandableDynamicFee, FeeBreakdown } from "@/components/DynamicFee" import { AssetSelectFormField } from "@/form/AssetSelectFormField" +import { WITHDRAW_FEE_RANGE } from "@/modules/liquidity/components/RemoveLiquidity/RemoveOmnipoolLiquidity.utils" import { TradeLimitRow, TradeLimitType, @@ -57,8 +59,6 @@ export const RemoveLiquidity = (props: RemoveLiquidityProps) => { ) } else if (isIsolatedPool) { return - } else if (props.positionId) { - return } else if (props.erc20Id && props.stableswapId) { return ( { ) } else if (props.stableswapId) { return + } else if (props.positionId) { + return } return null @@ -117,77 +119,74 @@ export const RemoveLiquidityForm = ({ : undefined return ( - <> + - - - - {!editable ? ( - - - - {t("common:currency", { - value: totalPositionShifted, - symbol: meta.symbol, - })} - - - ) : ( - - assetFieldName="asset" - amountFieldName="amount" - maxBalance={totalPositionShifted} - ignoreDisplayValue={isIsolatedPool} - assets={[]} - disabledAssetSelector - sx={{ pt: 0 }} + + + {!editable ? ( + + - )} - - - - - - {!isIsolatedPool && ( -
- - - - - {fee && feesBreakdown && ( - - )} -
- )} - - - - - -
-
- + + {t("common:currency", { + value: totalPositionShifted, + symbol: meta.symbol, + })} + +
+ ) : ( + + assetFieldName="asset" + amountFieldName="amount" + maxBalance={totalPositionShifted} + ignoreDisplayValue={isIsolatedPool} + assets={[]} + disabledAssetSelector + sx={{ pt: 0 }} + /> + )} + + + + + + {!isIsolatedPool && ( +
+ + + + + {fee && feesBreakdown && ( + + )} +
+ )} +
+
+ + + + ) } diff --git a/apps/main/src/modules/liquidity/components/RemoveLiquidity/RemoveMoneyMarketLiquidity.tsx b/apps/main/src/modules/liquidity/components/RemoveLiquidity/RemoveMoneyMarketLiquidity.tsx index 03bb5547b4..b631997858 100644 --- a/apps/main/src/modules/liquidity/components/RemoveLiquidity/RemoveMoneyMarketLiquidity.tsx +++ b/apps/main/src/modules/liquidity/components/RemoveLiquidity/RemoveMoneyMarketLiquidity.tsx @@ -5,6 +5,7 @@ import { Flex, ModalBody, ModalContentDivider, + ModalFooter, ModalHeader, Skeleton, Summary, @@ -105,14 +106,15 @@ export const RemoveMoneyMarketLiquidityForm = ( return ( - - - -
+ + + + + assetFieldName="asset" amountFieldName="amount" @@ -191,15 +193,15 @@ export const RemoveMoneyMarketLiquidityForm = ( healthFactor={healthFactor} /> )} - - - - - + -
+ + + + +
) } diff --git a/apps/main/src/modules/liquidity/components/RemoveLiquidity/RemoveOmnipoolLiquidity.tsx b/apps/main/src/modules/liquidity/components/RemoveLiquidity/RemoveOmnipoolLiquidity.tsx index f4afc93647..4d152d58e8 100644 --- a/apps/main/src/modules/liquidity/components/RemoveLiquidity/RemoveOmnipoolLiquidity.tsx +++ b/apps/main/src/modules/liquidity/components/RemoveLiquidity/RemoveOmnipoolLiquidity.tsx @@ -13,6 +13,7 @@ import { useState } from "react" import { FormProvider } from "react-hook-form" import { useTranslation } from "react-i18next" +import { RemoveSelectableStablepoolPositions } from "@/modules/liquidity/components/RemoveLiquidity/RemoveStablepoolLiquidity" import { AccountOmnipoolPosition, useAccountOmnipoolPositionsData, @@ -59,13 +60,24 @@ export const RemoveSelectablePositions = (props: RemoveLiquidityProps) => { } if (confirmedSelection) { - return ( - setConfirmedSelection(false)} - /> - ) + if (props.stableswapId) { + return ( + setConfirmedSelection(false)} + /> + ) + } else { + return ( + setConfirmedSelection(false)} + /> + ) + } } return ( diff --git a/apps/main/src/modules/liquidity/components/RemoveLiquidity/RemoveOmnipoolLiquidity.utils.ts b/apps/main/src/modules/liquidity/components/RemoveLiquidity/RemoveOmnipoolLiquidity.utils.ts index 6f013e6245..f74ebead39 100644 --- a/apps/main/src/modules/liquidity/components/RemoveLiquidity/RemoveOmnipoolLiquidity.utils.ts +++ b/apps/main/src/modules/liquidity/components/RemoveLiquidity/RemoveOmnipoolLiquidity.utils.ts @@ -43,9 +43,11 @@ export type TRemoveLiquidityFormValues = { asset: TSelectedAsset } -type RemoveLiquidityValues = { +export type RemoveOmnipoolResult = { tokensToGet: string tokensToGetShifted: string + minTokensToGet: string + minTokensToGetShifted: string hubToGet: string hubPayWith: string tokensPayWith: string @@ -53,9 +55,11 @@ type RemoveLiquidityValues = { minWithdrawalFee: string } -const defaultValues: RemoveLiquidityValues = { +const defaultRemoveOmnipoolLiquidityValues: RemoveOmnipoolResult = { tokensToGet: "0", tokensToGetShifted: "0", + minTokensToGet: "0", + minTokensToGetShifted: "0", hubToGet: "0", hubPayWith: "0", tokensPayWith: "0", @@ -63,7 +67,13 @@ const defaultValues: RemoveLiquidityValues = { minWithdrawalFee: "0", } -const useRemoveLiquidityOut = (poolId: string) => { +export const WITHDRAW_FEE_RANGE = { + low: 0.34, + high: 0.66, + full: [0.01, 0.34, 0.66, 1] as const, +} + +export const useRemoveOmnipoolLiquidityOut = (poolId: string) => { const { hub, getAssetWithFallback } = useAssets() const meta = getAssetWithFallback(poolId) const { data: oraclePriceData } = useOraclePrice( @@ -85,71 +95,123 @@ const useRemoveLiquidityOut = (poolId: string) => { ( position: OmnipoolPosition | OmnipoolDepositFull, removeSharesValue: string, - ) => { - if (omnipoolAssetData && oraclePrice && minWithdrawalFee) { - const lrnaSpotPrice = calculate_lrna_spot_price( - omnipoolAssetData.balance.toString(), - omnipoolAssetData.hubReserves.toString(), - ) - - const withdrawalFee = calculate_withdrawal_fee( - lrnaSpotPrice, - oraclePrice.toString(), - minWithdrawalFee.toString(), - ) - - if (!removeSharesValue) { - return { - ...defaultValues, - withdrawalFee: Big(scaleHuman(withdrawalFee, "q")) - .times(100) - .toString(), - minWithdrawalFee: minWithdrawalFee.toString(), - } - } - - const valueWithFee = getData(position, { - fee: withdrawalFee, - sharesValue: removeSharesValue, - }) - - const valueWithoutFee = getData(position, { - sharesValue: removeSharesValue, - }) + ): RemoveOmnipoolResult | undefined => { + if (!omnipoolAssetData || !oraclePrice || !minWithdrawalFee) + return undefined - if (!valueWithFee || !valueWithoutFee) return undefined - - const tokensToGet = Big(valueWithFee.currentValue) - .times(100 - slippage) - .div(100) - .toString() + const lrnaSpotPrice = calculate_lrna_spot_price( + omnipoolAssetData.balance.toString(), + omnipoolAssetData.hubReserves.toString(), + ) - const tokensToGetShifted = Big(valueWithFee.currentValueHuman) - .times(100 - slippage) - .div(100) - .toString() + const withdrawalFee = calculate_withdrawal_fee( + lrnaSpotPrice, + oraclePrice.toString(), + minWithdrawalFee.toString(), + ) + if (!removeSharesValue) { return { - tokensToGet, - tokensToGetShifted, - hubToGet: valueWithFee.currentHubValue, - hubPayWith: Big(valueWithoutFee.currentHubValueHuman) - .minus(valueWithFee.currentHubValueHuman) - .toString(), - tokensPayWith: Big(valueWithoutFee.currentValueHuman) - .minus(valueWithFee.currentValueHuman) - .toString(), + ...defaultRemoveOmnipoolLiquidityValues, withdrawalFee: Big(scaleHuman(withdrawalFee, "q")) .times(100) .toString(), minWithdrawalFee: minWithdrawalFee.toString(), } } + + const valueWithFee = getData(position, { + fee: withdrawalFee, + sharesValue: removeSharesValue, + }) + + const valueWithoutFee = getData(position, { + sharesValue: removeSharesValue, + }) + + if (!valueWithFee || !valueWithoutFee) return undefined + + const tokensToGet = valueWithFee.currentValue + const tokensToGetShifted = valueWithFee.currentValueHuman + + const minTokensToGet = Big(tokensToGet) + .times(100 - slippage) + .div(100) + .toString() + + const minTokensToGetShifted = Big(tokensToGetShifted) + .times(100 - slippage) + .div(100) + .toString() + + return { + tokensToGet, + tokensToGetShifted, + minTokensToGet, + minTokensToGetShifted, + hubToGet: valueWithFee.currentHubValue, + hubPayWith: Big(valueWithoutFee.currentHubValueHuman) + .minus(valueWithFee.currentHubValueHuman) + .toString(), + tokensPayWith: Big(valueWithoutFee.currentValueHuman) + .minus(valueWithFee.currentValueHuman) + .toString(), + withdrawalFee: Big(scaleHuman(withdrawalFee, "q")) + .times(100) + .toString(), + minWithdrawalFee: minWithdrawalFee.toString(), + } }, [getData, minWithdrawalFee, omnipoolAssetData, oraclePrice, slippage], ) } +const sumBigStrings = (a: string, b: string): string => + Big(a).plus(b).toString() + +export const getOmnipoolLiquidityOutTotal = ( + positionsOut: Array<{ + valuesOut?: RemoveOmnipoolResult + position: AccountOmnipoolPosition + }>, +) => { + const total = positionsOut.reduce((acc, { valuesOut }) => { + if (!valuesOut) return acc + + const { + tokensToGet, + tokensToGetShifted, + minTokensToGet, + minTokensToGetShifted, + hubToGet, + hubPayWith, + tokensPayWith, + withdrawalFee, + minWithdrawalFee, + } = valuesOut + + return { + tokensToGet: sumBigStrings(acc.tokensToGet, tokensToGet), + tokensToGetShifted: sumBigStrings( + acc.tokensToGetShifted, + tokensToGetShifted, + ), + minTokensToGet: sumBigStrings(acc.minTokensToGet, minTokensToGet), + minTokensToGetShifted: sumBigStrings( + acc.minTokensToGetShifted, + minTokensToGetShifted, + ), + hubToGet: sumBigStrings(acc.hubToGet, hubToGet), + hubPayWith: sumBigStrings(acc.hubPayWith, hubPayWith), + tokensPayWith: sumBigStrings(acc.tokensPayWith, tokensPayWith), + withdrawalFee: sumBigStrings(acc.withdrawalFee, withdrawalFee), + minWithdrawalFee: sumBigStrings(acc.minWithdrawalFee, minWithdrawalFee), + } + }, defaultRemoveOmnipoolLiquidityValues) + + return total +} + export const useRemoveSingleOmnipoolPosition = ({ poolId, position, @@ -179,7 +241,7 @@ export const useRemoveSingleOmnipoolPosition = ({ const amount = form.watch("amount") || "0" - const calculateLiquidityValues = useRemoveLiquidityOut(poolId) + const calculateLiquidityValues = useRemoveOmnipoolLiquidityOut(poolId) const removeShares = Big(amount) .div(totalPositionShifted) @@ -187,9 +249,10 @@ export const useRemoveSingleOmnipoolPosition = ({ .toFixed(0) const values = - calculateLiquidityValues(position, removeShares) ?? defaultValues + calculateLiquidityValues(position, removeShares) ?? + defaultRemoveOmnipoolLiquidityValues - const minAmountOut = values?.tokensToGet + const minAmountOut = values?.minTokensToGet const mutation = useMutation({ mutationFn: async (): Promise => { @@ -205,7 +268,7 @@ export const useRemoveSingleOmnipoolPosition = ({ : undefined const tOptions = { - value: values?.tokensToGetShifted, + value: values?.minTokensToGetShifted, symbol: meta.symbol, hub: hubValue, } @@ -260,10 +323,16 @@ export const useRemoveSingleOmnipoolPosition = ({ }) } - const feesBreakdown = [{ symbol: meta.symbol, value: values.tokensPayWith }] + const feesBreakdown = [ + { symbol: meta.symbol, value: values.tokensPayWith, id: meta.id }, + ] if (Big(values.hubPayWith).gt(0)) { - feesBreakdown.push({ symbol: hub.symbol, value: values.hubPayWith }) + feesBreakdown.push({ + symbol: hub.symbol, + value: values.hubPayWith, + id: hub.id, + }) } const isDeposit = isDepositPosition(position) @@ -302,7 +371,7 @@ export const useRemoveMultipleOmnipoolPositions = ({ asset: meta, }) - const calculateLiquidityValues = useRemoveLiquidityOut(poolId) + const calculateLiquidityValues = useRemoveOmnipoolLiquidityOut(poolId) let removeShares = Big(0) let totalPositionShifted = Big(0) @@ -317,37 +386,16 @@ export const useRemoveMultipleOmnipoolPositions = ({ const liquidityOutValues = positions.map((position) => { return { position, - values: calculateLiquidityValues(position, position.shares.toString()), + valuesOut: calculateLiquidityValues(position, position.shares.toString()), } }) - const values = liquidityOutValues.reduce( - (acc, { values }) => { - if (values) { - return { - tokensToGet: Big(acc.tokensToGet).plus(values.tokensToGet).toString(), - tokensToGetShifted: Big(acc.tokensToGetShifted) - .plus(values.tokensToGetShifted) - .toString(), - hubToGet: Big(acc.hubToGet).plus(values.hubToGet).toString(), - hubPayWith: Big(acc.hubPayWith).plus(values.hubPayWith).toString(), - tokensPayWith: Big(acc.tokensPayWith) - .plus(values.tokensPayWith) - .toString(), - withdrawalFee: values.withdrawalFee, - minWithdrawalFee: values.minWithdrawalFee, - } - } - - return acc - }, - defaultValues, - ) + const values = getOmnipoolLiquidityOutTotal(liquidityOutValues) const receiveAssets: TReceiveAsset[] = [ { asset: meta, - value: values.tokensToGet, + value: values.minTokensToGet, }, ] @@ -368,8 +416,8 @@ export const useRemoveMultipleOmnipoolPositions = ({ Papi["tx"]["OmnipoolLiquidityMining"]["withdraw_shares"] >[] }>( - (acc, { position, values }) => { - if (!values) return acc + (acc, { position, valuesOut }) => { + if (!valuesOut) return acc if (isOmnipoolDepositPosition(position)) { position.yield_farm_entries.forEach((entry) => { @@ -382,7 +430,7 @@ export const useRemoveMultipleOmnipoolPositions = ({ }) } - const minAmountOut = values.tokensToGet + const minAmountOut = valuesOut.minTokensToGet acc.liquidityTxs.push( papi.tx.Omnipool.remove_liquidity_with_limit({ @@ -421,10 +469,16 @@ export const useRemoveMultipleOmnipoolPositions = ({ if (!receiveAssets.length) return undefined - const feesBreakdown = [{ symbol: meta.symbol, value: values.tokensPayWith }] + const feesBreakdown = [ + { symbol: meta.symbol, value: values.tokensPayWith, id: meta.id }, + ] if (Big(values.hubPayWith).gt(0)) { - feesBreakdown.push({ symbol: hub.symbol, value: values.hubPayWith }) + feesBreakdown.push({ + symbol: hub.symbol, + value: values.hubPayWith, + id: hub.id, + }) } return { diff --git a/apps/main/src/modules/liquidity/components/RemoveLiquidity/RemoveStablepoolLiquidity.tsx b/apps/main/src/modules/liquidity/components/RemoveLiquidity/RemoveStablepoolLiquidity.tsx index 2a16fd87f2..c52a6e81e7 100644 --- a/apps/main/src/modules/liquidity/components/RemoveLiquidity/RemoveStablepoolLiquidity.tsx +++ b/apps/main/src/modules/liquidity/components/RemoveLiquidity/RemoveStablepoolLiquidity.tsx @@ -1,8 +1,11 @@ import { + Box, Button, ModalBody, ModalContentDivider, + ModalFooter, ModalHeader, + SliderTabs, SummaryRow, Text, Toggle, @@ -10,14 +13,21 @@ import { import { Flex } from "@galacticcouncil/ui/components/Flex" import { getToken } from "@galacticcouncil/ui/utils" import Big from "big.js" -import { Controller, FormProvider } from "react-hook-form" +import { t } from "i18next" +import { Controller, FormProvider, useFormContext } from "react-hook-form" import { useTranslation } from "react-i18next" +import { OmnipoolDepositFull } from "@/api/account" import { AssetLogo } from "@/components/AssetLogo" import { TAssetWithBalance } from "@/components/AssetSelectModal/AssetSelectModal.utils" +import { ExpandableDynamicFee, FeeBreakdown } from "@/components/DynamicFee" import { AssetSelectFormField } from "@/form/AssetSelectFormField" import { ReceiveAssets } from "@/modules/liquidity/components/RemoveLiquidity/ReceiveAssets" import { RemoveLiquiditySkeleton } from "@/modules/liquidity/components/RemoveLiquidity/RemoveLiquiditySkeleton" +import { + RemoveOmnipoolResult, + WITHDRAW_FEE_RANGE, +} from "@/modules/liquidity/components/RemoveLiquidity/RemoveOmnipoolLiquidity.utils" import { TradeLimitRow, TradeLimitType, @@ -26,187 +36,379 @@ import { TStablepoolDetails, useStablepoolReserves, } from "@/modules/liquidity/Liquidity.utils" -import { useAccountBalances } from "@/states/account" +import { useAssets } from "@/providers/assetsProvider" +import { + AccountOmnipoolPosition, + useAccountBalances, + useAccountOmnipoolPositionsData, +} from "@/states/account" import { useAssetPrice } from "@/states/displayAsset" import { RemoveLiquidityProps } from "./RemoveLiquidity" import { useAssetsToRemoveFromStablepool } from "./RemoveLiquidity.utils" import { TRemoveStablepoolLiquidityFormValues, - useStablepoolRemoveLiquidity, + TRemoveStablepoolLiquidityProps, + useRemoveOmnipoolLiquidity, + useRemoveStablepoolLiquidity, + useRemoveStablepoolOmnipoolLiquidity, + useRemoveStableswapSharesLiquidity, } from "./RemoveStablepoolLiquidity.utils" -export const RemoveStablepoolLiquidity = (props: RemoveLiquidityProps) => { - const { data: stablepoolData } = useStablepoolReserves( - props.stableswapId ?? props.poolId, +export const options = [ + { + id: "stablepool", + label: t( + "liquidity:liquidity.remove.stablepool.modal.position.option.stablepool", + ), + value: "stablepool", + }, + { + id: "omnipool", + label: t( + "liquidity:liquidity.remove.stablepool.modal.position.option.omnipool", + ), + value: "omnipool", + }, +] + +export const RemoveSelectableStablepoolPositions = ( + props: RemoveLiquidityProps & { + positions: AccountOmnipoolPosition[] + stableswapId: string + }, +) => { + const { data: stablepoolData } = useStablepoolReserves(props.stableswapId) + const { isBalanceLoading } = useAccountBalances() + + const reservesToRemove = useAssetsToRemoveFromStablepool({ + reserves: stablepoolData?.reserves ?? [], + }) + const initialReceiveAsset = reservesToRemove[0] + + if (!stablepoolData || isBalanceLoading || !initialReceiveAsset) + return + + return ( + ) +} + +export const RemoveStablepoolLiquidity = (props: RemoveLiquidityProps) => { + const { getRelatedAToken } = useAssets() + const stableswapId = props.stableswapId ?? props.poolId + const omnipoolId = getRelatedAToken(stableswapId)?.id ?? stableswapId + const { data: stablepoolData } = useStablepoolReserves(stableswapId) const { isBalanceLoading } = useAccountBalances() + const { getAssetPositions } = useAccountOmnipoolPositionsData() - const receiveAssets = useAssetsToRemoveFromStablepool({ + const reservesToRemove = useAssetsToRemoveFromStablepool({ reserves: stablepoolData?.reserves ?? [], }) - const initialReceiveAsset = receiveAssets[0] + const initialReceiveAsset = reservesToRemove[0] + const positionId = props.positionId if (!stablepoolData || isBalanceLoading || !initialReceiveAsset) return + if (positionId) { + const { all: omnipoolPositions } = getAssetPositions(omnipoolId) + + const position = omnipoolPositions.find( + (position) => position.positionId === positionId, + ) + + if (!position) return null + + return ( + + ) + } + return ( - ) } -const RemoveStablepoolLiquidityJSX = ({ - pool, - onBack, - onSubmitted, - closable, - initialReceiveAsset, - editable, - receiveAssets, -}: RemoveLiquidityProps & { - editable?: boolean - pool: TStablepoolDetails +type RemoveStablepoolPositionsWrapperProps = RemoveLiquidityProps & { + positions: AccountOmnipoolPosition[] initialReceiveAsset: TAssetWithBalance - receiveAssets: TAssetWithBalance[] -}) => { - const { t } = useTranslation(["liquidity", "common"]) - const { pool: poolData } = pool + stablepoolData: TStablepoolDetails + reservesToRemove: TAssetWithBalance[] +} - const { - form, - balance, - receiveAssetsProportionally, - fee, - removeAmountShifted, - onSubmit, - } = useStablepoolRemoveLiquidity({ - ...pool, - initialReceiveAsset, - onSubmitted, - }) +export type RemoveStablepoolSharesProps = Omit< + RemoveStablepoolPositionsWrapperProps, + "positions" +> + +export type RemoveStablepoolPositionsProps = + RemoveStablepoolPositionsWrapperProps & { + fee: string + feesBreakdown: FeeBreakdown[] + omnipoolPositionsOutTotal: RemoveOmnipoolResult + balance: string + omnipoolPositionsOutValues: { + position: AccountOmnipoolPosition + valuesOut: RemoveOmnipoolResult + }[] + deposits: OmnipoolDepositFull[] + } + +const RemoveStablepoolShares = (props: RemoveStablepoolSharesProps) => { + const { form, ...removeLiquidityProps } = + useRemoveStableswapSharesLiquidity(props) + return ( + + + + ) +} + +const RemoveStablepoolPositionsWrapper = ( + props: RemoveStablepoolPositionsWrapperProps, +) => { + const { positions, initialReceiveAsset, poolId } = props + + const { form, isFullRemove, ...removeLiquidityProps } = + useRemoveStablepoolLiquidity({ + initialReceiveAsset, + positions, + omnipoolId: poolId, + }) + + return ( + + {isFullRemove ? ( + + ) : ( + + )} + + ) +} + +const RemoveOmnipoolStablepoolPositions = ( + props: RemoveStablepoolPositionsProps, +) => { + const removeLiquidityProps = useRemoveStablepoolOmnipoolLiquidity(props) + return +} + +const RemoveOmnipoolPositions = (props: RemoveStablepoolPositionsProps) => { + const removeLiquidityProps = useRemoveOmnipoolLiquidity(props) + return +} + +const RemoveStablepoolLiquidityForm = ( + props: RemoveLiquidityProps & + Omit, +) => { + const { t } = useTranslation(["liquidity", "common"]) const { formState: { isValid }, handleSubmit, watch, - } = form - - const [asset, split] = watch(["asset", "split"]) + control, + } = useFormContext() - const { isValid: isValidPrice, price } = useAssetPrice( - fee ? poolData.id.toString() : undefined, - ) + const { + receiveAssets, + closable, + onBack, + fee, + onSubmit, + balance, + editable, + isFullRemove, + feesBreakdown, + reservesToRemove, + isRemoveShares, + deposits, + } = props - const feeDisplay = - fee && isValidPrice - ? Big(removeAmountShifted).times(fee).div(100).times(price).toString() - : undefined + const [asset, split, amountToRemove] = watch(["asset", "split", "amount"]) return ( - +
+ ( + onChange(option.id)} + disabled={disabled} + sx={{ flex: 1 }} + /> + )} + /> + + ) + } /> - - - - {editable ? ( - - - - {t("common:currency", { - value: balance, - symbol: asset.symbol, - })} + + + {!editable ? ( + + + + {/*Probably display hhub token as well*/} + {t("common:currency", { + value: amountToRemove, + symbol: asset.symbol, + })} + + + ) : ( + + assetFieldName="asset" + amountFieldName="amount" + label={t("common:withdraw")} + maxBalance={balance} + assets={[]} + sx={{ py: 0 }} + disabledAssetSelector + /> + )} + + ( + + + {t("liquidity.remove.stablepool.modal.proportionally")} + - ) : ( - - assetFieldName="asset" - amountFieldName="amount" - label={t("common:amount")} - maxBalance={balance} - assets={[]} - sx={{ py: 0 }} - disabledAssetSelector - /> )} - - - - ( - - - {t("liquidity.remove.stablepool.modal.proportionally")} - - - - )} + /> + + {!split && ( + + label={t("common:minimumReceived")} + assetFieldName="receiveAsset" + amountFieldName="receiveAmount" + maxBalance={balance} + assets={[]} + sortedAssets={reservesToRemove} + ignoreBalance + disabledInput + sx={{ p: 0 }} /> + )} + + {!!receiveAssets.length && ( + + + + )} + + - {!split ? ( - - label={t("common:minimumReceived")} - assetFieldName="receiveAsset" - amountFieldName="receiveAmount" - maxBalance={balance} - assets={[]} - sortedAssets={receiveAssets} - ignoreBalance - disabledInput - sx={{ p: 0 }} - /> - ) : ( - - - + {fee && ( + <> + + + )} + + + + + + + + ) +} - +const FeeColumn = ({ + fee, + feesBreakdown, +}: { + fee: string + feesBreakdown?: FeeBreakdown[] +}) => { + const { t } = useTranslation(["liquidity", "common"]) + const { watch } = useFormContext() -
- + const [amountToRemove, asset] = watch(["amount", "asset"]) - {!split && ( - <> - - - - )} -
+ const { isValid: isValidPrice, price } = useAssetPrice( + fee ? asset.id : undefined, + ) - + const feeDisplay = + fee && isValidPrice && amountToRemove + ? Big(amountToRemove).times(fee).div(100).times(price).toString() + : undefined - - -
-
-
+ return feesBreakdown ? ( + + ) : ( + ) } diff --git a/apps/main/src/modules/liquidity/components/RemoveLiquidity/RemoveStablepoolLiquidity.utils.ts b/apps/main/src/modules/liquidity/components/RemoveLiquidity/RemoveStablepoolLiquidity.utils.ts index 0950526c61..711713270f 100644 --- a/apps/main/src/modules/liquidity/components/RemoveLiquidity/RemoveStablepoolLiquidity.utils.ts +++ b/apps/main/src/modules/liquidity/components/RemoveLiquidity/RemoveStablepoolLiquidity.utils.ts @@ -1,22 +1,38 @@ import { calculate_liquidity_out_one_asset } from "@galacticcouncil/math-stableswap" +import { useAccount } from "@galacticcouncil/web3-connect" import { standardSchemaResolver } from "@hookform/resolvers/standard-schema" import { useMutation } from "@tanstack/react-query" import Big from "big.js" -import { useEffect, useMemo } from "react" -import { useForm } from "react-hook-form" +import { useCallback, useEffect, useMemo } from "react" +import { useForm, useFormContext } from "react-hook-form" import { useTranslation } from "react-i18next" -import { prop } from "remeda" +import { isNonNullish, prop } from "remeda" import * as z from "zod/v4" +import { + OmnipoolDepositFull, + omnipoolMiningPositionsKey, + omnipoolPositionsKey, +} from "@/api/account" import { TAssetData, TStableswap } from "@/api/assets" -import { StableSwapBase } from "@/api/pools" import { TSelectedAsset } from "@/components/AssetSelect/AssetSelect" import { TAssetWithBalance } from "@/components/AssetSelectModal/AssetSelectModal.utils" +import { TReceiveAsset } from "@/modules/liquidity/components/RemoveLiquidity/ReceiveAssets" import { TRemoveLiquidityFormValues } from "@/modules/liquidity/components/RemoveLiquidity/RemoveLiquidity.utils" -import { calculatePoolFee, TReserve } from "@/modules/liquidity/Liquidity.utils" +import { + getOmnipoolLiquidityOutTotal, + useRemoveOmnipoolLiquidityOut, +} from "@/modules/liquidity/components/RemoveLiquidity/RemoveOmnipoolLiquidity.utils" +import { + RemoveStablepoolPositionsProps, + RemoveStablepoolSharesProps, +} from "@/modules/liquidity/components/RemoveLiquidity/RemoveStablepoolLiquidity" +import { calculatePoolFee } from "@/modules/liquidity/Liquidity.utils" +import { useCreateBatchTx } from "@/modules/transactions/hooks/useBatchTx" import { useAssets } from "@/providers/assetsProvider" -import { useRpcProvider } from "@/providers/rpcProvider" -import { useAccountBalances } from "@/states/account" +import { Papi, useRpcProvider } from "@/providers/rpcProvider" +import { isOmnipoolDepositPosition, useAccountBalances } from "@/states/account" +import { AccountOmnipoolPosition } from "@/states/account" import { useTradeSettings } from "@/states/tradeSettings" import { useTransactionsStore } from "@/states/transactions" import { scale, scaleHuman, toBigInt, toDecimal } from "@/utils/formatting" @@ -27,19 +43,426 @@ export type TRemoveStablepoolLiquidityFormValues = split: boolean receiveAsset: TAssetData receiveAmount: string + option: "omnipool" | "stablepool" } -export const useStablepoolRemoveLiquidity = ({ - pool, - reserves, +export type TRemoveStablepoolLiquidityProps = + | ReturnType + | ReturnType + +export const useRemoveStablepoolLiquidity = ({ initialReceiveAsset, - onSubmitted, + positions, + omnipoolId, }: { - pool: StableSwapBase - reserves: TReserve[] initialReceiveAsset: TAssetWithBalance - onSubmitted: () => void + positions: AccountOmnipoolPosition[] + omnipoolId: string }) => { + const { getAssetWithFallback, hub } = useAssets() + const meta = getAssetWithFallback(omnipoolId) + + const calculateLiquidityValues = useRemoveOmnipoolLiquidityOut(omnipoolId) + const omnipoolPositionsOutValues = positions + .map((position) => { + const valuesOut = calculateLiquidityValues( + position, + position.shares.toString(), + ) + if (!valuesOut) return undefined + + return { + position, + valuesOut, + } + }) + .filter(isNonNullish) + + let totalPositionShares = Big(0) + let totalPositionShifted = Big(0) + const deposits: OmnipoolDepositFull[] = [] + + for (const position of positions) { + totalPositionShares = totalPositionShares.plus(position.shares.toString()) + totalPositionShifted = totalPositionShifted.plus( + position.data.currentValueHuman, + ) + + if (isOmnipoolDepositPosition(position)) { + deposits.push(position) + } + } + + const balance = totalPositionShifted.toString() + + const form = useRemoveStablepoolLiquidityForm({ + receiveAsset: initialReceiveAsset, + balance, + asset: meta, + initialAmount: balance, + }) + + const { watch, setValue } = form + + const [option, split] = watch(["option", "split"]) + const isFullRemove = option === "stablepool" + + const omnipoolPositionsOutTotal = getOmnipoolLiquidityOutTotal( + omnipoolPositionsOutValues, + ) + const { tokensPayWith, hubPayWith } = omnipoolPositionsOutTotal + + const fee = omnipoolPositionsOutTotal.withdrawalFee + const feesBreakdown = [ + { symbol: meta.symbol, value: tokensPayWith, id: meta.id }, + ] + + if (Big(hubPayWith).gt(0)) { + feesBreakdown.push({ + symbol: hub.symbol, + value: hubPayWith, + id: hub.id, + }) + } + + useEffect(() => { + if (!isFullRemove && !split) { + setValue("split", true) + } + }, [isFullRemove, setValue, split]) + + return { + form, + isFullRemove, + omnipoolPositionsOutValues, + balance, + fee, + feesBreakdown, + omnipoolPositionsOutTotal, + deposits, + } +} + +export const useRemoveOmnipoolLiquidity = ( + props: RemoveStablepoolPositionsProps, +) => { + const { hub, getAssetWithFallback } = useAssets() + const { watch } = useFormContext() + const [poolMeta] = watch(["asset"]) + const { papi } = useRpcProvider() + const createBatch = useCreateBatchTx() + const { t } = useTranslation("liquidity") + const { account } = useAccount() + + const { omnipoolPositionsOutValues, onSubmitted, omnipoolPositionsOutTotal } = + props + const { hubToGet, minTokensToGet } = omnipoolPositionsOutTotal + + const mutation = useMutation({ + mutationFn: async (): Promise => { + const liquidityTxs: ReturnType< + Papi["tx"]["Omnipool"]["remove_liquidity_with_limit"] + >[] = [] + const exitingFarmsTxs: ReturnType< + Papi["tx"]["OmnipoolLiquidityMining"]["withdraw_shares"] + >[] = [] + + for (const { + position, + valuesOut: { minTokensToGet }, + } of omnipoolPositionsOutValues) { + if (isOmnipoolDepositPosition(position)) { + position.yield_farm_entries.forEach((entry) => { + const tx = papi.tx.OmnipoolLiquidityMining.withdraw_shares({ + deposit_id: BigInt(position.miningId), + yield_farm_id: entry.yield_farm_id, + }) + + exitingFarmsTxs.push(tx) + }) + } + + liquidityTxs.push( + papi.tx.Omnipool.remove_liquidity_with_limit({ + amount: position.shares, + position_id: BigInt(position.positionId), + min_limit: BigInt(Big(minTokensToGet).toFixed(0)), + }), + ) + } + + const txs = [...exitingFarmsTxs, ...liquidityTxs] + + const toasts = { + submitted: t("liquidity.remove.modal.all.toast.submitted"), + success: t("liquidity.remove.modal.all.toast.success"), + error: t("liquidity.remove.modal.all.toast.submitted"), + } + + await createBatch({ + txs, + transaction: { + toasts, + invalidateQueries: [ + omnipoolPositionsKey(account?.address ?? ""), + omnipoolMiningPositionsKey(account?.address ?? ""), + ], + }, + options: { onSubmitted }, + }) + }, + }) + + const onSubmit = () => { + mutation.mutate() + } + + const receiveAssets: TReceiveAsset[] = [ + { + asset: getAssetWithFallback(poolMeta.id), + value: minTokensToGet, + }, + ] + + if (Big(hubToGet).gt(0)) { + receiveAssets.push({ + asset: hub, + value: hubToGet, + }) + } + + const editable = + omnipoolPositionsOutValues.length < 2 && + !omnipoolPositionsOutValues.some(({ position }) => + isOmnipoolDepositPosition(position), + ) + + return { + ...props, + onSubmit, + isFullRemove: false, + receiveAssets, + editable, + isRemoveShares: false, + } +} + +export const useRemoveStablepoolOmnipoolLiquidity = ( + props: RemoveStablepoolPositionsProps, +) => { + const { t } = useTranslation("liquidity") + const { hub } = useAssets() + const { papi } = useRpcProvider() + const createBatch = useCreateBatchTx() + const { + liquidity: { slippage }, + } = useTradeSettings() + const { account } = useAccount() + const { watch, setValue } = + useFormContext() + const [poolMeta, receiveAsset, split] = watch([ + "asset", + "receiveAsset", + "split", + ]) + + const { + stablepoolData: { pool, reserves }, + omnipoolPositionsOutTotal, + omnipoolPositionsOutValues, + onSubmitted, + } = props + const { tokensToGet, minTokensToGetShifted, hubToGet } = + omnipoolPositionsOutTotal + const stablepoolFee = calculatePoolFee(pool.fee) ?? "0" + const totalIssuance = pool.totalIssuance.toString() + const amplification = pool.amplification.toString() + const stablepoolSharesToGet = tokensToGet + + const getMinOneAssetToReceive = useCallback( + (amount: string) => { + if (!stablepoolFee) return undefined + + const maxValue = calculate_liquidity_out_one_asset( + JSON.stringify( + reserves.map((reserve) => ({ + amount: reserve.amount, + decimals: reserve.meta.decimals, + asset_id: reserve.asset_id, + })), + ), + amount, + Number(receiveAsset.id), + amplification, + totalIssuance, + scaleHuman(stablepoolFee, 2), + JSON.stringify(pool.pegs), + ) + + const value = Big(maxValue) + .minus(Big(slippage).times(maxValue).div(100)) + .toFixed(0) + + return value + }, + [ + stablepoolFee, + amplification, + pool.pegs, + receiveAsset.id, + reserves, + slippage, + totalIssuance, + ], + ) + + const getMinReservesToReceive = useCallback( + (amount: string) => { + return reserves.map((reserve) => { + const maxValue = Big(amount) + .div(totalIssuance) + .times(reserve.amount) + .toString() + + const value = Big(maxValue) + .minus(Big(slippage).times(maxValue).div(100)) + .toFixed(0) + + return { value, asset: reserve.meta } + }) + }, + [totalIssuance, reserves, slippage], + ) + + useEffect(() => { + if (!split) { + const minOneAssetToReceive = getMinOneAssetToReceive( + stablepoolSharesToGet, + ) + + if (minOneAssetToReceive) { + setValue( + "receiveAmount", + toDecimal(minOneAssetToReceive, receiveAsset.decimals), + ) + } + } + }, [ + setValue, + split, + getMinOneAssetToReceive, + receiveAsset.decimals, + stablepoolSharesToGet, + ]) + + const mutation = useMutation({ + mutationFn: async (): Promise => { + const txs = omnipoolPositionsOutValues.map(({ position, valuesOut }) => + papi.tx.OmnipoolLiquidityMining.remove_liquidity_stableswap_omnipool_and_exit_farms( + { + position_id: BigInt(position.positionId), + deposit_id: isOmnipoolDepositPosition(position) + ? BigInt(position.miningId) + : undefined, + omnipool_min_limit: BigInt( + Big(valuesOut.minTokensToGet).toFixed(0), + ), + stableswap_min_amounts_out: split + ? getMinReservesToReceive(valuesOut.tokensToGet).map((asset) => ({ + amount: BigInt(asset.value), + asset_id: Number(asset.asset.id), + })) + : [ + { + amount: BigInt( + getMinOneAssetToReceive(valuesOut.tokensToGet) ?? "0", + ), + asset_id: Number(receiveAsset.id), + }, + ], + }, + ), + ) + + const hubValue = + hubToGet !== "0" + ? t("liquidity.remove.modal.toast.hub", { + value: scaleHuman(hubToGet, hub.decimals), + }) + : undefined + + const tOptions = { + value: minTokensToGetShifted, + symbol: poolMeta.symbol, + hub: hubValue, + } + + const toasts = { + submitted: t("liquidity.remove.modal.toast.submitted", tOptions), + success: t("liquidity.remove.modal.toast.success", tOptions), + } + + await createBatch({ + txs, + transaction: { + toasts, + invalidateQueries: [ + omnipoolPositionsKey(account?.address ?? ""), + omnipoolMiningPositionsKey(account?.address ?? ""), + ], + }, + options: { onSubmitted }, + }) + }, + }) + + const onSubmit = () => { + mutation.mutate() + } + + const receiveAssets: TReceiveAsset[] = split + ? [...getMinReservesToReceive(stablepoolSharesToGet)] + : [] + + if (Big(hubToGet).gt(0)) { + receiveAssets.push({ + asset: hub, + value: hubToGet, + }) + } + + const fee = Big(props.fee) + .plus(!split ? stablepoolFee : 0) + .toString() + + const feesBreakdown = split + ? [...props.feesBreakdown] + : props.feesBreakdown.map((fee) => { + if (fee.id === hub.id) { + return fee + } + + return { + ...fee, + value: Big(fee.value).times(Big(stablepoolFee).plus(1)).toString(), + } + }) + + return { + ...props, + onSubmit, + fee, + feesBreakdown, + editable: false, + isFullRemove: true, + receiveAssets, + isRemoveShares: false, + } +} + +export const useRemoveStableswapSharesLiquidity = ( + props: RemoveStablepoolSharesProps, +) => { const { t } = useTranslation("liquidity") const { getAssetWithFallback } = useAssets() const { papi } = useRpcProvider() @@ -48,6 +471,8 @@ export const useStablepoolRemoveLiquidity = ({ liquidity: { slippage }, } = useTradeSettings() + const { stablepoolData, initialReceiveAsset, onSubmitted } = props + const { pool, reserves } = stablepoolData const { getTransferableBalance } = useAccountBalances() const meta = getAssetWithFallback(pool.id) as TStableswap const fee = calculatePoolFee(pool.fee) @@ -63,13 +488,15 @@ export const useStablepoolRemoveLiquidity = ({ asset: { ...meta, iconId: meta.underlyingAssetId }, }) - const removeAmountShifted = form.watch("amount") || "0" + const [removeAmountShifted = "0", receiveAsset, split] = form.watch([ + "amount", + "receiveAsset", + "split", + ]) const removeAmount = Big(scale(removeAmountShifted, meta.decimals)).toFixed(0) - const receiveAsset = form.watch("receiveAsset") - const split = form.watch("split") const totalIssuance = pool.totalIssuance.toString() - const receiveAssetsProportionally = useMemo(() => { + const receiveAssets = useMemo(() => { return reserves.map((reserve) => { const maxValue = Big(removeAmount) .div(totalIssuance) @@ -127,18 +554,15 @@ export const useStablepoolRemoveLiquidity = ({ ) form.setValue("receiveAmount", receiveAmount) } - }, [form, split, liquidityOutOneAsset, receiveAsset.decimals, slippage]) + }, [form, split, liquidityOutOneAsset, receiveAsset.decimals]) const mutation = useMutation({ mutationFn: async (): Promise => { - if (!receiveAssetsProportionally) - throw new Error("Receive assets not found") - const tx = split ? papi.tx.Stableswap.remove_liquidity({ pool_id: Number(pool.id), share_amount: BigInt(removeAmount), - min_amounts_out: receiveAssetsProportionally.map((asset) => ({ + min_amounts_out: receiveAssets.map((asset) => ({ amount: BigInt(asset.value), asset_id: Number(asset.asset.id), })), @@ -179,33 +603,40 @@ export const useStablepoolRemoveLiquidity = ({ } return { + ...props, form, balance: balanceShifted, fee, - receiveAssetsProportionally, - mutation, + receiveAssets, onSubmit, - removeAmountShifted, + editable: true, + isFullRemove: true, + feesBreakdown: undefined, + isRemoveShares: true, + deposits: [], } } export const useRemoveStablepoolLiquidityForm = ({ asset, balance, + initialAmount, receiveAsset, }: { asset?: TSelectedAsset receiveAsset: TAssetData balance: string + initialAmount?: string }) => { return useForm({ mode: "onChange", defaultValues: { - amount: "", + amount: initialAmount ?? "", asset, split: true, receiveAsset, receiveAmount: "", + option: "stablepool", }, resolver: standardSchemaResolver( z.object({ @@ -214,6 +645,7 @@ export const useRemoveStablepoolLiquidityForm = ({ split: z.boolean(), receiveAsset: z.custom(), receiveAmount: z.string(), + option: z.enum(["omnipool", "stablepool"]), }), ), }) diff --git a/apps/main/src/modules/wallet/assets/Transfer/TransferPositionModal.tsx b/apps/main/src/modules/wallet/assets/Transfer/TransferPositionModal.tsx index 01a0f30a4c..8c45cd58d2 100644 --- a/apps/main/src/modules/wallet/assets/Transfer/TransferPositionModal.tsx +++ b/apps/main/src/modules/wallet/assets/Transfer/TransferPositionModal.tsx @@ -128,7 +128,6 @@ export const TransferPositionModal: FC = ({ assetId, onClose }) => { > - label={t("transfer.modal.asset.label")} assetFieldName="asset" @@ -154,7 +153,6 @@ export const TransferPositionModal: FC = ({ assetId, onClose }) => { /> )} - { const { hub, getAssetWithFallback } = useAssets() - const { data: omnipoolTokensData = [], isLoading: isOmnipoolTokensLoading } = + const { dataMap: omnipoolTokensData, isLoading: isOmnipoolTokensLoading } = useOmnipoolAssetsData() const { data: ids } = useOmnipoolIds() @@ -146,75 +146,68 @@ export const useOmnipoolPositionData = ( ): OmnipoolPositionData | undefined => { const price = getAssetPrice(position.assetId).price const meta = getAssetWithFallback(position.assetId) - const omnipoolData = omnipoolTokensData.find( - (omnipoolTokenData) => - omnipoolTokenData.id.toString() === position.assetId, - ) + const omnipoolData = omnipoolTokensData?.get(Number(position.assetId)) + + if (!omnipoolData || !price) return undefined - if (omnipoolData && price) { - const { liquidity, hubLiquidity } = calculateLiquidityOut( - getLiquidityOutParams(omnipoolData, position, options), - ) + const { liquidity, hubLiquidity } = calculateLiquidityOut( + getLiquidityOutParams(omnipoolData, position, options), + ) - const initialValue = position.amount.toString() - const initialValueHuman = scaleHuman(initialValue, meta.decimals) - const initialDisplay = Big(initialValueHuman).times(price).toString() + const initialValue = position.amount.toString() + const initialValueHuman = scaleHuman(initialValue, meta.decimals) + const initialDisplay = Big(initialValueHuman).times(price).toString() - const currentValue = liquidity - const currentValueHuman = scaleHuman(currentValue, meta.decimals) - const currentDisplay = Big(currentValueHuman).times(price).toString() + const currentValue = liquidity + const currentValueHuman = scaleHuman(currentValue, meta.decimals) + const currentDisplay = Big(currentValueHuman).times(price).toString() - let currentHubValue = "0" - let currentHubValueHuman = "0" - let currentHubDisplay = "0" + let currentHubValue = "0" + let currentHubValueHuman = "0" + let currentHubDisplay = "0" - // total value (with hub amount) displayed in position asset - let currentTotalValue = currentValue - let currentTotalValueHuman = currentValueHuman - let currentTotalDisplay = currentDisplay + // total value (with hub amount) displayed in position asset + let currentTotalValue = currentValue + let currentTotalValueHuman = currentValueHuman + let currentTotalDisplay = currentDisplay - if (Big(hubLiquidity).gt(0)) { - const hubPrice = getAssetPrice(hub.id).price + if (Big(hubLiquidity).gt(0)) { + const hubPrice = getAssetPrice(hub.id).price - currentHubValue = hubLiquidity - currentHubValueHuman = scaleHuman(currentHubValue, hub.decimals) + currentHubValue = hubLiquidity + currentHubValueHuman = scaleHuman(currentHubValue, hub.decimals) - currentHubDisplay = Big(currentHubValueHuman) - .times(hubPrice) - .toString() + currentHubDisplay = Big(currentHubValueHuman).times(hubPrice).toString() - currentTotalDisplay = Big(currentTotalDisplay) - .plus(currentHubDisplay) - .toString() + currentTotalDisplay = Big(currentTotalDisplay) + .plus(currentHubDisplay) + .toString() - currentTotalValueHuman = Big(currentTotalDisplay) - .div(price) - .toString() + currentTotalValueHuman = Big(currentTotalDisplay).div(price).toString() - currentTotalValue = Big( - scale(currentTotalValueHuman, meta.decimals), - ).toFixed(0) - } + currentTotalValue = Big( + scale(currentTotalValueHuman, meta.decimals), + ).toFixed(0) + } - return { - currentValue, - currentValueHuman, - currentDisplay, + return { + currentValue, + currentValueHuman, + currentDisplay, - currentHubValue, - currentHubValueHuman, - currentHubDisplay, + currentHubValue, + currentHubValueHuman, + currentHubDisplay, - currentTotalValue, - currentTotalValueHuman, - currentTotalDisplay, + currentTotalValue, + currentTotalValueHuman, + currentTotalDisplay, - initialValue, - initialValueHuman, - initialDisplay, + initialValue, + initialValueHuman, + initialDisplay, - meta, - } + meta, } }, [getAssetPrice, getAssetWithFallback, hub, omnipoolTokensData], diff --git a/packages/ui/src/components/Modal/Modal.styled.ts b/packages/ui/src/components/Modal/Modal.styled.ts index def904e32b..aded5a45d2 100644 --- a/packages/ui/src/components/Modal/Modal.styled.ts +++ b/packages/ui/src/components/Modal/Modal.styled.ts @@ -88,10 +88,6 @@ export const SModalContent = styled(Content, { width: 100%; height: 100dvh; - & > div > :not([hidden]) ~ :not([hidden]) { - border-top: 1px solid ${theme.details.separators}; - } - &[data-state="open"] { animation: ${theme.animations.fadeInBottom}; animation-timing-function: ${theme.easings.outExpo}; @@ -144,6 +140,8 @@ export const SModalHeader = styled(Flex)` padding: var(--modal-content-padding); --modal-header-button-size: 2.215rem; + border-bottom: 1px solid ${({ theme }) => theme.details.separators}; + & > div:first-of-type { justify-content: space-between; align-items: center; @@ -196,6 +194,8 @@ export const SModalFooter = styled(Flex)( padding: ${theme.space.xl}; + border-bottom: 1px solid ${theme.details.separators}; + &:last-of-type { border-bottom-right-radius: ${theme.radii.xl}; border-bottom-left-radius: ${theme.radii.xl};