From 7b5d4c3255d3b0fb73675a64d41f0223b0d6e04b Mon Sep 17 00:00:00 2001 From: Bob van der Helm <34470358+bobthebuidlr@users.noreply.github.com> Date: Thu, 15 Jun 2023 13:00:46 +0200 Subject: [PATCH] Mp 2546 borrow asset modal (#255) * Refactor Modal folder and setup basic addassetsmodal * basic tables * Update basic logic * small fixes * Update closing for modals * fix slider update bug and set borrowing subtitle * fix store * add missing dependency * fix tests for VaultBorrowings * Add DisplayCurrency test for VaultBorrowings * trigger updated * update borrowModal import path * update imports for modals * updating paths again * update structure of modals directory * fix all file naming and relative imports * fix icon spacing button and jest.mocked import * fix icon classes for button * change Map to array and add BNCoin * add AssetImage * update logic for selecting borrow denoms --- .../Modals/vault/VaultBorrowings.test.tsx | 47 ++++- src/api/markets/getMarketBorrowings.ts | 6 +- src/components/Accordion.tsx | 8 +- src/components/Account/AccountSummary.tsx | 4 +- src/components/AssetImage.tsx | 19 ++ src/components/Borrow/BorrowTable.tsx | 3 +- src/components/Button/index.tsx | 10 +- src/components/Checkbox.tsx | 27 +++ src/components/Earn/vault/VaultCard.tsx | 7 +- src/components/Earn/vault/VaultExpanded.tsx | 7 +- src/components/Earn/vault/VaultLogo.tsx | 5 +- src/components/Icons/Search.svg | 3 + src/components/Icons/index.ts | 1 + src/components/Modal.tsx | 13 +- .../AddVaultAssets/AddVaultAssetTable.tsx | 129 ++++++++++++ .../AddVaultBorrowAssetsModal.tsx | 46 ++++ .../AddVaultBorrowAssetsModalContent.tsx | 96 +++++++++ .../useAddVaultAssetTableColumns.tsx | 54 +++++ .../Modals/{ => Borrow}/BorrowModal.tsx | 4 +- .../FundAndWithdrawModal.tsx | 3 +- .../FundAndWithdrawModalContent.tsx} | 0 src/components/Modals/ModalsContainer.tsx | 8 +- .../Modals/Vault/VaultBorrowings.tsx | 197 ++++++++++++++++++ .../Modals/Vault/VaultBorrowingsSubTitle.tsx | 49 +++++ .../VaultDeposits.tsx} | 0 .../VaultDepositsSubTitle.tsx} | 0 .../Modals/{vault => Vault}/VaultModal.tsx | 3 +- .../{vault => Vault}/VaultModalContent.tsx | 20 +- .../Modals/vault/VaultBorrowings.tsx | 112 ---------- src/components/SearchBar.tsx | 34 +++ src/components/Select/Option.tsx | 26 +-- src/components/TokenInput.tsx | 3 +- src/components/Tooltip/index.tsx | 3 +- src/constants/assets.ts | 2 + src/hooks/useMarketBorrowings.tsx | 3 +- src/hooks/usePrice.tsx | 2 +- src/hooks/useUpdateAccount.tsx | 33 +-- src/store/slices/modal.ts | 1 + src/types/classes/BNCoin.ts | 18 ++ src/types/interfaces/asset.d.ts | 9 +- .../store/{comon.d.ts => common.d.ts} | 4 +- src/types/interfaces/store/modals.d.ts | 14 +- src/utils/assets.ts | 4 + src/utils/vaults.ts | 7 +- 44 files changed, 845 insertions(+), 199 deletions(-) create mode 100644 src/components/AssetImage.tsx create mode 100644 src/components/Checkbox.tsx create mode 100644 src/components/Icons/Search.svg create mode 100644 src/components/Modals/AddVaultAssets/AddVaultAssetTable.tsx create mode 100644 src/components/Modals/AddVaultAssets/AddVaultBorrowAssetsModal.tsx create mode 100644 src/components/Modals/AddVaultAssets/AddVaultBorrowAssetsModalContent.tsx create mode 100644 src/components/Modals/AddVaultAssets/useAddVaultAssetTableColumns.tsx rename src/components/Modals/{ => Borrow}/BorrowModal.tsx (98%) rename src/components/Modals/{fundwithdraw => FundWithdraw}/FundAndWithdrawModal.tsx (92%) rename src/components/Modals/{fundwithdraw/FundWithdrawModalContent.tsx => FundWithdraw/FundAndWithdrawModalContent.tsx} (100%) create mode 100644 src/components/Modals/Vault/VaultBorrowings.tsx create mode 100644 src/components/Modals/Vault/VaultBorrowingsSubTitle.tsx rename src/components/Modals/{vault/VaultDeposit.tsx => Vault/VaultDeposits.tsx} (100%) rename src/components/Modals/{vault/VaultDepositSubTitle.tsx => Vault/VaultDepositsSubTitle.tsx} (100%) rename src/components/Modals/{vault => Vault}/VaultModal.tsx (95%) rename src/components/Modals/{vault => Vault}/VaultModalContent.tsx (77%) delete mode 100644 src/components/Modals/vault/VaultBorrowings.tsx create mode 100644 src/components/SearchBar.tsx create mode 100644 src/types/classes/BNCoin.ts rename src/types/interfaces/store/{comon.d.ts => common.d.ts} (100%) diff --git a/__tests__/components/Modals/vault/VaultBorrowings.test.tsx b/__tests__/components/Modals/vault/VaultBorrowings.test.tsx index 75301cb0..3c1f0c0a 100644 --- a/__tests__/components/Modals/vault/VaultBorrowings.test.tsx +++ b/__tests__/components/Modals/vault/VaultBorrowings.test.tsx @@ -1,25 +1,36 @@ import { render } from '@testing-library/react' -import VaultBorrowings from 'components/Modals/vault/VaultBorrowings' -import BigNumber from 'bignumber.js' +import { ASSETS } from 'constants/assets' +import { BN } from 'utils/helpers' +import useStore from 'store' +import DisplayCurrency from 'components/DisplayCurrency' +import VaultBorrowings, { VaultBorrowingsProps } from 'components/Modals/Vault/VaultBorrowings' jest.mock('hooks/usePrices', () => jest.fn(() => ({ data: [], })), ) + +jest.mock('hooks/usePrice', () => jest.fn(() => '1')) + jest.mock('hooks/useMarketAssets', () => jest.fn(() => ({ data: [], })), ) +jest.mock('components/DisplayCurrency') +const mockedDisplayCurrency = jest + .mocked(DisplayCurrency) + .mockImplementation(() =>
Display currency
) + describe('', () => { - const defaultProps: { - account: Account - defaultBorrowDenom: string - onChangeBorrowings: (borrowings: Map) => void - } = { + const defaultProps: VaultBorrowingsProps = { + primaryAsset: ASSETS[0], + secondaryAsset: ASSETS[1], + primaryAmount: BN(0), + secondaryAmount: BN(0), account: { id: 'test', deposits: [], @@ -27,12 +38,32 @@ describe('', () => { vaults: [], lends: [], }, - defaultBorrowDenom: 'test-denom', + borrowings: [], onChangeBorrowings: jest.fn(), } + beforeAll(() => { + useStore.setState({ + baseCurrency: ASSETS[0], + selectedBorrowDenoms: [ASSETS[1].denom], + }) + }) + + afterAll(() => { + useStore.clearState() + mockedDisplayCurrency.mockClear() + }) + it('should render', () => { const { container } = render() expect(container).toBeInTheDocument() }) + + it('should render DisplayCurrency correctly', () => { + expect(mockedDisplayCurrency).toHaveBeenCalledTimes(1) + expect(mockedDisplayCurrency).toHaveBeenCalledWith( + { coin: { denom: 'uosmo', amount: '0' } }, + expect.anything(), + ) + }) }) diff --git a/src/api/markets/getMarketBorrowings.ts b/src/api/markets/getMarketBorrowings.ts index 88bfb279..1d9946ef 100644 --- a/src/api/markets/getMarketBorrowings.ts +++ b/src/api/markets/getMarketBorrowings.ts @@ -2,17 +2,21 @@ import { BN } from 'utils/helpers' import getPrices from 'api/prices/getPrices' import getMarkets from 'api/markets/getMarkets' import getMarketLiquidity from 'api/markets/getMarketLiquidity' +import { getEnabledMarketAssets } from 'utils/assets' export default async function getMarketBorrowings(): Promise { const liquidity = await getMarketLiquidity() + const enabledAssets = getEnabledMarketAssets() const borrowEnabledMarkets = (await getMarkets()).filter((market: Market) => market.borrowEnabled) const prices = await getPrices() const borrow: BorrowAsset[] = borrowEnabledMarkets.map((market) => { const price = prices.find((coin) => coin.denom === market.denom)?.amount ?? '1' const amount = liquidity.find((coin) => coin.denom === market.denom)?.amount ?? '0' + const asset = enabledAssets.find((asset) => asset.denom === market.denom)! + return { - denom: market.denom, + ...asset, borrowRate: market.borrowRate ?? 0, liquidity: { amount: amount, diff --git a/src/components/Accordion.tsx b/src/components/Accordion.tsx index aa76e621..685d1530 100644 --- a/src/components/Accordion.tsx +++ b/src/components/Accordion.tsx @@ -1,10 +1,12 @@ -import Card from 'components/Card' +import classNames from 'classnames' -import AccordionContent, { Item } from './AccordionContent' +import Card from 'components/Card' +import AccordionContent, { Item } from 'components/AccordionContent' interface Props { items: Item[] allowMultipleOpen?: boolean + className?: string } export default function Accordion(props: Props) { @@ -19,7 +21,7 @@ export default function Accordion(props: Props) { } return ( -
+
{props.items.map((item, index) => ( diff --git a/src/components/Account/AccountSummary.tsx b/src/components/Account/AccountSummary.tsx index da49de3c..76f6550e 100644 --- a/src/components/Account/AccountSummary.tsx +++ b/src/components/Account/AccountSummary.tsx @@ -25,8 +25,8 @@ export default function AccountSummary(props: Props) { if (!props.account) return null return ( -
- +
+ + ) +} diff --git a/src/components/Borrow/BorrowTable.tsx b/src/components/Borrow/BorrowTable.tsx index 3320566a..2f6f9dc0 100644 --- a/src/components/Borrow/BorrowTable.tsx +++ b/src/components/Borrow/BorrowTable.tsx @@ -19,6 +19,7 @@ import Text from 'components/Text' import TitleAndSubCell from 'components/TitleAndSubCell' import { getEnabledMarketAssets } from 'utils/assets' import { formatPercent } from 'utils/formatters' +import AssetImage from 'components/AssetImage' type Props = { data: BorrowAsset[] | BorrowAssetActive[] @@ -40,7 +41,7 @@ export const BorrowTable = (props: Props) => { return (
- token + { const hasContent = !!(text || children) const iconClasses = ['flex items-center justify-center', iconClassName ?? 'h-4 w-4'] - const leftIconClasses = [iconClasses, hasContent && 'mr-2'] - const rightIconClasses = [iconClasses, hasContent && 'ml-2'] + const leftIconClasses = [...iconClasses, hasContent && 'mr-2'] + const rightIconClasses = [...iconClasses, hasContent && 'ml-2'] - return [leftIconClasses, rightIconClasses].map(classNames) + return [leftIconClasses, rightIconClasses] }, [children, iconClassName, text]) return ( @@ -111,10 +111,10 @@ const Button = React.forwardRef(function Button( ) : ( <> - {leftIcon && {leftIcon}} + {leftIcon && {leftIcon}} {shouldShowText && {text}} {children && children} - {rightIcon && {rightIcon}} + {rightIcon && {rightIcon}} {hasSubmenu && ( diff --git a/src/components/Checkbox.tsx b/src/components/Checkbox.tsx new file mode 100644 index 00000000..fee21e9b --- /dev/null +++ b/src/components/Checkbox.tsx @@ -0,0 +1,27 @@ +import classNames from 'classnames' + +import { Check } from 'components/Icons' + +interface Props { + checked: boolean + onChange: (checked: boolean) => void +} + +export default function Checkbox(props: Props) { + return ( + + ) +} diff --git a/src/components/Earn/vault/VaultCard.tsx b/src/components/Earn/vault/VaultCard.tsx index 8b70fbeb..619eac5e 100644 --- a/src/components/Earn/vault/VaultCard.tsx +++ b/src/components/Earn/vault/VaultCard.tsx @@ -19,7 +19,12 @@ export default function VaultCard(props: Props) { const currentAccount = useCurrentAccount() function openVaultModal() { - useStore.setState({ vaultModal: { vault: props.vault } }) + useStore.setState({ + vaultModal: { + vault: props.vault, + selectedBorrowDenoms: [props.vault.denoms.secondary], + }, + }) } return ( diff --git a/src/components/Earn/vault/VaultExpanded.tsx b/src/components/Earn/vault/VaultExpanded.tsx index 59f44dde..558b71e9 100644 --- a/src/components/Earn/vault/VaultExpanded.tsx +++ b/src/components/Earn/vault/VaultExpanded.tsx @@ -11,7 +11,12 @@ interface Props { export default function VaultExpanded(props: Props) { function enterVaultHandler() { - useStore.setState({ vaultModal: { vault: props.row.original } }) + useStore.setState({ + vaultModal: { + vault: props.row.original, + selectedBorrowDenoms: [props.row.original.denoms.secondary], + }, + }) } return ( diff --git a/src/components/Earn/vault/VaultLogo.tsx b/src/components/Earn/vault/VaultLogo.tsx index 3e32b4a9..6c479a5c 100644 --- a/src/components/Earn/vault/VaultLogo.tsx +++ b/src/components/Earn/vault/VaultLogo.tsx @@ -1,5 +1,6 @@ import Image from 'next/image' +import AssetImage from 'components/AssetImage' import { getAssetByDenom } from 'utils/assets' interface Props { @@ -15,10 +16,10 @@ export default function VaultLogo(props: Props) { return (
- {`${primaryAsset.symbol} +
- token +
) diff --git a/src/components/Icons/Search.svg b/src/components/Icons/Search.svg new file mode 100644 index 00000000..619e5b0f --- /dev/null +++ b/src/components/Icons/Search.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/Icons/index.ts b/src/components/Icons/index.ts index 46691942..09d8e84a 100644 --- a/src/components/Icons/index.ts +++ b/src/components/Icons/index.ts @@ -26,6 +26,7 @@ export { default as OverlayMark } from 'components/Icons/OverlayMark.svg' export { default as Plus } from 'components/Icons/Plus.svg' export { default as PlusCircled } from 'components/Icons/PlusCircled.svg' export { default as Questionmark } from 'components/Icons/Questionmark.svg' +export { default as Search } from 'components/Icons/Search.svg' export { default as Shield } from 'components/Icons/Shield.svg' export { default as SortAsc } from 'components/Icons/SortAsc.svg' export { default as SortDesc } from 'components/Icons/SortDesc.svg' diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx index 90c37c58..2ec67649 100644 --- a/src/components/Modal.tsx +++ b/src/components/Modal.tsx @@ -4,6 +4,7 @@ import { ReactNode, useEffect, useRef } from 'react' import Button from 'components/Button' import Card from 'components/Card' import { Cross } from 'components/Icons' +import Text from 'components/Text' interface Props { header: string | ReactNode @@ -12,6 +13,7 @@ interface Props { content?: ReactNode | string className?: string contentClassName?: string + modalClassName?: string open: boolean onClose: () => void } @@ -47,6 +49,7 @@ export default function Modal(props: Props) { 'w-[895px] border-none bg-transparent text-white', 'focus-visible:outline-none', 'backdrop:bg-black/50 backdrop:backdrop-blur-sm', + props.modalClassName, )} >
{props.header} -
{props.children ? props.children : props.content} diff --git a/src/components/Modals/AddVaultAssets/AddVaultAssetTable.tsx b/src/components/Modals/AddVaultAssets/AddVaultAssetTable.tsx new file mode 100644 index 00000000..fb40a281 --- /dev/null +++ b/src/components/Modals/AddVaultAssets/AddVaultAssetTable.tsx @@ -0,0 +1,129 @@ +import { + flexRender, + getCoreRowModel, + getSortedRowModel, + RowSelectionState, + SortingState, + useReactTable, +} from '@tanstack/react-table' +import { useEffect, useState } from 'react' +import classNames from 'classnames' + +import { SortAsc, SortDesc, SortNone } from 'components/Icons' +import Text from 'components/Text' +import useStore from 'store' +import useAddVaultAssetTableColumns from 'components/Modals/AddVaultAssets/useAddVaultAssetTableColumns' + +interface Props { + assets: BorrowAsset[] + onChangeSelected: (denoms: string[]) => void +} + +export default function AddVaultAssetTable(props: Props) { + const selectedDenoms = useStore((s) => s.addVaultBorrowingsModal?.selectedDenoms) || [] + const defaultSelected = props.assets.reduce((acc, asset, index) => { + if (selectedDenoms.includes(asset.denom)) { + acc[index] = true + } + return acc + }, {} as { [key: number]: boolean }) + + const [sorting, setSorting] = useState([{ id: 'symbol', desc: false }]) + const [selected, setSelected] = useState(defaultSelected) + const columns = useAddVaultAssetTableColumns() + + const table = useReactTable({ + data: props.assets, + columns, + state: { + sorting, + rowSelection: selected, + }, + onRowSelectionChange: setSelected, + onSortingChange: setSorting, + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), + }) + + useEffect(() => { + const selectedDenoms = props.assets + .filter((_, index) => selected[index]) + .map((asset) => asset.denom) + + props.onChangeSelected(selectedDenoms) + }, [selected, props]) + + return ( + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header, index) => { + return ( + + ) + })} + + ))} + + + {table.getRowModel().rows.map((row) => { + return ( + row.toggleSelected()} + > + {row.getVisibleCells().map((cell) => { + return ( + + ) + })} + + ) + })} + +
+
+ + {header.column.getCanSort() + ? { + asc: , + desc: , + false: , + }[header.column.getIsSorted() as string] ?? null + : null} + + + {flexRender(header.column.columnDef.header, header.getContext())} + +
+
+ {flexRender(cell.column.columnDef.cell, cell.getContext())} +
+ ) +} diff --git a/src/components/Modals/AddVaultAssets/AddVaultBorrowAssetsModal.tsx b/src/components/Modals/AddVaultAssets/AddVaultBorrowAssetsModal.tsx new file mode 100644 index 00000000..e4addb71 --- /dev/null +++ b/src/components/Modals/AddVaultAssets/AddVaultBorrowAssetsModal.tsx @@ -0,0 +1,46 @@ +import { useCallback, useState } from 'react' + +import Modal from 'components/Modal' +import useStore from 'store' +import Text from 'components/Text' +import { CircularProgress } from 'components/CircularProgress' +import AddVaultAssetsModalContent from 'components/Modals/AddVaultAssets/AddVaultBorrowAssetsModalContent' + +export default function AddVaultBorrowAssetsModal() { + const modal = useStore((s) => s.addVaultBorrowingsModal) + const vaultModal = useStore((s) => s.vaultModal) + const [selectedDenoms, setSelectedDenoms] = useState([]) + + function onClose() { + if (!vaultModal) return + + useStore.setState({ + addVaultBorrowingsModal: null, + vaultModal: { ...vaultModal, selectedBorrowDenoms: selectedDenoms }, + }) + } + + const updateSelectedDenoms = useCallback((denoms: string[]) => setSelectedDenoms(denoms), []) + + const showContent = modal && vaultModal?.vault + + return ( + Add Assets} + onClose={onClose} + modalClassName='max-w-[478px]' + headerClassName='bg-white/10 border-b-white/5 border-b items-center p-4' + > + {showContent ? ( + + ) : ( + + )} + + ) +} diff --git a/src/components/Modals/AddVaultAssets/AddVaultBorrowAssetsModalContent.tsx b/src/components/Modals/AddVaultAssets/AddVaultBorrowAssetsModalContent.tsx new file mode 100644 index 00000000..48eecb9d --- /dev/null +++ b/src/components/Modals/AddVaultAssets/AddVaultBorrowAssetsModalContent.tsx @@ -0,0 +1,96 @@ +import { useCallback, useMemo, useState } from 'react' + +import SearchBar from 'components/SearchBar' +import Text from 'components/Text' +import useMarketBorrowings from 'hooks/useMarketBorrowings' +import AddVaultAssetTable from 'components/Modals/AddVaultAssets/AddVaultAssetTable' + +interface Props { + vault: Vault + defaultSelectedDenoms: string[] + onChangeBorrowDenoms: (denoms: string[]) => void +} + +export default function AddVaultAssetsModalContent(props: Props) { + const [searchString, setSearchString] = useState('') + const { data: borrowAssets } = useMarketBorrowings() + const [selectedPoolDenoms, setSelectedPoolDenoms] = useState([]) + const [selectedOtherDenoms, setSelectedOtherDenoms] = useState([]) + + const filteredBorrowAssets: BorrowAsset[] = useMemo(() => { + return borrowAssets.filter( + (asset) => + asset.name.toLowerCase().includes(searchString.toLowerCase()) || + asset.denom.toLowerCase().includes(searchString.toLowerCase()) || + asset.symbol.toLowerCase().includes(searchString.toLowerCase()), + ) + }, [borrowAssets, searchString]) + + function onChangeSearchString(value: string) { + setSearchString(value) + } + + const [poolAssets, stableAssets] = useMemo( + () => + filteredBorrowAssets.reduce( + (acc, asset) => { + if ( + asset.denom === props.vault.denoms.primary || + asset.denom === props.vault.denoms.secondary + ) { + acc[0].push(asset) + } else if (asset.isStable) { + acc[1].push(asset) + } + return acc + }, + [[], []] as [BorrowAsset[], BorrowAsset[]], + ), + [filteredBorrowAssets, props.vault.denoms.primary, props.vault.denoms.secondary], + ) + + const onChangePoolDenoms = useCallback( + (denoms: string[]) => { + setSelectedPoolDenoms(denoms) + props.onChangeBorrowDenoms([...denoms, ...selectedOtherDenoms]) + }, + [props, selectedOtherDenoms], + ) + + const onChangeOtherDenoms = useCallback( + (denoms: string[]) => { + setSelectedOtherDenoms(denoms) + props.onChangeBorrowDenoms([...selectedPoolDenoms, ...denoms]) + }, + [props, selectedPoolDenoms], + ) + + return ( + <> +
+ +
+
+
+ Available Assets + + Leverage will be set at 50% for both assets by default + +
+ +
+ Assets not in the liquidity pool + + These are swapped for an asset within the pool. Toggle Custom Ratio in order to select + these assets below. + +
+ +
+ + ) +} diff --git a/src/components/Modals/AddVaultAssets/useAddVaultAssetTableColumns.tsx b/src/components/Modals/AddVaultAssets/useAddVaultAssetTableColumns.tsx new file mode 100644 index 00000000..8967cb72 --- /dev/null +++ b/src/components/Modals/AddVaultAssets/useAddVaultAssetTableColumns.tsx @@ -0,0 +1,54 @@ +import { ColumnDef } from '@tanstack/react-table' +import React from 'react' +import Image from 'next/image' + +import Checkbox from 'components/Checkbox' +import Text from 'components/Text' +import { formatPercent } from 'utils/formatters' +import { getAssetByDenom } from 'utils/assets' +import AssetImage from 'components/AssetImage' + +export default function useAddVaultAssetTableColumns() { + const columns = React.useMemo[]>( + () => [ + { + header: 'Asset', + accessorKey: 'symbol', + id: 'symbol', + cell: ({ row }) => { + const asset = getAssetByDenom(row.original.denom) + if (!asset) return null + + return ( +
+ + +
+ + {asset.symbol} + + {asset.name} +
+
+ ) + }, + }, + { + id: 'borrowRate', + accessorKey: 'borrowRate', + header: 'Borrow Rate', + cell: ({ row }) => ( + <> + + {formatPercent(row.original.borrowRate ?? 0)} + + APY + + ), + }, + ], + [], + ) + + return columns +} diff --git a/src/components/Modals/BorrowModal.tsx b/src/components/Modals/Borrow/BorrowModal.tsx similarity index 98% rename from src/components/Modals/BorrowModal.tsx rename to src/components/Modals/Borrow/BorrowModal.tsx index 8a328cf3..521ac774 100644 --- a/src/components/Modals/BorrowModal.tsx +++ b/src/components/Modals/Borrow/BorrowModal.tsx @@ -1,4 +1,3 @@ -import Image from 'next/image' import { useEffect, useState } from 'react' import AccountSummary from 'components/Account/AccountSummary' @@ -18,6 +17,7 @@ import useStore from 'store' import { hardcodedFee } from 'utils/contants' import { formatPercent, formatValue } from 'utils/formatters' import { BN } from 'utils/helpers' +import AssetImage from 'components/AssetImage' function getDebtAmount(modal: BorrowModal | null) { if (!(modal?.marketData as BorrowAssetActive)?.debt) return '0' @@ -26,7 +26,7 @@ function getDebtAmount(modal: BorrowModal | null) { function getAssetLogo(modal: BorrowModal | null) { if (!modal?.asset) return null - return {modal.asset.symbol} + return } export default function BorrowModal() { diff --git a/src/components/Modals/fundwithdraw/FundAndWithdrawModal.tsx b/src/components/Modals/FundWithdraw/FundAndWithdrawModal.tsx similarity index 92% rename from src/components/Modals/fundwithdraw/FundAndWithdrawModal.tsx rename to src/components/Modals/FundWithdraw/FundAndWithdrawModal.tsx index 98ecc583..660483b9 100644 --- a/src/components/Modals/fundwithdraw/FundAndWithdrawModal.tsx +++ b/src/components/Modals/FundWithdraw/FundAndWithdrawModal.tsx @@ -3,8 +3,7 @@ import Text from 'components/Text' import useCurrentAccount from 'hooks/useCurrentAccount' import useStore from 'store' import { CircularProgress } from 'components/CircularProgress' - -import FundWithdrawModalContent from './FundWithdrawModalContent' +import FundWithdrawModalContent from 'components/Modals/FundWithdraw/FundAndWithdrawModalContent' export default function FundAndWithdrawModal() { const currentAccount = useCurrentAccount() diff --git a/src/components/Modals/fundwithdraw/FundWithdrawModalContent.tsx b/src/components/Modals/FundWithdraw/FundAndWithdrawModalContent.tsx similarity index 100% rename from src/components/Modals/fundwithdraw/FundWithdrawModalContent.tsx rename to src/components/Modals/FundWithdraw/FundAndWithdrawModalContent.tsx diff --git a/src/components/Modals/ModalsContainer.tsx b/src/components/Modals/ModalsContainer.tsx index ae094433..0870ac79 100644 --- a/src/components/Modals/ModalsContainer.tsx +++ b/src/components/Modals/ModalsContainer.tsx @@ -1,6 +1,7 @@ -import BorrowModal from 'components/Modals/BorrowModal' -import FundAndWithdrawModal from 'components/Modals/fundwithdraw/FundAndWithdrawModal' -import VaultModal from 'components/Modals/vault/VaultModal' +import VaultModal from 'components/Modals/Vault/VaultModal' +import BorrowModal from 'components/Modals/Borrow/BorrowModal' +import FundAndWithdrawModal from 'components/Modals/FundWithdraw/FundAndWithdrawModal' +import AddVaultBorrowAssetsModal from 'components/Modals/AddVaultAssets/AddVaultBorrowAssetsModal' export default function ModalsContainer() { return ( @@ -8,6 +9,7 @@ export default function ModalsContainer() { + ) } diff --git a/src/components/Modals/Vault/VaultBorrowings.tsx b/src/components/Modals/Vault/VaultBorrowings.tsx new file mode 100644 index 00000000..ade5fe2b --- /dev/null +++ b/src/components/Modals/Vault/VaultBorrowings.tsx @@ -0,0 +1,197 @@ +import { useEffect, useMemo, useState } from 'react' +import BigNumber from 'bignumber.js' +import React from 'react' + +import { BN } from 'utils/helpers' +import { findCoinByDenom, getAssetByDenom } from 'utils/assets' +import Button from 'components/Button' +import TokenInput from 'components/TokenInput' +import Divider from 'components/Divider' +import Text from 'components/Text' +import { ArrowRight, ExclamationMarkCircled } from 'components/Icons' +import { formatPercent } from 'utils/formatters' +import Slider from 'components/Slider' +import usePrices from 'hooks/usePrices' +import useMarketAssets from 'hooks/useMarketAssets' +import { calculateMaxBorrowAmounts } from 'utils/vaults' +import useStore from 'store' +import DisplayCurrency from 'components/DisplayCurrency' +import usePrice from 'hooks/usePrice' +import { BNCoin } from 'types/classes/BNCoin' + +export interface VaultBorrowingsProps { + account: Account + borrowings: BNCoin[] + primaryAmount: BigNumber + secondaryAmount: BigNumber + primaryAsset: Asset + secondaryAsset: Asset + onChangeBorrowings: (borrowings: BNCoin[]) => void +} + +export default function VaultBorrowings(props: VaultBorrowingsProps) { + const { data: marketAssets } = useMarketAssets() + const { data: prices } = usePrices() + const primaryPrice = usePrice(props.primaryAsset.denom) + const secondaryPrice = usePrice(props.secondaryAsset.denom) + const baseCurrency = useStore((s) => s.baseCurrency) + const vaultModal = useStore((s) => s.vaultModal) + + const primaryValue = useMemo( + () => props.primaryAmount.times(primaryPrice), + [props.primaryAmount, primaryPrice], + ) + const secondaryValue = useMemo( + () => props.secondaryAmount.times(secondaryPrice), + [props.secondaryAmount, secondaryPrice], + ) + + const borrowingValue = useMemo(() => { + return props.borrowings.reduce((prev, curr) => { + const price = prices.find((price) => price.denom === curr.denom)?.amount + if (!price) return prev + + return prev.plus(curr.amount.times(price)) + }, BN(0) as BigNumber) + }, [props.borrowings, prices]) + + const totalValue = useMemo( + () => primaryValue.plus(secondaryValue).plus(borrowingValue), + [primaryValue, secondaryValue, borrowingValue], + ) + + useEffect(() => { + const selectedBorrowDenoms = vaultModal?.selectedBorrowDenoms || [] + if ( + props.borrowings.length === selectedBorrowDenoms.length && + props.borrowings.every((coin) => selectedBorrowDenoms.includes(coin.denom)) + ) { + return + } + + const updatedBorrowings = selectedBorrowDenoms.map((denom) => { + const amount = findCoinByDenom(denom, props.borrowings)?.amount || BN(0) + return new BNCoin({ + denom, + amount: amount.toString(), + }) + }) + props.onChangeBorrowings(updatedBorrowings) + }, [vaultModal, props]) + + const maxAmounts: BNCoin[] = useMemo( + () => + calculateMaxBorrowAmounts( + props.account, + marketAssets, + prices, + props.borrowings.map((coin) => coin.denom), + ), + [props.borrowings, marketAssets, prices, props.account], + ) + + const [percentage, setPercentage] = useState(0) + + function onChangeSlider(value: number) { + if (props.borrowings.length !== 1) return + + const denom = props.borrowings[0].denom + const currentAmount = props.borrowings[0].amount + const maxAmount = maxAmounts.find((coin) => coin.denom === denom)?.amount ?? BN(0) + const newBorrowings: BNCoin[] = [ + new BNCoin({ + denom, + amount: ( + maxAmount.plus(currentAmount).times(value).div(100).decimalPlaces(0) || BN(0) + ).toString(), + }), + ] + + props.onChangeBorrowings(newBorrowings) + setPercentage(value) + } + + function updateAssets(denom: string, amount: BigNumber) { + const index = props.borrowings.findIndex((coin) => coin.denom === denom) + props.borrowings[index].amount = amount + props.onChangeBorrowings([...props.borrowings]) + } + + function onDelete(denom: string) { + const index = props.borrowings.findIndex((coin) => coin.denom === denom) + props.borrowings.splice(index, 1) + props.onChangeBorrowings([...props.borrowings]) + if (!vaultModal) return + + useStore.setState({ + vaultModal: { + ...vaultModal, + selectedBorrowDenoms: props.borrowings.map((coin) => coin.denom), + }, + }) + } + + function addAsset() { + useStore.setState({ + addVaultBorrowingsModal: { + selectedDenoms: props.borrowings.map((coin) => coin.denom), + }, + }) + } + + return ( +
+ {props.borrowings.map((coin) => { + const asset = getAssetByDenom(coin.denom) + const maxAmount = maxAmounts.find((maxAmount) => maxAmount.denom === coin.denom)?.amount + if (!asset || !maxAmount) + return + return ( + updateAssets(coin.denom, amount)} + onDelete={() => onDelete(coin.denom)} + /> + ) + })} + {props.borrowings.length === 1 && } + {props.borrowings.length === 0 && ( +
+
+ +
+ + You have no borrowing assets selected. Click on select borrow assets if you would like + to add assets to borrow. + +
+ )} +
+ ) +} diff --git a/src/components/Modals/Vault/VaultBorrowingsSubTitle.tsx b/src/components/Modals/Vault/VaultBorrowingsSubTitle.tsx new file mode 100644 index 00000000..ff347dc5 --- /dev/null +++ b/src/components/Modals/Vault/VaultBorrowingsSubTitle.tsx @@ -0,0 +1,49 @@ +import BigNumber from 'bignumber.js' +import { useMemo } from 'react' + +import DisplayCurrency from 'components/DisplayCurrency' +import usePrices from 'hooks/usePrices' +import useStore from 'store' +import { formatAmountWithSymbol } from 'utils/formatters' +import { BN } from 'utils/helpers' +import { BNCoin } from 'types/classes/BNCoin' + +interface Props { + borrowings: BNCoin[] +} + +export default function VaultDepositSubTitle(props: Props) { + const baseCurrency = useStore((s) => s.baseCurrency) + const { data: prices } = usePrices() + + const [borrowingTexts, borrowingValue] = useMemo(() => { + const texts: string[] = [] + let borrowingValue = BN(0) + props.borrowings.map((coin) => { + const price = prices.find((p) => p.denom === coin.denom)?.amount + if (!price || coin.amount.isZero()) return + borrowingValue = borrowingValue.plus(coin.amount.times(price)) + texts.push( + formatAmountWithSymbol({ + denom: coin.denom, + amount: coin.amount.toString(), + }), + ) + }) + return [texts, borrowingValue] + }, [props.borrowings, prices]) + + return ( + <> + {borrowingTexts.join(' + ')} + {borrowingTexts.length > 0 && ( + <> + {` = `} + + + )} + + ) +} diff --git a/src/components/Modals/vault/VaultDeposit.tsx b/src/components/Modals/Vault/VaultDeposits.tsx similarity index 100% rename from src/components/Modals/vault/VaultDeposit.tsx rename to src/components/Modals/Vault/VaultDeposits.tsx diff --git a/src/components/Modals/vault/VaultDepositSubTitle.tsx b/src/components/Modals/Vault/VaultDepositsSubTitle.tsx similarity index 100% rename from src/components/Modals/vault/VaultDepositSubTitle.tsx rename to src/components/Modals/Vault/VaultDepositsSubTitle.tsx diff --git a/src/components/Modals/vault/VaultModal.tsx b/src/components/Modals/Vault/VaultModal.tsx similarity index 95% rename from src/components/Modals/vault/VaultModal.tsx rename to src/components/Modals/Vault/VaultModal.tsx index 0308ac4d..62141228 100644 --- a/src/components/Modals/vault/VaultModal.tsx +++ b/src/components/Modals/Vault/VaultModal.tsx @@ -5,8 +5,7 @@ import { ASSETS } from 'constants/assets' import useCurrentAccount from 'hooks/useCurrentAccount' import useStore from 'store' import { CircularProgress } from 'components/CircularProgress' - -import VaultModalContent from './VaultModalContent' +import VaultModalContent from 'components/Modals/Vault/VaultModalContent' export default function VaultModal() { const currentAccount = useCurrentAccount() diff --git a/src/components/Modals/vault/VaultModalContent.tsx b/src/components/Modals/Vault/VaultModalContent.tsx similarity index 77% rename from src/components/Modals/vault/VaultModalContent.tsx rename to src/components/Modals/Vault/VaultModalContent.tsx index 8f303727..f45292e1 100644 --- a/src/components/Modals/vault/VaultModalContent.tsx +++ b/src/components/Modals/Vault/VaultModalContent.tsx @@ -3,12 +3,13 @@ import { useCallback, useState } from 'react' import Accordion from 'components/Accordion' import AccountSummary from 'components/Account/AccountSummary' -import VaultBorrowings from 'components/Modals/vault/VaultBorrowings' -import VaultDeposit from 'components/Modals/vault/VaultDeposit' -import VaultDepositSubTitle from 'components/Modals/vault/VaultDepositSubTitle' import useIsOpenArray from 'hooks/useIsOpenArray' import { BN } from 'utils/helpers' import useUpdateAccount from 'hooks/useUpdateAccount' +import VaultBorrowingsSubTitle from 'components/Modals/Vault/VaultBorrowingsSubTitle' +import VaultDeposit from 'components/Modals/Vault/VaultDeposits' +import VaultBorrowings from 'components/Modals/Vault/VaultBorrowings' +import VaultDepositSubTitle from 'components/Modals/Vault/VaultDepositsSubTitle' interface Props { vault: Vault @@ -18,7 +19,10 @@ interface Props { } export default function VaultModalContent(props: Props) { - const { updatedAccount, onChangeBorrowings } = useUpdateAccount(props.account) + const { updatedAccount, onChangeBorrowings, borrowings } = useUpdateAccount( + props.account, + props.vault, + ) const [isOpen, toggleOpen] = useIsOpenArray(2, false) const [primaryAmount, setPrimaryAmount] = useState(BN(0)) const [secondaryAmount, setSecondaryAmount] = useState(BN(0)) @@ -41,6 +45,7 @@ export default function VaultModalContent(props: Props) { return (
( @@ -73,11 +78,16 @@ export default function VaultModalContent(props: Props) { renderContent: () => ( ), title: 'Borrow', + subTitle: , isOpen: isOpen[1], toggleOpen: (index: number) => toggleOpen(index), }, diff --git a/src/components/Modals/vault/VaultBorrowings.tsx b/src/components/Modals/vault/VaultBorrowings.tsx deleted file mode 100644 index bd8252cf..00000000 --- a/src/components/Modals/vault/VaultBorrowings.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { useMemo, useState } from 'react' -import BigNumber from 'bignumber.js' - -import { BN } from 'utils/helpers' -import { getAssetByDenom } from 'utils/assets' -import Button from 'components/Button' -import TokenInput from 'components/TokenInput' -import Divider from 'components/Divider' -import Text from 'components/Text' -import { ArrowRight } from 'components/Icons' -import { formatPercent } from 'utils/formatters' -import Slider from 'components/Slider' -import usePrices from 'hooks/usePrices' -import useMarketAssets from 'hooks/useMarketAssets' -import { calculateMaxBorrowAmounts } from 'utils/vaults' -import React from 'react' - -interface Props { - account: Account - defaultBorrowDenom: string - onChangeBorrowings: (borrowings: Map) => void -} - -export default function VaultBorrowings(props: Props) { - const { data: prices } = usePrices() - const { data: marketAssets } = useMarketAssets() - - const [borrowings, setBorrowings] = useState>( - new Map().set(props.defaultBorrowDenom, BN(0)), - ) - - const maxAmounts: Map = useMemo( - () => - calculateMaxBorrowAmounts(props.account, marketAssets, prices, Array.from(borrowings.keys())), - [borrowings, marketAssets, prices, props.account], - ) - - const [percentage, setPercentage] = useState(0) - - function onChangeSlider(value: number) { - if (borrowings.size !== 1) return - - const denom = Array.from(borrowings.keys())[0] - - const newBorrowings = new Map().set( - denom, - maxAmounts.get(denom)?.times(value).div(100).toPrecision(0) || BN(0), - ) - setBorrowings(newBorrowings) - props.onChangeBorrowings(newBorrowings) - setPercentage(value) - } - - function updateAssets(denom: string, amount: BigNumber) { - const newborrowings = new Map(borrowings) - newborrowings.set(denom, amount) - setBorrowings(newborrowings) - props.onChangeBorrowings(newborrowings) - } - - function onDelete(denom: string) { - const newborrowings = new Map(borrowings) - newborrowings.delete(denom) - setBorrowings(newborrowings) - props.onChangeBorrowings(newborrowings) - } - - function addAsset() { - const newborrowings = new Map(borrowings) - // Replace with denom parameter from the modal (MP-2546) - newborrowings.set('', BN(0)) - setBorrowings(newborrowings) - props.onChangeBorrowings(newborrowings) - } - - return ( -
- {Array.from(borrowings.entries()).map(([denom, amount]) => { - const asset = getAssetByDenom(denom) - if (!asset) return - return ( - updateAssets(denom, amount)} - onDelete={() => onDelete(denom)} - /> - ) - })} - {borrowings.size === 1 && } -
- ) -} diff --git a/src/components/SearchBar.tsx b/src/components/SearchBar.tsx new file mode 100644 index 00000000..5ad619d3 --- /dev/null +++ b/src/components/SearchBar.tsx @@ -0,0 +1,34 @@ +import classNames from 'classnames' +import { ChangeEvent } from 'react' + +import { Search } from 'components/Icons' + +interface Props { + value: string + placeholder: string + onChange: (value: string) => void +} + +export default function SearchBar(props: Props) { + function onChange(event: ChangeEvent) { + props.onChange(event.target.value) + } + + return ( +
+ + onChange(event)} + /> +
+ ) +} diff --git a/src/components/Select/Option.tsx b/src/components/Select/Option.tsx index 09411e9d..0a448691 100644 --- a/src/components/Select/Option.tsx +++ b/src/components/Select/Option.tsx @@ -6,6 +6,7 @@ import { ChevronDown } from 'components/Icons' import Text from 'components/Text' import { ASSETS } from 'constants/assets' import { formatValue } from 'utils/formatters' +import AssetImage from 'components/AssetImage' interface Props extends Option { isSelected?: boolean @@ -18,11 +19,7 @@ export default function Option(props: Props) { const isCoin = !!props.denom if (isCoin) { - const currentAsset = ASSETS.find((asset) => asset.denom === props.denom) - const symbol = currentAsset?.symbol ?? ASSETS[0].symbol - const logo = currentAsset?.logo ?? ASSETS[0].logo - const denom = currentAsset?.denom ?? ASSETS[0].denom - const decimals = currentAsset?.decimals ?? ASSETS[0].decimals + const asset = ASSETS.find((asset) => asset.denom === props.denom) || ASSETS[0] const balance = props.amount ?? '0' if (props.isDisplay) { @@ -30,8 +27,8 @@ export default function Option(props: Props) {
- {`${symbol} - {symbol} + + {asset.symbol} props?.onClick && props.onClick(denom)} + onClick={() => props?.onClick && props.onClick(asset.denom)} >
- {`${symbol} +
- {symbol} + {asset.symbol} - {formatValue(balance, { decimals, maxDecimals: 4, minDecimals: 0, rounded: true })} + {formatValue(balance, { + decimals: asset.decimals, + maxDecimals: 4, + minDecimals: 0, + rounded: true, + })} {formatValue(5, { maxDecimals: 2, minDecimals: 0, prefix: 'APY ', suffix: '%' })} - +
) diff --git a/src/components/TokenInput.tsx b/src/components/TokenInput.tsx index 4c2e7a13..8c680475 100644 --- a/src/components/TokenInput.tsx +++ b/src/components/TokenInput.tsx @@ -13,6 +13,7 @@ import { FormattedNumber } from 'components/FormattedNumber' import Button from 'components/Button' import { ExclamationMarkTriangle, TrashBin } from 'components/Icons' import { Tooltip } from 'components/Tooltip' +import AssetImage from 'components/AssetImage' interface Props { amount: BigNumber @@ -67,7 +68,7 @@ export default function TokenInput(props: Props) { /> ) : (
- token + {props.asset.symbol}
)} diff --git a/src/components/Tooltip/index.tsx b/src/components/Tooltip/index.tsx index 16d276d6..af7bde19 100644 --- a/src/components/Tooltip/index.tsx +++ b/src/components/Tooltip/index.tsx @@ -4,8 +4,7 @@ import { ReactNode } from 'react' import { Questionmark } from 'components/Icons' import useStore from 'store' - -import TooltipContent from './TooltipContent' +import TooltipContent from 'components/Tooltip/TooltipContent' interface Props { content: ReactNode | string diff --git a/src/constants/assets.ts b/src/constants/assets.ts index 04d899ae..8156f7eb 100644 --- a/src/constants/assets.ts +++ b/src/constants/assets.ts @@ -97,6 +97,7 @@ export const ASSETS: Asset[] = [ isEnabled: true, isMarket: true, isDisplayCurrency: true, + isStable: true, }, { symbol: 'USDC.n', @@ -112,5 +113,6 @@ export const ASSETS: Asset[] = [ isEnabled: IS_TESTNET, isMarket: IS_TESTNET, isDisplayCurrency: IS_TESTNET, + isStable: true, }, ] diff --git a/src/hooks/useMarketBorrowings.tsx b/src/hooks/useMarketBorrowings.tsx index b7342c8a..d117a589 100644 --- a/src/hooks/useMarketBorrowings.tsx +++ b/src/hooks/useMarketBorrowings.tsx @@ -4,6 +4,7 @@ import getMarketBorrowings from 'api/markets/getMarketBorrowings' export default function useMarketBorrowings() { return useSWR(`marketBorrowings`, getMarketBorrowings, { - suspense: true, + fallbackData: [], + suspense: false, }) } diff --git a/src/hooks/usePrice.tsx b/src/hooks/usePrice.tsx index 89918301..2b0de62d 100644 --- a/src/hooks/usePrice.tsx +++ b/src/hooks/usePrice.tsx @@ -1,4 +1,4 @@ -import usePrices from './usePrices' +import usePrices from 'hooks/usePrices' export default function usePrice(denom: string) { const { data: prices } = usePrices() diff --git a/src/hooks/useUpdateAccount.tsx b/src/hooks/useUpdateAccount.tsx index 46c0c127..37eccb04 100644 --- a/src/hooks/useUpdateAccount.tsx +++ b/src/hooks/useUpdateAccount.tsx @@ -1,10 +1,12 @@ import BigNumber from 'bignumber.js' import { useCallback, useState } from 'react' +import { BNCoin } from 'types/classes/BNCoin' import { BN } from 'utils/helpers' -export default function useUpdateAccount(account: Account) { +export default function useUpdateAccount(account: Account, vault: Vault) { const [updatedAccount, setUpdatedAccount] = useState(account) + const [borrowings, setBorrowings] = useState([]) function getCoin(denom: string, amount: BigNumber): Coin { return { @@ -14,32 +16,33 @@ export default function useUpdateAccount(account: Account) { } const onChangeBorrowings = useCallback( - (borrowings: Map) => { + (borrowings: BNCoin[]) => { const debts: Coin[] = [...account.debts] const deposits: Coin[] = [...account.deposits] const currentDebtDenoms = debts.map((debt) => debt.denom) const currentDepositDenoms = deposits.map((deposit) => deposit.denom) - borrowings.forEach((amount, denom) => { - if (amount.isZero()) return + borrowings.map((coin) => { + if (coin.amount.isZero()) return - if (currentDebtDenoms.includes(denom)) { - const index = currentDebtDenoms.indexOf(denom) - const newAmount = BN(debts[index].amount).plus(amount) - debts[index] = getCoin(denom, newAmount) + if (currentDebtDenoms.includes(coin.denom)) { + const index = currentDebtDenoms.indexOf(coin.denom) + const newAmount = BN(debts[index].amount).plus(coin.amount) + debts[index] = getCoin(coin.denom, newAmount) } else { - debts.push(getCoin(denom, amount)) + debts.push(coin.toCoin()) } - if (currentDepositDenoms.includes(denom)) { - const index = currentDepositDenoms.indexOf(denom) - const newAmount = BN(deposits[index].amount).plus(amount) - deposits[index] = getCoin(denom, newAmount) + if (currentDepositDenoms.includes(coin.denom)) { + const index = currentDepositDenoms.indexOf(coin.denom) + const newAmount = BN(deposits[index].amount).plus(coin.amount) + deposits[index] = getCoin(coin.denom, newAmount) } else { - deposits.push(getCoin(denom, amount)) + deposits.push(coin.toCoin()) } }) + setBorrowings(borrowings) setUpdatedAccount({ ...account, debts, @@ -49,5 +52,5 @@ export default function useUpdateAccount(account: Account) { [account], ) - return { updatedAccount, onChangeBorrowings } + return { borrowings, updatedAccount, onChangeBorrowings } } diff --git a/src/store/slices/modal.ts b/src/store/slices/modal.ts index 01a6559b..9779a4ed 100644 --- a/src/store/slices/modal.ts +++ b/src/store/slices/modal.ts @@ -2,6 +2,7 @@ import { GetState, SetState } from 'zustand' export default function createModalSlice(set: SetState, get: GetState) { return { + addVaultBorrowingsModal: null, borrowModal: null, createAccountModal: false, deleteAccountModal: false, diff --git a/src/types/classes/BNCoin.ts b/src/types/classes/BNCoin.ts new file mode 100644 index 00000000..99af37d3 --- /dev/null +++ b/src/types/classes/BNCoin.ts @@ -0,0 +1,18 @@ +import { BN } from 'utils/helpers' + +export class BNCoin { + public denom: string + public amount: BigNumber + + constructor(coin: Coin) { + this.denom = coin.denom + this.amount = BN(coin.amount) + } + + toCoin(): Coin { + return { + denom: this.denom, + amount: this.amount.toString(), + } + } +} diff --git a/src/types/interfaces/asset.d.ts b/src/types/interfaces/asset.d.ts index 947e7177..1040863e 100644 --- a/src/types/interfaces/asset.d.ts +++ b/src/types/interfaces/asset.d.ts @@ -13,14 +13,14 @@ interface Asset { isEnabled: boolean isMarket: boolean isDisplayCurrency?: boolean + isStable?: boolean } interface OtherAsset extends Omit { symbol: 'MARS' } -interface BorrowAsset { - denom: string +interface BorrowAsset extends Asset { borrowRate: number | null liquidity: { amount: string @@ -31,3 +31,8 @@ interface BorrowAsset { interface BorrowAssetActive extends BorrowAsset { debt: string } + +interface BigNumberCoin { + denom: string + amount: BigNumber +} diff --git a/src/types/interfaces/store/comon.d.ts b/src/types/interfaces/store/common.d.ts similarity index 100% rename from src/types/interfaces/store/comon.d.ts rename to src/types/interfaces/store/common.d.ts index 4216d63b..d25743c3 100644 --- a/src/types/interfaces/store/comon.d.ts +++ b/src/types/interfaces/store/common.d.ts @@ -1,10 +1,10 @@ interface CommonSlice { accounts: Account[] | null address?: string + balances: Coin[] + client?: import('@marsprotocol/wallet-connector').WalletClient enableAnimations: boolean isOpen: boolean - balances: Coin[] selectedAccount: string | null - client?: import('@marsprotocol/wallet-connector').WalletClient status: import('@marsprotocol/wallet-connector').WalletConnectionStatus } diff --git a/src/types/interfaces/store/modals.d.ts b/src/types/interfaces/store/modals.d.ts index d4bd64c7..bbc41004 100644 --- a/src/types/interfaces/store/modals.d.ts +++ b/src/types/interfaces/store/modals.d.ts @@ -1,12 +1,11 @@ interface ModalSlice { + addVaultBorrowingsModal: AddVaultBorrowingsModal | null borrowModal: BorrowModal | null createAccountModal: boolean deleteAccountModal: boolean fundAccountModal: boolean fundAndWithdrawModal: 'fund' | 'withdraw' | null - vaultModal: { - vault: Vault - } | null + vaultModal: VaultModal | null } interface BorrowModal { @@ -14,3 +13,12 @@ interface BorrowModal { marketData: BorrowAsset | BorrowAssetActive isRepay?: boolean } + +interface VaultModal { + vault: Vault + selectedBorrowDenoms: string[] +} + +interface AddVaultBorrowingsModal { + selectedDenoms: string[] +} diff --git a/src/utils/assets.ts b/src/utils/assets.ts index 80dc8c71..5c5e594a 100644 --- a/src/utils/assets.ts +++ b/src/utils/assets.ts @@ -19,3 +19,7 @@ export function getBaseAsset() { export function getDisplayCurrencies() { return ASSETS.filter((asset) => asset.isDisplayCurrency) } + +export function findCoinByDenom(denom: string, coins: BigNumberCoin[]) { + return coins.find((coin) => coin.denom === denom) +} diff --git a/src/utils/vaults.ts b/src/utils/vaults.ts index 2837379f..72c8f315 100644 --- a/src/utils/vaults.ts +++ b/src/utils/vaults.ts @@ -4,6 +4,7 @@ import { IS_TESTNET } from 'constants/env' import { TESTNET_VAULTS, VAULTS } from 'constants/vaults' import { BN } from 'utils/helpers' import { getNetCollateralValue } from 'utils/accounts' +import { BNCoin } from 'types/classes/BNCoin' export function getVaultMetaData(address: string) { const vaults = IS_TESTNET ? TESTNET_VAULTS : VAULTS @@ -16,8 +17,8 @@ export function calculateMaxBorrowAmounts( marketAssets: Market[], prices: Coin[], denoms: string[], -): Map { - const maxAmounts = new Map() +): BNCoin[] { + const maxAmounts: BNCoin[] = [] const collateralValue = getNetCollateralValue(account, marketAssets, prices) for (const denom of denoms) { @@ -29,7 +30,7 @@ export function calculateMaxBorrowAmounts( const borrowValue = BN(1).minus(borrowAsset.maxLtv).times(borrowAssetPrice) const amount = collateralValue.dividedBy(borrowValue).decimalPlaces(0) - maxAmounts.set(denom, amount) + maxAmounts.push(new BNCoin({ denom, amount: amount.toString() })) } return maxAmounts