diff --git a/.changeset/kind-areas-rescue.md b/.changeset/kind-areas-rescue.md new file mode 100644 index 0000000000..457ecd9b47 --- /dev/null +++ b/.changeset/kind-areas-rescue.md @@ -0,0 +1,5 @@ +--- +"@venusprotocol/evm": minor +--- + +improve high price impact handling + display USD values of balance updates diff --git a/apps/evm/package.json b/apps/evm/package.json index 3798470f6c..d5c36c348d 100644 --- a/apps/evm/package.json +++ b/apps/evm/package.json @@ -155,15 +155,7 @@ "whatwg-fetch": "^3.6.18" }, "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] + "production": [">0.2%", "not dead", "not op_mini all"], + "development": ["last 1 chrome version", "last 1 firefox version", "last 1 safari version"] } } diff --git a/apps/evm/src/clients/api/queries/getSwapQuote/useGetSwapQuote.ts b/apps/evm/src/clients/api/queries/getSwapQuote/useGetSwapQuote.ts index 8774c3c56f..efe14e6565 100644 --- a/apps/evm/src/clients/api/queries/getSwapQuote/useGetSwapQuote.ts +++ b/apps/evm/src/clients/api/queries/getSwapQuote/useGetSwapQuote.ts @@ -45,6 +45,7 @@ export const useGetSwapQuote = (input: TrimmedGetSwapQuoteInput, options?: Parti recipientAddress: params.leverageManagerContractAddress, }), ), + retry: false, refetchInterval, ...options, }); diff --git a/apps/evm/src/components/RiskAcknowledgementToggle/index.stories.tsx b/apps/evm/src/components/AcknowledgementToggle/index.stories.tsx similarity index 60% rename from apps/evm/src/components/RiskAcknowledgementToggle/index.stories.tsx rename to apps/evm/src/components/AcknowledgementToggle/index.stories.tsx index d888a5c1d4..541095db51 100644 --- a/apps/evm/src/components/RiskAcknowledgementToggle/index.stories.tsx +++ b/apps/evm/src/components/AcknowledgementToggle/index.stories.tsx @@ -1,12 +1,12 @@ import type { Meta } from '@storybook/react'; import { State } from 'react-powerplug'; -import { RiskAcknowledgementToggle } from '.'; +import { AcknowledgementToggle } from '.'; export default { - title: 'Components/RiskAcknowledgementToggle', - component: RiskAcknowledgementToggle, -} as Meta; + title: 'Components/AcknowledgementToggle', + component: AcknowledgementToggle, +} as Meta; const initialState: { value: boolean } = { value: false, @@ -15,7 +15,9 @@ const initialState: { value: boolean } = { export const Default = () => ( {({ state, setState }) => ( - setState({ value })} /> diff --git a/apps/evm/src/components/AcknowledgementToggle/index.tsx b/apps/evm/src/components/AcknowledgementToggle/index.tsx new file mode 100644 index 0000000000..402691f7c9 --- /dev/null +++ b/apps/evm/src/components/AcknowledgementToggle/index.tsx @@ -0,0 +1,24 @@ +import { cn } from '@venusprotocol/ui'; +import { NoticeError, Toggle, type ToggleProps } from 'components'; + +export type AcknowledgementToggleProps = Omit & { + label: string; + tooltip: string; +}; + +export const AcknowledgementToggle: React.FC = ({ + className, + label, + tooltip, + ...toggleProps +}) => ( +
+ + +
+ + +

{label}

+
+
+); diff --git a/apps/evm/src/components/BalanceUpdates/index.tsx b/apps/evm/src/components/BalanceUpdates/index.tsx index f6fdeb10d1..38155b8686 100644 --- a/apps/evm/src/components/BalanceUpdates/index.tsx +++ b/apps/evm/src/components/BalanceUpdates/index.tsx @@ -1,7 +1,17 @@ +import type BigNumber from 'bignumber.js'; import { LabeledInlineContent, type LabeledInlineContentProps, ValueUpdate } from 'components'; import { useTranslation } from 'libs/translations'; import type { BalanceMutation, Pool } from 'types'; -import { areAddressesEqual, formatTokensToReadableValue } from 'utilities'; +import { + areAddressesEqual, + formatCentsToReadableValue, + formatTokensToReadableValue, +} from 'utilities'; + +interface Row { + labeledInlineContentProps: LabeledInlineContentProps; + readableAmountDollars?: string; +} export interface BalanceUpdatesProps { pool: Pool; @@ -16,9 +26,7 @@ export const BalanceUpdates: React.FC = ({ }) => { const { t } = useTranslation(); - const balanceUpdateRows: LabeledInlineContentProps[] = balanceMutations.reduce< - LabeledInlineContentProps[] - >((acc, balanceMutation) => { + const balanceUpdateRows: Row[] = balanceMutations.reduce((acc, balanceMutation) => { // Skip VAI updates if (balanceMutation.type === 'vai') { return acc; @@ -53,26 +61,42 @@ export const BalanceUpdates: React.FC = ({ simulatedBalanceTokens = simulatedAsset?.userSupplyBalanceTokens; } - const row: LabeledInlineContentProps = { - iconSrc: asset.vToken.underlyingToken, - label, - children: ( - - ), + const original = formatTokensToReadableValue({ + token: asset.vToken.underlyingToken, + value: balanceTokens, + addSymbol: false, + }); + + const update = + simulatedBalanceTokens && + formatTokensToReadableValue({ + token: asset.vToken.underlyingToken, + value: simulatedBalanceTokens, + addSymbol: false, + }); + + const updateAmountTokens = simulatedBalanceTokens + ? simulatedBalanceTokens.minus(balanceTokens) + : undefined; + + let readableAmountDollars = updateAmountTokens + ? formatCentsToReadableValue({ + value: asset.tokenPriceCents.times(updateAmountTokens).absoluteValue(), + }) + : undefined; + + if (readableAmountDollars && updateAmountTokens) { + const sign = updateAmountTokens.isLessThan(0) ? '-' : '+'; + readableAmountDollars = `${sign} ${readableAmountDollars}`; + } + + const row: Row = { + readableAmountDollars, + labeledInlineContentProps: { + iconSrc: asset.vToken.underlyingToken, + label, + children: , + }, }; return [...acc, row]; @@ -80,8 +104,15 @@ export const BalanceUpdates: React.FC = ({ return (
- {balanceUpdateRows.map(row => ( - + {balanceUpdateRows.map(({ labeledInlineContentProps, readableAmountDollars }) => ( +
+ + + {readableAmountDollars &&

{readableAmountDollars}

} +
))}
); diff --git a/apps/evm/src/components/RiskAcknowledgementToggle/index.tsx b/apps/evm/src/components/RiskAcknowledgementToggle/index.tsx deleted file mode 100644 index c54de5019b..0000000000 --- a/apps/evm/src/components/RiskAcknowledgementToggle/index.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { cn } from '@venusprotocol/ui'; -import { NoticeError, Toggle, type ToggleProps } from 'components'; -import { useTranslation } from 'libs/translations'; - -export type RiskAcknowledgementToggleProps = ToggleProps; - -export const RiskAcknowledgementToggle: React.FC = ({ - className, - ...toggleProps -}) => { - const { t } = useTranslation(); - - return ( -
- - -
- - -

{t('operationForm.riskyOperation.toggleLabel')}

-
-
- ); -}; diff --git a/apps/evm/src/components/ValueUpdate/index.tsx b/apps/evm/src/components/ValueUpdate/index.tsx index fac7e27d48..01a83170f0 100644 --- a/apps/evm/src/components/ValueUpdate/index.tsx +++ b/apps/evm/src/components/ValueUpdate/index.tsx @@ -12,7 +12,7 @@ export const ValueUpdate: React.FC = ({ className, original, u {update && ( <> - + {update} diff --git a/apps/evm/src/components/index.ts b/apps/evm/src/components/index.ts index 09a04c9cfc..34f11410b6 100644 --- a/apps/evm/src/components/index.ts +++ b/apps/evm/src/components/index.ts @@ -50,7 +50,7 @@ export * from './Apy'; export * from './Page'; export * from './Carousel'; export * from './HealthFactorPill'; -export * from './RiskAcknowledgementToggle'; +export * from './AcknowledgementToggle'; export * from './AccountHealthBar'; export * from './AreaChart'; export * from './ChartTooltipContent'; diff --git a/apps/evm/src/constants/swap.ts b/apps/evm/src/constants/swap.ts index b83a3cd076..fca3e95ad4 100644 --- a/apps/evm/src/constants/swap.ts +++ b/apps/evm/src/constants/swap.ts @@ -1,4 +1,4 @@ export const DEFAULT_SLIPPAGE_TOLERANCE_PERCENTAGE = 0.5; -export const HIGH_PRICE_IMPACT_THRESHOLD_PERCENTAGE = 5; +export const HIGH_PRICE_IMPACT_THRESHOLD_PERCENTAGE = 3; export const MAXIMUM_PRICE_IMPACT_THRESHOLD_PERCENTAGE = 10; export const HIGH_SLIPPAGE_PERCENTAGE = 5; diff --git a/apps/evm/src/containers/MarketTable/__tests__/__snapshots__/index.eMode.spec.tsx.snap b/apps/evm/src/containers/MarketTable/__tests__/__snapshots__/index.eMode.spec.tsx.snap index 0b1c290457..114e5322b4 100644 --- a/apps/evm/src/containers/MarketTable/__tests__/__snapshots__/index.eMode.spec.tsx.snap +++ b/apps/evm/src/containers/MarketTable/__tests__/__snapshots__/index.eMode.spec.tsx.snap @@ -1,7 +1,7 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`MarketTable - Feature flag enabled: E-mode > renders correctly when user does not have any E-mode group enabled 1`] = `"Paused assetsMy assets onlyAssetAPY APY CollateralWalletXVS0.17%-6.48%100 XVS$127.86USDT4.02%5.77%-5.50%-6.51%900 USDT$900BUSD3.56%5.32%-5.82%110 BUSD$110Sort bySupply APY / LTVXVSAPY 0.17%APY -6.48%CollateralWallet100 XVS$127.86USDTAPY 4.02%5.77%APY -5.50%-6.51%CollateralWallet900 USDT$900BUSDAPY 3.56%5.32%APY -5.82%CollateralWallet110 BUSD$110"`; +exports[`MarketTable - Feature flag enabled: E-mode > renders correctly when user does not have any E-mode group enabled 1`] = `"Paused assetsMy assets onlyAssetAPY APY CollateralWalletXVS0.16%-6.48%100 XVS$127.86USDT4.01%5.76%-5.49%-6.51%900 USDT$900BUSD3.56%5.31%-5.81%110 BUSD$110Sort bySupply APY / LTVXVSAPY 0.16%APY -6.48%CollateralWallet100 XVS$127.86USDTAPY 4.01%5.76%APY -5.49%-6.51%CollateralWallet900 USDT$900BUSDAPY 3.56%5.31%APY -5.81%CollateralWallet110 BUSD$110"`; -exports[`MarketTable - Feature flag enabled: E-mode > renders correctly when user has an E-mode group enabled 1`] = `"Paused assetsMy assets onlyE-mode assets onlyAssetAPY APY CollateralWalletXVS0.17%-6.48%100 XVS$127.86USDT4.02%5.77%-5.50%-6.51%900 USDT$900BUSD3.56%5.32%-5.82%110 BUSD$110Sort bySupply APY / LTVXVSAPY 0.17%APY -6.48%CollateralWallet100 XVS$127.86USDTAPY 4.02%5.77%APY -5.50%-6.51%CollateralWallet900 USDT$900BUSDAPY 3.56%5.32%APY -5.82%CollateralWallet110 BUSD$110"`; +exports[`MarketTable - Feature flag enabled: E-mode > renders correctly when user has an E-mode group enabled 1`] = `"Paused assetsMy assets onlyE-mode assets onlyAssetAPY APY CollateralWalletXVS0.16%-6.48%100 XVS$127.86USDT4.01%5.76%-5.49%-6.51%900 USDT$900BUSD3.56%5.31%-5.81%110 BUSD$110Sort bySupply APY / LTVXVSAPY 0.16%APY -6.48%CollateralWallet100 XVS$127.86USDTAPY 4.01%5.76%APY -5.49%-6.51%CollateralWallet900 USDT$900BUSDAPY 3.56%5.31%APY -5.81%CollateralWallet110 BUSD$110"`; -exports[`MarketTable - Feature flag enabled: E-mode > shows E-mode assets only if controls are enabled and corresponding toggle is enabled 1`] = `"Paused assetsMy assets onlyE-mode assets onlyAssetAPY APY CollateralWalletXVS0.17%-6.48%100 XVS$127.86USDT4.02%5.77%-5.50%-6.51%900 USDT$900BUSD3.56%5.32%-5.82%110 BUSD$110Sort bySupply APY / LTVXVSAPY 0.17%APY -6.48%CollateralWallet100 XVS$127.86USDTAPY 4.02%5.77%APY -5.50%-6.51%CollateralWallet900 USDT$900BUSDAPY 3.56%5.32%APY -5.82%CollateralWallet110 BUSD$110"`; +exports[`MarketTable - Feature flag enabled: E-mode > shows E-mode assets only if controls are enabled and corresponding toggle is enabled 1`] = `"Paused assetsMy assets onlyE-mode assets onlyAssetAPY APY CollateralWalletXVS0.16%-6.48%100 XVS$127.86USDT4.01%5.76%-5.49%-6.51%900 USDT$900BUSD3.56%5.31%-5.81%110 BUSD$110Sort bySupply APY / LTVXVSAPY 0.16%APY -6.48%CollateralWallet100 XVS$127.86USDTAPY 4.01%5.76%APY -5.49%-6.51%CollateralWallet900 USDT$900BUSDAPY 3.56%5.31%APY -5.81%CollateralWallet110 BUSD$110"`; diff --git a/apps/evm/src/containers/MarketTable/__tests__/__snapshots__/index.spec.tsx.snap b/apps/evm/src/containers/MarketTable/__tests__/__snapshots__/index.spec.tsx.snap index 11acac92e0..919b5aed2f 100644 --- a/apps/evm/src/containers/MarketTable/__tests__/__snapshots__/index.spec.tsx.snap +++ b/apps/evm/src/containers/MarketTable/__tests__/__snapshots__/index.spec.tsx.snap @@ -1,9 +1,9 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`MarketTable > filters by search input 1`] = `"Paused assetsMy assets onlyAssetAPY APY CollateralWalletBUSD3.56%5.32%-5.82%110 BUSD$110Sort bySupply APY / LTVBUSDAPY 3.56%5.32%APY -5.82%CollateralWallet110 BUSD$110"`; +exports[`MarketTable > filters by search input 1`] = `"Paused assetsMy assets onlyAssetAPY APY CollateralWalletBUSD3.56%5.31%-5.81%110 BUSD$110Sort bySupply APY / LTVBUSDAPY 3.56%5.31%APY -5.81%CollateralWallet110 BUSD$110"`; -exports[`MarketTable > renders with pool data 1`] = `"Paused assetsMy assets onlyAssetAPY APY CollateralWalletXVS0.17%-6.48%100 XVS$127.86USDT4.02%5.77%-5.50%-6.51%900 USDT$900BUSD3.56%5.32%-5.82%110 BUSD$110Sort bySupply APY / LTVXVSAPY 0.17%APY -6.48%CollateralWallet100 XVS$127.86USDTAPY 4.02%5.77%APY -5.50%-6.51%CollateralWallet900 USDT$900BUSDAPY 3.56%5.32%APY -5.82%CollateralWallet110 BUSD$110"`; +exports[`MarketTable > renders with pool data 1`] = `"Paused assetsMy assets onlyAssetAPY APY CollateralWalletXVS0.16%-6.48%100 XVS$127.86USDT4.01%5.76%-5.49%-6.51%900 USDT$900BUSD3.56%5.31%-5.81%110 BUSD$110Sort bySupply APY / LTVXVSAPY 0.16%APY -6.48%CollateralWallet100 XVS$127.86USDTAPY 4.01%5.76%APY -5.49%-6.51%CollateralWallet900 USDT$900BUSDAPY 3.56%5.31%APY -5.81%CollateralWallet110 BUSD$110"`; -exports[`MarketTable > shows paused assets if controls are enabled and corresponding toggle is enabled 1`] = `"Paused assetsMy assets onlyAssetAPY APY CollateralWalletXVS0.17%-6.48%100 XVS$127.86USDC5.99%-7.94%-9.18%0 USDC$0USDT4.02%5.77%-5.50%-6.51%900 USDT$900BUSD3.56%5.32%-5.82%110 BUSD$110Sort bySupply APY / LTVXVSAPY 0.17%APY -6.48%CollateralWallet100 XVS$127.86USDCAPY 5.99%APY -7.94%-9.18%CollateralWallet0 USDC$0USDTAPY 4.02%5.77%APY -5.50%-6.51%CollateralWallet900 USDT$900BUSDAPY 3.56%5.32%APY -5.82%CollateralWallet110 BUSD$110"`; +exports[`MarketTable > shows paused assets if controls are enabled and corresponding toggle is enabled 1`] = `"Paused assetsMy assets onlyAssetAPY APY CollateralWalletXVS0.16%-6.48%100 XVS$127.86USDC5.99%-7.94%-9.17%0 USDC$0USDT4.01%5.76%-5.49%-6.51%900 USDT$900BUSD3.56%5.31%-5.81%110 BUSD$110Sort bySupply APY / LTVXVSAPY 0.16%APY -6.48%CollateralWallet100 XVS$127.86USDCAPY 5.99%APY -7.94%-9.17%CollateralWallet0 USDC$0USDTAPY 4.01%5.76%APY -5.49%-6.51%CollateralWallet900 USDT$900BUSDAPY 3.56%5.31%APY -5.81%CollateralWallet110 BUSD$110"`; -exports[`MarketTable > shows user assets only if controls are enabled and corresponding toggle is enabled 1`] = `"Paused assetsMy assets onlyAssetAPY APY CollateralWalletUSDT4.02%5.77%-5.50%-6.51%900 USDT$900BUSD3.56%5.32%-5.82%110 BUSD$110Sort bySupply APY / LTVUSDTAPY 4.02%5.77%APY -5.50%-6.51%CollateralWallet900 USDT$900BUSDAPY 3.56%5.32%APY -5.82%CollateralWallet110 BUSD$110"`; +exports[`MarketTable > shows user assets only if controls are enabled and corresponding toggle is enabled 1`] = `"Paused assetsMy assets onlyAssetAPY APY CollateralWalletUSDT4.01%5.76%-5.49%-6.51%900 USDT$900BUSD3.56%5.31%-5.81%110 BUSD$110Sort bySupply APY / LTVUSDTAPY 4.01%5.76%APY -5.49%-6.51%CollateralWallet900 USDT$900BUSDAPY 3.56%5.31%APY -5.81%CollateralWallet110 BUSD$110"`; diff --git a/apps/evm/src/libs/translations/translations/en.json b/apps/evm/src/libs/translations/translations/en.json index 51b82e19e4..4e7accf728 100644 --- a/apps/evm/src/libs/translations/translations/en.json +++ b/apps/evm/src/libs/translations/translations/en.json @@ -726,6 +726,7 @@ }, "repayTab": { "collateralTabTitle": "Collateral", + "noCollateralWarning": "You do not have any collateral to repay with.", "title": "Repay", "walletBalanceTabTitle": "Wallet" }, @@ -734,10 +735,6 @@ "highRisk": "High risk", "lowRisk": "Low risk" }, - "riskyOperation": { - "toggleLabel": "I acknowledge the risks involved", - "warning": "Your health factor will be low after this transaction, increasing the risk of liquidation" - }, "safeMaxButtonLabel": "SAFE MAX", "submitButtonLabel": { "boost": "Boost", @@ -767,6 +764,16 @@ "warning": { "swappingWithHighPriceImpactWarning": "The price impact of this transaction is high, which might indicate an unfavorable swap. Make sure the exchange rate and the amount of tokens exchanged meet your expectations." }, + "acknowledgements": { + "highPriceImpact": { + "label": "I acknowledge the high price impact", + "tooltip": "This transaction is expected to have a price impact of over {{priceImpactPercentage}}%" + }, + "riskyOperation": { + "label": "I acknowledge the risks involved", + "tooltip": "Your health factor will be low after this transaction, increasing the risk of liquidation" + } + }, "withdrawTabTitle": "Withdraw" }, "pagination": { diff --git a/apps/evm/src/pages/Account/Pools/Positions/__tests__/__snapshots__/index.eMode.spec.tsx.snap b/apps/evm/src/pages/Account/Pools/Positions/__tests__/__snapshots__/index.eMode.spec.tsx.snap index cd3fe039c1..cd3793d323 100644 --- a/apps/evm/src/pages/Account/Pools/Positions/__tests__/__snapshots__/index.eMode.spec.tsx.snap +++ b/apps/evm/src/pages/Account/Pools/Positions/__tests__/__snapshots__/index.eMode.spec.tsx.snap @@ -1,3 +1,3 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Positions - Feature flag enabled: E-mode > displays E-mode banner correctly when user has enabled an E-mode group 1`] = `"VenusHealth factor15.62Net APY0.02%Daily earnings$1Total supply$1.23MTotal borrow$123.33Borrow limit used:6.4%Limit:$1.92K.Health factor15.62Net APY0.02%Daily earnings$1Total supply$1.23MTotal borrow$123.33Borrow limit used:6.4%Limit:$1.92K.Health factor15.62Net APY0.02%Daily earnings$1Total supply$1.23MTotal borrow$123.33Borrow limit used:6.4%Limit:$1.92K.SuppliedAssetAPY Balancesorted descendingCollateralXVS0.17%90 XVS$115.08USDT4.02%5.77%100 USDT$100USDC5.99%100 USDC$99.99Sort bySupply balanceXVSAPY 0.17%Balance90 XVS$115.08CollateralUSDTAPY 4.02%5.77%Balance100 USDT$100CollateralUSDCAPY 5.99%Balance100 USDC$99.99CollateralBorrowedE-mode: StablecoinsAssetAPY Balancesorted descending% of limitBUSD-5.82%50 BUSD$500%USDT-5.50%-6.51%40 USDT$400%Sort byBorrow balanceBUSDAPY -5.82%Balance50 BUSD$50% of limit0%USDTAPY -5.50%-6.51%Balance40 USDT$40% of limit0%AssetsE-mode: StablecoinsSuppliedBorrowedE-mode: StablecoinsAssetAPY Balancesorted descendingCollateralXVS0.17%90 XVS$115.08USDT4.02%5.77%100 USDT$100USDC5.99%100 USDC$99.99Sort bySupply balanceXVSAPY 0.17%Balance90 XVS$115.08CollateralUSDTAPY 4.02%5.77%Balance100 USDT$100CollateralUSDCAPY 5.99%Balance100 USDC$99.99Collateral"`; +exports[`Positions - Feature flag enabled: E-mode > displays E-mode banner correctly when user has enabled an E-mode group 1`] = `"VenusHealth factor15.62Net APY0.02%Daily earnings$1Total supply$1.23MTotal borrow$123.33Borrow limit used:6.4%Limit:$1.92K.Health factor15.62Net APY0.02%Daily earnings$1Total supply$1.23MTotal borrow$123.33Borrow limit used:6.4%Limit:$1.92K.Health factor15.62Net APY0.02%Daily earnings$1Total supply$1.23MTotal borrow$123.33Borrow limit used:6.4%Limit:$1.92K.SuppliedAssetAPY Balancesorted descendingCollateralXVS0.16%90 XVS$115.08USDT4.01%5.76%100 USDT$100USDC5.99%100 USDC$99.99Sort bySupply balanceXVSAPY 0.16%Balance90 XVS$115.08CollateralUSDTAPY 4.01%5.76%Balance100 USDT$100CollateralUSDCAPY 5.99%Balance100 USDC$99.99CollateralBorrowedE-mode: StablecoinsAssetAPY Balancesorted descending% of limitBUSD-5.81%50 BUSD$500%USDT-5.49%-6.51%40 USDT$400%Sort byBorrow balanceBUSDAPY -5.81%Balance50 BUSD$50% of limit0%USDTAPY -5.49%-6.51%Balance40 USDT$40% of limit0%AssetsE-mode: StablecoinsSuppliedBorrowedE-mode: StablecoinsAssetAPY Balancesorted descendingCollateralXVS0.16%90 XVS$115.08USDT4.01%5.76%100 USDT$100USDC5.99%100 USDC$99.99Sort bySupply balanceXVSAPY 0.16%Balance90 XVS$115.08CollateralUSDTAPY 4.01%5.76%Balance100 USDT$100CollateralUSDCAPY 5.99%Balance100 USDC$99.99Collateral"`; diff --git a/apps/evm/src/pages/Account/Pools/Positions/__tests__/__snapshots__/index.spec.tsx.snap b/apps/evm/src/pages/Account/Pools/Positions/__tests__/__snapshots__/index.spec.tsx.snap index 5a43d8ba8e..58013b36fb 100644 --- a/apps/evm/src/pages/Account/Pools/Positions/__tests__/__snapshots__/index.spec.tsx.snap +++ b/apps/evm/src/pages/Account/Pools/Positions/__tests__/__snapshots__/index.spec.tsx.snap @@ -1,3 +1,3 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Positions > displays content correctly 1`] = `"VenusHealth factor15.62Net APY0.02%Daily earnings$1Total supply$1.23MTotal borrow$123.33Borrow limit used:6.4%Limit:$1.92K.Health factor15.62Net APY0.02%Daily earnings$1Total supply$1.23MTotal borrow$123.33Borrow limit used:6.4%Limit:$1.92K.Health factor15.62Net APY0.02%Daily earnings$1Total supply$1.23MTotal borrow$123.33Borrow limit used:6.4%Limit:$1.92K.SuppliedAssetAPY Balancesorted descendingCollateralXVS0.17%90 XVS$115.08USDT4.02%5.77%100 USDT$100USDC5.99%100 USDC$99.99Sort bySupply balanceXVSAPY 0.17%Balance90 XVS$115.08CollateralUSDTAPY 4.02%5.77%Balance100 USDT$100CollateralUSDCAPY 5.99%Balance100 USDC$99.99CollateralBorrowedAssetAPY Balancesorted descending% of limitBUSD-5.82%50 BUSD$500%USDT-5.50%-6.51%40 USDT$400%Sort byBorrow balanceBUSDAPY -5.82%Balance50 BUSD$50% of limit0%USDTAPY -5.50%-6.51%Balance40 USDT$40% of limit0%AssetsSuppliedBorrowedAssetAPY Balancesorted descendingCollateralXVS0.17%90 XVS$115.08USDT4.02%5.77%100 USDT$100USDC5.99%100 USDC$99.99Sort bySupply balanceXVSAPY 0.17%Balance90 XVS$115.08CollateralUSDTAPY 4.02%5.77%Balance100 USDT$100CollateralUSDCAPY 5.99%Balance100 USDC$99.99Collateral"`; +exports[`Positions > displays content correctly 1`] = `"VenusHealth factor15.62Net APY0.02%Daily earnings$1Total supply$1.23MTotal borrow$123.33Borrow limit used:6.4%Limit:$1.92K.Health factor15.62Net APY0.02%Daily earnings$1Total supply$1.23MTotal borrow$123.33Borrow limit used:6.4%Limit:$1.92K.Health factor15.62Net APY0.02%Daily earnings$1Total supply$1.23MTotal borrow$123.33Borrow limit used:6.4%Limit:$1.92K.SuppliedAssetAPY Balancesorted descendingCollateralXVS0.16%90 XVS$115.08USDT4.01%5.76%100 USDT$100USDC5.99%100 USDC$99.99Sort bySupply balanceXVSAPY 0.16%Balance90 XVS$115.08CollateralUSDTAPY 4.01%5.76%Balance100 USDT$100CollateralUSDCAPY 5.99%Balance100 USDC$99.99CollateralBorrowedAssetAPY Balancesorted descending% of limitBUSD-5.81%50 BUSD$500%USDT-5.49%-6.51%40 USDT$400%Sort byBorrow balanceBUSDAPY -5.81%Balance50 BUSD$50% of limit0%USDTAPY -5.49%-6.51%Balance40 USDT$40% of limit0%AssetsSuppliedBorrowedAssetAPY Balancesorted descendingCollateralXVS0.16%90 XVS$115.08USDT4.01%5.76%100 USDT$100USDC5.99%100 USDC$99.99Sort bySupply balanceXVSAPY 0.16%Balance90 XVS$115.08CollateralUSDTAPY 4.01%5.76%Balance100 USDT$100CollateralUSDCAPY 5.99%Balance100 USDC$99.99Collateral"`; diff --git a/apps/evm/src/pages/Account/Pools/__tests__/__snapshots__/index.spec.tsx.snap b/apps/evm/src/pages/Account/Pools/__tests__/__snapshots__/index.spec.tsx.snap index 1a7cf68519..2e6c864a47 100644 --- a/apps/evm/src/pages/Account/Pools/__tests__/__snapshots__/index.spec.tsx.snap +++ b/apps/evm/src/pages/Account/Pools/__tests__/__snapshots__/index.spec.tsx.snap @@ -1,5 +1,5 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Pools > displays content correctly 1`] = `"VenusMetaverseHealth factor15.62Net APY0.02%Daily earnings$1Total supply$1.23MTotal borrow$123.33Borrow limit used:6.4%Limit:$1.92K.Health factor15.62Net APY0.02%Daily earnings$1Total supply$1.23MTotal borrow$123.33Borrow limit used:6.4%Limit:$1.92K.Health factor15.62Net APY0.02%Daily earnings$1Total supply$1.23MTotal borrow$123.33Borrow limit used:6.4%Limit:$1.92K.SuppliedAssetAPY Balancesorted descendingCollateralXVS0.17%90 XVS$115.08USDT4.02%5.77%100 USDT$100USDC5.99%100 USDC$99.99Sort bySupply balanceXVSAPY 0.17%Balance90 XVS$115.08CollateralUSDTAPY 4.02%5.77%Balance100 USDT$100CollateralUSDCAPY 5.99%Balance100 USDC$99.99CollateralBorrowedAssetAPY Balancesorted descending% of limitBUSD-5.82%50 BUSD$500%USDT-5.50%-6.51%40 USDT$400%Sort byBorrow balanceBUSDAPY -5.82%Balance50 BUSD$50% of limit0%USDTAPY -5.50%-6.51%Balance40 USDT$40% of limit0%AssetsSuppliedBorrowedAssetAPY Balancesorted descendingCollateralXVS0.17%90 XVS$115.08USDT4.02%5.77%100 USDT$100USDC5.99%100 USDC$99.99Sort bySupply balanceXVSAPY 0.17%Balance90 XVS$115.08CollateralUSDTAPY 4.02%5.77%Balance100 USDT$100CollateralUSDCAPY 5.99%Balance100 USDC$99.99Collateral"`; +exports[`Pools > displays content correctly 1`] = `"VenusMetaverseHealth factor15.62Net APY0.02%Daily earnings$1Total supply$1.23MTotal borrow$123.33Borrow limit used:6.4%Limit:$1.92K.Health factor15.62Net APY0.02%Daily earnings$1Total supply$1.23MTotal borrow$123.33Borrow limit used:6.4%Limit:$1.92K.Health factor15.62Net APY0.02%Daily earnings$1Total supply$1.23MTotal borrow$123.33Borrow limit used:6.4%Limit:$1.92K.SuppliedAssetAPY Balancesorted descendingCollateralXVS0.16%90 XVS$115.08USDT4.01%5.76%100 USDT$100USDC5.99%100 USDC$99.99Sort bySupply balanceXVSAPY 0.16%Balance90 XVS$115.08CollateralUSDTAPY 4.01%5.76%Balance100 USDT$100CollateralUSDCAPY 5.99%Balance100 USDC$99.99CollateralBorrowedAssetAPY Balancesorted descending% of limitBUSD-5.81%50 BUSD$500%USDT-5.49%-6.51%40 USDT$400%Sort byBorrow balanceBUSDAPY -5.81%Balance50 BUSD$50% of limit0%USDTAPY -5.49%-6.51%Balance40 USDT$40% of limit0%AssetsSuppliedBorrowedAssetAPY Balancesorted descendingCollateralXVS0.16%90 XVS$115.08USDT4.01%5.76%100 USDT$100USDC5.99%100 USDC$99.99Sort bySupply balanceXVSAPY 0.16%Balance90 XVS$115.08CollateralUSDTAPY 4.01%5.76%Balance100 USDT$100CollateralUSDCAPY 5.99%Balance100 USDC$99.99Collateral"`; exports[`Pools > displays placeholder when there are no pools to display 1`] = `"No supply or borrow positions yetYour pool positions will appear hereBrowse"`; diff --git a/apps/evm/src/pages/Account/__tests__/__snapshots__/index.spec.tsx.snap b/apps/evm/src/pages/Account/__tests__/__snapshots__/index.spec.tsx.snap index d9ed0f94f3..7c59f3b805 100644 --- a/apps/evm/src/pages/Account/__tests__/__snapshots__/index.spec.tsx.snap +++ b/apps/evm/src/pages/Account/__tests__/__snapshots__/index.spec.tsx.snap @@ -1,3 +1,3 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Account > displays content correctly 1`] = `"Net worth$1.23M30D6M1YToday's change+$1.23M99.99%Absolute performance+$1.23MSummaryNet APY0.03%Daily earnings$1.08Total supply$1.23MTotal borrow$123.33Total vault stake$233Minted VAI$10PoolsVaultsVenusMetaverseHealth factor15.62Net APY0.02%Daily earnings$1Total supply$1.23MTotal borrow$123.33Borrow limit used:6.4%Limit:$1.92K.Health factor15.62Net APY0.02%Daily earnings$1Total supply$1.23MTotal borrow$123.33Borrow limit used:6.4%Limit:$1.92K.Health factor15.62Net APY0.02%Daily earnings$1Total supply$1.23MTotal borrow$123.33Borrow limit used:6.4%Limit:$1.92K.SuppliedAssetAPY Balancesorted descendingCollateralXVS0.17%90 XVS$115.08USDT4.02%5.77%100 USDT$100USDC5.99%100 USDC$99.99Sort bySupply balanceXVSAPY 0.17%Balance90 XVS$115.08CollateralUSDTAPY 4.02%5.77%Balance100 USDT$100CollateralUSDCAPY 5.99%Balance100 USDC$99.99CollateralBorrowedAssetAPY Balancesorted descending% of limitBUSD-5.82%50 BUSD$500%USDT-5.50%-6.51%40 USDT$400%Sort byBorrow balanceBUSDAPY -5.82%Balance50 BUSD$50% of limit0%USDTAPY -5.50%-6.51%Balance40 USDT$40% of limit0%AssetsSuppliedBorrowedAssetAPY Balancesorted descendingCollateralXVS0.17%90 XVS$115.08USDT4.02%5.77%100 USDT$100USDC5.99%100 USDC$99.99Sort bySupply balanceXVSAPY 0.17%Balance90 XVS$115.08CollateralUSDTAPY 4.02%5.77%Balance100 USDT$100CollateralUSDCAPY 5.99%Balance100 USDC$99.99Collateral"`; +exports[`Account > displays content correctly 1`] = `"Net worth$1.23M30D6M1YToday's change+$1.23M99.99%Absolute performance+$1.23MSummaryNet APY0.03%Daily earnings$1.08Total supply$1.23MTotal borrow$123.33Total vault stake$233Minted VAI$10PoolsVaultsVenusMetaverseHealth factor15.62Net APY0.02%Daily earnings$1Total supply$1.23MTotal borrow$123.33Borrow limit used:6.4%Limit:$1.92K.Health factor15.62Net APY0.02%Daily earnings$1Total supply$1.23MTotal borrow$123.33Borrow limit used:6.4%Limit:$1.92K.Health factor15.62Net APY0.02%Daily earnings$1Total supply$1.23MTotal borrow$123.33Borrow limit used:6.4%Limit:$1.92K.SuppliedAssetAPY Balancesorted descendingCollateralXVS0.16%90 XVS$115.08USDT4.01%5.76%100 USDT$100USDC5.99%100 USDC$99.99Sort bySupply balanceXVSAPY 0.16%Balance90 XVS$115.08CollateralUSDTAPY 4.01%5.76%Balance100 USDT$100CollateralUSDCAPY 5.99%Balance100 USDC$99.99CollateralBorrowedAssetAPY Balancesorted descending% of limitBUSD-5.81%50 BUSD$500%USDT-5.49%-6.51%40 USDT$400%Sort byBorrow balanceBUSDAPY -5.81%Balance50 BUSD$50% of limit0%USDTAPY -5.49%-6.51%Balance40 USDT$40% of limit0%AssetsSuppliedBorrowedAssetAPY Balancesorted descendingCollateralXVS0.16%90 XVS$115.08USDT4.01%5.76%100 USDT$100USDC5.99%100 USDC$99.99Sort bySupply balanceXVSAPY 0.16%Balance90 XVS$115.08CollateralUSDTAPY 4.01%5.76%Balance100 USDT$100CollateralUSDCAPY 5.99%Balance100 USDC$99.99Collateral"`; diff --git a/apps/evm/src/pages/Bridge/index.tsx b/apps/evm/src/pages/Bridge/index.tsx index 7c938704a9..f36db5df03 100644 --- a/apps/evm/src/pages/Bridge/index.tsx +++ b/apps/evm/src/pages/Bridge/index.tsx @@ -291,7 +291,7 @@ const BridgePage: React.FC = () => {
-
+
displays markets table correctly 1`] = `"Paused assetsMy assets onlyAssetTotal supplySupply APY sorted descendingTotal borrowBorrow APY LiquidityUSDT> 100T USDT$10.98T4.02%5.77%232.51M USDT$31.58M-5.50%-6.51%10 USDT$55.34MBUSD> 100T BUSD$10.54B3.56%5.32%142.66M BUSD$839.1M-5.82%10 BUSD$36.54MXVS> 100T XVS$2.78M0.17%1.85M XVS$709.25K-6.48%10 XVS$80.36MSort bySupply APY / LTVUSDTTotal supply> 100T USDT$10.98TSupply APY 4.02%5.77%Total borrow232.51M USDT$31.58MBorrow APY -5.50%-6.51%Liquidity10 USDT$55.34MBUSDTotal supply> 100T BUSD$10.54BSupply APY 3.56%5.32%Total borrow142.66M BUSD$839.1MBorrow APY -5.82%Liquidity10 BUSD$36.54MXVSTotal supply> 100T XVS$2.78MSupply APY 0.17%Total borrow1.85M XVS$709.25KBorrow APY -6.48%Liquidity10 XVS$80.36M"`; +exports[`Isolated Pools > displays markets table correctly 1`] = `"Paused assetsMy assets onlyAssetTotal supplySupply APY sorted descendingTotal borrowBorrow APY LiquidityUSDT> 100T USDT$10.98T4.01%5.76%232.51M USDT$31.58M-5.49%-6.51%10 USDT$55.34MBUSD> 100T BUSD$10.54B3.56%5.31%142.66M BUSD$839.1M-5.81%10 BUSD$36.54MXVS> 100T XVS$2.78M0.16%1.85M XVS$709.25K-6.48%10 XVS$80.36MSort bySupply APY / LTVUSDTTotal supply> 100T USDT$10.98TSupply APY 4.01%5.76%Total borrow232.51M USDT$31.58MBorrow APY -5.49%-6.51%Liquidity10 USDT$55.34MBUSDTotal supply> 100T BUSD$10.54BSupply APY 3.56%5.31%Total borrow142.66M BUSD$839.1MBorrow APY -5.81%Liquidity10 BUSD$36.54MXVSTotal supply> 100T XVS$2.78MSupply APY 0.16%Total borrow1.85M XVS$709.25KBorrow APY -6.48%Liquidity10 XVS$80.36M"`; -exports[`Isolated Pools > displays wallet balance column in table when wallet is connected 1`] = `"Paused assetsMy assets onlyAssetTotal supplySupply APY sorted descendingTotal borrowBorrow APY LiquidityUSDT> 100T USDT$10.98T4.02%5.77%232.51M USDT$31.58M-5.50%-6.51%10 USDT$55.34MBUSD> 100T BUSD$10.54B3.56%5.32%142.66M BUSD$839.1M-5.82%10 BUSD$36.54MXVS> 100T XVS$2.78M0.17%1.85M XVS$709.25K-6.48%10 XVS$80.36MSort bySupply APY / LTVUSDTTotal supply> 100T USDT$10.98TSupply APY 4.02%5.77%Total borrow232.51M USDT$31.58MBorrow APY -5.50%-6.51%Liquidity10 USDT$55.34MBUSDTotal supply> 100T BUSD$10.54BSupply APY 3.56%5.32%Total borrow142.66M BUSD$839.1MBorrow APY -5.82%Liquidity10 BUSD$36.54MXVSTotal supply> 100T XVS$2.78MSupply APY 0.17%Total borrow1.85M XVS$709.25KBorrow APY -6.48%Liquidity10 XVS$80.36M"`; +exports[`Isolated Pools > displays wallet balance column in table when wallet is connected 1`] = `"Paused assetsMy assets onlyAssetTotal supplySupply APY sorted descendingTotal borrowBorrow APY LiquidityUSDT> 100T USDT$10.98T4.01%5.76%232.51M USDT$31.58M-5.49%-6.51%10 USDT$55.34MBUSD> 100T BUSD$10.54B3.56%5.31%142.66M BUSD$839.1M-5.81%10 BUSD$36.54MXVS> 100T XVS$2.78M0.16%1.85M XVS$709.25K-6.48%10 XVS$80.36MSort bySupply APY / LTVUSDTTotal supply> 100T USDT$10.98TSupply APY 4.01%5.76%Total borrow232.51M USDT$31.58MBorrow APY -5.49%-6.51%Liquidity10 USDT$55.34MBUSDTotal supply> 100T BUSD$10.54BSupply APY 3.56%5.31%Total borrow142.66M BUSD$839.1MBorrow APY -5.81%Liquidity10 BUSD$36.54MXVSTotal supply> 100T XVS$2.78MSupply APY 0.16%Total borrow1.85M XVS$709.25KBorrow APY -6.48%Liquidity10 XVS$80.36M"`; diff --git a/apps/evm/src/pages/Market/OperationForm/ApyBreakdown/__tests__/__snapshots__/index.spec.tsx.snap b/apps/evm/src/pages/Market/OperationForm/ApyBreakdown/__tests__/__snapshots__/index.spec.tsx.snap index 2743a3615b..8c308b7521 100644 --- a/apps/evm/src/pages/Market/OperationForm/ApyBreakdown/__tests__/__snapshots__/index.spec.tsx.snap +++ b/apps/evm/src/pages/Market/OperationForm/ApyBreakdown/__tests__/__snapshots__/index.spec.tsx.snap @@ -1,13 +1,13 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`ApyBreakdown > renders correct values: 'borrow' 1`] = `"Borrow APY-4.97%Distribution APY0.52%Total APY6.50%"`; +exports[`ApyBreakdown > renders correct values: 'borrow' 1`] = `"Borrow APY-4.97%Distribution APY0.52%Total APY6.49%"`; -exports[`ApyBreakdown > renders correct values: 'multiple mutations' 1`] = `"Supply APY0.05%Distribution APY0.12%Borrow APY-4.97%Distribution APY0.52%Net APY7.67%"`; +exports[`ApyBreakdown > renders correct values: 'multiple mutations' 1`] = `"Supply APY0.05%Distribution APY0.11%Borrow APY-4.97%Distribution APY0.52%Net APY7.66%"`; exports[`ApyBreakdown > renders correct values: 'no mutations' 1`] = `"Total APY0%"`; -exports[`ApyBreakdown > renders correct values: 'repay' 1`] = `"Borrow APY-4.97%Distribution APY0.52%Total APY6.50%"`; +exports[`ApyBreakdown > renders correct values: 'repay' 1`] = `"Borrow APY-4.97%Distribution APY0.52%Total APY6.49%"`; -exports[`ApyBreakdown > renders correct values: 'supply' 1`] = `"Supply APY0.05%Distribution APY0.12%Total APY1.17%"`; +exports[`ApyBreakdown > renders correct values: 'supply' 1`] = `"Supply APY0.05%Distribution APY0.11%Total APY1.16%"`; -exports[`ApyBreakdown > renders correct values: 'withdraw' 1`] = `"Supply APY3.89%Distribution APY1.35%Prime APY0.75%1.75%Total APY7.99%"`; +exports[`ApyBreakdown > renders correct values: 'withdraw' 1`] = `"Supply APY3.88%Distribution APY1.35%Prime APY0.75%1.75%Total APY7.99%"`; diff --git a/apps/evm/src/pages/Market/OperationForm/BoostForm/SubmitSection/index.tsx b/apps/evm/src/pages/Market/OperationForm/BoostForm/SubmitSection/index.tsx index 7075317939..80d18df6ad 100644 --- a/apps/evm/src/pages/Market/OperationForm/BoostForm/SubmitSection/index.tsx +++ b/apps/evm/src/pages/Market/OperationForm/BoostForm/SubmitSection/index.tsx @@ -46,7 +46,11 @@ export const SubmitSection: React.FC = ({ const approveDelegate = () => updatePoolDelegateStatus({ approvedStatus: true }); const submitButtonLabel = useMemo(() => { - if (!isFormValid && formErrorCode !== 'REQUIRES_RISK_ACKNOWLEDGEMENT') { + if ( + !isFormValid && + formErrorCode !== 'REQUIRES_RISK_ACKNOWLEDGEMENT' && + formErrorCode !== 'REQUIRES_SWAP_PRICE_IMPACT_ACKNOWLEDGEMENT' + ) { return t('operationForm.submitButtonLabel.enterValidAmount'); } diff --git a/apps/evm/src/pages/Market/OperationForm/BoostForm/__tests__/index.spec.tsx b/apps/evm/src/pages/Market/OperationForm/BoostForm/__tests__/index.spec.tsx index 0261e5fcd3..16d8970f4e 100644 --- a/apps/evm/src/pages/Market/OperationForm/BoostForm/__tests__/index.spec.tsx +++ b/apps/evm/src/pages/Market/OperationForm/BoostForm/__tests__/index.spec.tsx @@ -14,7 +14,10 @@ import { HEALTH_FACTOR_LIQUIDATION_THRESHOLD, HEALTH_FACTOR_MODERATE_THRESHOLD, } from 'constants/healthFactor'; -import { MAXIMUM_PRICE_IMPACT_THRESHOLD_PERCENTAGE } from 'constants/swap'; +import { + HIGH_PRICE_IMPACT_THRESHOLD_PERCENTAGE, + MAXIMUM_PRICE_IMPACT_THRESHOLD_PERCENTAGE, +} from 'constants/swap'; import { useSimulateBalanceMutations } from 'hooks/useSimulateBalanceMutations'; import { VError } from 'libs/errors'; import { en } from 'libs/translations'; @@ -50,7 +53,6 @@ const testCases = [ }, }, ], - [ 'user health factor', { @@ -395,14 +397,21 @@ describe('BoostForm', () => { await checkSubmitButtonIsDisabled(); }); - it('disables submit button if amount to supply to open position is higher than asset supply cap', async () => { - const customFakeAsset: Asset = { - ...fakeAsset, - supplyCapTokens: new BigNumber(1000), - supplyBalanceTokens: new BigNumber(999), + it('disables submit button if amount to supply to open position is higher than supplied asset supply cap', async () => { + const customFakePool: Pool = { + ...fakePool, + assets: fakePool.assets.map(asset => + asset.vToken.symbol === 'vUSDT' + ? { + ...asset, + supplyCapTokens: new BigNumber(1000), + supplyBalanceTokens: new BigNumber(999), + } + : asset, + ), }; - const { getByTestId } = renderComponent(, { + const { getByTestId } = renderComponent(, { accountAddress: fakeAccountAddress, }); @@ -505,7 +514,70 @@ describe('BoostForm', () => { await waitFor(() => expect(tokenTextInput.value).toEqual('10')); // Check warning is displayed - expect(getByText(en.operationForm.riskyOperation.warning)); + expect(getByText(en.operationForm.acknowledgements.riskyOperation.tooltip)); + + // Check submit button is disabled + const submitButton = document.querySelector('button[type="submit"]') as HTMLButtonElement; + await waitFor(() => + expect(submitButton).toHaveTextContent(en.operationForm.submitButtonLabel.boost), + ); + expect(submitButton).toBeDisabled(); + + // Toggle acknowledgement + const toggle = getByRole('checkbox'); + fireEvent.click(toggle); + + await waitFor(() => expect(document.querySelector('button[type="submit"]')).toBeEnabled()); + }); + + it('prompts user to acknowledge high price impact', async () => { + (useGetSwapQuote as Mock).mockImplementation((input: GetExactInSwapQuoteInput) => ({ + isLoading: false, + data: { + swapQuote: { + fromToken: input.fromToken, + toToken: input.toToken, + direction: 'exact-in', + priceImpactPercentage: HIGH_PRICE_IMPACT_THRESHOLD_PERCENTAGE, + fromTokenAmountSoldMantissa: BigInt( + convertTokensToMantissa({ + value: input.fromTokenAmountTokens, + token: input.fromToken, + }).toFixed(), + ), + expectedToTokenAmountReceivedMantissa: 100000000n, + minimumToTokenAmountReceivedMantissa: 100000000n, + callData: '0x', + }, + }, + })); + + const { getByText, getByTestId, getByRole } = renderComponent( + , + { + accountAddress: fakeAccountAddress, + }, + ); + + // Enter amount in input + const tokenTextInput = await waitFor( + () => getByTestId(TEST_IDS.tokenTextField) as HTMLInputElement, + ); + fireEvent.change(tokenTextInput, { + target: { value: 10 }, + }); + + await waitFor(() => expect(tokenTextInput.value).toEqual('10')); + + // Check warning is displayed + expect( + getByText( + en.operationForm.acknowledgements.highPriceImpact.tooltip.replace( + '{{priceImpactPercentage}}', + `${HIGH_PRICE_IMPACT_THRESHOLD_PERCENTAGE}`, + ), + ), + ); // Check submit button is disabled const submitButton = document.querySelector('button[type="submit"]') as HTMLButtonElement; diff --git a/apps/evm/src/pages/Market/OperationForm/BoostForm/index.tsx b/apps/evm/src/pages/Market/OperationForm/BoostForm/index.tsx index d97de0756c..29a564e3f3 100644 --- a/apps/evm/src/pages/Market/OperationForm/BoostForm/index.tsx +++ b/apps/evm/src/pages/Market/OperationForm/BoostForm/index.tsx @@ -4,16 +4,20 @@ import { useEffect, useMemo, useState } from 'react'; import { useGetSwapQuote, useOpenLeveragedPosition } from 'clients/api'; import { + AcknowledgementToggle, Delimiter, Icon, LabeledInlineContent, type OptionalTokenBalance, - RiskAcknowledgementToggle, TokenListWrapper, TokenTextField, } from 'components'; import { NULL_ADDRESS } from 'constants/address'; import { HEALTH_FACTOR_MODERATE_THRESHOLD } from 'constants/healthFactor'; +import { + HIGH_PRICE_IMPACT_THRESHOLD_PERCENTAGE, + MAXIMUM_PRICE_IMPACT_THRESHOLD_PERCENTAGE, +} from 'constants/swap'; import { ConnectWallet } from 'containers/ConnectWallet'; import { SwapDetails } from 'containers/SwapDetails'; import useDebounceValue from 'hooks/useDebounceValue'; @@ -35,12 +39,13 @@ import { import { ApyBreakdown } from '../ApyBreakdown'; import { OperationDetails } from '../OperationDetails'; import { calculateAmountDollars } from '../calculateAmountDollars'; +import type { FormError } from '../types'; import { RiskSlider } from './RiskSlider'; import { SelectTokenField } from './SelectTokenField'; import { SubmitSection } from './SubmitSection'; import { calculateUserMaxBorrowTokens } from './calculateUserMaxBorrowTokens'; import TEST_IDS from './testIds'; -import useForm, { type FormValues, type UseFormInput } from './useForm'; +import useForm, { type FormErrorCode, type FormValues, type UseFormInput } from './useForm'; export interface BoostFormProps { asset: Asset; @@ -94,6 +99,7 @@ const BoostForm: React.FC = ({ asset: borrowedAsset, pool }) => suppliedToken: tokenBalances.length > 0 ? tokenBalances[0].token : borrowedAsset.vToken.underlyingToken, acknowledgeRisk: false, + acknowledgeHighPriceImpact: false, }; return values; @@ -234,8 +240,14 @@ const BoostForm: React.FC = ({ asset: borrowedAsset, pool }) => simulatedPool?.userHealthFactor !== undefined && simulatedPool?.userHealthFactor < HEALTH_FACTOR_MODERATE_THRESHOLD; - const { handleSubmit, isFormValid, formError } = useForm({ + const isHighPriceImpact = + swapQuote?.priceImpactPercentage !== undefined && + swapQuote?.priceImpactPercentage >= HIGH_PRICE_IMPACT_THRESHOLD_PERCENTAGE && + swapQuote?.priceImpactPercentage < MAXIMUM_PRICE_IMPACT_THRESHOLD_PERCENTAGE; + + const { handleSubmit, isFormValid, formErrors } = useForm({ borrowedAsset, + suppliedAsset, pool, simulatedPool, limitTokens, @@ -247,6 +259,7 @@ const BoostForm: React.FC = ({ asset: borrowedAsset, pool }) => setFormValues, initialFormValues, }); + const formError: FormError | undefined = formErrors[0]; // Convert input amount to percentage of limit const riskSliderValue = @@ -287,6 +300,13 @@ const BoostForm: React.FC = ({ asset: borrowedAsset, pool }) => })); }; + const handleToggleAcknowledgeHighPriceImpact = (checked: boolean) => { + setFormValues(currentFormValues => ({ + ...currentFormValues, + acknowledgeHighPriceImpact: checked, + })); + }; + const handleMaxButtonClick = () => { captureAmountSetAnalyticEvent({ amountTokens: limitTokens, @@ -329,8 +349,17 @@ const BoostForm: React.FC = ({ asset: borrowedAsset, pool }) => // TODO: capture event }; - const shouldAskUserRiskAcknowledgement = - isRiskyOperation && (!formError || formError?.code === 'REQUIRES_RISK_ACKNOWLEDGEMENT'); + const shouldAskRiskAcknowledgement = + isRiskyOperation && + (!formError || + formError.code === 'REQUIRES_RISK_ACKNOWLEDGEMENT' || + formError.code === 'REQUIRES_SWAP_PRICE_IMPACT_ACKNOWLEDGEMENT'); + + const shouldAskPriceImpactAcknowledgement = + isHighPriceImpact && + (!formError || + formError.code === 'REQUIRES_RISK_ACKNOWLEDGEMENT' || + formError.code === 'REQUIRES_SWAP_PRICE_IMPACT_ACKNOWLEDGEMENT'); const isDisabled = !accountAddress || @@ -368,6 +397,7 @@ const BoostForm: React.FC = ({ asset: borrowedAsset, pool }) => Number(formValues.amountTokens) > 0 && !!formError && formError.code !== 'REQUIRES_RISK_ACKNOWLEDGEMENT' && + formError.code !== 'REQUIRES_SWAP_PRICE_IMPACT_ACKNOWLEDGEMENT' && formError.code !== 'MISSING_DATA' } /> @@ -427,10 +457,23 @@ const BoostForm: React.FC = ({ asset: borrowedAsset, pool }) => balanceMutations={balanceMutations} /> - {shouldAskUserRiskAcknowledgement && ( - handleToggleAcknowledgeRisk(checked)} + label={t('operationForm.acknowledgements.riskyOperation.label')} + tooltip={t('operationForm.acknowledgements.riskyOperation.tooltip')} + /> + )} + + {shouldAskPriceImpactAcknowledgement && ( + handleToggleAcknowledgeHighPriceImpact(checked)} + label={t('operationForm.acknowledgements.highPriceImpact.label')} + tooltip={t('operationForm.acknowledgements.highPriceImpact.tooltip', { + priceImpactPercentage: HIGH_PRICE_IMPACT_THRESHOLD_PERCENTAGE, + })} /> )}
@@ -439,7 +482,7 @@ const BoostForm: React.FC = ({ asset: borrowedAsset, pool }) => diff --git a/apps/evm/src/pages/Market/OperationForm/BoostForm/useForm/index.tsx b/apps/evm/src/pages/Market/OperationForm/BoostForm/useForm/index.tsx index 05f9920d77..b560091a10 100644 --- a/apps/evm/src/pages/Market/OperationForm/BoostForm/useForm/index.tsx +++ b/apps/evm/src/pages/Market/OperationForm/BoostForm/useForm/index.tsx @@ -10,6 +10,7 @@ export * from './types'; export interface UseFormInput { borrowedAsset: Asset; + suppliedAsset: Asset; pool: Pool; limitTokens: BigNumber; onSubmit: () => Promise; @@ -25,11 +26,12 @@ export interface UseFormInput { interface UseFormOutput { handleSubmit: (e?: React.SyntheticEvent) => Promise; isFormValid: boolean; - formError?: FormError; + formErrors: FormError[]; } const useForm = ({ borrowedAsset, + suppliedAsset, pool, simulatedPool, limitTokens, @@ -41,8 +43,9 @@ const useForm = ({ getSwapQuoteError, onSubmit, }: UseFormInput): UseFormOutput => { - const { isFormValid, formError } = useFormValidation({ - asset: borrowedAsset, + const { isFormValid, formErrors } = useFormValidation({ + borrowedAsset, + suppliedAsset, pool, limitTokens, simulatedPool, @@ -75,7 +78,7 @@ const useForm = ({ return { handleSubmit, isFormValid, - formError, + formErrors, }; }; diff --git a/apps/evm/src/pages/Market/OperationForm/BoostForm/useForm/types.ts b/apps/evm/src/pages/Market/OperationForm/BoostForm/useForm/types.ts index 9ac5a0f02d..530cf22901 100644 --- a/apps/evm/src/pages/Market/OperationForm/BoostForm/useForm/types.ts +++ b/apps/evm/src/pages/Market/OperationForm/BoostForm/useForm/types.ts @@ -4,6 +4,7 @@ export interface FormValues { suppliedToken: Token; amountTokens: string; acknowledgeRisk: boolean; + acknowledgeHighPriceImpact: boolean; } export type FormErrorCode = @@ -16,6 +17,7 @@ export type FormErrorCode = | 'HIGHER_THAN_AVAILABLE_AMOUNT' | 'TOO_RISKY' | 'REQUIRES_RISK_ACKNOWLEDGEMENT' + | 'REQUIRES_SWAP_PRICE_IMPACT_ACKNOWLEDGEMENT' | 'NO_SWAP_QUOTE_FOUND' | 'MISSING_DATA' | 'SWAP_PRICE_IMPACT_TOO_HIGH'; diff --git a/apps/evm/src/pages/Market/OperationForm/BoostForm/useForm/useFormValidation.ts b/apps/evm/src/pages/Market/OperationForm/BoostForm/useForm/useFormValidation.ts index 333bf238bc..fdbbeb1f70 100644 --- a/apps/evm/src/pages/Market/OperationForm/BoostForm/useForm/useFormValidation.ts +++ b/apps/evm/src/pages/Market/OperationForm/BoostForm/useForm/useFormValidation.ts @@ -5,7 +5,10 @@ import { HEALTH_FACTOR_LIQUIDATION_THRESHOLD, HEALTH_FACTOR_MODERATE_THRESHOLD, } from 'constants/healthFactor'; -import { MAXIMUM_PRICE_IMPACT_THRESHOLD_PERCENTAGE } from 'constants/swap'; +import { + HIGH_PRICE_IMPACT_THRESHOLD_PERCENTAGE, + MAXIMUM_PRICE_IMPACT_THRESHOLD_PERCENTAGE, +} from 'constants/swap'; import type { VError } from 'libs/errors'; import { useTranslation } from 'libs/translations'; import type { Asset, Pool, SwapQuote } from 'types'; @@ -14,7 +17,8 @@ import type { FormError } from '../../types'; import type { FormErrorCode, FormValues } from './types'; interface UseFormValidationInput { - asset: Asset; + borrowedAsset: Asset; + suppliedAsset: Asset; pool: Pool; formValues: FormValues; limitTokens: BigNumber; @@ -26,11 +30,12 @@ interface UseFormValidationInput { interface UseFormValidationOutput { isFormValid: boolean; - formError?: FormError; + formErrors: FormError[]; } const useFormValidation = ({ - asset, + borrowedAsset, + suppliedAsset, pool, limitTokens, simulatedPool, @@ -41,36 +46,31 @@ const useFormValidation = ({ }: UseFormValidationInput): UseFormValidationOutput => { const { t } = useTranslation(); - const formError = useMemo | undefined>(() => { + const formErrors = useMemo[]>(() => { + const tmpErrors: FormError[] = []; + if (!pool?.userBorrowLimitCents || pool.userBorrowLimitCents.isEqualTo(0)) { - return { + tmpErrors.push({ code: 'NO_COLLATERALS', message: t('operationForm.error.noCollateral', { - tokenSymbol: asset.vToken.underlyingToken.symbol, + tokenSymbol: borrowedAsset.vToken.underlyingToken.symbol, }), - }; + }); } if ( - asset.borrowCapTokens && - asset.borrowBalanceTokens.isGreaterThanOrEqualTo(asset.borrowCapTokens) + borrowedAsset.borrowCapTokens && + borrowedAsset.borrowBalanceTokens.isGreaterThanOrEqualTo(borrowedAsset.borrowCapTokens) ) { - return { + tmpErrors.push({ code: 'BORROW_CAP_ALREADY_REACHED', message: t('operationForm.error.borrowCapReached', { assetBorrowCap: formatTokensToReadableValue({ - value: asset.borrowCapTokens, - token: asset.vToken.underlyingToken, + value: borrowedAsset.borrowCapTokens, + token: borrowedAsset.vToken.underlyingToken, }), }), - }; - } - - if (getSwapQuoteError?.code === 'noSwapQuoteFound') { - return { - code: 'NO_SWAP_QUOTE_FOUND', - message: t('operationForm.error.noSwapQuoteFound'), - }; + }); } const borrowedTokenAmountTokens = formValues.amountTokens @@ -78,101 +78,122 @@ const useFormValidation = ({ : undefined; if (!borrowedTokenAmountTokens || borrowedTokenAmountTokens.isLessThanOrEqualTo(0)) { - return { + tmpErrors.push({ code: 'EMPTY_TOKEN_AMOUNT', - }; + }); + } + + if (getSwapQuoteError?.code === 'noSwapQuoteFound') { + tmpErrors.push({ + code: 'NO_SWAP_QUOTE_FOUND', + message: t('operationForm.error.noSwapQuoteFound'), + }); } if ( - asset.borrowBalanceTokens.plus(borrowedTokenAmountTokens).isGreaterThan(asset.borrowCapTokens) + borrowedTokenAmountTokens && + borrowedAsset.borrowBalanceTokens + .plus(borrowedTokenAmountTokens) + .isGreaterThan(borrowedAsset.borrowCapTokens) ) { - return { + tmpErrors.push({ code: 'HIGHER_THAN_BORROW_CAP', message: t('operationForm.error.higherThanBorrowCap', { userMaxBorrowAmount: formatTokensToReadableValue({ - value: asset.borrowCapTokens.minus(asset.borrowBalanceTokens), - token: asset.vToken.underlyingToken, - maxDecimalPlaces: asset.vToken.underlyingToken.decimals, + value: borrowedAsset.borrowCapTokens.minus(borrowedAsset.borrowBalanceTokens), + token: borrowedAsset.vToken.underlyingToken, + maxDecimalPlaces: borrowedAsset.vToken.underlyingToken.decimals, }), assetBorrowCap: formatTokensToReadableValue({ - value: asset.borrowCapTokens, - token: asset.vToken.underlyingToken, - maxDecimalPlaces: asset.vToken.underlyingToken.decimals, + value: borrowedAsset.borrowCapTokens, + token: borrowedAsset.vToken.underlyingToken, + maxDecimalPlaces: borrowedAsset.vToken.underlyingToken.decimals, }), assetBorrowBalance: formatTokensToReadableValue({ - value: asset.borrowBalanceTokens, - token: asset.vToken.underlyingToken, - maxDecimalPlaces: asset.vToken.underlyingToken.decimals, + value: borrowedAsset.borrowBalanceTokens, + token: borrowedAsset.vToken.underlyingToken, + maxDecimalPlaces: borrowedAsset.vToken.underlyingToken.decimals, }), }), - }; + }); } if ( expectedSuppliedAmountTokens && - asset.supplyBalanceTokens + suppliedAsset.supplyBalanceTokens .plus(expectedSuppliedAmountTokens) - .isGreaterThan(asset.supplyCapTokens) + .isGreaterThan(suppliedAsset.supplyCapTokens) ) { - return { + tmpErrors.push({ code: 'HIGHER_THAN_SUPPLY_CAP', message: t('operationForm.error.higherThanSupplyCap', { userMaxSupplyAmount: formatTokensToReadableValue({ - value: asset.supplyCapTokens.minus(asset.supplyBalanceTokens), - token: asset.vToken.underlyingToken, - maxDecimalPlaces: asset.vToken.underlyingToken.decimals, + value: suppliedAsset.supplyCapTokens.minus(suppliedAsset.supplyBalanceTokens), + token: suppliedAsset.vToken.underlyingToken, + maxDecimalPlaces: suppliedAsset.vToken.underlyingToken.decimals, }), assetSupplyCap: formatTokensToReadableValue({ - value: asset.supplyCapTokens, - token: asset.vToken.underlyingToken, - maxDecimalPlaces: asset.vToken.underlyingToken.decimals, + value: suppliedAsset.supplyCapTokens, + token: suppliedAsset.vToken.underlyingToken, + maxDecimalPlaces: suppliedAsset.vToken.underlyingToken.decimals, }), assetSupplyBalance: formatTokensToReadableValue({ - value: asset.supplyBalanceTokens, - token: asset.vToken.underlyingToken, - maxDecimalPlaces: asset.vToken.underlyingToken.decimals, + value: suppliedAsset.supplyBalanceTokens, + token: suppliedAsset.vToken.underlyingToken, + maxDecimalPlaces: suppliedAsset.vToken.underlyingToken.decimals, }), }), - }; + }); } - const assetLiquidityTokens = new BigNumber(asset.liquidityCents).dividedBy( - asset.tokenPriceCents, + const assetLiquidityTokens = new BigNumber(borrowedAsset.liquidityCents).dividedBy( + borrowedAsset.tokenPriceCents, ); - if (borrowedTokenAmountTokens.isGreaterThan(assetLiquidityTokens)) { + if (borrowedTokenAmountTokens?.isGreaterThan(assetLiquidityTokens)) { // User is trying to borrow more than available liquidity - return { + tmpErrors.push({ code: 'HIGHER_THAN_LIQUIDITY', message: t('operationForm.error.higherThanAvailableLiquidity'), - }; + }); } - if (borrowedTokenAmountTokens.isGreaterThan(limitTokens)) { - return { + if (borrowedTokenAmountTokens?.isGreaterThan(limitTokens)) { + tmpErrors.push({ code: 'HIGHER_THAN_AVAILABLE_AMOUNT', message: t('operationForm.error.higherThanAvailableAmount'), - }; + }); } if ( simulatedPool?.userHealthFactor !== undefined && simulatedPool.userHealthFactor <= HEALTH_FACTOR_LIQUIDATION_THRESHOLD ) { - return { + tmpErrors.push({ code: 'TOO_RISKY', message: t('operationForm.error.tooRisky'), - }; + }); } if ( swapQuote && swapQuote?.priceImpactPercentage >= MAXIMUM_PRICE_IMPACT_THRESHOLD_PERCENTAGE ) { - return { + tmpErrors.push({ code: 'SWAP_PRICE_IMPACT_TOO_HIGH', message: t('operationForm.error.priceImpactTooHigh'), - }; + }); + } + + if ( + swapQuote && + swapQuote?.priceImpactPercentage >= HIGH_PRICE_IMPACT_THRESHOLD_PERCENTAGE && + swapQuote?.priceImpactPercentage < MAXIMUM_PRICE_IMPACT_THRESHOLD_PERCENTAGE && + !formValues.acknowledgeHighPriceImpact + ) { + tmpErrors.push({ + code: 'REQUIRES_SWAP_PRICE_IMPACT_ACKNOWLEDGEMENT', + }); } if ( @@ -180,18 +201,21 @@ const useFormValidation = ({ simulatedPool.userHealthFactor < HEALTH_FACTOR_MODERATE_THRESHOLD && !formValues.acknowledgeRisk ) { - return { + tmpErrors.push({ code: 'REQUIRES_RISK_ACKNOWLEDGEMENT', - }; + }); } if (!simulatedPool || !expectedSuppliedAmountTokens) { - return { + tmpErrors.push({ code: 'MISSING_DATA', - }; + }); } + + return tmpErrors; }, [ - asset, + borrowedAsset, + suppliedAsset, pool, limitTokens, simulatedPool, @@ -199,13 +223,14 @@ const useFormValidation = ({ expectedSuppliedAmountTokens, getSwapQuoteError, formValues.acknowledgeRisk, + formValues.acknowledgeHighPriceImpact, swapQuote, t, ]); return { - isFormValid: !formError, - formError, + isFormValid: !formErrors.length, + formErrors, }; }; diff --git a/apps/evm/src/pages/Market/OperationForm/BorrowForm/__tests__/index.spec.tsx b/apps/evm/src/pages/Market/OperationForm/BorrowForm/__tests__/index.spec.tsx index f004ab4900..bca90f80a7 100644 --- a/apps/evm/src/pages/Market/OperationForm/BorrowForm/__tests__/index.spec.tsx +++ b/apps/evm/src/pages/Market/OperationForm/BorrowForm/__tests__/index.spec.tsx @@ -363,7 +363,7 @@ describe('BorrowForm', () => { await waitFor(() => expect(tokenTextInput.value).toBe(marginWithRiskyThresholdTokens)); // Check warning is displayed - expect(getByText(en.operationForm.riskyOperation.warning)); + expect(getByText(en.operationForm.acknowledgements.riskyOperation.tooltip)); // Check submit button is disabled const submitButton = document.querySelector('button[type="submit"]') as HTMLButtonElement; diff --git a/apps/evm/src/pages/Market/OperationForm/BorrowForm/index.tsx b/apps/evm/src/pages/Market/OperationForm/BorrowForm/index.tsx index 497c0c4f88..2aa478b0c6 100644 --- a/apps/evm/src/pages/Market/OperationForm/BorrowForm/index.tsx +++ b/apps/evm/src/pages/Market/OperationForm/BorrowForm/index.tsx @@ -4,9 +4,9 @@ import { useEffect, useMemo, useState } from 'react'; import { cn } from '@venusprotocol/ui'; import { useBorrow } from 'clients/api'; import { + AcknowledgementToggle, Delimiter, LabeledInlineContent, - RiskAcknowledgementToggle, Toggle, TokenTextField, } from 'components'; @@ -364,7 +364,9 @@ export const BorrowFormUi: React.FC = ({ /> {shouldAskUserRiskAcknowledgement && ( - handleToggleAcknowledgeRisk(checked)} /> diff --git a/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/SubmitSection/index.tsx b/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/SubmitSection/index.tsx index 96b5daaebd..329de0e569 100644 --- a/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/SubmitSection/index.tsx +++ b/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/SubmitSection/index.tsx @@ -1,3 +1,4 @@ +import { cn } from '@venusprotocol/ui'; import { useMemo } from 'react'; import { PrimaryButton } from 'components'; @@ -15,6 +16,7 @@ export interface SubmitSectionProps { isLoading: boolean; poolComptrollerContractAddress: Address; formErrorCode?: FormErrorCode; + isRiskyOperation: boolean; } export const SubmitSection: React.FC = ({ @@ -22,6 +24,7 @@ export const SubmitSection: React.FC = ({ isLoading, formErrorCode, poolComptrollerContractAddress, + isRiskyOperation, }) => { const { t } = useTranslation(); @@ -43,7 +46,11 @@ export const SubmitSection: React.FC = ({ const approveDelegate = () => updatePoolDelegateStatus({ approvedStatus: true }); const submitButtonLabel = useMemo(() => { - if (!isFormValid && formErrorCode !== 'REQUIRES_RISK_ACKNOWLEDGEMENT') { + if ( + !isFormValid && + formErrorCode !== 'REQUIRES_RISK_ACKNOWLEDGEMENT' && + formErrorCode !== 'REQUIRES_SWAP_PRICE_IMPACT_ACKNOWLEDGEMENT' + ) { return t('operationForm.submitButtonLabel.enterValidAmount'); } @@ -55,7 +62,7 @@ export const SubmitSection: React.FC = ({ type="submit" loading={isLoading} disabled={!isFormValid || isLoading} - className="w-full" + className={cn('w-full', isRiskyOperation && 'border-red bg-red')} > {submitButtonLabel} diff --git a/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/__tests__/index.spec.tsx b/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/__tests__/index.spec.tsx index 1211869a34..f87bda89b8 100644 --- a/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/__tests__/index.spec.tsx +++ b/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/__tests__/index.spec.tsx @@ -18,7 +18,10 @@ import { HEALTH_FACTOR_LIQUIDATION_THRESHOLD, HEALTH_FACTOR_MODERATE_THRESHOLD, } from 'constants/healthFactor'; -import { MAXIMUM_PRICE_IMPACT_THRESHOLD_PERCENTAGE } from 'constants/swap'; +import { + HIGH_PRICE_IMPACT_THRESHOLD_PERCENTAGE, + MAXIMUM_PRICE_IMPACT_THRESHOLD_PERCENTAGE, +} from 'constants/swap'; import { useSimulateBalanceMutations } from 'hooks/useSimulateBalanceMutations'; import { VError } from 'libs/errors'; import { en } from 'libs/translations'; @@ -413,6 +416,63 @@ describe('RepayWithCollateralForm', () => { await checkSubmitButtonIsDisabled(); }); + it('prompts user to acknowledge high price impact', async () => { + const customFakeSwapQuote: SwapQuote = { + ...fakeExactInSwapQuote, + priceImpactPercentage: HIGH_PRICE_IMPACT_THRESHOLD_PERCENTAGE, + }; + + (useGetSwapQuote as Mock).mockImplementation(() => ({ + isLoading: false, + data: { + swapQuote: customFakeSwapQuote, + }, + })); + + const { getByTestId, getByText, getByRole } = renderComponent( + , + { + accountAddress: fakeAccountAddress, + }, + ); + + const selectTokenTextField = getByTestId( + getTokenTextFieldTestId({ + parentTestId: TEST_IDS.selectCollateralTokenTextField, + }), + ) as HTMLInputElement; + + // Enter amount in input + fireEvent.change(selectTokenTextField, { + target: { value: 10 }, + }); + + // Check warning is displayed + expect( + getByText( + en.operationForm.acknowledgements.highPriceImpact.tooltip.replace( + '{{priceImpactPercentage}}', + `${HIGH_PRICE_IMPACT_THRESHOLD_PERCENTAGE}`, + ), + ), + ); + + // Check submit button is disabled + const submitButton = document.querySelector('button[type="submit"]') as HTMLButtonElement; + await waitFor(() => + expect(submitButton).toHaveTextContent(en.operationForm.submitButtonLabel.repay), + ); + expect(submitButton).toBeDisabled(); + + // Toggle acknowledgement + const toggle = getByRole('checkbox'); + fireEvent.click(toggle); + + await checkSubmitButtonIsEnabled({ + textContent: en.operationForm.submitButtonLabel.repay, + }); + }); + it('prompts user to acknowledge risk if position would lower health factor to risky threshold', async () => { (useSimulateBalanceMutations as Mock).mockImplementation(() => ({ isLoading: false, @@ -443,7 +503,7 @@ describe('RepayWithCollateralForm', () => { }); // Check warning is displayed - expect(getByText(en.operationForm.riskyOperation.warning)); + expect(getByText(en.operationForm.acknowledgements.riskyOperation.tooltip)); // Check submit button is disabled const submitButton = document.querySelector('button[type="submit"]') as HTMLButtonElement; diff --git a/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/index.tsx b/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/index.tsx index fa305a40dd..fa613f2004 100644 --- a/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/index.tsx +++ b/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/index.tsx @@ -9,15 +9,19 @@ import { useRepayWithCollateral, } from 'clients/api'; import { + AcknowledgementToggle, Icon, LabeledInlineContent, type OptionalTokenBalance, - RiskAcknowledgementToggle, SelectTokenTextField, TokenTextField, } from 'components'; import { NULL_ADDRESS } from 'constants/address'; import { HEALTH_FACTOR_MODERATE_THRESHOLD } from 'constants/healthFactor'; +import { + HIGH_PRICE_IMPACT_THRESHOLD_PERCENTAGE, + MAXIMUM_PRICE_IMPACT_THRESHOLD_PERCENTAGE, +} from 'constants/swap'; import { ConnectWallet } from 'containers/ConnectWallet'; import { SwapDetails } from 'containers/SwapDetails'; import useDebounceValue from 'hooks/useDebounceValue'; @@ -25,6 +29,7 @@ import { useGetContractAddress } from 'hooks/useGetContractAddress'; import { useGetUserSlippageTolerance } from 'hooks/useGetUserSlippageTolerance'; import { useSimulateBalanceMutations } from 'hooks/useSimulateBalanceMutations'; import { VError } from 'libs/errors'; + import { useTranslation } from 'libs/translations'; import { useAccountAddress } from 'libs/wallet'; import type { Asset, BalanceMutation, Pool } from 'types'; @@ -38,10 +43,11 @@ import { } from 'utilities'; import { ApyBreakdown } from '../../ApyBreakdown'; import { OperationDetails } from '../../OperationDetails'; +import type { FormError } from '../../types'; import { Notice } from '../Notice'; import { SubmitSection } from './SubmitSection'; import TEST_IDS from './testIds'; -import useForm, { type FormValues, type UseFormInput } from './useForm'; +import useForm, { type FormErrorCode, type FormValues, type UseFormInput } from './useForm'; export interface RepayWithCollateralFormProps { asset: Asset; @@ -64,30 +70,44 @@ export const RepayWithCollateralForm: React.FC = ( const { mutateAsync: repayWithCollateral, isPending: isRepayWithCollateralLoading } = useRepayWithCollateral(); - const initialFormValues: FormValues = useMemo(() => { - let initialCollateralAsset = repaidAsset; + const tokenBalances = [...pool.assets] + // Sort by user supply balance + .sort((a, b) => compareBigNumbers(a.userSupplyBalanceCents, b.userSupplyBalanceCents, 'desc')) + .reduce((acc, asset) => { + if ( + // Skip vBNB + asset.vToken.symbol === 'vBNB' || + // Skip tokens for which user has no supply + asset.userSupplyBalanceCents.isEqualTo(0) + ) { + return acc; + } - // Find asset with the highest collateral factor and initialize form with it - let highestUserSupplyCents = new BigNumber(0); + const tokenBalance: OptionalTokenBalance = { + token: asset.vToken.underlyingToken, + balanceMantissa: convertTokensToMantissa({ + value: asset.userSupplyBalanceTokens, + token: asset.vToken.underlyingToken, + }), + }; - pool.assets.forEach(a => { - if (a.userSupplyBalanceCents.isGreaterThan(highestUserSupplyCents)) { - highestUserSupplyCents = a.userSupplyBalanceCents; - initialCollateralAsset = a; - } - }); + return [...acc, tokenBalance]; + }, []); + const initialFormValues: FormValues = useMemo(() => { const values: FormValues = { direction: 'exact-in', - collateralToken: initialCollateralAsset.vToken.underlyingToken, + collateralToken: + tokenBalances.length > 0 ? tokenBalances[0].token : repaidAsset.vToken.underlyingToken, collateralAmountTokens: '', repaidAmountTokens: '', acknowledgeRisk: false, + acknowledgeHighPriceImpact: false, repayFullLoan: false, }; return values; - }, [repaidAsset, pool.assets]); + }, [tokenBalances, repaidAsset]); const [formValues, setFormValues] = useState(initialFormValues); @@ -213,30 +233,6 @@ export const RepayWithCollateralForm: React.FC = ( }); const swapQuote = getSwapQuoteData?.swapQuote; - const tokenBalances = [...pool.assets] - // Sort by user supply balance - .sort((a, b) => compareBigNumbers(a.userSupplyBalanceCents, b.userSupplyBalanceCents, 'desc')) - .reduce((acc, asset) => { - if ( - // Skip vBNB - asset.vToken.symbol === 'vBNB' || - // Skip tokens for which user has no supply - asset.userSupplyBalanceCents.isEqualTo(0) - ) { - return acc; - } - - const tokenBalance: OptionalTokenBalance = { - token: asset.vToken.underlyingToken, - balanceMantissa: convertTokensToMantissa({ - value: asset.userSupplyBalanceTokens, - token: asset.vToken.underlyingToken, - }), - }; - - return [...acc, tokenBalance]; - }, []); - const balanceMutations: BalanceMutation[] = [ { type: 'asset', @@ -263,7 +259,12 @@ export const RepayWithCollateralForm: React.FC = ( simulatedPool?.userHealthFactor !== undefined && simulatedPool?.userHealthFactor < HEALTH_FACTOR_MODERATE_THRESHOLD; - const { handleSubmit, isFormValid, formError } = useForm({ + const isHighPriceImpact = + swapQuote?.priceImpactPercentage !== undefined && + swapQuote?.priceImpactPercentage >= HIGH_PRICE_IMPACT_THRESHOLD_PERCENTAGE && + swapQuote?.priceImpactPercentage < MAXIMUM_PRICE_IMPACT_THRESHOLD_PERCENTAGE; + + const { handleSubmit, isFormValid, formErrors } = useForm({ limitTokens, repaidAsset, simulatedPool, @@ -276,6 +277,8 @@ export const RepayWithCollateralForm: React.FC = ( getSwapQuoteError: getSwapQuoteError || undefined, }); + const formError: FormError | undefined = formErrors[0]; + const handleToggleAcknowledgeRisk = (checked: boolean) => { setFormValues(currentFormValues => ({ ...currentFormValues, @@ -283,6 +286,13 @@ export const RepayWithCollateralForm: React.FC = ( })); }; + const handleToggleAcknowledgeHighPriceImpact = (checked: boolean) => { + setFormValues(currentFormValues => ({ + ...currentFormValues, + acknowledgeHighPriceImpact: checked, + })); + }; + const handleMaxCollateralButtonClick = () => { setFormValues(currentFormValues => ({ ...currentFormValues, @@ -306,8 +316,17 @@ export const RepayWithCollateralForm: React.FC = ( })); }; - const shouldAskUserRiskAcknowledgement = - isRiskyOperation && (!formError || formError?.code === 'REQUIRES_RISK_ACKNOWLEDGEMENT'); + const shouldAskRiskAcknowledgement = + isRiskyOperation && + (!formError || + formError.code === 'REQUIRES_RISK_ACKNOWLEDGEMENT' || + formError.code === 'REQUIRES_SWAP_PRICE_IMPACT_ACKNOWLEDGEMENT'); + + const shouldAskPriceImpactAcknowledgement = + isHighPriceImpact && + (!formError || + formError.code === 'REQUIRES_RISK_ACKNOWLEDGEMENT' || + formError.code === 'REQUIRES_SWAP_PRICE_IMPACT_ACKNOWLEDGEMENT'); const isDisabled = !accountAddress || isSubmitting; @@ -326,6 +345,7 @@ export const RepayWithCollateralForm: React.FC = ( !isSubmitting && !!formError && formError.code !== 'REQUIRES_RISK_ACKNOWLEDGEMENT' && + formError.code !== 'REQUIRES_SWAP_PRICE_IMPACT_ACKNOWLEDGEMENT' && formError.code !== 'MISSING_DATA' && formError.code !== 'EMPTY_TOKEN_AMOUNT' && formError.code !== 'HIGHER_THAN_BORROW_BALANCE' @@ -454,18 +474,32 @@ export const RepayWithCollateralForm: React.FC = ( showApyBreakdown={false} /> - {shouldAskUserRiskAcknowledgement && ( - handleToggleAcknowledgeRisk(checked)} /> )} + + {shouldAskPriceImpactAcknowledgement && ( + handleToggleAcknowledgeHighPriceImpact(checked)} + label={t('operationForm.acknowledgements.highPriceImpact.label')} + tooltip={t('operationForm.acknowledgements.highPriceImpact.tooltip', { + priceImpactPercentage: HIGH_PRICE_IMPACT_THRESHOLD_PERCENTAGE, + })} + /> + )}
diff --git a/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/useForm/index.tsx b/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/useForm/index.tsx index e04bc97af6..992a94fca7 100644 --- a/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/useForm/index.tsx +++ b/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/useForm/index.tsx @@ -27,7 +27,7 @@ export interface UseFormInput { interface UseFormOutput { handleSubmit: (e?: React.SyntheticEvent) => Promise; isFormValid: boolean; - formError?: FormError; + formErrors: FormError[]; } const useForm = ({ @@ -42,7 +42,7 @@ const useForm = ({ getSwapQuoteError, isUsingSwap, }: UseFormInput): UseFormOutput => { - const { isFormValid, formError } = useFormValidation({ + const { isFormValid, formErrors } = useFormValidation({ limitTokens, formValues, simulatedPool, @@ -115,7 +115,7 @@ const useForm = ({ return { handleSubmit, isFormValid, - formError, + formErrors, }; }; diff --git a/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/useForm/types.ts b/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/useForm/types.ts index c9fdbaa27c..de03909d97 100644 --- a/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/useForm/types.ts +++ b/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/useForm/types.ts @@ -6,6 +6,7 @@ export interface FormValues { collateralAmountTokens: string; repaidAmountTokens: string; acknowledgeRisk: boolean; + acknowledgeHighPriceImpact: boolean; repayFullLoan: boolean; } @@ -15,6 +16,7 @@ export type FormErrorCode = | 'HIGHER_THAN_AVAILABLE_AMOUNT' | 'TOO_RISKY' | 'REQUIRES_RISK_ACKNOWLEDGEMENT' + | 'REQUIRES_SWAP_PRICE_IMPACT_ACKNOWLEDGEMENT' | 'NO_SWAP_QUOTE_FOUND' | 'MISSING_DATA' | 'SWAP_PRICE_IMPACT_TOO_HIGH'; diff --git a/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/useForm/useFormValidation.ts b/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/useForm/useFormValidation.ts index 861fa02a80..2fb3aa107b 100644 --- a/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/useForm/useFormValidation.ts +++ b/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/useForm/useFormValidation.ts @@ -5,7 +5,10 @@ import { HEALTH_FACTOR_LIQUIDATION_THRESHOLD, HEALTH_FACTOR_MODERATE_THRESHOLD, } from 'constants/healthFactor'; -import { MAXIMUM_PRICE_IMPACT_THRESHOLD_PERCENTAGE } from 'constants/swap'; +import { + HIGH_PRICE_IMPACT_THRESHOLD_PERCENTAGE, + MAXIMUM_PRICE_IMPACT_THRESHOLD_PERCENTAGE, +} from 'constants/swap'; import type { VError } from 'libs/errors'; import { useTranslation } from 'libs/translations'; import type { Asset, Pool, SwapQuote } from 'types'; @@ -24,7 +27,7 @@ interface UseFormValidationInput { interface UseFormValidationOutput { isFormValid: boolean; - formError?: FormError; + formErrors: FormError[]; } const useFormValidation = ({ @@ -38,7 +41,9 @@ const useFormValidation = ({ }: UseFormValidationInput): UseFormValidationOutput => { const { t } = useTranslation(); - const formError = useMemo | undefined>(() => { + const formErrors = useMemo[]>(() => { + const tmpErrors: FormError[] = []; + const collateralAmountTokens = formValues.collateralAmountTokens ? new BigNumber(formValues.collateralAmountTokens) : undefined; @@ -48,50 +53,61 @@ const useFormValidation = ({ : undefined; if (collateralAmountTokens?.isGreaterThan(limitTokens)) { - return { + tmpErrors.push({ code: 'HIGHER_THAN_AVAILABLE_AMOUNT', message: t('operationForm.error.higherThanAvailableAmount'), - }; + }); } if (repaidAmountTokens?.isGreaterThan(repaidAsset.userBorrowBalanceTokens)) { - return { + tmpErrors.push({ code: 'HIGHER_THAN_BORROW_BALANCE', message: t('operationForm.error.higherThanBorrowBalance'), - }; + }); + } + + if (!collateralAmountTokens?.isGreaterThan(0) || !repaidAmountTokens?.isGreaterThan(0)) { + tmpErrors.push({ + code: 'EMPTY_TOKEN_AMOUNT', + }); } if (getSwapQuoteError?.code === 'noSwapQuoteFound') { - return { + tmpErrors.push({ code: 'NO_SWAP_QUOTE_FOUND', message: t('operationForm.error.noSwapQuoteFound'), - }; + }); } - if (!collateralAmountTokens?.isGreaterThan(0) || !repaidAmountTokens?.isGreaterThan(0)) { - return { - code: 'EMPTY_TOKEN_AMOUNT', - }; + if ( + simulatedPool?.userHealthFactor !== undefined && + simulatedPool.userHealthFactor <= HEALTH_FACTOR_LIQUIDATION_THRESHOLD + ) { + tmpErrors.push({ + code: 'TOO_RISKY', + message: t('operationForm.error.tooRisky'), + }); } if ( swapQuote && swapQuote?.priceImpactPercentage >= MAXIMUM_PRICE_IMPACT_THRESHOLD_PERCENTAGE ) { - return { + tmpErrors.push({ code: 'SWAP_PRICE_IMPACT_TOO_HIGH', message: t('operationForm.error.priceImpactTooHigh'), - }; + }); } if ( - simulatedPool?.userHealthFactor !== undefined && - simulatedPool.userHealthFactor <= HEALTH_FACTOR_LIQUIDATION_THRESHOLD + swapQuote && + swapQuote?.priceImpactPercentage >= HIGH_PRICE_IMPACT_THRESHOLD_PERCENTAGE && + swapQuote?.priceImpactPercentage < MAXIMUM_PRICE_IMPACT_THRESHOLD_PERCENTAGE && + !formValues.acknowledgeHighPriceImpact ) { - return { - code: 'TOO_RISKY', - message: t('operationForm.error.tooRisky'), - }; + tmpErrors.push({ + code: 'REQUIRES_SWAP_PRICE_IMPACT_ACKNOWLEDGEMENT', + }); } if ( @@ -99,20 +115,23 @@ const useFormValidation = ({ simulatedPool.userHealthFactor < HEALTH_FACTOR_MODERATE_THRESHOLD && !formValues.acknowledgeRisk ) { - return { + tmpErrors.push({ code: 'REQUIRES_RISK_ACKNOWLEDGEMENT', - }; + }); } if (!simulatedPool || (!swapQuote && isUsingSwap)) { - return { + tmpErrors.push({ code: 'MISSING_DATA', - }; + }); } + + return tmpErrors; }, [ limitTokens, formValues.collateralAmountTokens, formValues.acknowledgeRisk, + formValues.acknowledgeHighPriceImpact, formValues.repaidAmountTokens, repaidAsset.userBorrowBalanceTokens, t, @@ -123,8 +142,8 @@ const useFormValidation = ({ ]); return { - isFormValid: !formError, - formError, + isFormValid: !formErrors.length, + formErrors, }; }; diff --git a/apps/evm/src/pages/Market/OperationForm/Repay/index.tsx b/apps/evm/src/pages/Market/OperationForm/Repay/index.tsx index 9e6969b6c6..f322708f70 100644 --- a/apps/evm/src/pages/Market/OperationForm/Repay/index.tsx +++ b/apps/evm/src/pages/Market/OperationForm/Repay/index.tsx @@ -1,4 +1,4 @@ -import { Tabs } from 'components'; +import { NoticeWarning, Tabs } from 'components'; import { useIsFeatureEnabled } from 'hooks/useIsFeatureEnabled'; import type { Tab } from 'hooks/useTabs'; import { useTranslation } from 'libs/translations'; @@ -39,6 +39,8 @@ export const Repay: React.FC = ({ asset, pool }) => { return repayWithWalletBalanceFormDom; } + const hasCollateral = pool.userBorrowLimitCents?.isGreaterThan(0); + const tabs: Tab[] = [ { id: 'repayWithWalletBalance', @@ -48,7 +50,11 @@ export const Repay: React.FC = ({ asset, pool }) => { { id: 'repayWithCollateral', title: t('operationForm.repayTab.collateralTabTitle'), - content: , + content: hasCollateral ? ( + + ) : ( + + ), }, ]; diff --git a/apps/evm/src/pages/Market/OperationForm/WithdrawForm/__tests__/index.spec.tsx b/apps/evm/src/pages/Market/OperationForm/WithdrawForm/__tests__/index.spec.tsx index c46fc82e38..df7bd8cbd8 100644 --- a/apps/evm/src/pages/Market/OperationForm/WithdrawForm/__tests__/index.spec.tsx +++ b/apps/evm/src/pages/Market/OperationForm/WithdrawForm/__tests__/index.spec.tsx @@ -341,7 +341,7 @@ describe('WithdrawForm', () => { await waitFor(() => expect(tokenTextInput.value).toBe(inputValue)); // Check warning is displayed - expect(getByText(en.operationForm.riskyOperation.warning)); + expect(getByText(en.operationForm.acknowledgements.riskyOperation.tooltip)); // Check submit button is disabled const submitButton = document.querySelector('button[type="submit"]') as HTMLButtonElement; diff --git a/apps/evm/src/pages/Market/OperationForm/WithdrawForm/index.tsx b/apps/evm/src/pages/Market/OperationForm/WithdrawForm/index.tsx index 5627d48d44..89592840b8 100644 --- a/apps/evm/src/pages/Market/OperationForm/WithdrawForm/index.tsx +++ b/apps/evm/src/pages/Market/OperationForm/WithdrawForm/index.tsx @@ -4,9 +4,9 @@ import { useEffect, useMemo, useState } from 'react'; import { cn } from '@venusprotocol/ui'; import { useGetVTokenBalance, useWithdraw } from 'clients/api'; import { + AcknowledgementToggle, Delimiter, LabeledInlineContent, - RiskAcknowledgementToggle, Toggle, TokenTextField, } from 'components'; @@ -377,7 +377,9 @@ export const WithdrawFormUi: React.FC = ({ /> {shouldAskUserRiskAcknowledgement && ( - handleToggleAcknowledgeRisk(checked)} /> diff --git a/apps/evm/src/pages/Market/__tests__/__snapshots__/index.spec.tsx.snap b/apps/evm/src/pages/Market/__tests__/__snapshots__/index.spec.tsx.snap index b2fb71a0fa..230902a22f 100644 --- a/apps/evm/src/pages/Market/__tests__/__snapshots__/index.spec.tsx.snap +++ b/apps/evm/src/pages/Market/__tests__/__snapshots__/index.spec.tsx.snap @@ -1,3 +1,3 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Market > displays content correctly 1`] = `"Supplying XVS to the Venus pool will enable you to borrow tokens from this pool exclusively. Show all marketsSupplyWithdrawBorrowRepayCollateralMAXSupply APY0.05%Distribution APY0.12%Total APY0.17%Connect walletSupply info0%Total supplied> $100T / > $100T> 100T / > 100T XVSCurrent APY0.17%Borrow info< 0.01%Total borrowed$2.36M / > $100T1.85M / > 100T XVSCurrent APY-6.48%Liquidation Threshold50%Liquidation Penalty4%Interest Rate ModelUtilization rateBorrow APYSupply APYE-mode infoStablecoinsCollateralBorrowableCollateralBorrowableMax LTV60%Liquidation threshold62%Liquidation penalty0%GameFiCollateralBorrowableCollateralBorrowableMax LTV60%Liquidation threshold62%Liquidation penalty0%Market infoDaily supplying interests$3.98Daily borrowing interests-$44.81Daily XVS distributed19.99MReserve factor25%Exchange rate1 XVS=49.589181 vXVS"`; +exports[`Market > displays content correctly 1`] = `"Supplying XVS to the Venus pool will enable you to borrow tokens from this pool exclusively. Show all marketsSupplyWithdrawBorrowRepayCollateralMAXSupply APY0.05%Distribution APY0.11%Total APY0.16%Connect walletSupply info0%Total supplied> $100T / > $100T> 100T / > 100T XVSCurrent APY0.16%Borrow info< 0.01%Total borrowed$2.36M / > $100T1.85M / > 100T XVSCurrent APY-6.48%Liquidation Threshold50%Liquidation Penalty4%Interest Rate ModelUtilization rateBorrow APYSupply APYE-mode infoStablecoinsCollateralBorrowableCollateralBorrowableMax LTV60%Liquidation threshold62%Liquidation penalty0%GameFiCollateralBorrowableCollateralBorrowableMax LTV60%Liquidation threshold62%Liquidation penalty0%Market infoDaily supplying interests$3.98Daily borrowing interests-$44.81Daily XVS distributed19.99MReserve factor25%Exchange rate1 XVS=49.589181 vXVS"`; diff --git a/apps/evm/src/pages/Pool/__tests__/__snapshots__/index.eMode.spec.tsx.snap b/apps/evm/src/pages/Pool/__tests__/__snapshots__/index.eMode.spec.tsx.snap index 7f02180261..4076e80b91 100644 --- a/apps/evm/src/pages/Pool/__tests__/__snapshots__/index.eMode.spec.tsx.snap +++ b/apps/evm/src/pages/Pool/__tests__/__snapshots__/index.eMode.spec.tsx.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Pool - Feature flag enabled: E-mode > Assets tab > renders correctly when pool has E-mode groups 1`] = `"AssetsE-modeTotal supply$20.99TTotal borrow$8.58TAvailable liquidity$12.4TAssets4Boost is available now!One-click leverage to boost your yield up to 5x.Learn moreBoost is available now!One-click leverage to boost your yield up to 5x.Venus is live on Binance Wallet Earn.Borrow USDT and share $400,000 in rewards!Learn moreVenus is live on Binance Wallet Earn.Borrow USDT and share $400,000 in rewards!Isolated pools are being sunsettedClaim your rewards and close your positionsLearn moreIsolated pools are being sunsettedClaim your rewards and close your positionsBelieve in something?Give your conviction a boost on probable.marketsStart NowBelieve in something?Give your conviction a boost on probable.marketsPaused assetsMy assets onlyE-mode assets onlyAssetTotal supplySupply APY sorted descendingTotal borrowBorrow APY LiquidityUSDT> 100T USDT$10.98T4.02%5.77%232.51M USDT$31.58M-5.50%-6.51%10 USDT$55.34MBUSD> 100T BUSD$10.54B3.56%5.32%142.66M BUSD$839.1M-5.82%10 BUSD$36.54MXVS> 100T XVS$2.78M0.17%1.85M XVS$709.25K-6.48%10 XVS$80.36MSort bySupply APY / LTVUSDTTotal supply> 100T USDT$10.98TSupply APY 4.02%5.77%Total borrow232.51M USDT$31.58MBorrow APY -5.50%-6.51%Liquidity10 USDT$55.34MBUSDTotal supply> 100T BUSD$10.54BSupply APY 3.56%5.32%Total borrow142.66M BUSD$839.1MBorrow APY -5.82%Liquidity10 BUSD$36.54MXVSTotal supply> 100T XVS$2.78MSupply APY 0.17%Total borrow1.85M XVS$709.25KBorrow APY -6.48%Liquidity10 XVS$80.36M"`; +exports[`Pool - Feature flag enabled: E-mode > Assets tab > renders correctly when pool has E-mode groups 1`] = `"AssetsE-modeTotal supply$20.99TTotal borrow$8.58TAvailable liquidity$12.4TAssets4Boost is available now!One-click leverage to boost your yield up to 5x.Learn moreBoost is available now!One-click leverage to boost your yield up to 5x.Venus is live on Binance Wallet Earn.Borrow USDT and share $400,000 in rewards!Learn moreVenus is live on Binance Wallet Earn.Borrow USDT and share $400,000 in rewards!Isolated pools are being sunsettedClaim your rewards and close your positionsLearn moreIsolated pools are being sunsettedClaim your rewards and close your positionsBelieve in something?Give your conviction a boost on probable.marketsStart NowBelieve in something?Give your conviction a boost on probable.marketsPaused assetsMy assets onlyE-mode assets onlyAssetTotal supplySupply APY sorted descendingTotal borrowBorrow APY LiquidityUSDT> 100T USDT$10.98T4.01%5.76%232.51M USDT$31.58M-5.49%-6.51%10 USDT$55.34MBUSD> 100T BUSD$10.54B3.56%5.31%142.66M BUSD$839.1M-5.81%10 BUSD$36.54MXVS> 100T XVS$2.78M0.16%1.85M XVS$709.25K-6.48%10 XVS$80.36MSort bySupply APY / LTVUSDTTotal supply> 100T USDT$10.98TSupply APY 4.01%5.76%Total borrow232.51M USDT$31.58MBorrow APY -5.49%-6.51%Liquidity10 USDT$55.34MBUSDTotal supply> 100T BUSD$10.54BSupply APY 3.56%5.31%Total borrow142.66M BUSD$839.1MBorrow APY -5.81%Liquidity10 BUSD$36.54MXVSTotal supply> 100T XVS$2.78MSupply APY 0.16%Total borrow1.85M XVS$709.25KBorrow APY -6.48%Liquidity10 XVS$80.36M"`; exports[`Pool - Feature flag enabled: E-mode > E-mode tab > filters assets correctly when using search 1`] = `"AssetsE-modeEnabling E-Mode allows you to maximize your borrowing power, however, borrowing is restricted to assets within the selected group. Learn moreSort by:Max LTVStablecoinsDisableStablecoinsDisableAssetCollateralBorrowableMax LTVsorted descendingThresholdPenaltySort byMax LTVDeFiHealth factor:15.621.66ModerateSwitchHealth factor:15.621.66ModerateBUSDCollateralBorrowableMax LTV90%Liquidation threshold92%Liquidation penalty30%DeFiHealth factor:15.621.66ModerateSwitchHealth factor:15.621.66ModerateAssetCollateralBorrowableMax LTVsorted descendingThresholdPenaltyBUSD90%92%30%Sort byMax LTVBUSDCollateralBorrowableMax LTV90%Threshold92%Penalty30%"`; diff --git a/apps/evm/src/pages/Pool/__tests__/__snapshots__/index.spec.tsx.snap b/apps/evm/src/pages/Pool/__tests__/__snapshots__/index.spec.tsx.snap index ff106377c4..fe6fecfad3 100644 --- a/apps/evm/src/pages/Pool/__tests__/__snapshots__/index.spec.tsx.snap +++ b/apps/evm/src/pages/Pool/__tests__/__snapshots__/index.spec.tsx.snap @@ -1,3 +1,3 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Pool > renders correctly 1`] = `"Total supply$20.99TTotal borrow$8.58TAvailable liquidity$12.4TAssets4Boost is available now!One-click leverage to boost your yield up to 5x.Learn moreBoost is available now!One-click leverage to boost your yield up to 5x.Venus is live on Binance Wallet Earn.Borrow USDT and share $400,000 in rewards!Learn moreVenus is live on Binance Wallet Earn.Borrow USDT and share $400,000 in rewards!Isolated pools are being sunsettedClaim your rewards and close your positionsLearn moreIsolated pools are being sunsettedClaim your rewards and close your positionsBelieve in something?Give your conviction a boost on probable.marketsStart NowBelieve in something?Give your conviction a boost on probable.marketsPaused assetsMy assets onlyAssetTotal supplySupply APY sorted descendingTotal borrowBorrow APY LiquidityUSDT> 100T USDT$10.98T4.02%5.77%232.51M USDT$31.58M-5.50%-6.51%10 USDT$55.34MBUSD> 100T BUSD$10.54B3.56%5.32%142.66M BUSD$839.1M-5.82%10 BUSD$36.54MXVS> 100T XVS$2.78M0.17%1.85M XVS$709.25K-6.48%10 XVS$80.36MSort bySupply APY / LTVUSDTTotal supply> 100T USDT$10.98TSupply APY 4.02%5.77%Total borrow232.51M USDT$31.58MBorrow APY -5.50%-6.51%Liquidity10 USDT$55.34MBUSDTotal supply> 100T BUSD$10.54BSupply APY 3.56%5.32%Total borrow142.66M BUSD$839.1MBorrow APY -5.82%Liquidity10 BUSD$36.54MXVSTotal supply> 100T XVS$2.78MSupply APY 0.17%Total borrow1.85M XVS$709.25KBorrow APY -6.48%Liquidity10 XVS$80.36M"`; +exports[`Pool > renders correctly 1`] = `"Total supply$20.99TTotal borrow$8.58TAvailable liquidity$12.4TAssets4Boost is available now!One-click leverage to boost your yield up to 5x.Learn moreBoost is available now!One-click leverage to boost your yield up to 5x.Venus is live on Binance Wallet Earn.Borrow USDT and share $400,000 in rewards!Learn moreVenus is live on Binance Wallet Earn.Borrow USDT and share $400,000 in rewards!Isolated pools are being sunsettedClaim your rewards and close your positionsLearn moreIsolated pools are being sunsettedClaim your rewards and close your positionsBelieve in something?Give your conviction a boost on probable.marketsStart NowBelieve in something?Give your conviction a boost on probable.marketsPaused assetsMy assets onlyAssetTotal supplySupply APY sorted descendingTotal borrowBorrow APY LiquidityUSDT> 100T USDT$10.98T4.01%5.76%232.51M USDT$31.58M-5.49%-6.51%10 USDT$55.34MBUSD> 100T BUSD$10.54B3.56%5.31%142.66M BUSD$839.1M-5.81%10 BUSD$36.54MXVS> 100T XVS$2.78M0.16%1.85M XVS$709.25K-6.48%10 XVS$80.36MSort bySupply APY / LTVUSDTTotal supply> 100T USDT$10.98TSupply APY 4.01%5.76%Total borrow232.51M USDT$31.58MBorrow APY -5.49%-6.51%Liquidity10 USDT$55.34MBUSDTotal supply> 100T BUSD$10.54BSupply APY 3.56%5.31%Total borrow142.66M BUSD$839.1MBorrow APY -5.81%Liquidity10 BUSD$36.54MXVSTotal supply> 100T XVS$2.78MSupply APY 0.16%Total borrow1.85M XVS$709.25KBorrow APY -6.48%Liquidity10 XVS$80.36M"`; diff --git a/apps/evm/src/pages/Vai/Borrow/index.tsx b/apps/evm/src/pages/Vai/Borrow/index.tsx index 0667c12bee..9e0fea125f 100644 --- a/apps/evm/src/pages/Vai/Borrow/index.tsx +++ b/apps/evm/src/pages/Vai/Borrow/index.tsx @@ -10,11 +10,11 @@ import { useMintVai, } from 'clients/api'; import { + AcknowledgementToggle, Delimiter, LabeledInlineContent, NoticeError, NoticeWarning, - RiskAcknowledgementToggle, Spinner, } from 'components'; import PLACEHOLDER_KEY from 'constants/placeholderKey'; @@ -320,7 +320,13 @@ export const Borrow: React.FC = () => { } + render={({ field }) => ( + + )} /> )} diff --git a/apps/evm/src/utilities/formatPercentageToReadableValue/__tests__/index.spec.ts b/apps/evm/src/utilities/formatPercentageToReadableValue/__tests__/index.spec.ts index 5604dee252..bc5881aac2 100644 --- a/apps/evm/src/utilities/formatPercentageToReadableValue/__tests__/index.spec.ts +++ b/apps/evm/src/utilities/formatPercentageToReadableValue/__tests__/index.spec.ts @@ -50,6 +50,6 @@ describe('utilities/formatPercentageToReadableValue', () => { it('should handle a string number', () => { const value = '200.567'; const result = formatPercentageToReadableValue(value); - expect(result).toBe('200.57%'); + expect(result).toBe('200.56%'); }); }); diff --git a/apps/evm/src/utilities/formatPercentageToReadableValue/index.ts b/apps/evm/src/utilities/formatPercentageToReadableValue/index.ts index fdd3a5b820..9ecd5ea604 100644 --- a/apps/evm/src/utilities/formatPercentageToReadableValue/index.ts +++ b/apps/evm/src/utilities/formatPercentageToReadableValue/index.ts @@ -32,7 +32,7 @@ const formatPercentageToReadableValue = (value: number | string | BigNumber | un readableValue = `${isNegative ? '-' : ''}${absoluteValue.toFormat( decimalPlaces, - BigNumber.ROUND_HALF_UP, + BigNumber.ROUND_DOWN, )}`; } diff --git a/apps/landing/package.json b/apps/landing/package.json index 7a83b792cf..e6bb493ec6 100644 --- a/apps/landing/package.json +++ b/apps/landing/package.json @@ -51,16 +51,8 @@ "vite-tsconfig-paths": "^5.1.4" }, "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] + "production": [">0.2%", "not dead", "not op_mini all"], + "development": ["last 1 chrome version", "last 1 firefox version", "last 1 safari version"] }, "resolutions": { "postcss": "^8.4.8" diff --git a/yarn.lock b/yarn.lock index 70d760fc1c..dd80b5dc4c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4731,7 +4731,7 @@ dependencies: semver "^7.3.5" -"@openzeppelin-3/contracts@npm:@openzeppelin/contracts@^3.4.2-solc-0.7": +"@openzeppelin-3/contracts@npm:@openzeppelin/contracts@^3.4.2-solc-0.7", "@openzeppelin/contracts-v0.7@npm:@openzeppelin/contracts@v3.4.2": version "3.4.2" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.2.tgz#d81f786fda2871d1eb8a8c5a73e455753ba53527" integrity sha512-z0zMCjyhhp4y7XKAcDAi3Vgms4T2PstwBdahiO0+9NaGICQKjynK3wduSRplTgk4LXmoO1yfDGO5RbjKYxtuxA== @@ -4746,11 +4746,6 @@ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.9.5.tgz#572b5da102fc9be1d73f34968e0ca56765969812" integrity sha512-f7L1//4sLlflAN7fVzJLoRedrf5Na3Oal5PZfIq55NFcVZ90EpV1q5xOvL4lFvg3MNICSDr2hH0JUBxwlxcoPg== -"@openzeppelin/contracts-v0.7@npm:@openzeppelin/contracts@v3.4.2": - version "3.4.2" - resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.2.tgz#d81f786fda2871d1eb8a8c5a73e455753ba53527" - integrity sha512-z0zMCjyhhp4y7XKAcDAi3Vgms4T2PstwBdahiO0+9NaGICQKjynK3wduSRplTgk4LXmoO1yfDGO5RbjKYxtuxA== - "@openzeppelin/contracts@3.4.2-solc-0.7": version "3.4.2-solc-0.7" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.2-solc-0.7.tgz#38f4dbab672631034076ccdf2f3201fab1726635" @@ -16250,9 +16245,9 @@ react-refresh@^0.17.0: integrity sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ== react-router@^7.6.0: - version "7.11.0" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-7.11.0.tgz#d3b91567fdbe910caf9064ea69b7b4d9264f2945" - integrity sha512-uI4JkMmjbWCZc01WVP2cH7ZfSzH91JAZUDd7/nIprDgWxBV1TkkmLToFh7EbMTcMak8URFRa2YoBL/W8GWnCTQ== + version "7.9.6" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-7.9.6.tgz#003c8de335fdd7390286a478dcfd9579c1826137" + integrity sha512-Y1tUp8clYRXpfPITyuifmSoE2vncSME18uVLgaqyxh9H35JWpIfzHo+9y3Fzh5odk/jxPW29IgLgzcdwxGqyNA== dependencies: cookie "^1.0.1" set-cookie-parser "^2.6.0" @@ -17526,16 +17521,7 @@ string-env-interpolation@^1.0.1: resolved "https://registry.yarnpkg.com/string-env-interpolation/-/string-env-interpolation-1.0.1.tgz#ad4397ae4ac53fe6c91d1402ad6f6a52862c7152" integrity sha512-78lwMoCcn0nNu8LszbP1UA7g55OeE4v7rCeWnM5B453rnNr4aq+5it3FEYtZrSEiMvHZOZ9Jlqb0OD0M2VInqg== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -17617,7 +17603,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -17631,13 +17617,6 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1, strip-ansi@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -19331,7 +19310,7 @@ workerpool@6.2.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -19349,15 +19328,6 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"