diff --git a/package-lock.json b/package-lock.json index 1a8ef3c..3bdcbfe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,14 @@ { "name": "blend-ui", - "version": "1.1.2", + "version": "1.1.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "blend-ui", - "version": "1.1.2", + "version": "1.1.3", "dependencies": { - "@blend-capital/blend-sdk": "2.0.3", + "@blend-capital/blend-sdk": "2.0.4", "@creit.tech/stellar-wallets-kit": "1.2.1", "@emotion/react": "^11.9.3", "@emotion/styled": "^11.9.3", @@ -191,9 +191,9 @@ } }, "node_modules/@blend-capital/blend-sdk": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@blend-capital/blend-sdk/-/blend-sdk-2.0.3.tgz", - "integrity": "sha512-KdtHfTNA+9RVuL9nXixLYsyNw8zieKXESPyaVPE7tQ+OX5fxOKXCXZXuS3z9ohiJJBzwARoMgI6IfgEw01JJhQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@blend-capital/blend-sdk/-/blend-sdk-2.0.4.tgz", + "integrity": "sha512-t3USvFpQmD9nm7y0H3fA7CEDM/nyTis+WVP44eUFQc6DXtDqTJCpXvqN2xVanGOSfgXAI2tAO+k3vA4p73tTwg==", "license": "MIT", "dependencies": { "@stellar/stellar-sdk": "12.2.0", diff --git a/package.json b/package.json index 1a4359b..c859664 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "blend-ui", - "version": "1.1.2", + "version": "1.1.3", "private": true, "type": "module", "scripts": { @@ -12,7 +12,7 @@ "lint": "next lint" }, "dependencies": { - "@blend-capital/blend-sdk": "2.0.3", + "@blend-capital/blend-sdk": "2.0.4", "@creit.tech/stellar-wallets-kit": "1.2.1", "@emotion/react": "^11.9.3", "@emotion/styled": "^11.9.3", diff --git a/src/components/backstop/BackstopAPR.tsx b/src/components/backstop/BackstopAPR.tsx index 6bbfecf..42ac2dc 100644 --- a/src/components/backstop/BackstopAPR.tsx +++ b/src/components/backstop/BackstopAPR.tsx @@ -1,10 +1,10 @@ import { BackstopPoolEst, FixedMath, PoolEstimate } from '@blend-capital/blend-sdk'; -import { HelpOutline } from '@mui/icons-material'; -import { Box, Tooltip, Typography } from '@mui/material'; +import { Box, Typography } from '@mui/material'; import { useBackstop, useBackstopPool, usePool, usePoolOracle } from '../../hooks/api'; import { getEmissionTextFromValue, toPercentage } from '../../utils/formatter'; import { FlameIcon } from '../common/FlameIcon'; import { PoolComponentProps } from '../common/PoolComponentProps'; +import { TooltipText } from '../common/TooltipText'; export const BackstopAPR: React.FC = ({ poolId, sx, ...props }) => { const { data: pool } = usePool(poolId); @@ -45,28 +45,9 @@ export const BackstopAPR: React.FC = ({ poolId, sx, ...props padding: '6px', }} > - - - - {'Backstop APR'} - - - - + + Backstop APR + = ({ poolId }) => > - - - - - - - - - + + + = ({ first, poolId, }) => { - const { connected, walletAddress, backstopDequeueWithdrawal, backstopWithdraw } = useWallet(); - + const { connected, walletAddress, backstopDequeueWithdrawal, backstopWithdraw, restore } = + useWallet(); const { viewType } = useSettings(); + const backstop = new BackstopContract(process.env.NEXT_PUBLIC_BACKSTOP ?? ''); + const actionArgs: PoolBackstopActionArgs = { + from: walletAddress, + pool_address: poolId, + amount: q4w.amount, + }; + // the BackstopQueueMod sets the expiration for unlocked withdrawals to 0 + const sim_op = + q4w.exp === BigInt(0) ? backstop.withdraw(actionArgs) : backstop.dequeueWithdrawal(actionArgs); + const { + data: simResult, + isLoading, + refetch: refetchSim, + } = useSimulateOperation(sim_op, q4w.exp === BigInt(0) || first); + const isRestore = + isLoading === false && simResult !== undefined && SorobanRpc.Api.isSimulationRestore(simResult); + const TOTAL_QUEUE_TIME_SECONDS = 21 * 24 * 60 * 60; const [timeLeft, setTimeLeft] = useState( @@ -42,13 +61,8 @@ export const BackstopQueueItem: React.FC = ({ return () => clearInterval(refreshInterval); }, [q4w]); - const handleClick = async (amount: bigint) => { + const handleClick = async () => { if (connected) { - let actionArgs: PoolBackstopActionArgs = { - from: walletAddress, - pool_address: poolId, - amount: BigInt(amount), - }; if (timeLeft > 0) { await backstopDequeueWithdrawal(actionArgs, false); } else { @@ -57,19 +71,58 @@ export const BackstopQueueItem: React.FC = ({ } }; - const wrapButtonTooptip = (condition: boolean, children: ReactNode) => { - return condition ? ( + const handleRestore = async () => { + if (simResult && SorobanRpc.Api.isSimulationRestore(simResult)) { + await restore(simResult); + refetchSim(); + } + }; + + const queueItemActionButton = (sx: SxProps) => { + const needsTooltip = !first || isRestore; + const tooltipMessage = !first + ? 'You can only unqueue the oldest withdrawal' + : 'This transaction ran into expired entries which need to be restored before proceeding.'; + + return needsTooltip ? ( - {children} + + {isRestore ? ( + handleRestore()} + palette={theme.palette.warning} + disabled={false} + sx={{ width: '100%' }} + > + Restore + + ) : ( + handleClick()} + palette={theme.palette.positive} + disabled={!first} + sx={{ width: '100%' }} + > + {timeLeft > 0 ? 'Unqueue' : 'Withdraw'} + + )} + ) : ( - children + handleClick()} + palette={theme.palette.positive} + disabled={!first} + sx={sx} + > + {timeLeft > 0 ? 'Unqueue' : 'Withdraw'} + ); }; @@ -137,32 +190,10 @@ export const BackstopQueueItem: React.FC = ({ {viewType === ViewType.REGULAR && - wrapButtonTooptip( - !first, - handleClick(q4w.amount)} - palette={theme.palette.positive} - disabled={!first} - sx={{ height: '35px', width: '108px', margin: '12px', padding: '6px' }} - > - {timeLeft > 0 ? 'Unqueue' : 'Withdraw'} - - )} + queueItemActionButton({ height: '35px', width: '108px', margin: '12px' })} {viewType !== ViewType.REGULAR && ( - - {wrapButtonTooptip( - !first, - handleClick(q4w.amount)} - palette={theme.palette.positive} - disabled={!first} - sx={{ height: '35px', width: '100%', margin: '12px', padding: '6px' }} - > - {timeLeft > 0 ? 'Unqueue' : 'Withdraw'} - - )} - + {queueItemActionButton({ height: '35px', width: '100%', margin: '12px' })} )} ); diff --git a/src/components/borrow/BorrowMarketList.tsx b/src/components/borrow/BorrowMarketList.tsx index 0e66805..781a7d5 100644 --- a/src/components/borrow/BorrowMarketList.tsx +++ b/src/components/borrow/BorrowMarketList.tsx @@ -1,9 +1,9 @@ -import HelpOutline from '@mui/icons-material/HelpOutline'; -import { Box, Tooltip, Typography } from '@mui/material'; +import { Box, Typography } from '@mui/material'; import { ViewType, useSettings } from '../../contexts'; import { usePool } from '../../hooks/api'; import { PoolComponentProps } from '../common/PoolComponentProps'; import { Skeleton } from '../common/Skeleton'; +import { TooltipText } from '../common/TooltipText'; import { BorrowMarketCard } from './BorrowMarketCard'; export const BorrowMarketList: React.FC = ({ poolId }) => { @@ -50,36 +50,19 @@ export const BorrowMarketList: React.FC = ({ poolId }) => { > Available - APR - + {viewType !== ViewType.MOBILE && ( - - - - Liability Factor - - - - + Liability Factor + )} diff --git a/src/components/borrow/BorrowPositionCard.tsx b/src/components/borrow/BorrowPositionCard.tsx index 7d83af3..440aa4d 100644 --- a/src/components/borrow/BorrowPositionCard.tsx +++ b/src/components/borrow/BorrowPositionCard.tsx @@ -30,7 +30,9 @@ export const BorrowPositionCard: React.FC = ({ const tableNum = viewType === ViewType.REGULAR ? 5 : 3; const tableWidth = `${(100 / tableNum).toFixed(2)}%`; - const buttonWidth = `${((100 / tableNum) * 1.5).toFixed(2)}%`; + const buttonWidth = `${((100 / tableNum) * (viewType === ViewType.REGULAR ? 1.5 : 1)).toFixed( + 2 + )}%`; const emissionsPerAsset = reserve.emissionsPerYearPerBorrowedAsset(); diff --git a/src/components/borrow/BorrowPositionList.tsx b/src/components/borrow/BorrowPositionList.tsx index 4a0840b..bb59b2a 100644 --- a/src/components/borrow/BorrowPositionList.tsx +++ b/src/components/borrow/BorrowPositionList.tsx @@ -5,6 +5,7 @@ import { usePool, usePoolOracle, usePoolUser } from '../../hooks/api'; import { PoolComponentProps } from '../common/PoolComponentProps'; import { Row } from '../common/Row'; import { Section, SectionSize } from '../common/Section'; +import { TooltipText } from '../common/TooltipText'; import { BorrowBanner } from './BorrowBanner'; import { BorrowPositionCard } from './BorrowPositionCard'; @@ -70,14 +71,12 @@ export const BorrowPositionList: React.FC = ({ poolId }) => Balance - APR - + = ({ @@ -15,12 +17,14 @@ export const StackedText: React.FC = ({ type, titleColor, textColor, + tooltip, ...props }) => { const textType = type ? type : 'normal'; const textVariant = textType == 'large' ? 'h2' : 'h4'; const muiTitleColor = titleColor ? titleColor : 'text.secondary'; const muiTextColor = textColor ? textColor : 'text.primary'; + const hasTooltip = tooltip !== undefined && tooltip !== ''; return ( = ({ ...props.sx, }} > - - {title} - + {hasTooltip ? ( + + {title} + + ) : ( + + {title} + + )} {text} diff --git a/src/components/common/TooltipText.tsx b/src/components/common/TooltipText.tsx new file mode 100644 index 0000000..80b9b6e --- /dev/null +++ b/src/components/common/TooltipText.tsx @@ -0,0 +1,67 @@ +import { Box, BoxProps, Tooltip, Typography } from '@mui/material'; +import React from 'react'; + +import { HelpOutline } from '@mui/icons-material'; + +export interface TooltipTextProps extends BoxProps { + tooltip: string; + width: string; + textColor?: string; + textVariant?: + | 'inherit' + | 'button' + | 'caption' + | 'h1' + | 'h2' + | 'h3' + | 'h4' + | 'h5' + | 'h6' + | 'overline' + | 'subtitle1' + | 'subtitle2' + | 'body1' + | 'body2'; +} + +export const TooltipText: React.FC = ({ + tooltip, + width, + children, + textColor = 'text.secondary', + textVariant = 'body2', + sx, +}) => { + return ( + + + + + {children} + + + + + + ); +}; diff --git a/src/components/lend/LendMarketList.tsx b/src/components/lend/LendMarketList.tsx index 1ae0849..cc80b01 100644 --- a/src/components/lend/LendMarketList.tsx +++ b/src/components/lend/LendMarketList.tsx @@ -1,9 +1,9 @@ -import HelpOutline from '@mui/icons-material/HelpOutline'; -import { Box, Tooltip, Typography } from '@mui/material'; +import { Box, Typography } from '@mui/material'; import { ViewType, useSettings } from '../../contexts'; import { usePool } from '../../hooks/api'; import { PoolComponentProps } from '../common/PoolComponentProps'; import { Skeleton } from '../common/Skeleton'; +import { TooltipText } from '../common/TooltipText'; import { LendMarketCard } from './LendMarketCard'; export const LendMarketList: React.FC = ({ poolId }) => { @@ -49,37 +49,20 @@ export const LendMarketList: React.FC = ({ poolId }) => { Wallet Balance - APR - + {viewType !== ViewType.MOBILE && ( - - - - Collateral Factor - - - - + Collateral Factor + )} diff --git a/src/components/lend/LendPositionCard.tsx b/src/components/lend/LendPositionCard.tsx index 5855f69..2f5c8d0 100644 --- a/src/components/lend/LendPositionCard.tsx +++ b/src/components/lend/LendPositionCard.tsx @@ -32,7 +32,9 @@ export const LendPositionCard: React.FC = ({ const tableNum = viewType === ViewType.REGULAR ? 5 : 3; const tableWidth = `${(100 / tableNum).toFixed(2)}%`; - const buttonWidth = `${((100 / tableNum) * 1.5).toFixed(2)}%`; + const buttonWidth = `${((100 / tableNum) * (viewType === ViewType.REGULAR ? 1.5 : 1)).toFixed( + 2 + )}%`; const viewTypeIsMobile = viewType === ViewType.MOBILE; return ( diff --git a/src/components/lend/LendPositionList.tsx b/src/components/lend/LendPositionList.tsx index d99fa16..b5f6b56 100644 --- a/src/components/lend/LendPositionList.tsx +++ b/src/components/lend/LendPositionList.tsx @@ -5,6 +5,7 @@ import { usePool, usePoolOracle, usePoolUser } from '../../hooks/api'; import { PoolComponentProps } from '../common/PoolComponentProps'; import { Row } from '../common/Row'; import { Section, SectionSize } from '../common/Section'; +import { TooltipText } from '../common/TooltipText'; import { LendBanner } from './LendBanner'; import { LendPositionCard } from './LendPositionCard'; @@ -70,14 +71,12 @@ export const LendPositionList: React.FC = ({ poolId }) => { Balance - APR - + ( + operation_str: string, + enabled: boolean = true +): UseQueryResult { + const { walletAddress, connected } = useWallet(); + const { network } = useSettings(); + return useQuery({ + staleTime: USER_STALE_TIME, + queryKey: ['sim', operation_str], + enabled: enabled && connected && walletAddress !== '', + queryFn: async () => { + if (walletAddress === '') { + throw new Error('No wallet address'); + } + let operation = xdr.Operation.fromXDR(operation_str, 'base64'); + let rpc = new SorobanRpc.Server(network.rpc, network.opts); + const account = new Account(walletAddress, '123'); + const tx_builder = new TransactionBuilder(account, { + networkPassphrase: network.passphrase, + fee: BASE_FEE, + timebounds: { minTime: 0, maxTime: Math.floor(Date.now() / 1000) + 5 * 60 * 1000 }, + }).addOperation(operation); + const transaction = tx_builder.build(); + return await rpc.simulateTransaction(transaction); + }, + }); +} + //********** Misc Data **********// /** diff --git a/src/pages/backstop-deposit.tsx b/src/pages/backstop-deposit.tsx index c243970..cc1bb4d 100644 --- a/src/pages/backstop-deposit.tsx +++ b/src/pages/backstop-deposit.tsx @@ -1,6 +1,5 @@ import { BackstopPoolEst, BackstopPoolUserEst } from '@blend-capital/blend-sdk'; -import { HelpOutline } from '@mui/icons-material'; -import { Box, Tooltip, Typography, useTheme } from '@mui/material'; +import { Box, Typography, useTheme } from '@mui/material'; import type { NextPage } from 'next'; import { useRouter } from 'next/router'; import { BackstopAPR } from '../components/backstop/BackstopAPR'; @@ -90,29 +89,12 @@ const BackstopDeposit: NextPage = () => {
- - - - - - +
{
- - - - - - +
diff --git a/src/pages/backstop.tsx b/src/pages/backstop.tsx index 6823ecc..62dd362 100644 --- a/src/pages/backstop.tsx +++ b/src/pages/backstop.tsx @@ -6,8 +6,7 @@ import { parseResult, } from '@blend-capital/blend-sdk'; import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; -import HelpOutline from '@mui/icons-material/HelpOutline'; -import { Box, Tooltip, Typography } from '@mui/material'; +import { Box, Typography } from '@mui/material'; import { SorobanRpc, scValToBigInt, xdr } from '@stellar/stellar-sdk'; import type { NextPage } from 'next'; import { useRouter } from 'next/router'; @@ -150,33 +149,16 @@ const Backstop: NextPage = () => {
- - - - - - +
diff --git a/src/pages/borrow.tsx b/src/pages/borrow.tsx index c55e6ab..8b89ce1 100644 --- a/src/pages/borrow.tsx +++ b/src/pages/borrow.tsx @@ -112,18 +112,20 @@ const Borrow: NextPage = () => { } sx={{ padding: '6px' }} + tooltip="The interest rate charged for a borrowed position. This rate will fluctuate based on the market conditions and is accrued to the borrowed position." >
diff --git a/src/pages/repay.tsx b/src/pages/repay.tsx index 1a0041f..54d382b 100644 --- a/src/pages/repay.tsx +++ b/src/pages/repay.tsx @@ -90,18 +90,20 @@ const Repay: NextPage = () => { } sx={{ width: '100%', padding: '6px' }} + tooltip="The interest rate charged for a borrowed position. This rate will fluctuate based on the market conditions and is accrued to the borrowed position." >
diff --git a/src/pages/supply.tsx b/src/pages/supply.tsx index 880f3ce..9017f49 100644 --- a/src/pages/supply.tsx +++ b/src/pages/supply.tsx @@ -110,18 +110,20 @@ const Supply: NextPage = () => { } sx={{ width: '100%', padding: '6px' }} + tooltip="The interest rate earned on a supplied position. This rate will fluctuate based on the market conditions and is accrued to the supplied position." >
diff --git a/src/pages/withdraw.tsx b/src/pages/withdraw.tsx index a32880f..cab2feb 100644 --- a/src/pages/withdraw.tsx +++ b/src/pages/withdraw.tsx @@ -83,18 +83,20 @@ const Withdraw: NextPage = () => { } sx={{ width: '100%', padding: '6px' }} + tooltip="The interest rate earned on a supplied position. This rate will fluctuate based on the market conditions and is accrued to the supplied position." >
diff --git a/src/utils/formatter.ts b/src/utils/formatter.ts index f2e26b5..7d0eecd 100644 --- a/src/utils/formatter.ts +++ b/src/utils/formatter.ts @@ -119,5 +119,5 @@ export function toTimeSpan(secondsLeft: number): string { } export function getEmissionTextFromValue(value: number, symbol: string) { - return ` This position earns ${toBalance(value, 7)} BLND a year per ${symbol}`; + return `This position earns an additional ${toBalance(value, 7)} BLND per year per ${symbol}.`; } diff --git a/src/utils/txSim.tsx b/src/utils/txSim.tsx index 1350f00..9eada30 100644 --- a/src/utils/txSim.tsx +++ b/src/utils/txSim.tsx @@ -4,6 +4,7 @@ import { SorobanRpc } from '@stellar/stellar-sdk'; import { OpaqueButton } from '../components/common/OpaqueButton'; import { useWallet } from '../contexts/wallet'; import theme from '../theme'; + export function RestoreButton({ simResponse, }: { @@ -25,6 +26,7 @@ export function RestoreButton({ ); } + export function getErrorFromSim( input: string | undefined, decimals: number, @@ -77,7 +79,7 @@ export function getErrorFromSim( errorProps.isMaxDisabled = false; errorProps.disabledType = 'warning'; errorProps.reason = - 'This transaction ran into expired entries that need to be restored before proceeding.'; + 'This transaction ran into expired entries which need to be restored before proceeding.'; return errorProps; } if (simulationResult && SorobanRpc.Api.isSimulationError(simulationResult)) {