From a894c476ea3efde30e7af023ac9cec7240f0371d Mon Sep 17 00:00:00 2001 From: therealemjy Date: Tue, 12 May 2026 09:27:52 +0200 Subject: [PATCH 1/3] feat: simplify CellGroup component --- apps/evm/src/components/Icon/icons/switch.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/evm/src/components/Icon/icons/switch.tsx b/apps/evm/src/components/Icon/icons/switch.tsx index 16e36f0d53..c5013c5959 100644 --- a/apps/evm/src/components/Icon/icons/switch.tsx +++ b/apps/evm/src/components/Icon/icons/switch.tsx @@ -12,16 +12,16 @@ const SvgSwitch = (props: SVGProps) => ( ); From aae8286ff2c4358f2462adbacf3e9348309e0656 Mon Sep 17 00:00:00 2001 From: therealemjy Date: Wed, 20 May 2026 09:32:46 +0200 Subject: [PATCH 2/3] feat: add token switch to Trade page --- apps/evm/src/components/Icon/icons/switch.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/evm/src/components/Icon/icons/switch.tsx b/apps/evm/src/components/Icon/icons/switch.tsx index c5013c5959..16e36f0d53 100644 --- a/apps/evm/src/components/Icon/icons/switch.tsx +++ b/apps/evm/src/components/Icon/icons/switch.tsx @@ -12,16 +12,16 @@ const SvgSwitch = (props: SVGProps) => ( ); From a2ba89b922de4db954f4b1655bc5779d819de118 Mon Sep 17 00:00:00 2001 From: therealemjy Date: Wed, 20 May 2026 14:17:08 +0200 Subject: [PATCH 3/3] feat: improve Trade features --- .changeset/open-coats-make.md | 6 ++ apps/evm/src/__mocks__/models/trade.ts | 12 +++ .../__tests__/index.spec.ts | 34 +++++-- .../api/queries/getRawTradePositions/index.ts | 6 ++ .../src/components/LayeredValues/index.tsx | 2 +- .../src/components/Slider/TickMark/index.tsx | 28 ++++++ apps/evm/src/components/Slider/index.tsx | 91 +++++++++++++------ .../src/components/TokenListWrapper/index.tsx | 8 +- .../__snapshots__/index.spec.ts.snap | 6 ++ .../index.ts | 9 ++ .../libs/translations/translations/en.json | 8 +- .../libs/translations/translations/ja.json | 8 +- .../libs/translations/translations/th.json | 8 +- .../libs/translations/translations/tr.json | 8 +- .../libs/translations/translations/vi.json | 8 +- .../translations/translations/zh-Hans.json | 8 +- .../translations/translations/zh-Hant.json | 8 +- .../__snapshots__/index.spec.ts.snap | 2 + .../OpenForm/useGetNewTradePosition/index.ts | 1 + .../Trade/PairInfo/TokenSelect/index.tsx | 13 ++- apps/evm/src/pages/Trade/PairInfo/index.tsx | 32 ++++--- .../RowFooter/StatusTab/index.tsx | 12 ++- .../Trade/Positions/PositionList/index.tsx | 2 +- .../useColumns/LiquidationPrice/index.tsx | 36 ++++++++ .../PositionList/useColumns/index.tsx | 25 +++-- apps/evm/src/types/index.ts | 2 + .../__snapshots__/index.spec.ts.snap | 8 ++ .../__tests__/index.spec.ts | 4 + .../utilities/formatToTradePosition/index.ts | 10 ++ package.json | 6 +- packages/ui/src/theme.css | 10 +- 31 files changed, 338 insertions(+), 83 deletions(-) create mode 100644 .changeset/open-coats-make.md create mode 100644 apps/evm/src/components/Slider/TickMark/index.tsx create mode 100644 apps/evm/src/pages/Trade/Positions/PositionList/useColumns/LiquidationPrice/index.tsx diff --git a/.changeset/open-coats-make.md b/.changeset/open-coats-make.md new file mode 100644 index 0000000000..5362509d6c --- /dev/null +++ b/.changeset/open-coats-make.md @@ -0,0 +1,6 @@ +--- +"@venusprotocol/ui": minor +"@venusprotocol/evm": minor +--- + +improve Trade features diff --git a/apps/evm/src/__mocks__/models/trade.ts b/apps/evm/src/__mocks__/models/trade.ts index ecaf4b2d67..d1e3e79afd 100644 --- a/apps/evm/src/__mocks__/models/trade.ts +++ b/apps/evm/src/__mocks__/models/trade.ts @@ -117,6 +117,10 @@ export const tradePositions: TradePosition[] = [ value: xvsAsset.userSupplyBalanceTokens, token: xvsAsset.vToken.underlyingToken, }), + dsaUtilizedBalanceMantissa: convertTokensToMantissa({ + value: xvsAsset.userSupplyBalanceTokens.minus(1), + token: xvsAsset.vToken.underlyingToken, + }), longVTokenAddress: usdtAsset.vToken.address, shortVTokenAddress: busdAsset.vToken.address, leverageFactor: 2, @@ -132,6 +136,10 @@ export const tradePositions: TradePosition[] = [ value: usdcAsset.userSupplyBalanceTokens, token: usdcAsset.vToken.underlyingToken, }), + dsaUtilizedBalanceMantissa: convertTokensToMantissa({ + value: usdcAsset.userSupplyBalanceTokens.minus(1), + token: usdcAsset.vToken.underlyingToken, + }), longVTokenAddress: usdtAsset.vToken.address, shortVTokenAddress: busdAsset.vToken.address, leverageFactor: 3, @@ -147,6 +155,10 @@ export const tradePositions: TradePosition[] = [ value: usdcAsset.userSupplyBalanceTokens, token: usdcAsset.vToken.underlyingToken, }), + dsaUtilizedBalanceMantissa: convertTokensToMantissa({ + value: usdcAsset.userSupplyBalanceTokens.minus(1), + token: usdcAsset.vToken.underlyingToken, + }), longVTokenAddress: usdcAsset.vToken.address, shortVTokenAddress: usdtAsset.vToken.address, leverageFactor: 1.5, diff --git a/apps/evm/src/clients/api/queries/getRawTradePositions/__tests__/index.spec.ts b/apps/evm/src/clients/api/queries/getRawTradePositions/__tests__/index.spec.ts index b7cbd5314b..fbae086832 100644 --- a/apps/evm/src/clients/api/queries/getRawTradePositions/__tests__/index.spec.ts +++ b/apps/evm/src/clients/api/queries/getRawTradePositions/__tests__/index.spec.ts @@ -1,13 +1,14 @@ +import BigNumber from 'bignumber.js'; import type { PublicClient } from 'viem'; import type { Mock } from 'vitest'; import fakeAddress, { altAddress } from '__mocks__/models/address'; import { poolData } from '__mocks__/models/pools'; import tokens from '__mocks__/models/tokens'; -import { apiTradePositions, tradePositions } from '__mocks__/models/trade'; +import { apiTradePositions } from '__mocks__/models/trade'; import { logError } from 'libs/errors'; import { ChainId } from 'types'; -import { restService } from 'utilities'; +import { formatToTradePosition, restService } from 'utilities'; import { type GetRawTradePositionsInput, getRawTradePositions } from '..'; import { getPools } from '../../useGetPools/getPools'; @@ -33,6 +34,25 @@ const fakeInput: GetRawTradePositionsInput = { tokens, }; +const formatApiTradePosition = (apiTradePosition: (typeof apiTradePositions)[number]) => + formatToTradePosition({ + pool: poolData[0], + chainId: fakeInput.chainId, + positionAccountAddress: apiTradePosition.positionAccountAddress, + dsaVTokenAddress: apiTradePosition.dsaVTokenAddress, + dsaBalanceMantissa: new BigNumber( + apiTradePosition.capitalUtilization.suppliedPrincipalMantissa || 0, + ), + dsaUtilizedBalanceMantissa: new BigNumber( + apiTradePosition.capitalUtilization.capitalUtilizedMantissa || 0, + ), + longVTokenAddress: apiTradePosition.longVTokenAddress, + shortVTokenAddress: apiTradePosition.shortVTokenAddress, + leverageFactor: Number(apiTradePosition.effectiveLeverageRatio ?? 0), + unrealizedPnlCents: Number(apiTradePosition.pnl?.unrealizedPnlUsd ?? 0) * 100, + unrealizedPnlPercentage: Number(apiTradePosition.pnl?.unrealizedPnlRatio ?? 0) * 100, + }); + describe('getRawTradePositions', () => { beforeEach(() => { (restService as Mock).mockResolvedValue({ @@ -104,10 +124,10 @@ describe('getRawTradePositions', () => { it('returns positions in the correct format on success', async () => { const response = await getRawTradePositions(fakeInput); - const expectedPositions = tradePositions.slice(0, 2).map((position, index) => ({ - ...position, - unrealizedPnlPercentage: Number(apiTradePositions[index].pnl?.unrealizedPnlRatio ?? 0) * 100, - })); + const expectedPositions = apiTradePositions + .slice(0, 2) + .map(formatApiTradePosition) + .filter(position => position !== undefined); expect(response).toEqual({ positions: expectedPositions, @@ -164,7 +184,7 @@ describe('getRawTradePositions', () => { const response = await getRawTradePositions(fakeInput); expect(response).toEqual({ - positions: [tradePositions[0]], + positions: [formatApiTradePosition(apiTradePositions[0])], }); expect(logError).toHaveBeenCalledTimes(1); expect(logError).toHaveBeenCalledWith( diff --git a/apps/evm/src/clients/api/queries/getRawTradePositions/index.ts b/apps/evm/src/clients/api/queries/getRawTradePositions/index.ts index ae0513734f..31e2f636b5 100644 --- a/apps/evm/src/clients/api/queries/getRawTradePositions/index.ts +++ b/apps/evm/src/clients/api/queries/getRawTradePositions/index.ts @@ -88,8 +88,13 @@ export const getRawTradePositions = async ({ apiTradePosition.capitalUtilization.suppliedPrincipalMantissa || 0, ); + let dsaUtilizedBalanceMantissa = new BigNumber( + apiTradePosition.capitalUtilization.capitalUtilizedMantissa || 0, + ); + if (userDsaAssetSupplyBalanceMantissa) { dsaBalanceMantissa = BigNumber.min(userDsaAssetSupplyBalanceMantissa, dsaBalanceMantissa); + dsaUtilizedBalanceMantissa = BigNumber.min(dsaUtilizedBalanceMantissa, dsaBalanceMantissa); } const tradePosition = formatToTradePosition({ @@ -99,6 +104,7 @@ export const getRawTradePositions = async ({ longVTokenAddress: apiTradePosition.longVTokenAddress, shortVTokenAddress: apiTradePosition.shortVTokenAddress, dsaBalanceMantissa, + dsaUtilizedBalanceMantissa, leverageFactor: Number(apiTradePosition.effectiveLeverageRatio ?? 0), unrealizedPnlCents: apiTradePosition.pnl ? Number(apiTradePosition.pnl.unrealizedPnlUsd) * 100 diff --git a/apps/evm/src/components/LayeredValues/index.tsx b/apps/evm/src/components/LayeredValues/index.tsx index 86b3533c4c..02b363593c 100644 --- a/apps/evm/src/components/LayeredValues/index.tsx +++ b/apps/evm/src/components/LayeredValues/index.tsx @@ -1,7 +1,7 @@ import { cn } from '@venusprotocol/ui'; export interface LayeredValuesProps { - topValue: string | number; + topValue: React.ReactNode; bottomValue?: string | number; className?: string; bottomValueClassName?: string; diff --git a/apps/evm/src/components/Slider/TickMark/index.tsx b/apps/evm/src/components/Slider/TickMark/index.tsx new file mode 100644 index 0000000000..b72a29d808 --- /dev/null +++ b/apps/evm/src/components/Slider/TickMark/index.tsx @@ -0,0 +1,28 @@ +import { Slot } from '@radix-ui/react-slot'; +import { cn } from '@venusprotocol/ui'; + +export interface TickMarkProps extends React.HTMLAttributes { + isActive?: boolean; + asChild?: boolean; + className?: string; +} + +export const TickMark: React.FC = ({ + isActive = false, + className, + asChild, + ...otherProps +}) => { + const Comp = asChild ? Slot : 'div'; + + return ( + + ); +}; diff --git a/apps/evm/src/components/Slider/index.tsx b/apps/evm/src/components/Slider/index.tsx index 87579a76db..04a034d824 100644 --- a/apps/evm/src/components/Slider/index.tsx +++ b/apps/evm/src/components/Slider/index.tsx @@ -1,5 +1,9 @@ import * as SliderPrimitive from '@radix-ui/react-slider'; import { cn } from '@venusprotocol/ui'; +import { useState } from 'react'; +import { TickMark } from './TickMark'; + +const tickMarks = [25, 50, 75]; export interface SliderProps { value: number; @@ -21,34 +25,65 @@ export const Slider: React.FC = ({ disabled = false, className, rangeClassName, -}) => ( - onChange(newValue)} - className={cn('relative flex w-full touch-none items-center select-none', className)} - disabled={disabled} - > - - - - - { + const [isValueIndicatorVisible, setIsValueIndicatorVisible] = useState(false); + + const showValueIndicator = () => setIsValueIndicatorVisible(true); + const hideValueIndicator = () => setIsValueIndicatorVisible(false); + + const valuePercentage = Math.round(((value - min) * 100) / (max - min)); + + return ( + onChange(newValue)} className={cn( - 'block size-5 shrink-0 outline-hidden rounded-full border-white border-4 bg-blue shadow-sm transition-[color,box-shadow]', + 'relative flex w-full touch-none items-center select-none', !disabled && 'cursor-pointer', + className, )} - /> - -); + disabled={disabled} + > + + + + + {tickMarks.map(tickMark => ( + = tickMark} + /> + ))} + + + {isValueIndicatorVisible && ( +
+ {valuePercentage}% +
+ )} +
+
+ ); +}; diff --git a/apps/evm/src/components/TokenListWrapper/index.tsx b/apps/evm/src/components/TokenListWrapper/index.tsx index ad995a2aa1..9f5449328e 100644 --- a/apps/evm/src/components/TokenListWrapper/index.tsx +++ b/apps/evm/src/components/TokenListWrapper/index.tsx @@ -11,6 +11,7 @@ import { TextField } from '../TextField'; import { getTokenListItemTestId } from './testIdGetters'; export interface OptionalTokenBalance extends Omit { + isDeemed?: boolean; balanceMantissa?: BigNumber; } @@ -69,7 +70,7 @@ export const TokenListWrapper: React.FC = ({ // If b is non-negative and a is negative, b comes first return 1; - }) as TokenBalance[], + }) as OptionalTokenBalance[], [tokenBalances], ); @@ -152,7 +153,10 @@ export const TokenListWrapper: React.FC = ({ }) } > - + {tokenBalance.balanceMantissa && ( diff --git a/apps/evm/src/hooks/useGetTradePositions/__tests__/__snapshots__/index.spec.ts.snap b/apps/evm/src/hooks/useGetTradePositions/__tests__/__snapshots__/index.spec.ts.snap index 2e1036ba36..7f3f4c4465 100644 --- a/apps/evm/src/hooks/useGetTradePositions/__tests__/__snapshots__/index.spec.ts.snap +++ b/apps/evm/src/hooks/useGetTradePositions/__tests__/__snapshots__/index.spec.ts.snap @@ -110,6 +110,8 @@ exports[`useGetTradePositions > enriches positions with wallet balances from the }, "dsaBalanceCents": 11508.0606, "dsaBalanceTokens": "90", + "dsaUtilizedBalanceCents": 11380.19326, + "dsaUtilizedBalanceTokens": "89", "entryPriceTokens": "0.5", "leverageFactor": 2, "liquidationPriceTokens": "-0.09424715604122129993", @@ -1268,6 +1270,8 @@ exports[`useGetTradePositions > falls back to the raw Trade position balances wh }, "dsaBalanceCents": 11508.0606, "dsaBalanceTokens": "90", + "dsaUtilizedBalanceCents": 11380.19326, + "dsaUtilizedBalanceTokens": "89", "entryPriceTokens": "0.5", "leverageFactor": 2, "liquidationPriceTokens": "-0.09424715604122129993", @@ -2426,6 +2430,8 @@ exports[`useGetTradePositions > passes the expected query params when the wallet }, "dsaBalanceCents": 11508.0606, "dsaBalanceTokens": "90", + "dsaUtilizedBalanceCents": 11380.19326, + "dsaUtilizedBalanceTokens": "89", "entryPriceTokens": "0.5", "leverageFactor": 2, "liquidationPriceTokens": "-0.09424715604122129993", diff --git a/apps/evm/src/hooks/useSimulateTradePositionMutations/index.ts b/apps/evm/src/hooks/useSimulateTradePositionMutations/index.ts index c57944297c..16f5965baf 100644 --- a/apps/evm/src/hooks/useSimulateTradePositionMutations/index.ts +++ b/apps/evm/src/hooks/useSimulateTradePositionMutations/index.ts @@ -34,6 +34,11 @@ export const useSimulateTradeMutations = ({ }); } + const dsaUtilizedBalanceTokens = BigNumber.min( + position.dsaUtilizedBalanceTokens, + dsaBalanceTokens, + ); + const simulatedTradePosition = simulatedPool && formatToTradePosition({ @@ -43,6 +48,10 @@ export const useSimulateTradeMutations = ({ value: dsaBalanceTokens, token: position.dsaAsset.vToken.underlyingToken, }), + dsaUtilizedBalanceMantissa: convertTokensToMantissa({ + value: dsaUtilizedBalanceTokens, + token: position.dsaAsset.vToken.underlyingToken, + }), positionAccountAddress: position.positionAccountAddress, dsaVTokenAddress: position.dsaAsset.vToken.address, longVTokenAddress: position.longAsset.vToken.address, diff --git a/apps/evm/src/libs/translations/translations/en.json b/apps/evm/src/libs/translations/translations/en.json index 010829403c..e6e801a39e 100644 --- a/apps/evm/src/libs/translations/translations/en.json +++ b/apps/evm/src/libs/translations/translations/en.json @@ -1733,6 +1733,9 @@ "collateralColumn": { "label": "Collateral" }, + "collateralUtilizationColumn": { + "label": "Collateral utilization" + }, "healthFactor": { "label": "Health factor", "tooltip": "Liquidation at < 1.0" @@ -1748,10 +1751,11 @@ "table": { "entryPrice": { "title": "EP", - "tooltip": "The entry price ratio of this position (short asset amount ÷ long asset amount), reflecting the relative value between the two tokens from entry" + "tooltip": "The entry price ratio of this position (short asset amount ÷ long asset amount, or long asset amount ÷ short asset amount, depending on the selected base token), reflecting the relative value between the two tokens at entry." }, "liquidationPrice": { - "title": "Liq." + "title": "Liq.", + "tooltip": "The liquidation price is calculated based on the current base token price and may change as the base token price moves. Always keep an eye on the Health Factor (HF)." }, "longColumn": { "title": "Long" diff --git a/apps/evm/src/libs/translations/translations/ja.json b/apps/evm/src/libs/translations/translations/ja.json index 253276352b..06dcb29dde 100644 --- a/apps/evm/src/libs/translations/translations/ja.json +++ b/apps/evm/src/libs/translations/translations/ja.json @@ -1733,6 +1733,9 @@ "collateralColumn": { "label": "担保" }, + "collateralUtilizationColumn": { + "label": "担保利用率" + }, "healthFactor": { "label": "健全性ファクター", "tooltip": "清算ライン: < 1.0" @@ -1748,10 +1751,11 @@ "table": { "entryPrice": { "title": "EP", - "tooltip": "このポジションのエントリー価格比率(ショート資産量 ÷ ロング資産量)で、エントリー時点からの2つのトークン間の相対的な価値を示します" + "tooltip": "このポジションのエントリー価格比率です(ショート資産量 ÷ ロング資産量、または選択したベーストークンに応じてロング資産量 ÷ ショート資産量)。エントリー時点での2つのトークン間の相対的な価値を示します。" }, "liquidationPrice": { - "title": "Liq." + "title": "Liq.", + "tooltip": "清算価格は現在のベーストークン価格に基づいて計算され、ベーストークン価格の変動に応じて変わる可能性があります。ヘルスファクター(HF)を常に確認してください。" }, "longColumn": { "title": "ロング" diff --git a/apps/evm/src/libs/translations/translations/th.json b/apps/evm/src/libs/translations/translations/th.json index 1a5dbce9d7..c801bf84c7 100644 --- a/apps/evm/src/libs/translations/translations/th.json +++ b/apps/evm/src/libs/translations/translations/th.json @@ -1733,6 +1733,9 @@ "collateralColumn": { "label": "หลักประกัน" }, + "collateralUtilizationColumn": { + "label": "อัตราการใช้หลักประกัน" + }, "healthFactor": { "label": "ตัวคูณความปลอดภัย", "tooltip": "ถูกชำระบัญชีเมื่อ < 1.0" @@ -1748,10 +1751,11 @@ "table": { "entryPrice": { "title": "EP", - "tooltip": "อัตราส่วนราคาเข้าของโพสิชันนี้ (จำนวนสินทรัพย์ Short ÷ จำนวนสินทรัพย์ Long) ซึ่งสะท้อนมูลค่าเปรียบเทียบระหว่างโทเค็นทั้งสองนับตั้งแต่ตอนเข้า" + "tooltip": "อัตราส่วนราคาเข้าของโพสิชันนี้ (จำนวนสินทรัพย์ Short ÷ จำนวนสินทรัพย์ Long หรือจำนวนสินทรัพย์ Long ÷ จำนวนสินทรัพย์ Short ขึ้นอยู่กับ base token ที่เลือก) ซึ่งสะท้อนมูลค่าเปรียบเทียบระหว่างโทเค็นทั้งสอง ณ ตอนเข้า" }, "liquidationPrice": { - "title": "Liq." + "title": "Liq.", + "tooltip": "ราคา Liquidation คำนวณจากราคาปัจจุบันของ base token และอาจเปลี่ยนแปลงตามการเคลื่อนไหวของราคา base token โปรดติดตาม Health Factor (HF) อยู่เสมอ" }, "longColumn": { "title": "Long" diff --git a/apps/evm/src/libs/translations/translations/tr.json b/apps/evm/src/libs/translations/translations/tr.json index deb0d425db..61a90b3630 100644 --- a/apps/evm/src/libs/translations/translations/tr.json +++ b/apps/evm/src/libs/translations/translations/tr.json @@ -1733,6 +1733,9 @@ "collateralColumn": { "label": "Teminat" }, + "collateralUtilizationColumn": { + "label": "Teminat kullanım oranı" + }, "healthFactor": { "label": "Sağlık faktörü", "tooltip": "Likidasyon < 1.0" @@ -1748,10 +1751,11 @@ "table": { "entryPrice": { "title": "EP", - "tooltip": "Bu pozisyonun giriş fiyat oranı (short varlık miktarı ÷ long varlık miktarı), girişten itibaren iki token arasındaki göreli değeri yansıtır" + "tooltip": "Bu pozisyonun giriş fiyat oranı (seçilen baz tokena bağlı olarak short varlık miktarı ÷ long varlık miktarı veya long varlık miktarı ÷ short varlık miktarı) olup, giriş anında iki token arasındaki göreli değeri yansıtır." }, "liquidationPrice": { - "title": "Liq." + "title": "Liq.", + "tooltip": "Likidasyon fiyatı mevcut baz token fiyatına göre hesaplanır ve baz token fiyatı hareket ettikçe değişebilir. Sağlık Faktörünü (HF) her zaman takip edin." }, "longColumn": { "title": "Long" diff --git a/apps/evm/src/libs/translations/translations/vi.json b/apps/evm/src/libs/translations/translations/vi.json index 757319082a..000cdcdfcb 100644 --- a/apps/evm/src/libs/translations/translations/vi.json +++ b/apps/evm/src/libs/translations/translations/vi.json @@ -1733,6 +1733,9 @@ "collateralColumn": { "label": "Tài sản thế chấp" }, + "collateralUtilizationColumn": { + "label": "Tỷ lệ sử dụng tài sản thế chấp" + }, "healthFactor": { "label": "Hệ số an toàn", "tooltip": "Thanh lý tại < 1.0" @@ -1748,10 +1751,11 @@ "table": { "entryPrice": { "title": "EP", - "tooltip": "Tỷ lệ giá vào lệnh của vị thế này (số lượng tài sản Short ÷ số lượng tài sản Long), phản ánh giá trị tương đối giữa hai token kể từ lúc vào lệnh" + "tooltip": "Tỷ lệ giá vào lệnh của vị thế này (số lượng tài sản Short ÷ số lượng tài sản Long hoặc số lượng tài sản Long ÷ số lượng tài sản Short, tùy theo token cơ sở được chọn), phản ánh giá trị tương đối giữa hai token tại thời điểm vào lệnh." }, "liquidationPrice": { - "title": "Liq." + "title": "Liq.", + "tooltip": "Giá thanh lý được tính dựa trên giá token cơ sở hiện tại và có thể thay đổi khi giá token cơ sở biến động. Hãy luôn theo dõi Health Factor (HF)." }, "longColumn": { "title": "Long" diff --git a/apps/evm/src/libs/translations/translations/zh-Hans.json b/apps/evm/src/libs/translations/translations/zh-Hans.json index 2588b3ccab..26be0a32d7 100644 --- a/apps/evm/src/libs/translations/translations/zh-Hans.json +++ b/apps/evm/src/libs/translations/translations/zh-Hans.json @@ -1733,6 +1733,9 @@ "collateralColumn": { "label": "可抵押" }, + "collateralUtilizationColumn": { + "label": "抵押品利用率" + }, "healthFactor": { "label": "健康系数", "tooltip": "清算阈值 < 1.0" @@ -1748,10 +1751,11 @@ "table": { "entryPrice": { "title": "EP", - "tooltip": "该仓位的入场价格比率(空头资产数量 ÷ 多头资产数量),反映自入场以来两种代币之间的相对价值" + "tooltip": "该仓位的入场价格比率(空头资产数量 ÷ 多头资产数量,或多头资产数量 ÷ 空头资产数量,具体取决于所选基础代币),反映两种代币在入场时的相对价值。" }, "liquidationPrice": { - "title": "清算价" + "title": "清算价", + "tooltip": "清算价格基于当前基础代币价格计算,并可能随着基础代币价格变动而变化。请始终关注健康系数(HF)。" }, "longColumn": { "title": "多头" diff --git a/apps/evm/src/libs/translations/translations/zh-Hant.json b/apps/evm/src/libs/translations/translations/zh-Hant.json index 6454565405..0b6accfd88 100644 --- a/apps/evm/src/libs/translations/translations/zh-Hant.json +++ b/apps/evm/src/libs/translations/translations/zh-Hant.json @@ -1733,6 +1733,9 @@ "collateralColumn": { "label": "抵押" }, + "collateralUtilizationColumn": { + "label": "抵押品利用率" + }, "healthFactor": { "label": "健康係數", "tooltip": "清算閾值 < 1.0" @@ -1748,10 +1751,11 @@ "table": { "entryPrice": { "title": "EP", - "tooltip": "此倉位的入場價格比率(空頭資產數量 ÷ 多頭資產數量),反映自入場以來兩種代幣之間的相對價值" + "tooltip": "此倉位的入場價格比率(空頭資產數量 ÷ 多頭資產數量,或多頭資產數量 ÷ 空頭資產數量,取決於所選基礎代幣),反映兩種代幣在入場時的相對價值。" }, "liquidationPrice": { - "title": "清算價" + "title": "清算價", + "tooltip": "清算價格是根據當前基礎代幣價格計算,並可能隨著基礎代幣價格變動而改變。請始終留意健康係數(HF)。" }, "longColumn": { "title": "多頭" diff --git a/apps/evm/src/pages/Trade/OperationForm/OpenForm/useGetNewTradePosition/__tests__/__snapshots__/index.spec.ts.snap b/apps/evm/src/pages/Trade/OperationForm/OpenForm/useGetNewTradePosition/__tests__/__snapshots__/index.spec.ts.snap index d055503ca4..705d2b0138 100644 --- a/apps/evm/src/pages/Trade/OperationForm/OpenForm/useGetNewTradePosition/__tests__/__snapshots__/index.spec.ts.snap +++ b/apps/evm/src/pages/Trade/OperationForm/OpenForm/useGetNewTradePosition/__tests__/__snapshots__/index.spec.ts.snap @@ -107,6 +107,8 @@ exports[`useGetNewTradePosition > returns a new base position built from the cor }, "dsaBalanceCents": 0, "dsaBalanceTokens": "0", + "dsaUtilizedBalanceCents": 0, + "dsaUtilizedBalanceTokens": "0", "entryPriceTokens": "0", "leverageFactor": 2, "liquidationPriceTokens": "0", diff --git a/apps/evm/src/pages/Trade/OperationForm/OpenForm/useGetNewTradePosition/index.ts b/apps/evm/src/pages/Trade/OperationForm/OpenForm/useGetNewTradePosition/index.ts index 40964feb7e..d20117e120 100644 --- a/apps/evm/src/pages/Trade/OperationForm/OpenForm/useGetNewTradePosition/index.ts +++ b/apps/evm/src/pages/Trade/OperationForm/OpenForm/useGetNewTradePosition/index.ts @@ -101,6 +101,7 @@ export const useGetNewTradePosition = () => { positionAccountAddress: NULL_ADDRESS, dsaVTokenAddress: dsaAsset.vToken.address, dsaBalanceMantissa: new BigNumber(0), + dsaUtilizedBalanceMantissa: new BigNumber(0), longVTokenAddress: longAsset.vToken.address, shortVTokenAddress: shortAsset.vToken.address, leverageFactor: BigNumber.min(maximumLeverageFactor, DEFAULT_LEVERAGE_FACTOR).toNumber(), diff --git a/apps/evm/src/pages/Trade/PairInfo/TokenSelect/index.tsx b/apps/evm/src/pages/Trade/PairInfo/TokenSelect/index.tsx index 48b0024a76..67c0e9a2cb 100644 --- a/apps/evm/src/pages/Trade/PairInfo/TokenSelect/index.tsx +++ b/apps/evm/src/pages/Trade/PairInfo/TokenSelect/index.tsx @@ -1,13 +1,14 @@ import { SelectButton, cn } from '@venusprotocol/ui'; -import { Icon, TokenIconWithSymbol, TokenListWrapper } from 'components'; +import { Icon, type OptionalTokenBalance, TokenIconWithSymbol, TokenListWrapper } from 'components'; import { useTranslation } from 'libs/translations'; import { useState } from 'react'; import type { Token } from 'types'; +import { compareBooleans } from 'utilities'; export interface TokenSelectProps { selectedToken: Token; - tokens: Token[]; + tokenBalances: OptionalTokenBalance[]; onChangeSelectedToken: (token: Token) => void; type: 'long' | 'short'; displayCommonTokenButtons?: boolean; @@ -19,7 +20,7 @@ export interface TokenSelectProps { export const TokenSelect: React.FC = ({ className, selectedToken, - tokens, + tokenBalances, onChangeSelectedToken, 'data-testid': testId, type, @@ -31,11 +32,15 @@ export const TokenSelect: React.FC = ({ const showTokenList = () => setIsTokenListShown(true); const hideTokenList = () => setIsTokenListShown(false); + const sortedTokenBalances = [...tokenBalances].sort((a, b) => + compareBooleans(a.isDeemed ?? false, b.isDeemed ?? false, 'asc'), + ); + return ( ({ token }))} + tokenBalances={sortedTokenBalances} onClose={hideTokenList} isListShown={isTokenListShown} selectedToken={selectedToken} diff --git a/apps/evm/src/pages/Trade/PairInfo/index.tsx b/apps/evm/src/pages/Trade/PairInfo/index.tsx index 3d5ba28e65..92222a0085 100644 --- a/apps/evm/src/pages/Trade/PairInfo/index.tsx +++ b/apps/evm/src/pages/Trade/PairInfo/index.tsx @@ -2,7 +2,7 @@ import { cn } from '@venusprotocol/ui'; import BigNumber from 'bignumber.js'; import { useSearchParams } from 'react-router'; -import { Apy, CellGroup, type CellProps, Icon } from 'components'; +import { Apy, CellGroup, type CellProps, Icon, type OptionalTokenBalance } from 'components'; import { PLACEHOLDER_KEY } from 'constants/placeholders'; import { useTranslation } from 'libs/translations'; import type { Asset, Token } from 'types'; @@ -63,43 +63,53 @@ export const PairInfo: React.FC = ({ changePercentage, priceCents data: { borrowAssets, supplyAssets }, } = useGetTradeAssets(); - const { longAsset, longTokens } = supplyAssets.reduce<{ + const { longAsset, longTokenBalances } = supplyAssets.reduce<{ longAsset?: Asset; - longTokens: Token[]; + longTokenBalances: OptionalTokenBalance[]; }>( (acc, asset) => { if (areTokensEqual(asset.vToken.underlyingToken, longToken)) { acc.longAsset = asset; } + const tokenBalance: OptionalTokenBalance = { + token: asset.vToken.underlyingToken, + isDeemed: asset.disabledTokenActions.includes('supply'), + }; + return { ...acc, - longTokens: [...acc.longTokens, asset.vToken.underlyingToken], + longTokenBalances: [...acc.longTokenBalances, tokenBalance], }; }, { longAsset: undefined, - longTokens: [], + longTokenBalances: [], }, ); - const { shortAsset, shortTokens } = borrowAssets.reduce<{ + const { shortAsset, shortTokenBalances } = borrowAssets.reduce<{ shortAsset?: Asset; - shortTokens: Token[]; + shortTokenBalances: OptionalTokenBalance[]; }>( (acc, asset) => { if (areTokensEqual(asset.vToken.underlyingToken, shortToken)) { acc.shortAsset = asset; } + const tokenBalance: OptionalTokenBalance = { + token: asset.vToken.underlyingToken, + isDeemed: asset.disabledTokenActions.includes('borrow') || !asset.isBorrowable, + }; + return { ...acc, - shortTokens: [...acc.shortTokens, asset.vToken.underlyingToken], + shortTokenBalances: [...acc.shortTokenBalances, tokenBalance], }; }, { shortAsset: undefined, - shortTokens: [], + shortTokenBalances: [], }, ); @@ -151,7 +161,7 @@ export const PairInfo: React.FC = ({ changePercentage, priceCents @@ -159,7 +169,7 @@ export const PairInfo: React.FC = ({ changePercentage, priceCents diff --git a/apps/evm/src/pages/Trade/Positions/PositionList/RowFooter/StatusTab/index.tsx b/apps/evm/src/pages/Trade/Positions/PositionList/RowFooter/StatusTab/index.tsx index cf79eaa372..b99dec7f64 100644 --- a/apps/evm/src/pages/Trade/Positions/PositionList/RowFooter/StatusTab/index.tsx +++ b/apps/evm/src/pages/Trade/Positions/PositionList/RowFooter/StatusTab/index.tsx @@ -29,6 +29,13 @@ export const StatusTab: React.FC = ({ row }) => { value: row.netValueCents, }), }, + { + label: t('trade.positions.status.collateralUtilizationColumn.label'), + value: formatTokensToReadableValue({ + value: row.dsaUtilizedBalanceTokens, + token: row.dsaAsset.vToken.underlyingToken, + }), + }, { label: t('trade.positions.status.netApy.label'), value: formatPercentageToReadableValue(row.netApyPercentage), @@ -38,7 +45,10 @@ export const StatusTab: React.FC = ({ row }) => { return (
- +
diff --git a/apps/evm/src/pages/Trade/Positions/PositionList/index.tsx b/apps/evm/src/pages/Trade/Positions/PositionList/index.tsx index 8a0a9073ff..6bb0f0f15e 100644 --- a/apps/evm/src/pages/Trade/Positions/PositionList/index.tsx +++ b/apps/evm/src/pages/Trade/Positions/PositionList/index.tsx @@ -52,7 +52,7 @@ export const PositionList: React.FC = ({ positions }) => { return (