* Build(deps): bump @splinetool/runtime from 0.9.477 to 0.9.482 (#544)

Bumps @splinetool/runtime from 0.9.477 to 0.9.482.

---
updated-dependencies:
- dependency-name: "@splinetool/runtime"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Build(deps-dev): bump @types/node from 20.7.0 to 20.8.6 (#548)

Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.7.0 to 20.8.6.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Build(deps): bump @sentry/nextjs from 7.73.0 to 7.74.0 (#545)

Bumps [@sentry/nextjs](https://github.com/getsentry/sentry-javascript) from 7.73.0 to 7.74.0.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/7.73.0...7.74.0)

---
updated-dependencies:
- dependency-name: "@sentry/nextjs"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Mp 3245 usehlsvaults hook (#541)

*  routing and pages for HLS

*  create hooks for fetching HLS vaults and Strategies

* Share accounts (#539)

* feat: do not redirect to wallet on portfolio page

* fix: use connected wallet for AccountMenu

* fix: fixed ghost AccountDetails

* feat: created ShareBar and share functionality

* fix: don’t show shareBar if no address is present

* fix: stupid 'next/navigation'

* tidy: format

* fix: fixed tests

*  routing and pages for HLS (#538)

* 🐛 use useAccountIds

* fix: fixed the tests

* fix: accountIds is now a suspense

---------

Co-authored-by: Bob van der Helm <34470358+bobthebuidlr@users.noreply.github.com>

* 🐛 fix build

---------

Co-authored-by: Linkie Link <linkielink.dev@gmail.com>

* Mp 2837 pre commit hook (#549)

* MP-2837: added husys and lint-staged

* MP-2837: enabled lint-staged

* MP-2837: setup .prettierignore

* MP-2837: setup .prettierignore

* MP-3483: all Depo. Caps are now % filled (#551)

* MP-3487: changed the copy of the bridging intro screen (#553)

* MP-3482: replaced Max LTV with Max. Leverage (#550)

* added hatched health masks (#552)

* added hatched health masks

* Mp 2837 pre commit hook (#549)

* MP-2837: added husys and lint-staged

* MP-2837: enabled lint-staged

* MP-2837: setup .prettierignore

* MP-2837: setup .prettierignore

* MP-3483: all Depo. Caps are now % filled (#551)

* MP-3487: changed the copy of the bridging intro screen (#553)

* MP-3482: replaced Max LTV with Max. Leverage (#550)

* sneak: change filled to used

* fix: fixed the foregroundColor on increase and my ocd

* ♻️ refactor table (Farm) (#555)

* ♻️ refactor table (Farm)

* 🧽 clean up PR

* 🧽 clean up PR

* Build(deps): bump @babel/traverse from 7.21.2 to 7.23.2 (#554)

Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.21.2 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix: rename withdraw to unlend (#557)

* Full refactor tables (#556)

* 📈 Improve structure generic Table component

* ♻️ Update Borrow Table and overall structure of Table comp

* ♻️ Update Lend table

*  add loading state for lend table

* 🧪 Fix unit tests

*  Add available HLS Vaults page (#558)

* Table updates (#559)

* fix: adjusted table colors and hover interactions

* fix: added actionButtons back and changed lend to APY

* fix: build update

* tidy: fixed the CircularProgress indicators on the loading  modals

* fix: relative import

* env: updated shuttle, keplr and version (#566)

* fix: fixed dust left when trying to buy max amount without leverage (#565)

* feat: added squidrouter to the bridges (#561)

* feat: added squidrouter to the bridges

* fix: copy update

* MP-3521: updated the APR calculation (#572)

* Table fixes (#563)

* fix: fixed the sorting of the tables

* fix: added sorting functions

* fix: farm sorting for deposit cap

* fix: fixed Row types

* Build(deps-dev): bump prettier-plugin-tailwindcss from 0.5.5 to 0.5.6 (#567)

Bumps [prettier-plugin-tailwindcss](https://github.com/tailwindlabs/prettier-plugin-tailwindcss) from 0.5.5 to 0.5.6.
- [Release notes](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/compare/v0.5.5...v0.5.6)

---
updated-dependencies:
- dependency-name: prettier-plugin-tailwindcss
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Build(deps): bump react-router-dom from 6.16.0 to 6.17.0 (#571)

Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.16.0 to 6.17.0.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.17.0/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

*  HLS: Add info modal (#573)

* MP-3484: remember summaryAccount tabs and auto expand both (#574)

* User feedback (#575)

* feat: added debt indicator and adjusted the borrowModal

* fix: wallet interaction fix

* Add usdc noble (#576)

* env: added USDC.n

* env: updated usdc noble variables

* fix: fixed the pool on USDC for devnet purposes

* 🐛 Fix initial status of chart (#577)

* Mp 3480 persist last trading pair (#578)

* MP-3480: remove favourite asset and prepare localStore

* env: updated shuttle, keplr and version (#566)

* fix: fixed dust left when trying to buy max amount without leverage (#565)

* feat: added squidrouter to the bridges (#561)

* feat: added squidrouter to the bridges

* fix: copy update

* MP-3521: updated the APR calculation (#572)

* Table fixes (#563)

* fix: fixed the sorting of the tables

* fix: added sorting functions

* fix: farm sorting for deposit cap

* fix: fixed Row types

* Build(deps-dev): bump prettier-plugin-tailwindcss from 0.5.5 to 0.5.6 (#567)

Bumps [prettier-plugin-tailwindcss](https://github.com/tailwindlabs/prettier-plugin-tailwindcss) from 0.5.5 to 0.5.6.
- [Release notes](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/compare/v0.5.5...v0.5.6)

---
updated-dependencies:
- dependency-name: prettier-plugin-tailwindcss
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Build(deps): bump react-router-dom from 6.16.0 to 6.17.0 (#571)

Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.16.0 to 6.17.0.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.17.0/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

*  HLS: Add info modal (#573)

* MP-3484: remember summaryAccount tabs and auto expand both (#574)

* User feedback (#575)

* feat: added debt indicator and adjusted the borrowModal

* fix: wallet interaction fix

* Add usdc noble (#576)

* env: added USDC.n

* env: updated usdc noble variables

* fix: fixed the pool on USDC for devnet purposes

* 🐛 Fix initial status of chart (#577)

* MP-3480: persist trading pair

* fix: updated according to feedback

* fix:  remove pair from Trading View header

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bob van der Helm <34470358+bobthebuidlr@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bob van der Helm <34470358+bobthebuidlr@users.noreply.github.com>
This commit is contained in:
Linkie Link 2023-10-24 16:41:40 +02:00 committed by GitHub
parent 7272b1eee0
commit 3dc1752ae2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
164 changed files with 3656 additions and 2241 deletions

View File

@ -1,46 +1,37 @@
# DEVNET #
NEXT_PUBLIC_NETWORK=devnet NEXT_PUBLIC_NETWORK=devnet
NEXT_PUBLIC_CHAIN_ID=devnet NEXT_PUBLIC_CHAIN_ID=devnet
NEXT_PUBLIC_RPC=https://rpc.devnet.osmosis.zone/ NEXT_PUBLIC_RPC=https://rpc.devnet.osmosis.zone/
NEXT_PUBLIC_GQL=https://devnet-osmosis-gql.marsprotocol.io/graphql NEXT_PUBLIC_GQL=https://devnet-osmosis-gql.marsprotocol.io/graphql
NEXT_PUBLIC_REST=https://lcd.devnet.osmosis.zone/ NEXT_PUBLIC_REST=https://lcd.devnet.osmosis.zone/
NEXT_PUBLIC_SWAP=https://testnet.osmosis.zone NEXT_PUBLIC_ZAPPER=osmo1yhh8mhthj5jn5c6ty59z3tpsk554qxmlkrkcderw6jls0pcg8zxsdjdj94
NEXT_PUBLIC_VAULT_APR=https://api.marsprotocol.io/v1/vaults/osmosis NEXT_PUBLIC_PARAMS=osmo1aye5qcer5n52crrkaf35jprsad2807q6kg3eeeu7k79h4slxfausfqhc9y
NEXT_PUBLIC_ACCOUNT_NFT=osmo1pdr8mvj2ky9hzj5pjp026apfmd0pacd3xrzx3mzazy7lulnsdrkq96gzk3
NEXT_PUBLIC_ORACLE=osmo156elt2tp5455q9a6vfrvnpncxyd33cxm9z2lgguwg6dgws9tedps5tq3rc
NEXT_PUBLIC_RED_BANK=osmo1vxpdcw092n9rngvekve8g324c2yjx56496ck98ag4sdxr4p4zd4q0wr7x6
NEXT_PUBLIC_CREDIT_MANAGER=osmo1m83kw2vehyt9urxf79qa9rxk8chgs4464e5h8s37yhnw3pwauuqq7lux8r
NEXT_PUBLIC_INCENTIVES=osmo1r9w7k774vcxeuvq6ctq0z2j6wkkxpskucgjkqt0uu7u07l03s3js6ukge4
NEXT_PUBLIC_ZAPPER=osmo1q4kkvuy8wc9fs8sfm7zyeh4k25vssd0l68nrph8s7unvq5jdq67swrepj4
NEXT_PUBLIC_SWAPPER=osmo1wee0z8c7tcawyl647eapqs4a88q8jpa7ddy6nn2nrs7t47p2zhxswetwla
NEXT_PUBLIC_PARAMS=osmo1pzszwkyy0x9cu6p2uknwa3wccr79xwmqn9gj66fnjnayr28tzp6qh2n4qg
NEXT_PUBLIC_PYTH=osmo13ge29x4e2s63a8ytz2px8gurtyznmue4a69n5275692v3qn3ks8q7cwck7
NEXT_PUBLIC_API=http://localhost:3000/api
NEXT_PUBLIC_PYTH_ENDPOINT=https://xc-mainnet.pyth.network/api
NEXT_PUBLIC_MAINNET_REST=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-lcd-front/
NEXT_PUBLIC_CANDLES_ENDPOINT=https://api.thegraph.com/subgraphs/name/donovansolms/osmosis-tv-candles-test
NEXT_PUBLIC_WALLET_CONNECT_ID=d93fdffb159bae5ec87d8fee4cdbb045
CHARTING_LIBRARY_REPOSITORY=github.com/tradingview/charting_library
CHARTING_LIBRARY_ACCESS_TOKEN=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
CHARTING_LIBRARY_USERNAME=mars-git-demo
# MAINNET # # MAINNET #
# NEXT_PUBLIC_NETWORK=mainnet NEXT_PUBLIC_NETWORK=mainnet
# NEXT_PUBLIC_CHAIN_ID=osmosis-1 NEXT_PUBLIC_CHAIN_ID=osmosis-1
# NEXT_PUBLIC_RPC=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-rpc-front/ NEXT_PUBLIC_RPC=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-rpc-front/
# NEXT_PUBLIC_GQL=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-hive-front/graphql NEXT_PUBLIC_GQL=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-hive-front/graphql
# NEXT_PUBLIC_REST=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-lcd-front/ NEXT_PUBLIC_REST=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-lcd-front/
# NEXT_PUBLIC_SWAP=https://app.osmosis.zone NEXT_PUBLIC_ZAPPER=osmo17qwvc70pzc9mudr8t02t3pl74hhqsgwnskl734p4hug3s8mkerdqzduf7c
# NEXT_PUBLIC_VAULT_APR=https://api.marsprotocol.io/v1/vaults/osmosis NEXT_PUBLIC_PARAMS=osmo1nlmdxt9ctql2jr47qd4fpgzg84cjswxyw6q99u4y4u4q6c2f5ksq7ysent
# NEXT_PUBLIC_ACCOUNT_NFT=osmo1450hrg6dv2l58c0rvdwx8ec2a0r6dd50hn4frk370tpvqjhy8khqw7sw09
# NEXT_PUBLIC_ORACLE=osmo1mhznfr60vjdp2gejhyv2gax9nvyyzhd3z0qcwseyetkfustjauzqycsy2g
# NEXT_PUBLIC_RED_BANK=osmo1c3ljch9dfw5kf52nfwpxd2zmj2ese7agnx0p9tenkrryasrle5sqf3ftpg
# NEXT_PUBLIC_CREDIT_MANAGER=osmo1f2m24wktq0sw3c0lexlg7fv4kngwyttvzws3a3r3al9ld2s2pvds87jqvf
# NEXT_PUBLIC_INCENTIVES=osmo1nkahswfr8shg8rlxqwup0vgahp0dk4x8w6tkv3rra8rratnut36sk22vrm
# NEXT_PUBLIC_ZAPPER=osmo17qwvc70pzc9mudr8t02t3pl74hhqsgwnskl734p4hug3s8mkerdqzduf7c
# NEXT_PUBLIC_SWAPPER=osmo1wee0z8c7tcawyl647eapqs4a88q8jpa7ddy6nn2nrs7t47p2zhxswetwla
# NEXT_PUBLIC_API=http://localhost:3000/api
# NEXT_PUBLIC_PYTH_ENDPOINT=https://xc-mainnet.pyth.network/api
# NEXT_PUBLIC_MAINNET_REST=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-lcd-front/
# NEXT_PUBLIC_WALLET_CONNECT_ID=d93fdffb159bae5ec87d8fee4cdbb045
# COMMON #
NEXT_PUBLIC_SWAP=https://app.osmosis.zone
NEXT_PUBLIC_VAULT_APR=https://api.marsprotocol.io/v1/vaults/osmosis
NEXT_PUBLIC_ACCOUNT_NFT=osmo1450hrg6dv2l58c0rvdwx8ec2a0r6dd50hn4frk370tpvqjhy8khqw7sw09
NEXT_PUBLIC_ORACLE=osmo1mhznfr60vjdp2gejhyv2gax9nvyyzhd3z0qcwseyetkfustjauzqycsy2g
NEXT_PUBLIC_RED_BANK=osmo1c3ljch9dfw5kf52nfwpxd2zmj2ese7agnx0p9tenkrryasrle5sqf3ftpg
NEXT_PUBLIC_CREDIT_MANAGER=osmo1f2m24wktq0sw3c0lexlg7fv4kngwyttvzws3a3r3al9ld2s2pvds87jqvf
NEXT_PUBLIC_INCENTIVES=osmo1nkahswfr8shg8rlxqwup0vgahp0dk4x8w6tkv3rra8rratnut36sk22vrm
NEXT_PUBLIC_SWAPPER=osmo1wee0z8c7tcawyl647eapqs4a88q8jpa7ddy6nn2nrs7t47p2zhxswetwla
NEXT_PUBLIC_PYTH=osmo13ge29x4e2s63a8ytz2px8gurtyznmue4a69n5275692v3qn3ks8q7cwck7
NEXT_PUBLIC_PYTH_ENDPOINT=https://hermes.pyth.network/api
NEXT_PUBLIC_MAINNET_REST=https://osmosis.rpc.p2p.world/4dqst8e8Cgd2HMb2HDNkimP7NIkcbjuk/lcd/
NEXT_PUBLIC_CANDLES_ENDPOINT=https://osmosis-candles.marsprotocol.io/
NEXT_PUBLIC_WALLET_CONNECT_ID=d93fdffb159bae5ec87d8fee4cdbb045
CHARTING_LIBRARY_REPOSITORY=github.com/tradingview/charting_library
CHARTING_LIBRARY_ACCESS_TOKEN=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
CHARTING_LIBRARY_USERNAME=mars-git-demo

4
.husky/pre-commit Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged

View File

@ -1,3 +1,4 @@
src/utils/charting_library src/utils/charting_library
src/utils/datafeeds src/utils/datafeeds
src/types/generated src/types/generated
.husky

View File

@ -1,11 +1,11 @@
import { render } from '@testing-library/react' import { render } from '@testing-library/react'
import { CircularProgress } from 'components/CircularProgress' import { CircularProgress } from 'components/CircularProgress'
import { REDUCE_MOTION_KEY } from 'constants/localStore' import { LocalStorageKeys } from 'constants/localStorageKeys'
describe('<CircularProgress />', () => { describe('<CircularProgress />', () => {
afterAll(() => { afterAll(() => {
localStorage.removeItem(REDUCE_MOTION_KEY) localStorage.removeItem(LocalStorageKeys.REDUCE_MOTION)
}) })
it('should render', () => { it('should render', () => {
@ -15,7 +15,7 @@ describe('<CircularProgress />', () => {
}) })
it('should render `...` when animations disabled', () => { it('should render `...` when animations disabled', () => {
localStorage.setItem(REDUCE_MOTION_KEY, 'true') localStorage.setItem(LocalStorageKeys.REDUCE_MOTION, 'true')
const { getByText } = render(<CircularProgress />) const { getByText } = render(<CircularProgress />)
const threeDots = getByText('...') const threeDots = getByText('...')
@ -24,7 +24,7 @@ describe('<CircularProgress />', () => {
}) })
it('should render the component with animation classes when animations enabled', () => { it('should render the component with animation classes when animations enabled', () => {
localStorage.setItem(REDUCE_MOTION_KEY, 'false') localStorage.setItem(LocalStorageKeys.REDUCE_MOTION, 'false')
const { container } = render(<CircularProgress />) const { container } = render(<CircularProgress />)
const progressWithAnimations = container.querySelector('.animate-progress') const progressWithAnimations = container.querySelector('.animate-progress')

View File

@ -1,38 +0,0 @@
import { render } from '@testing-library/react'
import MarketDetails from 'components/MarketAssetTable/MarketDetails'
import { ASSETS } from 'constants/assets'
import { BN } from 'utils/helpers'
const data: LendingMarketTableData = {
asset: ASSETS[0],
marketDepositAmount: BN('890546916'),
accountLentValue: BN('0.5498406009348686811'),
marketLiquidityAmount: BN('629396551'),
marketDepositCap: BN('2500000000000'),
marketLiquidityRate: 0.017,
marketLiquidationThreshold: 0.61,
marketMaxLtv: 0.59,
borrowEnabled: true,
}
jest.mock('hooks/useDisplayCurrencyPrice', () => () => {
const { BN } = require('utils/helpers')
return {
getConversionRate: () => BN(1),
convertAmount: () => BN(1),
symbol: 'MARS',
}
})
describe('<LendingDetails />', () => {
afterAll(() => {
jest.unmock('hooks/usePrices')
})
it('should render', () => {
const { container } = render(<MarketDetails type='lend' data={data} />)
expect(container).toBeInTheDocument()
})
})

View File

@ -14,6 +14,7 @@ const mockedDepositedVault: DepositedVault = {
...TESTNET_VAULTS_META_DATA[0], ...TESTNET_VAULTS_META_DATA[0],
status: 'active', status: 'active',
apy: 1, apy: 1,
apr: null,
ltv: { ltv: {
max: 0.65, max: 0.65,
liq: 0.7, liq: 0.7,

View File

@ -1,88 +0,0 @@
import { render } from '@testing-library/react'
import DisplayCurrency from 'components/DisplayCurrency'
import VaultBorrowings, { VaultBorrowingsProps } from 'components/Modals/Vault/VaultBorrowings'
import { ASSETS } from 'constants/assets'
import { TESTNET_VAULTS_META_DATA } from 'constants/vaults'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { BN } from 'utils/helpers'
jest.mock('hooks/usePrices', () =>
jest.fn(() => ({
data: [],
})),
)
jest.mock('hooks/usePrice', () => jest.fn(() => '1'))
jest.mock('hooks/useMarketAssets', () =>
jest.fn(() => ({
data: [],
})),
)
jest.mock('hooks/broadcast/useDepositVault', () => jest.fn(() => ({ actions: [] })))
jest.mock('components/DisplayCurrency')
jest.mock('hooks/useHealthComputer', () =>
jest.fn(() => ({
computeMaxBorrowAmount: () => {},
})),
)
const mockedDisplayCurrency = jest
.mocked(DisplayCurrency)
.mockImplementation(() => <div>Display currency</div>)
const mockedVault: Vault = {
...TESTNET_VAULTS_META_DATA[0],
apy: 0,
ltv: {
liq: 0.2,
max: 0.1,
},
cap: {
denom: 'test',
max: BN(10),
used: BN(2),
},
}
describe('<VaultBorrowings />', () => {
const defaultProps: VaultBorrowingsProps = {
primaryAsset: ASSETS[0],
secondaryAsset: ASSETS[1],
vault: mockedVault,
borrowings: [],
deposits: [],
onChangeBorrowings: jest.fn(),
depositActions: [],
depositCapReachedCoins: [],
displayCurrency: 'uosmo',
}
beforeAll(() => {
useStore.setState({
baseCurrency: ASSETS[0],
})
})
afterAll(() => {
useStore.clearState()
mockedDisplayCurrency.mockClear()
})
it('should render', () => {
const { container } = render(<VaultBorrowings {...defaultProps} />)
expect(container).toBeInTheDocument()
})
it('should render DisplayCurrency correctly', () => {
expect(mockedDisplayCurrency).toHaveBeenCalledTimes(1)
expect(mockedDisplayCurrency).toHaveBeenCalledWith(
{ coin: new BNCoin({ denom: 'usd', amount: '0' }) },
expect.anything(),
)
})
})

View File

@ -1,6 +1,5 @@
import { render } from '@testing-library/react' import { render } from '@testing-library/react'
import { TooltipType } from 'components/Tooltip'
import TooltipContent from 'components/Tooltip/TooltipContent' import TooltipContent from 'components/Tooltip/TooltipContent'
describe('<Tooltip />', () => { describe('<Tooltip />', () => {

View File

@ -1,6 +1,6 @@
{ {
"name": "mars-v2-frontend", "name": "mars-v2-frontend",
"version": "2.0.0", "version": "2.0.1",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "yarn validate-env && next build", "build": "yarn validate-env && next build",
@ -12,15 +12,22 @@
"prettier-check": "prettier --ignore-path .gitignore --check ./src/", "prettier-check": "prettier --ignore-path .gitignore --check ./src/",
"start": "next start", "start": "next start",
"validate-env": "node ./validate-env", "validate-env": "node ./validate-env",
"install-charting-library": "dotenv -e .env.local node install_charting_library.js" "install-charting-library": "dotenv -e .env.local node install_charting_library.js",
"prepare": "husky install"
},
"lint-staged": {
"*.ts*": [
"eslint ./src/ ./__tests__/ --fix",
"prettier --write ./src/ ./__tests__/"
]
}, },
"dependencies": { "dependencies": {
"@cosmjs/cosmwasm-stargate": "^0.31.1", "@cosmjs/cosmwasm-stargate": "^0.31.1",
"@delphi-labs/shuttle-react": "^3.9.0", "@delphi-labs/shuttle-react": "^3.9.1",
"@keplr-wallet/cosmos": "^0.12.32", "@keplr-wallet/cosmos": "^0.12.35",
"@sentry/nextjs": "^7.73.0", "@sentry/nextjs": "^7.74.0",
"@splinetool/react-spline": "^2.2.6", "@splinetool/react-spline": "^2.2.6",
"@splinetool/runtime": "^0.9.477", "@splinetool/runtime": "^0.9.482",
"@tanstack/react-table": "^8.10.6", "@tanstack/react-table": "^8.10.6",
"@tippyjs/react": "^4.2.6", "@tippyjs/react": "^4.2.6",
"bignumber.js": "^9.1.2", "bignumber.js": "^9.1.2",
@ -36,7 +43,7 @@
"react-draggable": "^4.4.6", "react-draggable": "^4.4.6",
"react-helmet-async": "^1.3.0", "react-helmet-async": "^1.3.0",
"react-qr-code": "^2.0.12", "react-qr-code": "^2.0.12",
"react-router-dom": "^6.16.0", "react-router-dom": "^6.17.0",
"react-spring": "^9.7.3", "react-spring": "^9.7.3",
"react-toastify": "^9.1.3", "react-toastify": "^9.1.3",
"react-use-clipboard": "^1.0.9", "react-use-clipboard": "^1.0.9",
@ -52,7 +59,7 @@
"@types/debounce-promise": "^3.1.7", "@types/debounce-promise": "^3.1.7",
"@types/lodash.debounce": "^4.0.7", "@types/lodash.debounce": "^4.0.7",
"@types/lodash.throttle": "^4.1.7", "@types/lodash.throttle": "^4.1.7",
"@types/node": "^20.7.0", "@types/node": "^20.8.6",
"@types/react": "18.2.28", "@types/react": "18.2.28",
"@types/react-dom": "18.2.13", "@types/react-dom": "18.2.13",
"@types/react-helmet": "^6.1.7", "@types/react-helmet": "^6.1.7",
@ -63,11 +70,13 @@
"eslint": "^8.51.0", "eslint": "^8.51.0",
"eslint-config-next": "^13.5.4", "eslint-config-next": "^13.5.4",
"eslint-plugin-import": "^2.28.1", "eslint-plugin-import": "^2.28.1",
"husky": "^8.0.3",
"identity-obj-proxy": "^3.0.0", "identity-obj-proxy": "^3.0.0",
"jest": "^29.7.0", "jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0", "jest-environment-jsdom": "^29.7.0",
"lint-staged": "^15.0.1",
"prettier": "^3.0.3", "prettier": "^3.0.3",
"prettier-plugin-tailwindcss": "^0.5.5", "prettier-plugin-tailwindcss": "^0.5.6",
"shelljs": "^0.8.5", "shelljs": "^0.8.5",
"tailwindcss": "^3.3.3", "tailwindcss": "^3.3.3",
"typescript": "5.2.2" "typescript": "5.2.2"

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -1,25 +0,0 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 120 120" style="enable-background:new 0 0 120 120;" xml:space="preserve">
<path fill="#2775CA" d="M60,120c33.2,0,60-26.8,60-60S93.2,0,60,0S0,26.8,0,60S26.8,120,60,120z"/>
<path fill="#FFFFFF" d="M76.5,69.5c0-8.8-5.3-11.8-15.7-13c-7.5-1-9-3-9-6.5c0-3.5,2.5-5.8,7.5-5.8c4.5,0,7,1.5,8.2,5.3
c0.2,0.8,1,1.3,1.7,1.3h4c1,0,1.7-0.8,1.7-1.7v-0.2c-1-5.5-5.5-9.7-11.2-10.3v-6c0-1-0.8-1.7-2-2H58c-1,0-1.7,0.7-2,2v5.8
c-7.5,1-12.2,6-12.2,12.2c0,8.2,5,11.5,15.5,12.7c7,1.3,9.2,2.8,9.2,6.7c0,4-3.5,6.7-8.2,6.7c-6.5,0-8.8-2.8-9.5-6.5
c-0.2-1-1-1.5-1.7-1.5h-4.3c-1,0-1.7,0.8-1.7,1.7v0.2c1,6.2,5,10.7,13.3,12v6c0,1,0.8,1.7,2,2H62c1,0,1.7-0.8,2-2v-6
C71.5,81.5,76.5,76.2,76.5,69.5L76.5,69.5z"/>
<path fill="#FFFFFF" d="M47.3,95.8c-19.5-7-29.5-28.7-22.3-48c3.8-10.5,12-18.5,22.3-22.3c1-0.5,1.5-1.3,1.5-2.5v-3.5
c0-1-0.5-1.7-1.5-2c-0.2,0-0.8,0-1,0.2C22.5,25.3,9.5,50.5,17,74.3c4.5,14,15.2,24.7,29.2,29.2c1,0.5,2,0,2.3-1
c0.2-0.2,0.2-0.5,0.2-1V98C48.7,97.2,48,96.2,47.3,95.8L47.3,95.8z M73.8,17.8c-1-0.5-2,0-2.3,1c-0.2,0.2-0.2,0.5-0.2,1v3.5
c0,1,0.8,2,1.5,2.5c19.5,7,29.5,28.7,22.3,48C91.2,84.3,83,92.3,72.7,96c-1,0.5-1.5,1.3-1.5,2.5v3.5c0,1,0.5,1.7,1.5,2
c0.2,0,0.8,0,1-0.2C97.5,96.3,110.5,71,103,47.3C98.5,33,87.5,22.3,73.8,17.8L73.8,17.8z"/>
<path d="M100,120c11,0,20-9,20-20s-9-20-20-20s-20,9-20,20S89,120,100,120z"/>
<path fill="#FFFFFF" d="M109.3,87c-2,0-3.5,1.6-3.5,3.6c0,0.5,0,1.1,0,1.6c-0.3-0.2-0.5-0.5-0.6-0.7
c-2.3-3.5-6.5-5.2-10.5-4.2c-4.3,1.1-7.4,4.5-7.6,8.8c-0.2,4.5-0.1,9.1,0,13.6c0,1.9,1.8,3.4,3.7,3.3c1.9-0.1,3.4-1.6,3.5-3.5
c0-0.6,0-1.1,0-1.7c0,0,0.1,0,0.1,0c0.2,0.3,0.3,0.5,0.5,0.8c2.3,3.6,6.8,5.2,10.9,4.1c4.1-1.1,7.1-4.9,7.2-9c0.1-4.3,0-8.7,0-13
C113,88.6,111.3,87,109.3,87z M93.4,95.9c-0.1,0.4-0.1,0.9-0.1,1.3c0,4,0,7.9,0,11.9c0,1.6-1,2.8-2.5,2.8c-1.6,0.1-2.7-1-2.7-2.7
c0-2.1,0-4.2,0-6.3c0,0,0,0,0.1,0c0-2.3-0.1-4.7,0-7c0.2-4,3.9-7.5,7.9-7.9c5.4-0.5,9.9,3.7,9.8,9.2c-0.1,2,0,3.9,0,5.9
c0,1.3-1,2.4-2.3,2.6c-1.2,0.2-2.4-0.6-2.8-1.8c-0.1-0.3-0.1-0.6-0.1-1c0-2,0-4.1,0-6.1c0-1.8-1.4-3.3-3-3.5
C95.6,93.1,93.9,94.1,93.4,95.9z M111.9,104c-0.1,4-3.5,7.4-7.5,7.9c-4.7,0.6-8.9-2.3-9.9-6.9c-0.2-1.1-0.2-2.2-0.2-3.3
c0-1.6,0-3.1,0-4.7c0-1.6,1.1-2.8,2.6-2.8c1.5,0,2.6,1.2,2.6,2.8c0,2,0,4,0,6.1c0,1.7,1,3,2.7,3.5c1.5,0.4,3.2-0.2,4-1.6
c0.3-0.6,0.5-1.3,0.5-1.9c0.1-2,0-4,0-6c0-2.1,0-4.2,0-6.3c0-1.1,0.5-2,1.5-2.5c1-0.4,1.9-0.3,2.7,0.3c0.7,0.5,1,1.3,1,2.1
C112,95.1,112.1,99.5,111.9,104z"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,28 @@
<svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 120 120"
style="enable-background: new 0 0 120 120"
xml:space="preserve"
>
<path fill="#2775CA" d="M60,120c33.2,0,60-26.8,60-60S93.2,0,60,0S0,26.8,0,60S26.8,120,60,120z" />
<path
fill="#FFFFFF"
d="M76.5,69.5c0-8.8-5.3-11.8-15.7-13c-7.5-1-9-3-9-6.5c0-3.5,2.5-5.8,7.5-5.8c4.5,0,7,1.5,8.2,5.3
c0.2,0.8,1,1.3,1.7,1.3h4c1,0,1.7-0.8,1.7-1.7v-0.2c-1-5.5-5.5-9.7-11.2-10.3v-6c0-1-0.8-1.7-2-2H58c-1,0-1.7,0.7-2,2v5.8
c-7.5,1-12.2,6-12.2,12.2c0,8.2,5,11.5,15.5,12.7c7,1.3,9.2,2.8,9.2,6.7c0,4-3.5,6.7-8.2,6.7c-6.5,0-8.8-2.8-9.5-6.5
c-0.2-1-1-1.5-1.7-1.5h-4.3c-1,0-1.7,0.8-1.7,1.7v0.2c1,6.2,5,10.7,13.3,12v6c0,1,0.8,1.7,2,2H62c1,0,1.7-0.8,2-2v-6
C71.5,81.5,76.5,76.2,76.5,69.5L76.5,69.5z"
/>
<path
fill="#FFFFFF"
d="M47.3,95.8c-19.5-7-29.5-28.7-22.3-48c3.8-10.5,12-18.5,22.3-22.3c1-0.5,1.5-1.3,1.5-2.5v-3.5
c0-1-0.5-1.7-1.5-2c-0.2,0-0.8,0-1,0.2C22.5,25.3,9.5,50.5,17,74.3c4.5,14,15.2,24.7,29.2,29.2c1,0.5,2,0,2.3-1
c0.2-0.2,0.2-0.5,0.2-1V98C48.7,97.2,48,96.2,47.3,95.8L47.3,95.8z M73.8,17.8c-1-0.5-2,0-2.3,1c-0.2,0.2-0.2,0.5-0.2,1v3.5
c0,1,0.8,2,1.5,2.5c19.5,7,29.5,28.7,22.3,48C91.2,84.3,83,92.3,72.7,96c-1,0.5-1.5,1.3-1.5,2.5v3.5c0,1,0.5,1.7,1.5,2
c0.2,0,0.8,0,1-0.2C97.5,96.3,110.5,71,103,47.3C98.5,33,87.5,22.3,73.8,17.8L73.8,17.8z"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,28 @@
import { getParamsQueryClient } from 'api/cosmwasm-client'
import getAssetParams from 'api/params/getAssetParams'
import { BN } from 'utils/helpers'
import { resolveHLSStrategies } from 'utils/resolvers'
export default async function getHLSStakingAssets() {
const assetParams = await getAssetParams()
const client = await getParamsQueryClient()
const HLSAssets = assetParams.filter((asset) => asset.credit_manager.hls)
const strategies = resolveHLSStrategies('coin', HLSAssets)
const depositCaps$ = strategies.map((strategy) =>
client.totalDeposit({ denom: strategy.denoms.deposit }),
)
return Promise.all(depositCaps$).then((depositCaps) => {
return depositCaps.map((depositCap, index) => {
return {
...strategies[index],
depositCap: {
denom: depositCap.denom,
used: BN(depositCap.amount),
max: BN(depositCap.cap),
},
} as HLSStrategy
})
})
}

View File

@ -1,3 +1,4 @@
import getAssetParams from 'api/params/getAssetParams'
import getAprs from 'api/vaults/getVaultAprs' import getAprs from 'api/vaults/getVaultAprs'
import { getVaultConfigs } from 'api/vaults/getVaultConfigs' import { getVaultConfigs } from 'api/vaults/getVaultConfigs'
import { getVaultUtilizations } from 'api/vaults/getVaultUtilizations' import { getVaultUtilizations } from 'api/vaults/getVaultUtilizations'
@ -6,13 +7,17 @@ import { TESTNET_VAULTS_META_DATA, VAULTS_META_DATA } from 'constants/vaults'
import { NETWORK } from 'types/enums/network' import { NETWORK } from 'types/enums/network'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
import { convertAprToApy } from 'utils/parsers' import { convertAprToApy } from 'utils/parsers'
import { resolveHLSStrategies } from 'utils/resolvers'
export default async function getVaults(): Promise<Vault[]> { export default async function getVaults(): Promise<Vault[]> {
const assetParams = await getAssetParams()
const vaultConfigs = await getVaultConfigs() const vaultConfigs = await getVaultConfigs()
const $vaultUtilizations = getVaultUtilizations(vaultConfigs) const $vaultUtilizations = getVaultUtilizations(vaultConfigs)
const $aprs = getAprs() const $aprs = getAprs()
const vaultMetaDatas = const vaultMetaDatas =
ENV.NETWORK === NETWORK.TESTNET ? TESTNET_VAULTS_META_DATA : VAULTS_META_DATA ENV.NETWORK === NETWORK.TESTNET ? TESTNET_VAULTS_META_DATA : VAULTS_META_DATA
const HLSAssets = assetParams.filter((asset) => asset.credit_manager.hls)
const hlsStrategies = resolveHLSStrategies('vault', HLSAssets)
const vaults: Vault[] = [] const vaults: Vault[] = []
await Promise.all([$vaultUtilizations, $aprs]).then(([vaultUtilizations, aprs]) => { await Promise.all([$vaultUtilizations, $aprs]).then(([vaultUtilizations, aprs]) => {
@ -43,6 +48,17 @@ export default async function getVaults(): Promise<Vault[]> {
}, },
} }
const hlsStrategy = hlsStrategies.find(
(strategy) => strategy.denoms.deposit === vaultConfig.addr,
)
if (hlsStrategy) {
vault.hls = {
maxLTV: hlsStrategy.maxLTV,
maxLeverage: hlsStrategy.maxLeverage,
borrowDenom: hlsStrategy.denoms.borrow,
}
}
vaults.push(vault) vaults.push(vault)
}) })
}) })

View File

@ -20,14 +20,13 @@ export default function AccordionContent(props: Props) {
const { title, renderContent, isOpen, renderSubTitle, toggleOpen } = props.item const { title, renderContent, isOpen, renderSubTitle, toggleOpen } = props.item
return ( return (
<div key={title} className='group border-b-white/10 [&:not(:last-child)]:border-b'> <div key={title} className='border-b border-collapse group border-white/20 last:border-b-0'>
<div <div
onClick={() => toggleOpen(props.index)} onClick={() => toggleOpen(props.index)}
className={classNames( className={classNames(
'mb-0 flex hover:cursor-pointer items-center justify-between border-t border-white/10 bg-white/10 p-4 text-white', 'mb-0 flex hover:cursor-pointer items-center justify-between bg-white/10 p-4 text-white border-b border-transparent',
'group-[&:first-child]:border-t-0 group-[[open]]:border-b',
'[&::marker]:hidden [&::marker]:content-[""]', '[&::marker]:hidden [&::marker]:content-[""]',
isOpen && 'border-b [&:first-child]:border-t-0', isOpen && 'border-white/20',
)} )}
> >
<div> <div>

View File

@ -45,7 +45,6 @@ export default function Index(props: Props) {
const navigate = useNavigate() const navigate = useNavigate()
const { pathname } = useLocation() const { pathname } = useLocation()
const address = useStore((s) => s.address) const address = useStore((s) => s.address)
const baseCurrency = useStore((s) => s.baseCurrency)
const [sorting, setSorting] = useState<SortingState>([]) const [sorting, setSorting] = useState<SortingState>([])
const updatedAccount = useStore((s) => s.updatedAccount) const updatedAccount = useStore((s) => s.updatedAccount)
const accountBalanceData = useAccountBalanceData({ const accountBalanceData = useAccountBalanceData({
@ -65,6 +64,7 @@ export default function Index(props: Props) {
return ( return (
<Text size='xs'> <Text size='xs'>
{row.original.symbol} {row.original.symbol}
{row.original.type === 'borrowing' && <span className='ml-1 text-loss'>(debt)</span>}
{row.original.type === 'lending' && <span className='ml-1 text-profit'>(lent)</span>} {row.original.type === 'lending' && <span className='ml-1 text-profit'>(lent)</span>}
{row.original.type === 'vault' && <span className='ml-1 text-profit'>(farm)</span>} {row.original.type === 'vault' && <span className='ml-1 text-profit'>(farm)</span>}
</Text> </Text>
@ -186,7 +186,7 @@ export default function Index(props: Props) {
return ( return (
<table className='w-full'> <table className='w-full'>
<thead className='border-b border-white/5'> <thead className='border-b border-white/10'>
{table.getHeaderGroups().map((headerGroup) => ( {table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}> <tr key={headerGroup.id}>
{headerGroup.headers.map((header) => { {headerGroup.headers.map((header) => {

View File

@ -37,14 +37,10 @@ export default function AccountComposition(props: Props) {
const { account } = props const { account } = props
const hasChanged = !!updatedAccount const hasChanged = !!updatedAccount
const { data: prices } = usePrices() const { data: prices } = usePrices()
const { availableAssets: borrowAvailableAssets, accountBorrowedAssets } = const { data } = useBorrowMarketAssetsTableData(false)
useBorrowMarketAssetsTableData() const borrowAssetsData = useMemo(() => data?.allAssets || [], [data])
const { availableAssets: lendingAvailableAssets, accountLentAssets } = const { availableAssets: lendingAvailableAssets, accountLentAssets } =
useLendingMarketAssetsTableData() useLendingMarketAssetsTableData()
const borrowAssetsData = useMemo(
() => [...borrowAvailableAssets, ...accountBorrowedAssets],
[borrowAvailableAssets, accountBorrowedAssets],
)
const lendingAssetsData = useMemo( const lendingAssetsData = useMemo(
() => [...lendingAvailableAssets, ...accountLentAssets], () => [...lendingAvailableAssets, ...accountLentAssets],
[lendingAvailableAssets, accountLentAssets], [lendingAvailableAssets, accountLentAssets],

View File

@ -14,7 +14,7 @@ import { HealthGauge } from 'components/HealthGauge'
import { ThreeDots } from 'components/Icons' import { ThreeDots } from 'components/Icons'
import Text from 'components/Text' import Text from 'components/Text'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { REDUCE_MOTION_KEY } from 'constants/localStore' import { LocalStorageKeys } from 'constants/localStorageKeys'
import { ORACLE_DENOM } from 'constants/oracle' import { ORACLE_DENOM } from 'constants/oracle'
import useAccountId from 'hooks/useAccountId' import useAccountId from 'hooks/useAccountId'
import useAccountIds from 'hooks/useAccountIds' import useAccountIds from 'hooks/useAccountIds'
@ -56,7 +56,10 @@ interface Props {
function AccountDetails(props: Props) { function AccountDetails(props: Props) {
const { account } = props const { account } = props
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion) const [reduceMotion] = useLocalStorage<boolean>(
LocalStorageKeys.REDUCE_MOTION,
DEFAULT_SETTINGS.reduceMotion,
)
const updatedAccount = useStore((s) => s.updatedAccount) const updatedAccount = useStore((s) => s.updatedAccount)
const accountDetailsExpanded = useStore((s) => s.accountDetailsExpanded) const accountDetailsExpanded = useStore((s) => s.accountDetailsExpanded)
const { health } = useHealthComputer(account) const { health } = useHealthComputer(account)
@ -76,14 +79,12 @@ function AccountDetails(props: Props) {
return updatedLeverage return updatedLeverage
}, [updatedAccount, prices, leverage]) }, [updatedAccount, prices, leverage])
const { availableAssets: borrowAvailableAssets, accountBorrowedAssets } = const { data } = useBorrowMarketAssetsTableData(false)
useBorrowMarketAssetsTableData() const borrowAssetsData = useMemo(() => data?.allAssets || [], [data])
const { availableAssets: lendingAvailableAssets, accountLentAssets } = const { availableAssets: lendingAvailableAssets, accountLentAssets } =
useLendingMarketAssetsTableData() useLendingMarketAssetsTableData()
const borrowAssetsData = useMemo(
() => [...borrowAvailableAssets, ...accountBorrowedAssets],
[borrowAvailableAssets, accountBorrowedAssets],
)
const lendingAssetsData = useMemo( const lendingAssetsData = useMemo(
() => [...lendingAvailableAssets, ...accountLentAssets], () => [...lendingAvailableAssets, ...accountLentAssets],
[lendingAvailableAssets, accountLentAssets], [lendingAvailableAssets, accountLentAssets],

View File

@ -9,7 +9,7 @@ import Text from 'components/Text'
import TokenInputWithSlider from 'components/TokenInput/TokenInputWithSlider' import TokenInputWithSlider from 'components/TokenInput/TokenInputWithSlider'
import WalletBridges from 'components/Wallet/WalletBridges' import WalletBridges from 'components/Wallet/WalletBridges'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { LEND_ASSETS_KEY } from 'constants/localStore' import { LocalStorageKeys } from 'constants/localStorageKeys'
import { BN_ZERO } from 'constants/math' import { BN_ZERO } from 'constants/math'
import useAutoLend from 'hooks/useAutoLend' import useAutoLend from 'hooks/useAutoLend'
import useLocalStorage from 'hooks/useLocalStorage' import useLocalStorage from 'hooks/useLocalStorage'
@ -38,7 +38,7 @@ export default function AccountFundContent(props: Props) {
const walletAssetModal = useStore((s) => s.walletAssetsModal) const walletAssetModal = useStore((s) => s.walletAssetsModal)
const [isConfirming, setIsConfirming] = useState(false) const [isConfirming, setIsConfirming] = useState(false)
const [lendAssets, setLendAssets] = useLocalStorage<boolean>( const [lendAssets, setLendAssets] = useLocalStorage<boolean>(
LEND_ASSETS_KEY, LocalStorageKeys.LEND_ASSETS,
DEFAULT_SETTINGS.lendAssets, DEFAULT_SETTINGS.lendAssets,
) )
const [fundingAssets, setFundingAssets] = useState<BNCoin[]>([]) const [fundingAssets, setFundingAssets] = useState<BNCoin[]>([])

View File

@ -28,14 +28,10 @@ export default function AccountStats(props: Props) {
[account, prices], [account, prices],
) )
const { health } = useHealthComputer(account) const { health } = useHealthComputer(account)
const { availableAssets: borrowAvailableAssets, accountBorrowedAssets } = const { data } = useBorrowMarketAssetsTableData(false)
useBorrowMarketAssetsTableData() const borrowAssetsData = useMemo(() => data?.allAssets || [], [data])
const { availableAssets: lendingAvailableAssets, accountLentAssets } = const { availableAssets: lendingAvailableAssets, accountLentAssets } =
useLendingMarketAssetsTableData() useLendingMarketAssetsTableData()
const borrowAssetsData = useMemo(
() => [...borrowAvailableAssets, ...accountBorrowedAssets],
[borrowAvailableAssets, accountBorrowedAssets],
)
const lendingAssetsData = useMemo( const lendingAssetsData = useMemo(
() => [...lendingAvailableAssets, ...accountLentAssets], () => [...lendingAvailableAssets, ...accountLentAssets],
[lendingAvailableAssets, accountLentAssets], [lendingAvailableAssets, accountLentAssets],

View File

@ -11,7 +11,7 @@ import Overlay from 'components/Overlay'
import Text from 'components/Text' import Text from 'components/Text'
import WalletBridges from 'components/Wallet/WalletBridges' import WalletBridges from 'components/Wallet/WalletBridges'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { LEND_ASSETS_KEY } from 'constants/localStore' import { LocalStorageKeys } from 'constants/localStorageKeys'
import useAccountId from 'hooks/useAccountId' import useAccountId from 'hooks/useAccountId'
import useAccountIds from 'hooks/useAccountIds' import useAccountIds from 'hooks/useAccountIds'
import useAutoLend from 'hooks/useAutoLend' import useAutoLend from 'hooks/useAutoLend'
@ -39,7 +39,10 @@ export default function AccountMenuContent() {
const [showMenu, setShowMenu] = useToggle() const [showMenu, setShowMenu] = useToggle()
const [isCreating, setIsCreating] = useToggle() const [isCreating, setIsCreating] = useToggle()
const transactionFeeCoinBalance = useCurrentWalletBalance(baseCurrency.denom) const transactionFeeCoinBalance = useCurrentWalletBalance(baseCurrency.denom)
const [lendAssets] = useLocalStorage<boolean>(LEND_ASSETS_KEY, DEFAULT_SETTINGS.lendAssets) const [lendAssets] = useLocalStorage<boolean>(
LocalStorageKeys.LEND_ASSETS,
DEFAULT_SETTINGS.lendAssets,
)
const { enableAutoLendAccountId } = useAutoLend() const { enableAutoLendAccountId } = useAutoLend()
const hasCreditAccounts = !!accountIds?.length const hasCreditAccounts = !!accountIds?.length

View File

@ -1,5 +1,5 @@
import classNames from 'classnames' import classNames from 'classnames'
import { HTMLAttributes, useMemo } from 'react' import { HTMLAttributes, useCallback, useMemo } from 'react'
import Accordion from 'components/Accordion' import Accordion from 'components/Accordion'
import AccountBalancesTable from 'components/Account/AccountBalancesTable' import AccountBalancesTable from 'components/Account/AccountBalancesTable'
@ -10,12 +10,14 @@ import DisplayCurrency from 'components/DisplayCurrency'
import { FormattedNumber } from 'components/FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
import { ArrowRight } from 'components/Icons' import { ArrowRight } from 'components/Icons'
import Text from 'components/Text' import Text from 'components/Text'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { LocalStorageKeys } from 'constants/localStorageKeys'
import { BN_ZERO } from 'constants/math' import { BN_ZERO } from 'constants/math'
import { ORACLE_DENOM } from 'constants/oracle' import { ORACLE_DENOM } from 'constants/oracle'
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData' import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
import useHealthComputer from 'hooks/useHealthComputer' import useHealthComputer from 'hooks/useHealthComputer'
import useIsOpenArray from 'hooks/useIsOpenArray'
import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData' import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData'
import useLocalStorage from 'hooks/useLocalStorage'
import usePrices from 'hooks/usePrices' import usePrices from 'hooks/usePrices'
import useStore from 'store' import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
@ -26,7 +28,10 @@ interface Props {
} }
export default function AccountSummary(props: Props) { export default function AccountSummary(props: Props) {
const [isOpen, toggleOpen] = useIsOpenArray(2, true) const [accountSummaryTabs, setAccountSummaryTabs] = useLocalStorage<boolean[]>(
LocalStorageKeys.ACCOUNT_SUMMARY_TABS,
DEFAULT_SETTINGS.accountSummaryTabs,
)
const { data: prices } = usePrices() const { data: prices } = usePrices()
const updatedAccount = useStore((s) => s.updatedAccount) const updatedAccount = useStore((s) => s.updatedAccount)
const accountBalance = useMemo( const accountBalance = useMemo(
@ -36,15 +41,11 @@ export default function AccountSummary(props: Props) {
: BN_ZERO, : BN_ZERO,
[props.account, updatedAccount, prices], [props.account, updatedAccount, prices],
) )
const { availableAssets: borrowAvailableAssets, accountBorrowedAssets } = const { data } = useBorrowMarketAssetsTableData(false)
useBorrowMarketAssetsTableData() const borrowAssetsData = useMemo(() => data?.allAssets || [], [data])
const { availableAssets: lendingAvailableAssets, accountLentAssets } = const { availableAssets: lendingAvailableAssets, accountLentAssets } =
useLendingMarketAssetsTableData() useLendingMarketAssetsTableData()
const borrowAssetsData = useMemo(
() => [...borrowAvailableAssets, ...accountBorrowedAssets],
[borrowAvailableAssets, accountBorrowedAssets],
)
const lendingAssetsData = useMemo( const lendingAssetsData = useMemo(
() => [...lendingAvailableAssets, ...accountLentAssets], () => [...lendingAvailableAssets, ...accountLentAssets],
[lendingAvailableAssets, accountLentAssets], [lendingAvailableAssets, accountLentAssets],
@ -63,6 +64,13 @@ export default function AccountSummary(props: Props) {
return updatedLeverage return updatedLeverage
}, [updatedAccount, prices, leverage]) }, [updatedAccount, prices, leverage])
const handleToggle = useCallback(
(index: number) => {
setAccountSummaryTabs(accountSummaryTabs.map((tab, i) => (i === index ? !tab : tab)))
},
[accountSummaryTabs, setAccountSummaryTabs],
)
if (!props.account) return null if (!props.account) return null
return ( return (
<div className='h-[546px] min-w-92.5 basis-92.5 max-w-screen overflow-y-scroll scrollbar-hide'> <div className='h-[546px] min-w-92.5 basis-92.5 max-w-screen overflow-y-scroll scrollbar-hide'>
@ -110,8 +118,8 @@ export default function AccountSummary(props: Props) {
title: `Credit Account ${props.account.id} Composition`, title: `Credit Account ${props.account.id} Composition`,
renderContent: () => renderContent: () =>
props.account ? <AccountComposition account={props.account} /> : null, props.account ? <AccountComposition account={props.account} /> : null,
isOpen: isOpen[0], isOpen: accountSummaryTabs[0],
toggleOpen: (index: number) => toggleOpen(index), toggleOpen: (index: number) => handleToggle(index),
renderSubTitle: () => <></>, renderSubTitle: () => <></>,
}, },
{ {
@ -124,8 +132,8 @@ export default function AccountSummary(props: Props) {
lendingData={lendingAssetsData} lendingData={lendingAssetsData}
/> />
) : null, ) : null,
isOpen: isOpen[1], isOpen: accountSummaryTabs[1],
toggleOpen: (index: number) => toggleOpen(index), toggleOpen: (index: number) => handleToggle(index),
renderSubTitle: () => <></>, renderSubTitle: () => <></>,
}, },
]} ]}

View File

@ -3,7 +3,7 @@ import { useMemo } from 'react'
import { Tooltip } from 'components/Tooltip' import { Tooltip } from 'components/Tooltip'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { REDUCE_MOTION_KEY } from 'constants/localStore' import { LocalStorageKeys } from 'constants/localStorageKeys'
import useHealthColorAndLabel from 'hooks/useHealthColorAndLabel' import useHealthColorAndLabel from 'hooks/useHealthColorAndLabel'
import useLocalStorage from 'hooks/useLocalStorage' import useLocalStorage from 'hooks/useLocalStorage'
import { getHealthIndicatorColors } from 'utils/healthIndicator' import { getHealthIndicatorColors } from 'utils/healthIndicator'
@ -30,7 +30,10 @@ function calculateHealth(health: number): number {
export default function HealthBar(props: Props) { export default function HealthBar(props: Props) {
const { health, updatedHealth } = props const { health, updatedHealth } = props
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion) const [reduceMotion] = useLocalStorage<boolean>(
LocalStorageKeys.REDUCE_MOTION,
DEFAULT_SETTINGS.reduceMotion,
)
const width = calculateHealth(health) const width = calculateHealth(health)
const updatedWidth = calculateHealth(updatedHealth ?? 0) const updatedWidth = calculateHealth(updatedHealth ?? 0)
const isUpdated = updatedWidth > 0 && updatedWidth !== width const isUpdated = updatedWidth > 0 && updatedWidth !== width
@ -55,12 +58,53 @@ export default function HealthBar(props: Props) {
<rect x='46' fill='#FFFFFF' width='47' height='4' /> <rect x='46' fill='#FFFFFF' width='47' height='4' />
<path fill='#FFFFFF' d='M95.5,0H182c1.1,0,2,0.9,2,2s-0.9,2-2,2H95.5V0z' /> <path fill='#FFFFFF' d='M95.5,0H182c1.1,0,2,0.9,2,2s-0.9,2-2,2H95.5V0z' />
</mask> </mask>
<mask id='backgroundHealthBarMask'>
<rect x='62.1' fill='white' width='2.4' height='4' />
<rect x='48' fill='white' width='2' height='4' />
<rect x='57.3' fill='white' width='2.4' height='4' />
<rect x='52.5' fill='white' width='2.4' height='4' />
<rect x='66.9' fill='white' width='2.4' height='4' />
<rect x='86.1' fill='white' width='2.4' height='4' />
<rect x='81.3' fill='white' width='2.4' height='4' />
<rect x='71.7' fill='white' width='2.4' height='4' />
<rect x='90.9' fill='white' width='2.1' height='4' />
<rect x='76.5' fill='white' width='2.4' height='4' />
<rect x='119.2' fill='white' width='2.4' height='4' />
<rect x='143.2' fill='white' width='2.4' height='4' />
<rect x='138.4' fill='white' width='2.4' height='4' />
<rect x='133.6' fill='white' width='2.4' height='4' />
<rect x='124' fill='white' width='2.4' height='4' />
<rect x='100' fill='white' width='2.4' height='4' />
<rect x='104.8' fill='white' width='2.4' height='4' />
<rect x='109.6' fill='white' width='2.4' height='4' />
<rect x='114.4' fill='white' width='2.4' height='4' />
<rect x='128.8' fill='white' width='2.4' height='4' />
<rect x='172' fill='white' width='2.4' height='4' />
<rect x='176.8' fill='white' width='2.4' height='4' />
<rect x='95.5' fill='white' width='2.1' height='4' />
<path fill='white' d='M182,0h-0.4v4h0.4c1.1,0,2-0.9,2-2S183.1,0,182,0z' />
<rect x='162.4' fill='white' width='2.4' height='4' />
<rect x='152.8' fill='white' width='2.4' height='4' />
<rect x='157.6' fill='white' width='2.4' height='4' />
<rect x='167.2' fill='white' width='2.4' height='4' />
<rect x='148' fill='white' width='2.4' height='4' />
<rect x='17.2' fill='white' width='2.4' height='4' />
<rect x='12.4' fill='white' width='2.4' height='4' />
<rect x='3.1' fill='white' width='2.1' height='4' />
<rect x='7.6' fill='white' width='2.4' height='4' />
<rect x='22' fill='white' width='2.4' height='4' />
<rect x='41.2' fill='white' width='2.4' height='4' />
<rect x='36.4' fill='white' width='2.4' height='4' />
<rect x='26.8' fill='white' width='2.4' height='4' />
<path fill='white' d='M0.7,0.5C0.3,0.9,0,1.4,0,2s0.3,1.1,0.7,1.5V0.5z' />
<rect x='31.6' fill='white' width='2.4' height='4' />
</mask>
<rect className='fill-white/10' width='184' height='4' mask='url(#healthBarMask)' /> <rect className='fill-white/10' width='184' height='4' mask='url(#healthBarMask)' />
<rect <rect
className={classNames(backgroundColor, !reduceMotion && 'transition-all duration-500')} className={classNames(backgroundColor, !reduceMotion && 'transition-all duration-500')}
width={isUpdated && isIncrease ? updatedWidth : width} width={isUpdated && isIncrease ? updatedWidth : width}
height='4' height='4'
mask='url(#healthBarMask)' mask={isUpdated ? 'url(#backgroundHealthBarMask)' : 'url(#healthBarMask)'}
/> />
{isUpdated && ( {isUpdated && (
<rect <rect

View File

@ -12,13 +12,16 @@ import {
import { FormattedNumber } from 'components/FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
import Text from 'components/Text' import Text from 'components/Text'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { REDUCE_MOTION_KEY } from 'constants/localStore' import { LocalStorageKeys } from 'constants/localStorageKeys'
import { BN_ZERO } from 'constants/math'
import useLocalStorage from 'hooks/useLocalStorage' import useLocalStorage from 'hooks/useLocalStorage'
import { formatValue } from 'utils/formatters' import { formatValue } from 'utils/formatters'
import { BN_ZERO } from 'constants/math'
export const RiskChart = ({ data }: RiskChartProps) => { export const RiskChart = ({ data }: RiskChartProps) => {
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion) const [reduceMotion] = useLocalStorage<boolean>(
LocalStorageKeys.REDUCE_MOTION,
DEFAULT_SETTINGS.reduceMotion,
)
const currentRisk = BN_ZERO const currentRisk = BN_ZERO
return ( return (

View File

@ -11,11 +11,14 @@ interface Props {
export default function AmountAndValue(props: Props) { export default function AmountAndValue(props: Props) {
const amount = demagnify(props.amount.toString(), props.asset) const amount = demagnify(props.amount.toString(), props.asset)
const isZero = amount === 0
const isBelowMinAmount = amount < MIN_AMOUNT
const displayAmount = isBelowMinAmount ? MIN_AMOUNT : amount
return ( return (
<div className='flex flex-col gap-[0.5] text-xs'> <div className='flex flex-col gap-[0.5] text-xs'>
<FormattedNumber <FormattedNumber
amount={amount < MIN_AMOUNT ? MIN_AMOUNT : amount} amount={isZero ? 0 : displayAmount}
smallerThanThreshold={amount < MIN_AMOUNT} smallerThanThreshold={!isZero && isBelowMinAmount}
options={{ abbreviated: true, maxDecimals: MAX_AMOUNT_DECIMALS }} options={{ abbreviated: true, maxDecimals: MAX_AMOUNT_DECIMALS }}
animate animate
/> />

View File

@ -1,11 +1,14 @@
import classNames from 'classnames' import classNames from 'classnames'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { REDUCE_MOTION_KEY } from 'constants/localStore' import { LocalStorageKeys } from 'constants/localStorageKeys'
import useLocalStorage from 'hooks/useLocalStorage' import useLocalStorage from 'hooks/useLocalStorage'
export default function Background() { export default function Background() {
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion) const [reduceMotion] = useLocalStorage<boolean>(
LocalStorageKeys.REDUCE_MOTION,
DEFAULT_SETTINGS.reduceMotion,
)
return ( return (
<div className='background pointer-events-none fixed inset-0 h-full w-full overflow-hidden bg-body'> <div className='background pointer-events-none fixed inset-0 h-full w-full overflow-hidden bg-body'>

View File

@ -1,129 +0,0 @@
import { ColumnDef, Row, Table } from '@tanstack/react-table'
import { useCallback, useMemo } from 'react'
import AmountAndValue from 'components/AmountAndValue'
import AssetImage from 'components/Asset/AssetImage'
import BorrowActionButtons from 'components/Borrow/BorrowActionButtons'
import { FormattedNumber } from 'components/FormattedNumber'
import { ChevronDown, ChevronUp } from 'components/Icons'
import Loading from 'components/Loading'
import AssetListTable from 'components/MarketAssetTable'
import MarketAssetTableRow from 'components/MarketAssetTable/MarketAssetTableRow'
import MarketDetails from 'components/MarketAssetTable/MarketDetails'
import TitleAndSubCell from 'components/TitleAndSubCell'
import { BN_ZERO } from 'constants/math'
import { getEnabledMarketAssets } from 'utils/assets'
interface Props {
title: string
data: BorrowMarketTableData[]
}
export default function BorrowTable(props: Props) {
const { title, data } = props
const shouldShowAccountBorrowed = !!data[0]?.debt
const marketAssets = getEnabledMarketAssets()
const rowRenderer = useCallback(
(row: Row<BorrowMarketTableData>, table: Table<BorrowMarketTableData>) => {
return (
<MarketAssetTableRow
key={`borrow-asset-${row.id}`}
isExpanded={row.getIsExpanded()}
resetExpanded={table.resetExpanded}
rowData={row}
expandedActionButtons={<BorrowActionButtons data={row.original} />}
expandedDetails={<MarketDetails data={row.original} type='borrow' />}
/>
)
},
[],
)
const columns = useMemo<ColumnDef<BorrowMarketTableData>[]>(
() => [
{
accessorKey: 'asset.name',
header: 'Asset',
id: 'symbol',
cell: ({ row }) => {
const asset = row.original.asset
return (
<div className='flex items-center flex-1 gap-3'>
<AssetImage asset={asset} size={32} />
<TitleAndSubCell
title={asset.symbol}
sub={asset.name}
className='text-left min-w-15'
/>
</div>
)
},
},
...(shouldShowAccountBorrowed
? [
{
accessorKey: 'debt',
header: 'Borrowed',
cell: (info: any) => {
const borrowAsset = info.row.original as BorrowMarketTableData
const asset = marketAssets.find((asset) => asset.denom === borrowAsset.asset.denom)
if (!asset) return null
return <AmountAndValue asset={asset} amount={borrowAsset?.debt ?? BN_ZERO} />
},
},
]
: []),
{
accessorKey: 'borrowRate',
header: 'Borrow Rate',
cell: ({ row }) => {
if (row.original.borrowRate === null) {
return <Loading />
}
return (
<FormattedNumber
className='justify-end text-xs'
amount={row.original.borrowRate * 100}
options={{ minDecimals: 2, maxDecimals: 2, suffix: '%' }}
animate
/>
)
},
},
{
accessorKey: 'liquidity',
header: 'Liquidity Available',
cell: ({ row }) => {
const { liquidity, asset: borrowAsset } = row.original
const asset = marketAssets.find((asset) => asset.denom === borrowAsset.denom)
if (!asset) return null
if (liquidity === null) {
return <Loading />
}
return <AmountAndValue asset={asset} amount={liquidity.amount ?? BN_ZERO} />
},
},
{
accessorKey: 'manage',
enableSorting: false,
header: 'Manage',
cell: ({ row }) => (
<div className='flex items-center justify-end'>
<div className='w-4'>{row.getIsExpanded() ? <ChevronUp /> : <ChevronDown />}</div>
</div>
),
},
],
[shouldShowAccountBorrowed, marketAssets],
)
return <AssetListTable title={title} rowRenderer={rowRenderer} columns={columns} data={data} />
}

View File

@ -0,0 +1,37 @@
import React from 'react'
import AvailableBorrowingsTable from 'components/Borrow/Table/AvailableBorrowingsTable'
import DepositedBorrowingsTable from 'components/Borrow/Table/DepositedBorrowingsTable'
import { BN_ZERO } from 'constants/math'
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
import { getBorrowEnabledAssets } from 'utils/assets'
export default function Borrowings() {
const { data } = useBorrowMarketAssetsTableData()
if (!data?.allAssets?.length) {
return <Fallback />
}
return (
<>
<DepositedBorrowingsTable data={data.accountBorrowedAssets} isLoading={false} />
<AvailableBorrowingsTable data={data.availableAssets} isLoading={false} />
</>
)
}
function Fallback() {
const assets = getBorrowEnabledAssets()
const data: BorrowMarketTableData[] = assets.map((asset) => ({
asset,
borrowRate: null,
liquidity: null,
marketMaxLtv: 0,
marketDepositAmount: BN_ZERO,
marketLiquidityRate: 0,
marketLiquidityAmount: BN_ZERO,
marketLiquidationThreshold: 0,
}))
return <AvailableBorrowingsTable data={data} isLoading />
}

View File

@ -0,0 +1,46 @@
import { Row } from '@tanstack/react-table'
import { Table as TanstackTable } from '@tanstack/table-core/build/lib/types'
import { useCallback } from 'react'
import BorrowActionButtons from 'components/Borrow/BorrowActionButtons'
import { NAME_META } from 'components/Borrow/Table/Columns/Name'
import useAvailableColumns from 'components/Borrow/Table/Columns/useAvailableColumns'
import MarketDetails from 'components/MarketDetails'
import Table from 'components/Table'
import ActionButtonRow from 'components/Table/ActionButtonRow'
type Props = {
data: BorrowMarketTableData[]
isLoading: boolean
}
export default function AvailableBorrowingsTable(props: Props) {
const columns = useAvailableColumns()
const renderExpanded = useCallback(
(row: Row<BorrowMarketTableData>, _: TanstackTable<BorrowMarketTableData>) => {
const currentRow = row as Row<BorrowMarketTableData>
return (
<>
<ActionButtonRow row={currentRow}>
<BorrowActionButtons data={row.original} />
</ActionButtonRow>
<MarketDetails row={currentRow} type='borrow' />
</>
)
},
[],
)
if (!props.data.length) return null
return (
<Table
title='Available to Borrow'
columns={columns}
data={props.data}
initialSorting={[{ id: NAME_META.id, desc: false }]}
renderExpanded={renderExpanded}
/>
)
}

View File

@ -0,0 +1,25 @@
import React from 'react'
import { FormattedNumber } from 'components/FormattedNumber'
import Loading from 'components/Loading'
export const BORROW_RATE_META = { accessorKey: 'borrowRate', header: 'Borrow Rate' }
interface Props {
borrowRate: number | null
}
export default function BorrowRate(props: Props) {
if (props.borrowRate === null) {
return <Loading />
}
return (
<FormattedNumber
className='justify-end text-xs'
amount={props.borrowRate * 100}
options={{ minDecimals: 2, maxDecimals: 2, suffix: '%' }}
animate
/>
)
}

View File

@ -0,0 +1,42 @@
import { Row } from '@tanstack/react-table'
import AmountAndValue from 'components/AmountAndValue'
import { BN_ZERO } from 'constants/math'
import { byDenom } from 'utils/array'
import { getEnabledMarketAssets } from 'utils/assets'
export const DEBT_META = {
accessorKey: 'debt',
header: 'Debt',
}
export const debtSortingFn = (
a: Row<BorrowMarketTableData>,
b: Row<BorrowMarketTableData>,
): number => {
const assetA = a.original.asset
const assetB = b.original.asset
if (!a.original.debt || !b.original.debt) return 0
const assetAPrice = (a.original.liquidity?.value ?? BN_ZERO).div(
a.original.liquidity?.amount ?? BN_ZERO,
)
const assetBPrice = (b.original.liquidity?.value ?? BN_ZERO).div(
b.original.liquidity?.amount ?? BN_ZERO,
)
const debtA = a.original.debt.times(assetAPrice).shiftedBy(-assetA.decimals)
const debtB = b.original.debt.times(assetBPrice).shiftedBy(-assetB.decimals)
return debtA.minus(debtB).toNumber()
}
interface Props {
data: BorrowMarketTableData
}
export default function Debt(props: Props) {
const marketAssets = getEnabledMarketAssets()
const asset = marketAssets.find(byDenom(props.data.asset.denom))
if (!asset) return null
return <AmountAndValue asset={asset} amount={props.data?.debt ?? BN_ZERO} />
}

View File

@ -0,0 +1,44 @@
import { Row } from '@tanstack/react-table'
import AmountAndValue from 'components/AmountAndValue'
import Loading from 'components/Loading'
import { BN_ZERO } from 'constants/math'
import { byDenom } from 'utils/array'
import { getEnabledMarketAssets } from 'utils/assets'
import { demagnify } from 'utils/formatters'
export const LIQUIDITY_META = {
accessorKey: 'liquidity',
header: 'Liquidity Available',
id: 'liquidity',
}
export const liquiditySortingFn = (
a: Row<BorrowMarketTableData>,
b: Row<BorrowMarketTableData>,
): number => {
const assetA = a.original.asset
const assetB = b.original.asset
const liquidityA = demagnify(a.original.liquidity?.amount ?? 0, assetA)
const liquidityB = demagnify(b.original.liquidity?.amount ?? 0, assetB)
return liquidityA - liquidityB
}
interface Props {
data: BorrowMarketTableData
}
export default function Liquidity(props: Props) {
const { liquidity, asset: borrowAsset } = props.data
const marketAssets = getEnabledMarketAssets()
const asset = marketAssets.find(byDenom(borrowAsset.denom))
if (!asset) return null
if (liquidity === null) {
return <Loading />
}
return <AmountAndValue asset={asset} amount={liquidity.amount ?? BN_ZERO} />
}

View File

@ -0,0 +1,17 @@
import React from 'react'
import { ChevronDown, ChevronUp } from 'components/Icons'
export const MANAGE_META = { accessorKey: 'manage', enableSorting: false, header: 'Manage' }
interface Props {
isExpanded: boolean
}
export default function Manage(props: Props) {
return (
<div className='flex items-center justify-end'>
<div className='w-4'>{props.isExpanded ? <ChevronUp /> : <ChevronDown />}</div>
</div>
)
}

View File

@ -0,0 +1,18 @@
import AssetImage from 'components/Asset/AssetImage'
import TitleAndSubCell from 'components/TitleAndSubCell'
export const NAME_META = { accessorKey: 'asset.symbol', header: 'Asset', id: 'symbol' }
interface Props {
data: BorrowMarketTableData
}
export default function Name(props: Props) {
const { asset } = props.data
return (
<div className='flex items-center flex-1 gap-3'>
<AssetImage asset={asset} size={32} />
<TitleAndSubCell title={asset.symbol} sub={asset.name} className='text-left min-w-15' />
</div>
)
}

View File

@ -0,0 +1,34 @@
import { ColumnDef } from '@tanstack/react-table'
import { useMemo } from 'react'
import BorrowRate, { BORROW_RATE_META } from 'components/Borrow/Table/Columns/BorrowRate'
import Liquidity, {
LIQUIDITY_META,
liquiditySortingFn,
} from 'components/Borrow/Table/Columns/Liquidity'
import Manage, { MANAGE_META } from 'components/Borrow/Table/Columns/Manage'
import Name, { NAME_META } from 'components/Borrow/Table/Columns/Name'
export default function useAvailableColumns() {
return useMemo<ColumnDef<BorrowMarketTableData>[]>(() => {
return [
{
...NAME_META,
cell: ({ row }) => <Name data={row.original} />,
},
{
...BORROW_RATE_META,
cell: ({ row }) => <BorrowRate borrowRate={row.original.borrowRate} />,
},
{
...LIQUIDITY_META,
cell: ({ row }) => <Liquidity data={row.original} />,
sortingFn: liquiditySortingFn,
},
{
...MANAGE_META,
cell: ({ row }) => <Manage isExpanded={row.getIsExpanded()} />,
},
]
}, [])
}

View File

@ -0,0 +1,40 @@
import { ColumnDef } from '@tanstack/react-table'
import { useMemo } from 'react'
import BorrowRate, { BORROW_RATE_META } from 'components/Borrow/Table/Columns/BorrowRate'
import Debt, { DEBT_META, debtSortingFn } from 'components/Borrow/Table/Columns/Debt'
import Liquidity, {
LIQUIDITY_META,
liquiditySortingFn,
} from 'components/Borrow/Table/Columns/Liquidity'
import Manage, { MANAGE_META } from 'components/Borrow/Table/Columns/Manage'
import Name, { NAME_META } from 'components/Borrow/Table/Columns/Name'
export default function useDepositedColumns() {
return useMemo<ColumnDef<BorrowMarketTableData>[]>(() => {
return [
{
...NAME_META,
cell: ({ row }) => <Name data={row.original} />,
},
{
...DEBT_META,
cell: ({ row }) => <Debt data={row.original} />,
sortingFn: debtSortingFn,
},
{
...BORROW_RATE_META,
cell: ({ row }) => <BorrowRate borrowRate={row.original.borrowRate} />,
},
{
...LIQUIDITY_META,
cell: ({ row }) => <Liquidity data={row.original} />,
sortingFn: liquiditySortingFn,
},
{
...MANAGE_META,
cell: ({ row }) => <Manage isExpanded={row.getIsExpanded()} />,
},
]
}, [])
}

View File

@ -0,0 +1,42 @@
import { Row } from '@tanstack/react-table'
import { useCallback } from 'react'
import BorrowActionButtons from 'components/Borrow/BorrowActionButtons'
import { NAME_META } from 'components/Borrow/Table/Columns/Name'
import useDepositedColumns from 'components/Borrow/Table/Columns/useDepositedColumns'
import MarketDetails from 'components/MarketDetails'
import Table from 'components/Table'
import ActionButtonRow from 'components/Table/ActionButtonRow'
type Props = {
data: BorrowMarketTableData[]
isLoading: boolean
}
export default function DepositedBorrowingsTable(props: Props) {
const columns = useDepositedColumns()
const renderExpanded = useCallback((row: Row<BorrowMarketTableData>) => {
const currentRow = row as Row<BorrowMarketTableData>
return (
<>
<ActionButtonRow row={currentRow}>
<BorrowActionButtons data={row.original} />
</ActionButtonRow>
<MarketDetails row={row} type='borrow' />
</>
)
}, [])
if (!props.data.length) return null
return (
<Table
title='Borrowed Assets'
columns={columns}
data={props.data}
initialSorting={[{ id: NAME_META.id, desc: false }]}
renderExpanded={renderExpanded}
/>
)
}

View File

@ -5,7 +5,7 @@ import { FormattedNumber } from 'components/FormattedNumber'
import Text from 'components/Text' import Text from 'components/Text'
import { Tooltip } from 'components/Tooltip' import { Tooltip } from 'components/Tooltip'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { REDUCE_MOTION_KEY } from 'constants/localStore' import { LocalStorageKeys } from 'constants/localStorageKeys'
import useLocalStorage from 'hooks/useLocalStorage' import useLocalStorage from 'hooks/useLocalStorage'
interface Props { interface Props {
@ -31,7 +31,10 @@ export const BorrowCapacity = ({
hideValues, hideValues,
decimals = 2, decimals = 2,
}: Props) => { }: Props) => {
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion) const [reduceMotion] = useLocalStorage<boolean>(
LocalStorageKeys.REDUCE_MOTION,
DEFAULT_SETTINGS.reduceMotion,
)
const [percentOfMaxRound, setPercentOfMaxRound] = useState(0) const [percentOfMaxRound, setPercentOfMaxRound] = useState(0)
const [percentOfMaxRange, setPercentOfMaxRange] = useState(0) const [percentOfMaxRange, setPercentOfMaxRange] = useState(0)
const [limitPercentOfMax, setLimitPercentOfMax] = useState(0) const [limitPercentOfMax, setLimitPercentOfMax] = useState(0)

View File

@ -17,7 +17,7 @@ import { glowElement } from 'components/Button/utils'
import { CircularProgress } from 'components/CircularProgress' import { CircularProgress } from 'components/CircularProgress'
import { ChevronDown } from 'components/Icons' import { ChevronDown } from 'components/Icons'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { REDUCE_MOTION_KEY } from 'constants/localStore' import { LocalStorageKeys } from 'constants/localStorageKeys'
import useLocalStorage from 'hooks/useLocalStorage' import useLocalStorage from 'hooks/useLocalStorage'
const Button = React.forwardRef(function Button( const Button = React.forwardRef(function Button(
@ -44,7 +44,10 @@ const Button = React.forwardRef(function Button(
}: ButtonProps, }: ButtonProps,
ref, ref,
) { ) {
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion) const [reduceMotion] = useLocalStorage<boolean>(
LocalStorageKeys.REDUCE_MOTION,
DEFAULT_SETTINGS.reduceMotion,
)
const isDisabled = disabled || showProgressIndicator const isDisabled = disabled || showProgressIndicator
const shouldShowText = text && !children const shouldShowText = text && !children
const shouldShowGlowElement = variant === 'solid' && !isDisabled && !reduceMotion const shouldShowGlowElement = variant === 'solid' && !isDisabled && !reduceMotion

View File

@ -2,7 +2,7 @@ import classNames from 'classnames'
import { CheckCircled } from 'components/Icons' import { CheckCircled } from 'components/Icons'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { REDUCE_MOTION_KEY } from 'constants/localStore' import { LocalStorageKeys } from 'constants/localStorageKeys'
import useLocalStorage from 'hooks/useLocalStorage' import useLocalStorage from 'hooks/useLocalStorage'
interface Props { interface Props {
@ -12,7 +12,10 @@ interface Props {
} }
export const CheckMark = ({ color = '#FFFFFF', size = 20, className }: Props) => { export const CheckMark = ({ color = '#FFFFFF', size = 20, className }: Props) => {
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion) const [reduceMotion] = useLocalStorage<boolean>(
LocalStorageKeys.REDUCE_MOTION,
DEFAULT_SETTINGS.reduceMotion,
)
const classes = classNames('inline-block relative', className) const classes = classNames('inline-block relative', className)
if (reduceMotion) if (reduceMotion)

View File

@ -1,27 +1,39 @@
import classNames from 'classnames' import classNames from 'classnames'
import { Check } from 'components/Icons' import { Check } from 'components/Icons'
import Text from 'components/Text'
interface Props { interface Props {
checked: boolean checked: boolean
onChange: (checked: boolean) => void onChange: (checked: boolean) => void
text?: string
} }
export default function Checkbox(props: Props) { export default function Checkbox(props: Props) {
return ( return (
<button <label className='flex gap-2 items-center cursor-pointer'>
onClick={() => props.onChange(props.checked)} <input
role='checkbox' onChange={() => props.onChange(props.checked)}
aria-checked={props.checked} checked={props.checked}
className={classNames( type='checkbox'
'h-5 w-5 rounded-sm p-0.5', className='opacity-0 absolute'
props.checked && 'relative isolate overflow-hidden rounded-sm', />
props.checked && <div
'before:content-[" "] before:absolute before:inset-0 before:-z-1 before:rounded-sm before:p-[1px] before:border-glas', className={classNames(
props.checked ? 'bg-white/20' : 'border border-white/60', 'h-5 w-5 rounded-sm p-0.5',
props.checked && 'relative isolate overflow-hidden rounded-sm',
props.checked &&
'before:content-[" "] before:absolute before:inset-0 before:-z-1 before:rounded-sm before:p-[1px] before:border-glas',
props.checked ? 'bg-white/20' : 'border border-white/60',
)}
>
{props.checked && <Check />}
</div>
{props.text && (
<Text size='xs' className='text-white/60'>
{props.text}
</Text>
)} )}
> </label>
{props.checked && <Check />}
</button>
) )
} }

View File

@ -1,7 +1,7 @@
import classNames from 'classnames' import classNames from 'classnames'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { REDUCE_MOTION_KEY } from 'constants/localStore' import { LocalStorageKeys } from 'constants/localStorageKeys'
import useLocalStorage from 'hooks/useLocalStorage' import useLocalStorage from 'hooks/useLocalStorage'
interface Props { interface Props {
@ -11,7 +11,10 @@ interface Props {
} }
export const CircularProgress = ({ color = '#FFFFFF', size = 20, className }: Props) => { export const CircularProgress = ({ color = '#FFFFFF', size = 20, className }: Props) => {
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion) const [reduceMotion] = useLocalStorage<boolean>(
LocalStorageKeys.REDUCE_MOTION,
DEFAULT_SETTINGS.reduceMotion,
)
const borderWidth = `${size / 10}px` const borderWidth = `${size / 10}px`
const borderColor = `${color} transparent transparent transparent` const borderColor = `${color} transparent transparent transparent`
const loaderClasses = classNames('inline-block relative', className) const loaderClasses = classNames('inline-block relative', className)

View File

@ -3,7 +3,7 @@ import { useMemo } from 'react'
import { FormattedNumber } from 'components/FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { DISPLAY_CURRENCY_KEY } from 'constants/localStore' import { LocalStorageKeys } from 'constants/localStorageKeys'
import { ORACLE_DENOM } from 'constants/oracle' import { ORACLE_DENOM } from 'constants/oracle'
import useLocalStorage from 'hooks/useLocalStorage' import useLocalStorage from 'hooks/useLocalStorage'
import usePrices from 'hooks/usePrices' import usePrices from 'hooks/usePrices'
@ -22,7 +22,7 @@ interface Props {
export default function DisplayCurrency(props: Props) { export default function DisplayCurrency(props: Props) {
const displayCurrencies = getDisplayCurrencies() const displayCurrencies = getDisplayCurrencies()
const [displayCurrency] = useLocalStorage<string>( const [displayCurrency] = useLocalStorage<string>(
DISPLAY_CURRENCY_KEY, LocalStorageKeys.DISPLAY_CURRENCY,
DEFAULT_SETTINGS.displayCurrency, DEFAULT_SETTINGS.displayCurrency,
) )
const { data: prices } = usePrices() const { data: prices } = usePrices()

View File

@ -0,0 +1,22 @@
import React from 'react'
import useAvailableColumns from 'components/Earn/Farm/Table/Columns/useAvailableColumns'
import Table from 'components/Table'
type Props = {
data: Vault[]
isLoading: boolean
}
export default function AvailableVaultsTable(props: Props) {
const columns = useAvailableColumns({ isLoading: props.isLoading })
return (
<Table
title='Available vaults'
columns={columns}
data={props.data}
initialSorting={[{ id: 'name', desc: true }]}
/>
)
}

View File

@ -0,0 +1,25 @@
import React from 'react'
import { FormattedNumber } from 'components/FormattedNumber'
import Loading from 'components/Loading'
export const APY_META = { accessorKey: 'apy', header: 'APY' }
interface Props {
vault: Vault | DepositedVault
}
export default function Apy(props: Props) {
const { vault } = props
if (vault.apy === null) return <Loading />
return (
<FormattedNumber
amount={vault.apy ?? 0}
options={{ minDecimals: 2, maxDecimals: 2, suffix: '%' }}
className='text-xs'
animate
/>
)
}

View File

@ -0,0 +1,32 @@
import React from 'react'
import ActionButton from 'components/Button/ActionButton'
import Loading from 'components/Loading'
import useStore from 'store'
interface Props {
vault: Vault | DepositedVault
isLoading: boolean
}
export const Deposit = (props: Props) => {
const { vault } = props
function enterVaultHandler() {
useStore.setState({
vaultModal: {
vault,
selectedBorrowDenoms: [vault.denoms.secondary],
isCreate: true,
},
})
}
if (props.isLoading) return <Loading />
return (
<div className='flex items-center justify-end'>
<ActionButton onClick={enterVaultHandler} color='tertiary' text='Deposit' />
</div>
)
}

View File

@ -0,0 +1,57 @@
import { Row } from '@tanstack/react-table'
import { FormattedNumber } from 'components/FormattedNumber'
import Loading from 'components/Loading'
import TitleAndSubCell from 'components/TitleAndSubCell'
import { VAULT_DEPOSIT_BUFFER } from 'constants/vaults'
import { getAssetByDenom } from 'utils/assets'
export const DEPOSIT_CAP_META = { accessorKey: 'cap', header: 'Deposit Cap' }
interface Props {
vault: Vault | DepositedVault
isLoading: boolean
}
export const depositCapSortingFn = (
a: Row<Vault> | Row<DepositedVault>,
b: Row<Vault> | Row<DepositedVault>,
): number => {
const depositCapA = a.original.cap.max
const depositCapB = b.original.cap.max
return depositCapA.minus(depositCapB).toNumber()
}
export default function DepositCap(props: Props) {
const { vault } = props
if (props.isLoading) return <Loading />
const percent = vault.cap.used
.dividedBy(vault.cap.max.multipliedBy(VAULT_DEPOSIT_BUFFER))
.multipliedBy(100)
.integerValue()
const decimals = getAssetByDenom(vault.cap.denom)?.decimals ?? 6
return (
<TitleAndSubCell
title={
<FormattedNumber
amount={vault.cap.max.toNumber()}
options={{ minDecimals: 2, abbreviated: true, decimals }}
className='text-xs'
animate
/>
}
sub={
<FormattedNumber
amount={percent.toNumber()}
options={{ minDecimals: 2, maxDecimals: 2, suffix: '% Filled' }}
className='text-xs'
animate
/>
}
/>
)
}

View File

@ -0,0 +1,24 @@
import classNames from 'classnames'
import React from 'react'
import { ChevronDown } from 'components/Icons'
import Loading from 'components/Loading'
export const DETAILS_META = { accessorKey: 'details', enableSorting: false, header: 'Deposit' }
interface Props {
isLoading: boolean
isExpanded: boolean
}
export default function Details(props: Props) {
if (props.isLoading) return <Loading />
return (
<div className='flex items-center justify-end'>
<div className={classNames('w-4', props.isExpanded && 'rotate-180')}>
<ChevronDown />
</div>
</div>
)
}

View File

@ -0,0 +1,23 @@
import React from 'react'
import { FormattedNumber } from 'components/FormattedNumber'
import Loading from 'components/Loading'
export const LTV_MAX_META = { accessorKey: 'ltv.max', header: 'Max LTV' }
interface Props {
vault: Vault | DepositedVault
isLoading: boolean
}
export default function MaxLtv(props: Props) {
const { vault } = props
if (props.isLoading) return <Loading />
return (
<FormattedNumber
amount={vault.ltv.max * 100}
options={{ minDecimals: 0, maxDecimals: 0, suffix: '%' }}
className='text-xs'
animate
/>
)
}

View File

@ -0,0 +1,72 @@
import classNames from 'classnames'
import React from 'react'
import VaultLogo from 'components/Earn/Farm/VaultLogo'
import Text from 'components/Text'
import TitleAndSubCell from 'components/TitleAndSubCell'
import { VaultStatus } from 'types/enums/vault'
import { produceCountdown } from 'utils/formatters'
export const NAME_META = { id: 'name', header: 'Vault', accessorKey: 'name' }
interface Props {
vault: Vault | DepositedVault
}
export default function Name(props: Props) {
const { vault } = props
const timeframe = vault.lockup.timeframe[0]
const unlockDuration = !!timeframe ? ` - (${vault.lockup.duration}${timeframe})` : ''
let remainingTime = 0
let status: VaultStatus = VaultStatus.ACTIVE
if ('status' in vault) {
status = vault.status as VaultStatus
if (vault.status === VaultStatus.UNLOCKING && vault.unlocksAt) {
remainingTime = vault.unlocksAt - Date.now()
}
}
return (
<div className='flex'>
<VaultLogo vault={vault} />
<TitleAndSubCell
className='ml-2 mr-2 text-left'
title={`${vault.name}${unlockDuration}`}
sub={vault.provider}
/>
{status === VaultStatus.UNLOCKING && (
<Text
className='group/label relative h-5 w-[84px] rounded-sm bg-green text-center leading-5 text-white'
size='xs'
>
<span
className={classNames(
'absolute inset-0 text-center',
'opacity-100 transition-opacity duration-500',
'group-hover/label:opacity-0 group-[.is-expanded]/row:opacity-0',
)}
>
Unlocking
</span>
<span
className={classNames(
'absolute inset-0 text-center',
'opacity-0 transition-opacity duration-500',
'group-hover/label:opacity-100 group-[.is-expanded]/row:opacity-100',
)}
>
{produceCountdown(remainingTime)}
</span>
</Text>
)}
{status === VaultStatus.UNLOCKED && (
<Text
className='h-5 w-[84px] rounded-sm bg-green text-center leading-5 text-white'
size='xs'
>
Unlocked
</Text>
)}
</div>
)
}

View File

@ -0,0 +1,23 @@
import React from 'react'
import DisplayCurrency from 'components/DisplayCurrency'
import { ORACLE_DENOM } from 'constants/oracle'
import { BNCoin } from 'types/classes/BNCoin'
export const POSITION_VALUE_META = {
header: 'Pos. Value',
}
interface Props {
vault: DepositedVault
isLoading: boolean
}
export default function PositionValue(props: Props) {
const { vault } = props
const positionValue = vault.values.primary
.plus(vault.values.secondary)
.plus(vault.values.unlocking)
.plus(vault.values.unlocked)
const coin = BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, positionValue)
return <DisplayCurrency coin={coin} className='text-xs' />
}

View File

@ -0,0 +1,20 @@
import React from 'react'
import DisplayCurrency from 'components/DisplayCurrency'
import Loading from 'components/Loading'
import { BNCoin } from 'types/classes/BNCoin'
export const TVL_META = { accessorKey: 'tvl', header: 'TVL' }
interface Props {
vault: Vault | DepositedVault
isLoading: boolean
}
export default function TVL(props: Props) {
const { vault } = props
if (props.isLoading) return <Loading />
const coin = BNCoin.fromDenomAndBigNumber(vault.cap.denom, vault.cap.used)
return <DisplayCurrency coin={coin} className='text-xs' />
}

View File

@ -0,0 +1,50 @@
import { ColumnDef } from '@tanstack/react-table'
import { useMemo } from 'react'
import Apy, { APY_META } from 'components/Earn/Farm/Table/Columns/Apy'
import { Deposit } from 'components/Earn/Farm/Table/Columns/Deposit'
import DepositCap, {
DEPOSIT_CAP_META,
depositCapSortingFn,
} from 'components/Earn/Farm/Table/Columns/DepositCap'
import MaxLTV, { LTV_MAX_META } from 'components/Earn/Farm/Table/Columns/MaxLTV'
import Name, { NAME_META } from 'components/Earn/Farm/Table/Columns/Name'
import TVL, { TVL_META } from 'components/Earn/Farm/Table/Columns/TVL'
import { DETAILS_META } from './Details'
interface Props {
isLoading: boolean
}
export default function useAvailableColumns(props: Props) {
return useMemo<ColumnDef<Vault | DepositedVault>[]>(() => {
return [
{
...NAME_META,
cell: ({ row }) => <Name vault={row.original as Vault} />,
},
{
...APY_META,
cell: ({ row }) => <Apy vault={row.original as Vault} />,
},
{
...TVL_META,
cell: ({ row }) => <TVL vault={row.original as Vault} isLoading={props.isLoading} />,
},
{
...DEPOSIT_CAP_META,
cell: ({ row }) => <DepositCap vault={row.original as Vault} isLoading={props.isLoading} />,
sortingFn: depositCapSortingFn,
},
{
...LTV_MAX_META,
cell: ({ row }) => <MaxLTV vault={row.original as Vault} isLoading={props.isLoading} />,
},
{
...DETAILS_META,
cell: ({ row }) => <Deposit vault={row.original as Vault} isLoading={props.isLoading} />,
},
]
}, [props.isLoading])
}

View File

@ -0,0 +1,63 @@
import { ColumnDef, Row } from '@tanstack/react-table'
import { useMemo } from 'react'
import Apy, { APY_META } from 'components/Earn/Farm/Table/Columns/Apy'
import DepositCap, {
DEPOSIT_CAP_META,
depositCapSortingFn,
} from 'components/Earn/Farm/Table/Columns/DepositCap'
import Details, { DETAILS_META } from 'components/Earn/Farm/Table/Columns/Details'
import MaxLTV, { LTV_MAX_META } from 'components/Earn/Farm/Table/Columns/MaxLTV'
import Name, { NAME_META } from 'components/Earn/Farm/Table/Columns/Name'
import PositionValue, {
POSITION_VALUE_META,
} from 'components/Earn/Farm/Table/Columns/PositionValue'
import TVL, { TVL_META } from 'components/Earn/Farm/Table/Columns/TVL'
interface Props {
isLoading: boolean
}
export default function useDepositedColumns(props: Props) {
return useMemo<ColumnDef<DepositedVault>[]>(() => {
return [
{
...NAME_META,
cell: ({ row }) => <Name vault={row.original as DepositedVault} />,
},
{
...POSITION_VALUE_META,
cell: ({ row }: { row: Row<DepositedVault> }) => (
<PositionValue vault={row.original as DepositedVault} isLoading={props.isLoading} />
),
},
{
...APY_META,
cell: ({ row }) => <Apy vault={row.original as DepositedVault} />,
},
{
...TVL_META,
cell: ({ row }) => (
<TVL vault={row.original as DepositedVault} isLoading={props.isLoading} />
),
},
{
...DEPOSIT_CAP_META,
cell: ({ row }) => (
<DepositCap vault={row.original as DepositedVault} isLoading={props.isLoading} />
),
sortingFn: depositCapSortingFn,
},
{
...LTV_MAX_META,
cell: ({ row }) => (
<MaxLTV vault={row.original as DepositedVault} isLoading={props.isLoading} />
),
},
{
...DETAILS_META,
cell: ({ row }) => <Details isLoading={props.isLoading} isExpanded={row.getIsExpanded()} />,
},
]
}, [props.isLoading])
}

View File

@ -0,0 +1,33 @@
import { Row } from '@tanstack/react-table'
import { Table as TanStackTable } from '@tanstack/table-core/build/lib/types'
import React, { useCallback } from 'react'
import useDepositedColumns from 'components/Earn/Farm/Table/Columns/useDepositedColumns'
import VaultExpanded from 'components/Earn/Farm/VaultExpanded'
import Table from 'components/Table'
type Props = {
data: DepositedVault[]
isLoading: boolean
}
export default function DepositedVaultsTable(props: Props) {
const columns = useDepositedColumns({ isLoading: props.isLoading })
const renderExpanded = useCallback(
(row: Row<DepositedVault>, table: TanStackTable<DepositedVault>) => (
<VaultExpanded row={row} resetExpanded={table.resetExpanded} />
),
[],
)
return (
<Table
title='Deposited Vaults'
columns={columns}
data={props.data}
initialSorting={[{ id: 'name', desc: true }]}
renderExpanded={renderExpanded}
/>
)
}

View File

@ -6,23 +6,23 @@ import Button from 'components/Button'
import { AccountArrowDown, LockLocked, LockUnlocked, Plus } from 'components/Icons' import { AccountArrowDown, LockLocked, LockUnlocked, Plus } from 'components/Icons'
import { Tooltip } from 'components/Tooltip' import { Tooltip } from 'components/Tooltip'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { SLIPPAGE_KEY } from 'constants/localStore' import { LocalStorageKeys } from 'constants/localStorageKeys'
import useAccountId from 'hooks/useAccountId' import useAccountId from 'hooks/useAccountId'
import useLocalStorage from 'hooks/useLocalStorage' import useLocalStorage from 'hooks/useLocalStorage'
import useStore from 'store' import useStore from 'store'
import { VaultStatus } from 'types/enums/vault' import { VaultStatus } from 'types/enums/vault'
interface Props { interface Props {
row: Row<Vault | DepositedVault> row: Row<DepositedVault>
resetExpanded: (defaultState?: boolean | undefined) => void resetExpanded: (defaultState?: boolean | undefined) => void
} }
export default function VaultExpanded(props: Props) { export default function VaultExpanded(props: Props) {
const vault = props.row.original as DepositedVault const vault = props.row.original
const accountId = useAccountId() const accountId = useAccountId()
const [isConfirming, setIsConfirming] = useState(false) const [isConfirming, setIsConfirming] = useState(false)
const withdrawFromVaults = useStore((s) => s.withdrawFromVaults) const withdrawFromVaults = useStore((s) => s.withdrawFromVaults)
const [slippage] = useLocalStorage<number>(SLIPPAGE_KEY, DEFAULT_SETTINGS.slippage) const [slippage] = useLocalStorage<number>(LocalStorageKeys.SLIPPAGE, DEFAULT_SETTINGS.slippage)
function depositMoreHandler() { function depositMoreHandler() {
useStore.setState({ useStore.setState({
@ -105,7 +105,7 @@ export default function VaultExpanded(props: Props) {
return ( return (
<tr <tr
key={props.row.id} key={props.row.id}
className='hover:cursor-pointer bg-black/20 transition-colors' className='transition-colors hover:cursor-pointer bg-black/20'
onClick={(e) => { onClick={(e) => {
e.preventDefault() e.preventDefault()
const isExpanded = props.row.getIsExpanded() const isExpanded = props.row.getIsExpanded()
@ -113,7 +113,7 @@ export default function VaultExpanded(props: Props) {
!isExpanded && props.row.toggleExpanded() !isExpanded && props.row.toggleExpanded()
}} }}
> >
<td colSpan={!status ? 7 : 8}> <td colSpan={props.row.getAllCells().length} className='p-0'>
<div className='flex justify-end gap-3 p-4 align-center'> <div className='flex justify-end gap-3 p-4 align-center'>
{status && <DepositMoreButton />} {status && <DepositMoreButton />}
{status === VaultStatus.ACTIVE && <UnlockButton />} {status === VaultStatus.ACTIVE && <UnlockButton />}

View File

@ -12,9 +12,9 @@ export const VaultRow = (props: AssetRowProps) => {
<tr <tr
key={props.row.id} key={props.row.id}
className={classNames( className={classNames(
'bg-white/3 group/row border-b border-t border-white/5 transition-colors hover:bg-white/5', 'group/row border-t border-white/10 transition-colors hover:bg-white/5',
vault.status && 'hover:cursor-pointer', vault.status && 'hover:cursor-pointer',
props.row.getIsExpanded() && 'is-expanded', props.row.getIsExpanded() ? 'is-expanded bg-black/20' : 'border-b',
)} )}
onClick={(e) => { onClick={(e) => {
e.preventDefault() e.preventDefault()

View File

@ -1,303 +0,0 @@
import {
ColumnDef,
flexRender,
getCoreRowModel,
getSortedRowModel,
Row,
SortingState,
useReactTable,
} from '@tanstack/react-table'
import classNames from 'classnames'
import React from 'react'
import ActionButton from 'components/Button/ActionButton'
import DisplayCurrency from 'components/DisplayCurrency'
import VaultExpanded from 'components/Earn/Farm/VaultExpanded'
import VaultLogo from 'components/Earn/Farm/VaultLogo'
import { VaultRow } from 'components/Earn/Farm/VaultRow'
import { FormattedNumber } from 'components/FormattedNumber'
import { ChevronDown, SortAsc, SortDesc, SortNone } from 'components/Icons'
import Loading from 'components/Loading'
import Text from 'components/Text'
import TitleAndSubCell from 'components/TitleAndSubCell'
import { ORACLE_DENOM } from 'constants/oracle'
import { VAULT_DEPOSIT_BUFFER } from 'constants/vaults'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { VaultStatus } from 'types/enums/vault'
import { getAssetByDenom } from 'utils/assets'
import { formatPercent, produceCountdown } from 'utils/formatters'
type Props = {
data: Vault[] | DepositedVault[]
isLoading?: boolean
}
export const VaultTable = (props: Props) => {
const [sorting, setSorting] = React.useState<SortingState>([{ id: 'name', desc: true }])
const columns = React.useMemo<ColumnDef<Vault | DepositedVault>[]>(() => {
return [
{
header: 'Vault',
accessorKey: 'name',
cell: ({ row }) => {
const vault = row.original as DepositedVault
const timeframe = vault.lockup.timeframe[0]
const unlockDuration = !!timeframe ? ` - (${vault.lockup.duration}${timeframe})` : ''
const status = vault.status
let remainingTime = 0
if (status === VaultStatus.UNLOCKING && vault.unlocksAt) {
remainingTime = vault.unlocksAt - Date.now()
}
return (
<div className='flex'>
<VaultLogo vault={vault} />
<TitleAndSubCell
className='ml-2 mr-2 text-left'
title={`${vault.name}${unlockDuration}`}
sub={vault.provider}
/>
{status === VaultStatus.UNLOCKING && (
<Text
className='group/label relative h-5 w-[84px] rounded-sm bg-green text-center leading-5 text-white'
size='xs'
>
<span
className={classNames(
'absolute inset-0 text-center',
'opacity-100 transition-opacity duration-500',
'group-hover/label:opacity-0 group-[.is-expanded]/row:opacity-0',
)}
>
Unlocking
</span>
<span
className={classNames(
'absolute inset-0 text-center',
'opacity-0 transition-opacity duration-500',
'group-hover/label:opacity-100 group-[.is-expanded]/row:opacity-100',
)}
>
{produceCountdown(remainingTime)}
</span>
</Text>
)}
{status === VaultStatus.UNLOCKED && (
<Text
className='h-5 w-[84px] rounded-sm bg-green text-center leading-5 text-white'
size='xs'
>
Unlocked
</Text>
)}
</div>
)
},
},
...((props.data[0] as DepositedVault)?.values
? [
{
header: 'Pos. Value',
cell: ({ row }: { row: Row<DepositedVault | Vault> }) => {
const vault = row.original as DepositedVault
const positionValue = vault.values.primary
.plus(vault.values.secondary)
.plus(vault.values.unlocking)
.plus(vault.values.unlocked)
const coin = BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, positionValue)
return <DisplayCurrency coin={coin} className='text-xs' />
},
},
]
: []),
{
accessorKey: 'apy',
header: 'APY',
cell: ({ row }) => {
const vault = row.original as DepositedVault
if (vault.apy === null) return <Loading />
return (
<FormattedNumber
amount={vault.apy ?? 0}
options={{ minDecimals: 2, maxDecimals: 2, suffix: '%' }}
className='text-xs'
animate
/>
)
},
},
{
accessorKey: 'tvl',
header: 'TVL',
cell: ({ row }) => {
const vault = row.original as DepositedVault
if (props.isLoading) return <Loading />
const coin = new BNCoin({
denom: vault.cap.denom,
amount: vault.cap.used.toString(),
})
return <DisplayCurrency coin={coin} className='text-xs' />
},
},
{
accessorKey: 'cap',
header: 'Depo. Cap',
cell: ({ row }) => {
const vault = row.original as DepositedVault
if (props.isLoading) return <Loading />
const percent = vault.cap.used
.dividedBy(vault.cap.max.multipliedBy(VAULT_DEPOSIT_BUFFER))
.multipliedBy(100)
.integerValue()
const decimals = getAssetByDenom(vault.cap.denom)?.decimals ?? 6
return (
<TitleAndSubCell
title={
<FormattedNumber
amount={vault.cap.max.toNumber()}
options={{ minDecimals: 2, abbreviated: true, decimals }}
className='text-xs'
animate
/>
}
sub={
<FormattedNumber
amount={percent.toNumber()}
options={{ minDecimals: 2, maxDecimals: 2, suffix: '% Filled' }}
className='text-xs'
animate
/>
}
/>
)
},
},
{
accessorKey: 'ltv.max',
header: 'Max LTV',
cell: ({ row }) => {
if (props.isLoading) return <Loading />
return <Text className='text-xs'>{formatPercent(row.original.ltv.max)}</Text>
},
},
{
accessorKey: 'details',
enableSorting: false,
header: (data) => {
const tableData = data.table.options.data as DepositedVault[]
if (tableData.length && tableData[0].status) return 'Details'
return 'Deposit'
},
cell: ({ row }) => {
const vault = row.original as DepositedVault
function enterVaultHandler() {
useStore.setState({
vaultModal: {
vault,
selectedBorrowDenoms: [vault.denoms.secondary],
isCreate: true,
},
})
}
if (props.isLoading) return <Loading />
return (
<div className='flex items-center justify-end'>
{vault.status ? (
<div className={classNames('w-4', row.getIsExpanded() && 'rotate-180')}>
<ChevronDown />
</div>
) : (
<ActionButton onClick={enterVaultHandler} color='tertiary' text='Deposit' />
)}
</div>
)
},
},
]
}, [props.data, props.isLoading])
const table = useReactTable({
data: props.data,
columns: columns,
state: {
sorting,
},
onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
})
return (
<table className='w-full'>
<thead className='bg-black/20'>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<th
key={header.id}
onClick={header.column.getToggleSortingHandler()}
className={classNames(
'px-4 py-3',
header.column.getCanSort() && 'hover:cursor-pointer',
header.id === 'symbol' ? 'text-left' : 'text-right',
)}
>
<div
className={classNames(
'flex',
header.id === 'name' ? 'justify-start' : 'justify-end',
'align-center',
)}
>
<span className='w-6 h-6 text-white'>
{header.column.getCanSort()
? {
asc: <SortAsc />,
desc: <SortDesc />,
false: <SortNone />,
}[header.column.getIsSorted() as string] ?? null
: null}
</span>
<Text
tag='span'
size='xs'
className='flex items-center font-normal text-white/40'
>
{flexRender(header.column.columnDef.header, header.getContext())}
</Text>
</div>
</th>
)
})}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map((row) => {
if (row.getIsExpanded()) {
return (
<React.Fragment key={`${row.id}_subrow`}>
<VaultRow row={row} resetExpanded={table.resetExpanded} />
<VaultExpanded row={row} resetExpanded={table.resetExpanded} />
</React.Fragment>
)
}
return (
<VaultRow key={row.original.address} row={row} resetExpanded={table.resetExpanded} />
)
})}
</tbody>
</table>
)
}

View File

@ -4,7 +4,7 @@ import Button from 'components/Button'
import { ChevronRight } from 'components/Icons' import { ChevronRight } from 'components/Icons'
import NotificationBanner from 'components/NotificationBanner' import NotificationBanner from 'components/NotificationBanner'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { SLIPPAGE_KEY } from 'constants/localStore' import { LocalStorageKeys } from 'constants/localStorageKeys'
import useAccountId from 'hooks/useAccountId' import useAccountId from 'hooks/useAccountId'
import useLocalStorage from 'hooks/useLocalStorage' import useLocalStorage from 'hooks/useLocalStorage'
import useStore from 'store' import useStore from 'store'
@ -17,7 +17,7 @@ export default function VaultUnlockBanner(props: Props) {
const accountId = useAccountId() const accountId = useAccountId()
const [isConfirming, setIsConfirming] = useState(false) const [isConfirming, setIsConfirming] = useState(false)
const withdrawFromVaults = useStore((s) => s.withdrawFromVaults) const withdrawFromVaults = useStore((s) => s.withdrawFromVaults)
const [slippage] = useLocalStorage<number>(SLIPPAGE_KEY, DEFAULT_SETTINGS.slippage) const [slippage] = useLocalStorage<number>(LocalStorageKeys.SLIPPAGE, DEFAULT_SETTINGS.slippage)
async function handleWithdraw() { async function handleWithdraw() {
if (!accountId) return if (!accountId) return

View File

@ -1,7 +1,7 @@
import { Suspense, useMemo } from 'react' import { Suspense, useMemo } from 'react'
import Card from 'components/Card' import AvailableVaultsTable from 'components/Earn/Farm/Table/AvailableVaultsTable'
import { VaultTable } from 'components/Earn/Farm/VaultTable' import DepositedVaultsTable from 'components/Earn/Farm/Table/DepositedVaultsTable'
import VaultUnlockBanner from 'components/Earn/Farm/VaultUnlockBanner' import VaultUnlockBanner from 'components/Earn/Farm/VaultUnlockBanner'
import { ENV } from 'constants/env' import { ENV } from 'constants/env'
import { BN_ZERO } from 'constants/math' import { BN_ZERO } from 'constants/math'
@ -12,15 +12,10 @@ import useVaults from 'hooks/useVaults'
import { NETWORK } from 'types/enums/network' import { NETWORK } from 'types/enums/network'
import { VaultStatus } from 'types/enums/vault' import { VaultStatus } from 'types/enums/vault'
interface Props { function Content() {
type: 'available' | 'deposited'
}
function Content(props: Props) {
const accountId = useAccountId() const accountId = useAccountId()
const { data: vaults } = useVaults() const { data: vaults } = useVaults()
const { data: depositedVaults } = useDepositedVaults(accountId || '') const { data: depositedVaults } = useDepositedVaults(accountId || '')
const isAvailable = props.type === 'available'
const vaultsMetaData = const vaultsMetaData =
ENV.NETWORK === NETWORK.TESTNET ? TESTNET_VAULTS_META_DATA : VAULTS_META_DATA ENV.NETWORK === NETWORK.TESTNET ? TESTNET_VAULTS_META_DATA : VAULTS_META_DATA
@ -44,13 +39,9 @@ function Content(props: Props) {
) )
}, [vaults, depositedVaults, vaultsMetaData]) }, [vaults, depositedVaults, vaultsMetaData])
const vaultsToDisplay = isAvailable ? available : deposited
if (!vaultsToDisplay.length) return null
const unlockedVaults: DepositedVault[] = [] const unlockedVaults: DepositedVault[] = []
if (!isAvailable && depositedVaults?.length > 0) { if (depositedVaults?.length > 0) {
depositedVaults.forEach((vault) => { depositedVaults.forEach((vault) => {
if (vault.status === VaultStatus.UNLOCKED) { if (vault.status === VaultStatus.UNLOCKED) {
unlockedVaults.push(vault) unlockedVaults.push(vault)
@ -60,13 +51,11 @@ function Content(props: Props) {
return ( return (
<> <>
{!isAvailable && <VaultUnlockBanner vaults={unlockedVaults} />} <VaultUnlockBanner vaults={unlockedVaults} />
<Card {deposited.length && (
className='w-full h-fit bg-white/5' <DepositedVaultsTable data={deposited as DepositedVault[]} isLoading={false} />
title={isAvailable ? 'Available vaults' : 'Deposited'} )}
> {available.length && <AvailableVaultsTable data={available as Vault[]} isLoading={false} />}
<VaultTable data={vaultsToDisplay} />
</Card>
</> </>
) )
} }
@ -88,25 +77,13 @@ function Fallback() {
}, },
})) }))
return ( return <AvailableVaultsTable data={mockVaults} isLoading />
<Card className='w-full h-fit bg-white/5' title='Available vaults'>
<VaultTable data={mockVaults} isLoading />
</Card>
)
} }
export function AvailableVaults() { export default function Vaults() {
return ( return (
<Suspense fallback={<Fallback />}> <Suspense fallback={<Fallback />}>
<Content type='available' /> <Content />
</Suspense>
)
}
export function DepositedVaults() {
return (
<Suspense fallback={null}>
<Content type='deposited' />
</Suspense> </Suspense>
) )
} }

View File

@ -3,7 +3,7 @@ import { useCallback } from 'react'
import { ACCOUNT_MENU_BUTTON_ID } from 'components/Account/AccountMenuContent' import { ACCOUNT_MENU_BUTTON_ID } from 'components/Account/AccountMenuContent'
import Button from 'components/Button' import Button from 'components/Button'
import ActionButton from 'components/Button/ActionButton' import ActionButton from 'components/Button/ActionButton'
import { ArrowDownLine, ArrowUpLine, Enter } from 'components/Icons' import { ArrowDownLine, ArrowUpLine, Enter, ExclamationMarkCircled } from 'components/Icons'
import Text from 'components/Text' import Text from 'components/Text'
import { Tooltip } from 'components/Tooltip' import { Tooltip } from 'components/Tooltip'
import ConditionalWrapper from 'hocs/ConditionalWrapper' import ConditionalWrapper from 'hocs/ConditionalWrapper'
@ -33,12 +33,13 @@ export default function LendingActionButtons(props: Props) {
const accountId = useAccountId() const accountId = useAccountId()
const hasNoDeposit = !!(!assetDepositAmount && address && accountId) const hasNoDeposit = !!(!assetDepositAmount && address && accountId)
const handleWithdraw = useCallback(() => { const handleUnlend = useCallback(() => {
if (isAutoLendEnabledForCurrentAccount) { if (isAutoLendEnabledForCurrentAccount) {
showAlertDialog({ showAlertDialog({
icon: <ExclamationMarkCircled width={18} />,
title: 'Disable Automatically Lend Assets', title: 'Disable Automatically Lend Assets',
description: content:
"Your auto-lend feature is currently enabled. To recover your funds, please confirm if you'd like to disable this feature in order to continue.", "Your auto-lend feature is currently enabled. To unlend your funds, please confirm if you'd like to disable this feature in order to continue.",
positiveButton: { positiveButton: {
onClick: () => document.getElementById(ACCOUNT_MENU_BUTTON_ID)?.click(), onClick: () => document.getElementById(ACCOUNT_MENU_BUTTON_ID)?.click(),
text: 'Continue to Account Settings', text: 'Continue to Account Settings',
@ -62,10 +63,10 @@ export default function LendingActionButtons(props: Props) {
leftIcon={<ArrowDownLine />} leftIcon={<ArrowDownLine />}
iconClassName={iconClassnames} iconClassName={iconClassnames}
color='secondary' color='secondary'
onClick={handleWithdraw} onClick={handleUnlend}
className={buttonClassnames} className={buttonClassnames}
> >
Withdraw Unlend
</Button> </Button>
)} )}

View File

@ -1,140 +0,0 @@
import { ColumnDef, Row, Table } from '@tanstack/react-table'
import { useCallback, useMemo } from 'react'
import AmountAndValue from 'components/AmountAndValue'
import AssetImage from 'components/Asset/AssetImage'
import AssetRate from 'components/Asset/AssetRate'
import LendingActionButtons from 'components/Earn/Lend/LendingActionButtons'
import { FormattedNumber } from 'components/FormattedNumber'
import { ChevronDown, ChevronUp } from 'components/Icons'
import AssetListTable from 'components/MarketAssetTable'
import MarketAssetTableRow from 'components/MarketAssetTable/MarketAssetTableRow'
import MarketDetails from 'components/MarketAssetTable/MarketDetails'
import TitleAndSubCell from 'components/TitleAndSubCell'
import { BN_ZERO } from 'constants/math'
import { convertLiquidityRateToAPR } from 'utils/formatters'
import { BN } from 'utils/helpers'
interface Props {
title: string
data: LendingMarketTableData[]
}
export default function LendingMarketsTable(props: Props) {
const { title, data } = props
const shouldShowAccountDeposit = !!data[0]?.accountLentValue
const rowRenderer = useCallback(
(row: Row<LendingMarketTableData>, table: Table<LendingMarketTableData>) => {
return (
<MarketAssetTableRow
key={`lend-asset-${row.id}`}
isExpanded={row.getIsExpanded()}
resetExpanded={table.resetExpanded}
rowData={row}
expandedActionButtons={<LendingActionButtons data={row.original} />}
expandedDetails={<MarketDetails data={row.original} type='lend' />}
/>
)
},
[],
)
const columns = useMemo<ColumnDef<LendingMarketTableData>[]>(
() => [
{
accessorKey: 'asset.name',
header: 'Asset',
id: 'symbol',
cell: ({ row }) => {
const asset = row.original.asset
return (
<div className='flex items-center flex-1 gap-3'>
<AssetImage asset={asset} size={32} />
<TitleAndSubCell
title={asset.symbol}
sub={asset.name}
className='text-left min-w-15'
/>
</div>
)
},
},
...(shouldShowAccountDeposit
? [
{
accessorKey: 'accountDepositValue',
header: 'Deposited',
cell: ({ row }) => {
const amount = row.original.accountLentAmount
return (
<AmountAndValue
asset={row.original.asset}
amount={amount ? BN(amount) : BN_ZERO}
/>
)
},
} as ColumnDef<LendingMarketTableData>,
]
: []),
{
accessorKey: 'marketLiquidityRate',
header: 'APR',
cell: ({ row }) => {
return (
<AssetRate
rate={convertLiquidityRateToAPR(row.original.marketLiquidityRate)}
isEnabled={row.original.borrowEnabled}
className='justify-end text-xs'
type='apr'
orientation='ltr'
/>
)
},
},
{
accessorKey: 'marketDepositCap',
header: 'Depo. Cap',
cell: ({ row }) => {
const { marketDepositCap, marketDepositAmount, asset } = row.original
const remainingCap = row.original.marketDepositCap.minus(marketDepositAmount)
return (
<TitleAndSubCell
className='text-xs'
title={
<FormattedNumber
amount={marketDepositCap.toNumber()}
options={{ abbreviated: true, decimals: asset.decimals }}
animate
/>
}
sub={
<FormattedNumber
amount={remainingCap.toNumber()}
options={{ abbreviated: true, decimals: asset.decimals, suffix: ` left` }}
animate
/>
}
/>
)
},
},
{
accessorKey: 'manage',
enableSorting: false,
header: 'Manage',
cell: ({ row }) => (
<div className='flex items-center justify-end'>
<div className='w-4'>{row.getIsExpanded() ? <ChevronUp /> : <ChevronDown />}</div>
</div>
),
},
],
[shouldShowAccountDeposit],
)
return <AssetListTable title={title} rowRenderer={rowRenderer} columns={columns} data={data} />
}

View File

@ -0,0 +1,37 @@
import React from 'react'
import AvailableLendsTable from 'components/Earn/Lend/Table/AvailableLendsTable'
import DepositedLendsTable from 'components/Earn/Lend/Table/DepositedLendsTable'
import { BN_ZERO } from 'constants/math'
import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData'
import { getLendEnabledAssets } from 'utils/assets'
export default function Lends() {
const { accountLentAssets, availableAssets, allAssets } = useLendingMarketAssetsTableData()
if (!allAssets?.length) {
return <Fallback />
}
return (
<>
<DepositedLendsTable data={accountLentAssets} isLoading={false} />
<AvailableLendsTable data={availableAssets} isLoading={false} />
</>
)
}
function Fallback() {
const assets = getLendEnabledAssets()
const data: LendingMarketTableData[] = assets.map((asset) => ({
asset,
marketDepositCap: BN_ZERO,
borrowEnabled: false,
marketMaxLtv: 0,
marketDepositAmount: BN_ZERO,
marketLiquidityRate: 0,
marketLiquidityAmount: BN_ZERO,
marketLiquidationThreshold: 0,
}))
return <AvailableLendsTable data={data} isLoading />
}

View File

@ -0,0 +1,42 @@
import { Row } from '@tanstack/react-table'
import { useCallback } from 'react'
import LendingActionButtons from 'components/Earn/Lend/LendingActionButtons'
import { NAME_META } from 'components/Earn/Lend/Table/Columns/Name'
import useAvailableColumns from 'components/Earn/Lend/Table/Columns/useAvailableColumns'
import MarketDetails from 'components/MarketDetails'
import Table from 'components/Table'
import ActionButtonRow from 'components/Table/ActionButtonRow'
type Props = {
data: LendingMarketTableData[]
isLoading: boolean
}
export default function AvailableLendsTable(props: Props) {
const columns = useAvailableColumns({ isLoading: props.isLoading })
const renderExpanded = useCallback(
(row: Row<LendingMarketTableData>) => (
<>
<ActionButtonRow row={row}>
<LendingActionButtons data={row.original} />
</ActionButtonRow>
<MarketDetails row={row} type='lend' />
</>
),
[],
)
if (!props.data.length) return null
return (
<Table
title='Available Markets'
columns={columns}
data={props.data}
initialSorting={[{ id: NAME_META.id, desc: false }]}
renderExpanded={renderExpanded}
/>
)
}

View File

@ -0,0 +1,24 @@
import AssetRate from 'components/Asset/AssetRate'
import Loading from 'components/Loading'
import { convertAprToApy } from 'utils/parsers'
export const APY_META = { accessorKey: 'marketLiquidityRate', header: 'APY' }
interface Props {
marketLiquidityRate: number
borrowEnabled: boolean
isLoading: boolean
}
export default function Apr(props: Props) {
if (props.isLoading) return <Loading />
return (
<AssetRate
rate={convertAprToApy((props.marketLiquidityRate ?? 0) * 100, 365)}
isEnabled={props.borrowEnabled}
className='justify-end text-xs'
type='apy'
orientation='ltr'
/>
)
}

View File

@ -0,0 +1,55 @@
import { Row } from '@tanstack/react-table'
import { FormattedNumber } from 'components/FormattedNumber'
import Loading from 'components/Loading'
import TitleAndSubCell from 'components/TitleAndSubCell'
import { demagnify } from 'utils/formatters'
export const DEPOSIT_CAP_META = {
accessorKey: 'marketDepositCap',
header: 'Depo. Cap',
id: 'marketDepositCap',
}
export const marketDepositCapSortingFn = (
a: Row<LendingMarketTableData>,
b: Row<LendingMarketTableData>,
): number => {
const assetA = a.original.asset
const assetB = b.original.asset
if (!a.original.marketDepositCap || !b.original.marketDepositCap) return 0
const marketDepositCapA = demagnify(a.original.marketDepositCap, assetA)
const marketDepositCapB = demagnify(b.original.marketDepositCap, assetB)
return marketDepositCapA - marketDepositCapB
}
interface Props {
isLoading: boolean
data: LendingMarketTableData
}
export default function DepositCap(props: Props) {
if (props.isLoading) return <Loading />
const { marketDepositCap, marketDepositAmount, asset } = props.data
const percent = marketDepositAmount.dividedBy(marketDepositCap).multipliedBy(100)
return (
<TitleAndSubCell
className='text-xs'
title={
<FormattedNumber
amount={marketDepositCap.toNumber()}
options={{ abbreviated: true, decimals: asset.decimals }}
animate
/>
}
sub={
<FormattedNumber
amount={percent.toNumber()}
options={{ minDecimals: 2, maxDecimals: 2, suffix: '% used' }}
animate
/>
}
/>
)
}

View File

@ -0,0 +1,32 @@
import { Row } from '@tanstack/react-table'
import AmountAndValue from 'components/AmountAndValue'
import { BN_ZERO } from 'constants/math'
import { BN } from 'utils/helpers'
export const DEPOSIT_VALUE_META = {
accessorKey: 'accountLentValue',
header: 'Deposited',
}
export const depositedSortingFn = (
a: Row<LendingMarketTableData>,
b: Row<LendingMarketTableData>,
): number => {
const depositValueA = BN(a.original?.accountLentValue ?? 0)
const depositValueB = BN(b.original?.accountLentValue ?? 0)
return depositValueA.minus(depositValueB).toNumber()
}
interface Props {
asset: Asset
lentAmount?: string
}
export default function DepositValue(props: Props) {
return (
<AmountAndValue
asset={props.asset}
amount={props.lentAmount ? BN(props.lentAmount) : BN_ZERO}
/>
)
}

View File

@ -0,0 +1,16 @@
import React from 'react'
import { ChevronDown, ChevronUp } from 'components/Icons'
export const MANAGE_META = { accessorKey: 'manage', enableSorting: false, header: 'Manage' }
interface Props {
isExpanded: boolean
}
export default function Manage(props: Props) {
return (
<div className='flex items-center justify-end'>
<div className='w-4'>{props.isExpanded ? <ChevronUp /> : <ChevronDown />}</div>
</div>
)
}

View File

@ -0,0 +1,16 @@
import AssetImage from 'components/Asset/AssetImage'
import TitleAndSubCell from 'components/TitleAndSubCell'
export const NAME_META = { accessorKey: 'asset.symbol', header: 'Asset', id: 'symbol' }
interface Props {
asset: Asset
}
export default function Name(props: Props) {
const { asset } = props
return (
<div className='flex items-center flex-1 gap-3'>
<AssetImage asset={asset} size={32} />
<TitleAndSubCell title={asset.symbol} sub={asset.name} className='text-left min-w-15' />
</div>
)
}

View File

@ -0,0 +1,44 @@
import { ColumnDef } from '@tanstack/react-table'
import { useMemo } from 'react'
import Apy, { APY_META } from 'components/Earn/Lend/Table/Columns/Apy'
import DepositCap, {
DEPOSIT_CAP_META,
marketDepositCapSortingFn,
} from 'components/Earn/Lend/Table/Columns/DepositCap'
import Manage, { MANAGE_META } from 'components/Earn/Lend/Table/Columns/Manage'
import Name, { NAME_META } from 'components/Earn/Lend/Table/Columns/Name'
interface Props {
isLoading: boolean
}
export default function useAvailableColumns(props: Props) {
return useMemo<ColumnDef<LendingMarketTableData>[]>(() => {
return [
{
...NAME_META,
cell: ({ row }) => <Name asset={row.original.asset} />,
},
{
...APY_META,
cell: ({ row }) => (
<Apy
isLoading={props.isLoading}
borrowEnabled={row.original.borrowEnabled}
marketLiquidityRate={row.original.marketLiquidityRate}
/>
),
},
{
...DEPOSIT_CAP_META,
cell: ({ row }) => <DepositCap isLoading={props.isLoading} data={row.original} />,
sortingFn: marketDepositCapSortingFn,
},
{
...MANAGE_META,
cell: ({ row }) => <Manage isExpanded={row.getIsExpanded()} />,
},
]
}, [props.isLoading])
}

View File

@ -0,0 +1,55 @@
import { ColumnDef } from '@tanstack/react-table'
import { useMemo } from 'react'
import Apy, { APY_META } from 'components/Earn/Lend/Table/Columns/Apy'
import DepositCap, {
DEPOSIT_CAP_META,
marketDepositCapSortingFn,
} from 'components/Earn/Lend/Table/Columns/DepositCap'
import DepositValue, {
DEPOSIT_VALUE_META,
depositedSortingFn,
} from 'components/Earn/Lend/Table/Columns/DepositValue'
import Manage, { MANAGE_META } from 'components/Earn/Lend/Table/Columns/Manage'
import Name, { NAME_META } from 'components/Earn/Lend/Table/Columns/Name'
interface Props {
isLoading: boolean
}
export default function useDepositedColumns(props: Props) {
return useMemo<ColumnDef<LendingMarketTableData>[]>(() => {
return [
{
...NAME_META,
cell: ({ row }) => <Name asset={row.original.asset} />,
},
{
...DEPOSIT_VALUE_META,
cell: ({ row }) => (
<DepositValue asset={row.original.asset} lentAmount={row.original.accountLentAmount} />
),
sortingFn: depositedSortingFn,
},
{
...APY_META,
cell: ({ row }) => (
<Apy
isLoading={props.isLoading}
borrowEnabled={row.original.borrowEnabled}
marketLiquidityRate={row.original.marketLiquidityRate}
/>
),
},
{
...DEPOSIT_CAP_META,
cell: ({ row }) => <DepositCap isLoading={props.isLoading} data={row.original} />,
sortingFn: marketDepositCapSortingFn,
},
{
...MANAGE_META,
cell: ({ row }) => <Manage isExpanded={row.getIsExpanded()} />,
},
]
}, [props.isLoading])
}

View File

@ -0,0 +1,42 @@
import { Row } from '@tanstack/react-table'
import { useCallback } from 'react'
import LendingActionButtons from 'components/Earn/Lend/LendingActionButtons'
import { NAME_META } from 'components/Earn/Lend/Table/Columns/Name'
import useDepositedColumns from 'components/Earn/Lend/Table/Columns/useDepositedColumns'
import MarketDetails from 'components/MarketDetails'
import Table from 'components/Table'
import ActionButtonRow from 'components/Table/ActionButtonRow'
type Props = {
data: LendingMarketTableData[]
isLoading: boolean
}
export default function DepositedLendsTable(props: Props) {
const columns = useDepositedColumns({ isLoading: props.isLoading })
const renderExpanded = useCallback(
(row: Row<LendingMarketTableData>) => (
<>
<ActionButtonRow row={row}>
<LendingActionButtons data={row.original} />
</ActionButtonRow>
<MarketDetails row={row} type='lend' />
</>
),
[],
)
if (!props.data.length) return null
return (
<Table
title='Lent Assets'
columns={columns}
data={props.data}
initialSorting={[{ id: NAME_META.id, desc: false }]}
renderExpanded={renderExpanded}
/>
)
}

View File

@ -3,7 +3,7 @@ import React, { useEffect, useRef } from 'react'
import { animated, useSpring } from 'react-spring' import { animated, useSpring } from 'react-spring'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { REDUCE_MOTION_KEY } from 'constants/localStore' import { LocalStorageKeys } from 'constants/localStorageKeys'
import useLocalStorage from 'hooks/useLocalStorage' import useLocalStorage from 'hooks/useLocalStorage'
import { formatValue } from 'utils/formatters' import { formatValue } from 'utils/formatters'
@ -19,7 +19,7 @@ interface Props {
export const FormattedNumber = React.memo( export const FormattedNumber = React.memo(
(props: Props) => { (props: Props) => {
const [reduceMotion] = useLocalStorage<boolean>( const [reduceMotion] = useLocalStorage<boolean>(
REDUCE_MOTION_KEY, LocalStorageKeys.REDUCE_MOTION,
DEFAULT_SETTINGS.reduceMotion, DEFAULT_SETTINGS.reduceMotion,
) )
const prevAmountRef = useRef<number>(0) const prevAmountRef = useRef<number>(0)

View File

@ -3,7 +3,7 @@ import { ReactElement, ReactNode } from 'react'
import { Tooltip } from 'components/Tooltip' import { Tooltip } from 'components/Tooltip'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { REDUCE_MOTION_KEY } from 'constants/localStore' import { LocalStorageKeys } from 'constants/localStorageKeys'
import useLocalStorage from 'hooks/useLocalStorage' import useLocalStorage from 'hooks/useLocalStorage'
interface Props { interface Props {
@ -27,7 +27,10 @@ export const Gauge = ({
icon, icon,
labelClassName, labelClassName,
}: Props) => { }: Props) => {
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion) const [reduceMotion] = useLocalStorage<boolean>(
LocalStorageKeys.REDUCE_MOTION,
DEFAULT_SETTINGS.reduceMotion,
)
const radius = 16 const radius = 16
const percentageValue = percentage > 100 ? 100 : percentage < 0 ? 0 : percentage const percentageValue = percentage > 100 ? 100 : percentage < 0 ? 0 : percentage
const circlePercent = 100 - percentageValue const circlePercent = 100 - percentageValue

View File

@ -0,0 +1,64 @@
import { Suspense, useMemo } from 'react'
import { NAME_META } from 'components/Earn/Farm/Table/Columns/Name'
import Table from 'components/Table'
import { ENV } from 'constants/env'
import { BN_ZERO } from 'constants/math'
import { TESTNET_VAULTS_META_DATA, VAULTS_META_DATA } from 'constants/vaults'
import useVaults from 'hooks/useVaults'
import { NETWORK } from 'types/enums/network'
import useAvailableColumns from './Table/Columns/useAvailableColumns'
const title = 'Available HLS Vaults'
function Content() {
const { data: vaults } = useVaults()
const hlsVaults: Vault[] = useMemo(() => vaults?.filter((vault) => vault.hls) || [], [vaults])
const columns = useAvailableColumns({ isLoading: false })
return (
<Table
title={title}
columns={columns}
data={hlsVaults}
initialSorting={[{ id: NAME_META.id, desc: true }]}
/>
)
}
export default function AvailableHlsVaults() {
return (
<Suspense fallback={<Fallback />}>
<Content />
</Suspense>
)
}
function Fallback() {
const columns = useAvailableColumns({ isLoading: true })
const vaults = ENV.NETWORK === NETWORK.TESTNET ? TESTNET_VAULTS_META_DATA : VAULTS_META_DATA
const mockVaults: Vault[] = vaults.map((vault) => ({
...vault,
apy: null,
apr: null,
ltv: {
max: 0,
liq: 0,
},
cap: {
denom: 'denom',
used: BN_ZERO,
max: BN_ZERO,
},
}))
return (
<Table
title={title}
columns={columns}
data={mockVaults}
initialSorting={[{ id: NAME_META.id, desc: true }]}
/>
)
}

View File

@ -0,0 +1,31 @@
import React from 'react'
import Button from 'components/Button'
import { PlusSquared } from 'components/Icons'
import Intro from 'components/Intro'
import { DocURL } from 'types/enums/docURL'
export default function HlsFarmIntro() {
return (
<Intro
bg='farm'
text={
<>
<span className='text-white'>Leveraged farming</span> is a strategy where users borrow
funds to increase their yield farming position, aiming to earn more in rewards than the
cost of the borrowed assets.
</>
}
>
<Button
text='Learn how to Farm'
leftIcon={<PlusSquared />}
onClick={(e) => {
e.preventDefault()
window.open(DocURL.FARM_INTRO_URL, '_blank')
}}
color='secondary'
/>
</Intro>
)
}

View File

@ -0,0 +1,40 @@
import React from 'react'
import { FormattedNumber } from 'components/FormattedNumber'
import Loading from 'components/Loading'
import useMarketBorrowings from 'hooks/useMarketBorrowings'
export const APY_META = { accessorKey: 'apy', header: 'APY Range' }
interface Props {
vault: Vault
}
export default function Apy(props: Props) {
const { vault } = props
const { data: marketBorrowings } = useMarketBorrowings()
const borrowRate = marketBorrowings.find((asset) => asset.denom === vault.hls?.borrowDenom)
?.borrowRate
if (vault.apy === null || borrowRate === null) return <Loading />
const APYs = [vault.apy, vault.apy * (vault.hls?.maxLeverage || 1) - (borrowRate || 0) * 100]
return (
<>
<FormattedNumber
amount={Math.min(...APYs)}
options={{ minDecimals: 2, maxDecimals: 2, suffix: '-' }}
className='text-xs inline'
animate
/>
<FormattedNumber
amount={Math.max(...APYs)}
options={{ minDecimals: 2, maxDecimals: 2, suffix: '%' }}
className='text-xs inline'
animate
/>
</>
)
}

View File

@ -0,0 +1,104 @@
import classNames from 'classnames'
import { useCallback } from 'react'
import ActionButton from 'components/Button/ActionButton'
import { Enter } from 'components/Icons'
import Loading from 'components/Loading'
import Text from 'components/Text'
import { LocalStorageKeys } from 'constants/localStorageKeys'
import useAlertDialog from 'hooks/useAlertDialog'
import useLocalStorage from 'hooks/useLocalStorage'
export const DEPOSIT_META = { accessorKey: 'deposit', header: 'Deposit' }
interface Props {
vault: Vault
isLoading: boolean
}
export default function Deposit(props: Props) {
const { vault } = props
const [showHlsInfo, setShowHlsInfo] = useLocalStorage<boolean>(
LocalStorageKeys.HLS_INFORMATION,
true,
)
const { open: openAlertDialog, close } = useAlertDialog()
const showHlsInfoModal = useCallback(() => {
if (!showHlsInfo) return
openAlertDialog({
title: 'Understanding HLS Positions',
content: (
<div className='flex flex-col gap-8 pt-2 pb-8 pr-10'>
{INFO_ITEMS.map((item) => (
<div key={item.title} className='grid grid-cols-[min-content,auto]'>
<span
className={classNames(
'rounded-sm relative h-10 w-10 p-3 bg-white/10 mr-6 grid place-items-center',
'before:content-[" "] before:absolute before:inset-0 before:rounded-sm before:p-[1px] before:border-glas before:-z-1',
)}
>
{item.icon}
</span>
<span className='flex flex-col'>
<Text>{item.title}</Text>
<Text className='text-sm text-white/60'>{item.description}</Text>
</span>
</div>
))}
</div>
),
positiveButton: {
text: 'Continue',
icon: <Enter />,
onClick: enterVaultHandler,
},
negativeButton: {
text: 'Cancel',
onClick: () => {
setShowHlsInfo(true)
close()
},
},
checkbox: {
text: "Don't show again",
onClick: (isChecked: boolean) => setShowHlsInfo(!isChecked),
},
})
}, [close, enterVaultHandler, openAlertDialog, setShowHlsInfo, showHlsInfo])
function enterVaultHandler() {
showHlsInfoModal()
return
}
if (props.isLoading) return <Loading />
return (
<div className='flex items-center justify-end'>
<ActionButton onClick={enterVaultHandler} color='tertiary' text='Deposit' />
</div>
)
}
const INFO_ITEMS = [
{
icon: <Enter width={16} height={16} />,
title: 'One account, one position',
description:
'A minted HLS account can only have a single position tied to it, in order to limit risk.',
},
{
icon: <Enter />,
title: 'Funded from your wallet',
description: 'To fund your HLS position, funds will have to come directly from your wallet.',
},
{
icon: <Enter />,
title: 'Accounts are reusable',
description:
'If you exited a position from a minted account, this account can be reused for a new position.',
},
]

View File

@ -0,0 +1,19 @@
import React from 'react'
import { FormattedNumber } from 'components/FormattedNumber'
export const MAX_LEV_META = { accessorKey: 'hls.maxLeverage', header: 'Max Leverage' }
interface Props {
vault: Vault
}
export default function MaxLeverage(props: Props) {
return (
<FormattedNumber
amount={props.vault.hls?.maxLeverage || 1}
options={{ minDecimals: 2, maxDecimals: 2, suffix: 'x' }}
className='text-xs'
animate
/>
)
}

View File

@ -0,0 +1,51 @@
import { ColumnDef } from '@tanstack/react-table'
import React, { useMemo } from 'react'
import DepositCap, { DEPOSIT_CAP_META } from 'components/Earn/Farm/Table/Columns/DepositCap'
import MaxLTV, { LTV_MAX_META } from 'components/Earn/Farm/Table/Columns/MaxLTV'
import Name, { NAME_META } from 'components/Earn/Farm/Table/Columns/Name'
import TVL, { TVL_META } from 'components/Earn/Farm/Table/Columns/TVL'
import Apy, { APY_META } from 'components/HLS/Farm/Table/Columns/APY'
import Deposit, { DEPOSIT_META } from 'components/HLS/Farm/Table/Columns/Deposit'
import MaxLeverage, { MAX_LEV_META } from 'components/HLS/Farm/Table/Columns/MaxLeverage'
interface Props {
isLoading: boolean
}
export default function useAvailableColumns(props: Props) {
// const
return useMemo<ColumnDef<Vault>[]>(
() => [
{
...NAME_META,
cell: ({ row }) => <Name vault={row.original as Vault} />,
},
{
...APY_META,
cell: ({ row }) => <Apy vault={row.original as Vault} />,
},
{
...MAX_LEV_META,
cell: ({ row }) => <MaxLeverage vault={row.original} />,
},
{
...TVL_META,
cell: ({ row }) => <TVL vault={row.original as Vault} isLoading={props.isLoading} />,
},
{
...DEPOSIT_CAP_META,
cell: ({ row }) => <DepositCap vault={row.original as Vault} isLoading={props.isLoading} />,
},
{
...LTV_MAX_META,
cell: ({ row }) => <MaxLTV vault={row.original as Vault} isLoading={props.isLoading} />,
},
{
...DEPOSIT_META,
cell: ({ row }) => <Deposit vault={row.original as Vault} isLoading={props.isLoading} />,
},
],
[props.isLoading],
)
}

View File

@ -0,0 +1,7 @@
interface Props {
data: HLSStrategy[] | DepositedHLSStrategy[]
}
export default function Index(props: Props) {
return null
}

View File

@ -0,0 +1,3 @@
export default function AvailableHlsStakingAssets() {
return null
}

View File

@ -0,0 +1,31 @@
import React from 'react'
import Button from 'components/Button'
import { PlusSquared } from 'components/Icons'
import Intro from 'components/Intro'
import { DocURL } from 'types/enums/docURL'
export default function HLSStakingIntro() {
return (
<Intro
bg='borrow'
text={
<>
<span className='text-white'>Leverage staking</span> is a strategy where users borrow
funds to increase their staking position, aiming to earn more in rewards than the cost of
the borrowed assets.
</>
}
>
<Button
text='Learn how to Stake'
leftIcon={<PlusSquared />}
onClick={(e) => {
e.preventDefault()
window.open(DocURL.FARM_INTRO_URL, '_blank')
}}
color='secondary'
/>
</Intro>
)
}

View File

@ -4,7 +4,7 @@ import { useMemo } from 'react'
import { Heart } from 'components/Icons' import { Heart } from 'components/Icons'
import { Tooltip } from 'components/Tooltip' import { Tooltip } from 'components/Tooltip'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { REDUCE_MOTION_KEY } from 'constants/localStore' import { LocalStorageKeys } from 'constants/localStorageKeys'
import useHealthColorAndLabel from 'hooks/useHealthColorAndLabel' import useHealthColorAndLabel from 'hooks/useHealthColorAndLabel'
import useLocalStorage from 'hooks/useLocalStorage' import useLocalStorage from 'hooks/useLocalStorage'
import { computeHealthGaugePercentage } from 'utils/accounts' import { computeHealthGaugePercentage } from 'utils/accounts'
@ -25,7 +25,10 @@ const ROTATION = {
export const HealthGauge = ({ diameter = 40, health = 0, updatedHealth = 0 }: Props) => { export const HealthGauge = ({ diameter = 40, health = 0, updatedHealth = 0 }: Props) => {
const [color, label] = useHealthColorAndLabel(health, 'text') const [color, label] = useHealthColorAndLabel(health, 'text')
const [updatedColor, updatedLabel] = useHealthColorAndLabel(updatedHealth ?? 0, 'text') const [updatedColor, updatedLabel] = useHealthColorAndLabel(updatedHealth ?? 0, 'text')
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion) const [reduceMotion] = useLocalStorage<boolean>(
LocalStorageKeys.REDUCE_MOTION,
DEFAULT_SETTINGS.reduceMotion,
)
const percentage = useMemo(() => computeHealthGaugePercentage(health), [health]) const percentage = useMemo(() => computeHealthGaugePercentage(health), [health])
const updatedPercentage = useMemo( const updatedPercentage = useMemo(
() => computeHealthGaugePercentage(updatedHealth), () => computeHealthGaugePercentage(updatedHealth),
@ -77,6 +80,140 @@ export const HealthGauge = ({ diameter = 40, health = 0, updatedHealth = 0 }: Pr
/> />
</g> </g>
</mask> </mask>
<mask id='backgroundMask'>
<g>
<path
fill='white'
d='M137.1,538.3c-5.8-6.5-11.3-13.3-16.5-20.4l-57.8,31.9c7.4,10.5,15.3,20.7,23.8,30.3L137.1,538.3z'
/>
<path
fill='white'
d='M179.4,577.1c-8.1-6-15.8-12.4-23.1-19.2L105.5,600c10.3,10,21.3,19.5,32.9,28.3L179.4,577.1z'
/>
<path
fill='white'
d='M79.5,437.5c-5.6-17.2-24.1-26.6-41.3-21.1c-7.9,2.6-14.3,8-18.2,14.8c-4.5,7.7-5.8,17.2-2.8,26.5c0.3,1,0.6,1.9,1,2.9
l62.7-19.4C80.3,440,79.9,438.7,79.5,437.5z'
/>
<path
fill='white'
d='M105.6,495.1c-5.4-9.2-10.4-18.7-14.8-28.5l-63,19.5c5.9,14.1,12.8,27.7,20.4,40.7L105.6,495.1z'
/>
<path
fill='white'
d='M223.5,604.3c-7.3-3.6-14.5-7.6-21.5-11.9L160.8,644c10.4,6.7,21.1,12.8,32,18.3L223.5,604.3z'
/>
<path
fill='white'
d='M275.6,624c-9.2-2.5-18.2-5.4-27.1-8.8l-30.8,58.4c13,5.3,26.2,9.8,39.8,13.5L275.6,624z'
/>
<path
fill='white'
d='M352.5,633.9l-6.1,65.4c1.1,0,2.1,0,3.2,0c18.1,0,32.8-14.7,32.8-32.8C382.4,649.4,369.3,635.4,352.5,633.9z'
/>
<path
fill='white'
d='M325.3,632.7c-7.7-0.6-15.4-1.6-22.9-2.9l-18.3,63.4c11.6,2.2,23.3,3.8,35.1,4.8L325.3,632.7z'
/>
<path
fill='white'
d='M519.5,577.5c-5.8,4.3-11.8,8.4-17.9,12.3l31.7,57.4c9.7-6,19.1-12.4,28.1-19.3L519.5,577.5z'
/>
<path
fill='white'
d='M559.1,541.7c-5.9,6.5-12.1,12.6-18.5,18.4l41.9,50.5c9.8-8.8,19.1-18.1,27.9-28L559.1,541.7z'
/>
<path
fill='white'
d='M477.9,603.2c-7.8,3.9-15.9,7.5-24.1,10.8l19.4,62.7c12.5-4.7,24.7-10.1,36.4-16.1L477.9,603.2z'
/>
<path
fill='white'
d='M589,502.6c-3,4.7-6.2,9.4-9.5,14c-1,1.4-2,2.7-3,4.1l51.2,40.9c1.6-2.1,3.3-4.3,4.9-6.5c5.2-7.1,10-14.4,14.6-21.7
L589,502.6z'
/>
<path
fill='white'
d='M666.6,316.8c-18.1,0-32.8,14.7-32.8,32.8c0,10.1-0.5,20-1.6,29.9l65.3,6.1c1.2-11.9,1.8-23.9,1.8-36
C699.4,331.4,684.7,316.7,666.6,316.8z'
/>
<path
fill='white'
d='M428.4,624.4c-3.8,2.7-6.9,6.2-9.2,10.2c-4.5,7.8-5.7,17.4-2.8,26.4c4.4,13.6,16.8,22.3,30.3,22.6L428.4,624.4z'
/>
<path
fill='white'
d='M613.8,454.5c-3.3,8.3-7,16.5-11.1,24.5l58,30.6c6.2-12,11.7-24.3,16.4-36.9L613.8,454.5z'
/>
<path
fill='white'
d='M628.1,406.5c-1.5,7.5-3.3,14.9-5.5,22.2l63.1,18.2c3.3-11.3,5.9-22.7,8-34.3L628.1,406.5z'
/>
<path
fill='white'
d='M458.8,87.2c10.3,4.3,20.3,9.2,30.1,14.7l30.6-58c-13.7-7.6-27.9-14.3-42.4-20L458.8,87.2z'
/>
<path
fill='white'
d='M512,116.4c7.5,5.3,14.8,10.9,21.8,16.8l40.9-51.2c-10.1-8.5-20.8-16.5-31.9-23.9L512,116.4z'
/>
<path
fill='white'
d='M343.4,65.5c11.6-0.3,23.1,0.2,34.6,1.4L384,1.6c-15.9-1.6-31.9-2.1-48-1.4L343.4,65.5z'
/>
<path
fill='white'
d='M574.3,175.6l50.5-41.9c-9.2-11.8-19.2-22.9-29.9-33.4l-41.2,51.6C561,159.4,567.9,167.3,574.3,175.6z'
/>
<path
fill='white'
d='M32.7,382.2c16.6,0,30.2-12.2,32.5-28.1L0,348c0,0.5,0,0.9,0,1.4C0,367.6,14.7,382.2,32.7,382.2z'
/>
<path
fill='white'
d='M590,198c4.6,7.3,8.8,14.7,12.8,22.4l57.4-31.7c-5.9-11.3-12.3-22.3-19.4-32.9L590,198z'
/>
<path
fill='white'
d='M619.8,261.7c5.6,17.2,24.1,26.6,41.3,21.1c17.2-5.6,26.6-24.1,21.1-41.3c-3.1-9.5-6.6-18.9-10.5-28L614,245.4
C616.1,250.8,618.1,256.2,619.8,261.7z'
/>
<path
fill='white'
d='M404.9,70.8c9.5,1.9,18.9,4.3,28.1,7.1L451.2,15C438,11,424.6,7.8,411,5.3L404.9,70.8z'
/>
<path
fill='white'
d='M286.5,72.5c6.2-1.4,12.4-2.6,18.7-3.6c3.6-0.6,7.3-1.1,10.9-1.5l-7.3-65.1c-4.6,0.5-9.2,1.2-13.8,1.9
c-9.4,1.5-18.7,3.4-27.8,5.6L286.5,72.5z'
/>
<path
fill='white'
d='M66.5,326.8c0.8-9.8,2.1-19.4,3.8-29L7.1,279.6c-2.8,13.6-4.8,27.3-6,41.1L66.5,326.8z'
/>
<path
fill='white'
d='M132.5,166.2c0.4-0.4,0.7-0.9,1.1-1.3c7.4-8.6,15.2-16.7,23.5-24.3l-41.8-50.4c-11.1,10-21.6,20.8-31.4,32.2
c-0.8,1-1.6,2-2.5,3L132.5,166.2z'
/>
<path
fill='white'
d='M100.1,213.6c4.8-8.8,10.1-17.4,15.8-25.7L64.8,147c-6.4,9-12.3,18.2-17.8,27.7c-1.6,2.8-3.1,5.6-4.7,8.4L100.1,213.6z'
/>
<path
fill='white'
d='M195.5,35.8l31.7,57.4c10.7-5.1,21.8-9.6,33.1-13.3l-19.3-62.6C225.4,22.4,210.2,28.6,195.5,35.8z'
/>
<path
fill='white'
d='M76.5,271.2c3.2-11.2,7.1-22.3,11.7-33l-58-30.6c-6.6,14.8-12.1,30-16.6,45.5L76.5,271.2z'
/>
<path
fill='white'
d='M178,123.1c8.1-6.1,16.5-11.8,25.2-17.1l-31.7-57.3c-12.2,7.3-24.1,15.3-35.4,24L178,123.1z'
/>
</g>
</mask>
<circle <circle
r={RADIUS} r={RADIUS}
cx={RADIUS} cx={RADIUS}
@ -97,7 +234,7 @@ export const HealthGauge = ({ diameter = 40, health = 0, updatedHealth = 0 }: Pr
strokeDashoffset={isUpdated && isIncrease ? updatedPercentage : percentage} strokeDashoffset={isUpdated && isIncrease ? updatedPercentage : percentage}
strokeLinecap='round' strokeLinecap='round'
style={ROTATION} style={ROTATION}
mask='url(#mask)' mask={isUpdated ? 'url(#backgroundMask)' : 'url(#mask)'}
className={classNames( className={classNames(
backgroundColor, backgroundColor,
'stroke-current', 'stroke-current',

View File

@ -1,73 +0,0 @@
import { flexRender, Row } from '@tanstack/react-table'
import classNames from 'classnames'
import Text from 'components/Text'
type Props<TData> = {
rowData: Row<TData>
resetExpanded: (defaultState?: boolean | undefined) => void
isExpanded: boolean
expandedActionButtons?: JSX.Element
expandedDetails?: JSX.Element
}
function AssetListTableRow<TData>(props: Props<TData>) {
const renderFullRow = (key: string, content: JSX.Element) => (
<tr key={key} className='bg-black/20'>
<td
className='border-b border-white border-opacity-10 p-4'
colSpan={props.rowData.getAllCells().length}
>
{content}
</td>
</tr>
)
const renderExpanded = () => {
return (
<>
{props.expandedActionButtons &&
renderFullRow(
`${props.rowData.id}-expanded-actions`,
<div className='flex flex-1 flex-row justify-between'>
<Text className='mt-1 flex p-0 font-bold' size='base'>
Details
</Text>
<div>{props.expandedActionButtons}</div>
</div>,
)}
{props.expandedDetails &&
renderFullRow(`${props.rowData.id}-expanded-details`, props.expandedDetails)}
</>
)
}
return (
<>
<tr
key={props.rowData.id}
className={classNames(
'hover:cursor-pointer transition-colors',
props.rowData.getIsExpanded() ? 'bg-black/20' : 'bg-white/0 hover:bg-white/5',
)}
onClick={() => {
const isExpanded = props.rowData.getIsExpanded()
props.resetExpanded()
!isExpanded && props.rowData.toggleExpanded()
}}
>
{props.rowData.getVisibleCells().map((cell) => {
return (
<td key={cell.id} className={'p-4 text-right'}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
)
})}
</tr>
{props.isExpanded && renderExpanded()}
</>
)
}
export default AssetListTableRow

View File

@ -1,3 +1,4 @@
import { Row } from '@tanstack/react-table'
import { useMemo } from 'react' import { useMemo } from 'react'
import { FormattedNumber } from 'components/FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
@ -5,7 +6,7 @@ import TitleAndSubCell from 'components/TitleAndSubCell'
import useDisplayCurrencyPrice from 'hooks/useDisplayCurrencyPrice' import useDisplayCurrencyPrice from 'hooks/useDisplayCurrencyPrice'
interface Props { interface Props {
data: BorrowMarketTableData | LendingMarketTableData row: Row<BorrowMarketTableData | LendingMarketTableData>
type: 'borrow' | 'lend' type: 'borrow' | 'lend'
} }
@ -15,7 +16,7 @@ interface Detail {
title: string title: string
} }
export default function MarketDetails({ data, type }: Props) { export default function MarketDetails({ row, type }: Props) {
const { const {
convertAmount, convertAmount,
getConversionRate, getConversionRate,
@ -28,7 +29,7 @@ export default function MarketDetails({ data, type }: Props) {
marketDepositAmount, marketDepositAmount,
marketLiquidityAmount, marketLiquidityAmount,
marketLiquidationThreshold, marketLiquidationThreshold,
} = data } = row.original
const totalBorrowed = marketDepositAmount.minus(marketLiquidityAmount) const totalBorrowed = marketDepositAmount.minus(marketLiquidityAmount)
@ -36,7 +37,6 @@ export default function MarketDetails({ data, type }: Props) {
const isDollar = displayCurrencySymbol === '$' const isDollar = displayCurrencySymbol === '$'
function getLendingMarketDetails() { function getLendingMarketDetails() {
const depositCap = (data as LendingMarketTableData).marketDepositCap
return [ return [
{ {
amount: convertAmount(asset, marketDepositAmount).toNumber(), amount: convertAmount(asset, marketDepositAmount).toNumber(),
@ -107,7 +107,6 @@ export default function MarketDetails({ data, type }: Props) {
if (type === 'lend') return getLendingMarketDetails() if (type === 'lend') return getLendingMarketDetails()
return getBorrowMarketDetails() return getBorrowMarketDetails()
}, [ }, [
data,
type, type,
asset, asset,
marketDepositAmount, marketDepositAmount,
@ -120,23 +119,27 @@ export default function MarketDetails({ data, type }: Props) {
]) ])
return ( return (
<div className='flex justify-between flex-1 bg-white rounded-md bg-opacity-5'> <tr>
{details.map((detail, index) => ( <td colSpan={row.getAllCells().length} className='p-4 border-b bg-black/20 border-white/10'>
<TitleAndSubCell <div className='flex justify-between flex-1 rounded-md bg-white/5'>
key={index} {details.map((detail, index) => (
className='text-center' <TitleAndSubCell
containerClassName='m-5 ml-10 mr-10 space-y-1' key={index}
title={ className='text-center'
<FormattedNumber containerClassName='m-5 ml-10 mr-10 space-y-1'
className='text-xs text-center' title={
amount={detail.amount} <FormattedNumber
options={detail.options} className='text-xs text-center'
animate amount={detail.amount}
options={detail.options}
animate
/>
}
sub={detail.title}
/> />
} ))}
sub={detail.title} </div>
/> </td>
))} </tr>
</div>
) )
} }

View File

@ -3,14 +3,14 @@ import { Cross, ExclamationMarkCircled } from 'components/Icons'
import Text from 'components/Text' import Text from 'components/Text'
import { TextLink } from 'components/TextLink' import { TextLink } from 'components/TextLink'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { MIGRATION_BANNER_KEY } from 'constants/localStore' import { LocalStorageKeys } from 'constants/localStorageKeys'
import useLocalStorage from 'hooks/useLocalStorage' import useLocalStorage from 'hooks/useLocalStorage'
import useStore from 'store' import useStore from 'store'
import { DocURL } from 'types/enums/docURL' import { DocURL } from 'types/enums/docURL'
export default function MigrationBanner() { export default function MigrationBanner() {
const [_, setMigrationBanner] = useLocalStorage<boolean>( const [_, setMigrationBanner] = useLocalStorage<boolean>(
MIGRATION_BANNER_KEY, LocalStorageKeys.MIGRATION_BANNER,
DEFAULT_SETTINGS.migrationBanner, DEFAULT_SETTINGS.migrationBanner,
) )

View File

@ -4,25 +4,22 @@ import { Enter, InfoCircle } from 'components/Icons'
import useAlertDialog from 'hooks/useAlertDialog' import useAlertDialog from 'hooks/useAlertDialog'
interface Props { interface Props {
content: string | JSX.Element
title: string title: string
description: string | JSX.Element icon?: JSX.Element
closeHandler: () => void closeHandler: () => void
positiveButton: AlertDialogButton positiveButton: AlertDialogButton
} }
export default function AccountDeleteAlertDialog(props: Props) { export default function AccountDeleteAlertDialog(props: Props) {
const { open: showAlertDialog } = useAlertDialog() const { open: showAlertDialog } = useAlertDialog()
const { title, description, closeHandler, positiveButton } = props const { title, content, closeHandler, positiveButton } = props
useEffect(() => { useEffect(() => {
showAlertDialog({ showAlertDialog({
icon: ( icon: <InfoCircle />,
<div className='flex w-full h-full p-3'>
<InfoCircle />
</div>
),
title, title,
description, content,
negativeButton: { negativeButton: {
text: 'Cancel', text: 'Cancel',
icon: <Enter />, icon: <Enter />,
@ -30,7 +27,7 @@ export default function AccountDeleteAlertDialog(props: Props) {
}, },
positiveButton, positiveButton,
}) })
}, [showAlertDialog, title, description, closeHandler, positiveButton]) }, [showAlertDialog, closeHandler, positiveButton, title, content])
return null return null
} }

View File

@ -2,7 +2,7 @@ import { useCallback, useMemo } from 'react'
import { useLocation, useNavigate, useParams } from 'react-router-dom' import { useLocation, useNavigate, useParams } from 'react-router-dom'
import AssetBalanceRow from 'components/Asset/AssetBalanceRow' import AssetBalanceRow from 'components/Asset/AssetBalanceRow'
import { ArrowRight } from 'components/Icons' import { ArrowRight, ExclamationMarkCircled } from 'components/Icons'
import AccountDeleteAlertDialog from 'components/Modals/Account/AccountDeleteAlertDialog' import AccountDeleteAlertDialog from 'components/Modals/Account/AccountDeleteAlertDialog'
import Text from 'components/Text' import Text from 'components/Text'
import useStore from 'store' import useStore from 'store'
@ -51,7 +51,8 @@ function AccountDeleteModal(props: Props) {
return ( return (
<AccountDeleteAlertDialog <AccountDeleteAlertDialog
title='Repay your Debts to delete your account' title='Repay your Debts to delete your account'
description='You must repay all borrowings before deleting your account.' icon={<ExclamationMarkCircled width={18} />}
content='You must repay all borrowings before deleting your account.'
closeHandler={closeDeleteAccountModal} closeHandler={closeDeleteAccountModal}
positiveButton={{ positiveButton={{
text: 'Repay Debts', text: 'Repay Debts',
@ -68,7 +69,7 @@ function AccountDeleteModal(props: Props) {
return ( return (
<AccountDeleteAlertDialog <AccountDeleteAlertDialog
title='Close your positions to delete your account' title='Close your positions to delete your account'
description='You must first close your farming positions before deleting your account.' content='You must first close your farming positions before deleting your account.'
closeHandler={closeDeleteAccountModal} closeHandler={closeDeleteAccountModal}
positiveButton={{ positiveButton={{
text: 'Close Positions', text: 'Close Positions',
@ -85,7 +86,7 @@ function AccountDeleteModal(props: Props) {
return ( return (
<AccountDeleteAlertDialog <AccountDeleteAlertDialog
title={`Delete Credit Account ${accountId}`} title={`Delete Credit Account ${accountId}`}
description='Deleting your Credit Account is irreversible.' content='Deleting your Credit Account is irreversible.'
closeHandler={closeDeleteAccountModal} closeHandler={closeDeleteAccountModal}
positiveButton={{ positiveButton={{
text: 'Delete Account', text: 'Delete Account',
@ -98,7 +99,7 @@ function AccountDeleteModal(props: Props) {
return ( return (
<AccountDeleteAlertDialog <AccountDeleteAlertDialog
title={`Delete Credit Account ${accountId}`} title={`Delete Credit Account ${accountId}`}
description={ content={
<> <>
<Text className='mt-2 text-white/50' size='sm'> <Text className='mt-2 text-white/50' size='sm'>
The following assets within your Credit Account will be sent to your wallet. The following assets within your Credit Account will be sent to your wallet.

View File

@ -75,7 +75,7 @@ export default function AddVaultAssetsModalContent(props: Props) {
return ( return (
<> <>
<div className='border-b border-white/5 bg-white/10 px-4 py-3'> <div className='px-4 py-3 border-b border-white/5 bg-white/10'>
<SearchBar <SearchBar
value={searchString} value={searchString}
placeholder={`Search for e.g. "ETH" or "Ethereum"`} placeholder={`Search for e.g. "ETH" or "Ethereum"`}
@ -90,6 +90,7 @@ export default function AddVaultAssetsModalContent(props: Props) {
</Text> </Text>
</div> </div>
<AssetSelectTable <AssetSelectTable
isBorrow={true}
assets={poolAssets} assets={poolAssets}
onChangeSelected={onChangePoolDenoms} onChangeSelected={onChangePoolDenoms}
selectedDenoms={selectedPoolDenoms} selectedDenoms={selectedPoolDenoms}
@ -102,6 +103,7 @@ export default function AddVaultAssetsModalContent(props: Props) {
</Text> </Text>
</div> </div>
<AssetSelectTable <AssetSelectTable
isBorrow={true}
assets={stableAssets} assets={stableAssets}
onChangeSelected={onChangeOtherDenoms} onChangeSelected={onChangeOtherDenoms}
selectedDenoms={selectedOtherDenoms} selectedDenoms={selectedOtherDenoms}

View File

@ -40,7 +40,9 @@ export default function AddVaultBorrowAssetsModal() {
onChangeBorrowDenoms={updateSelectedDenoms} onChangeBorrowDenoms={updateSelectedDenoms}
/> />
) : ( ) : (
<CircularProgress /> <div className='flex items-center justify-center w-full h-[380px]'>
<CircularProgress />
</div>
)} )}
<div className='flex w-full p-4'> <div className='flex w-full p-4'>
<Button className='w-full' onClick={onClose} color='tertiary' text='Select Assets' /> <Button className='w-full' onClick={onClose} color='tertiary' text='Select Assets' />

View File

@ -1,12 +1,12 @@
import classNames from 'classnames' import classNames from 'classnames'
import { useState } from 'react'
import Button from 'components/Button' import Button from 'components/Button'
import { ExclamationMarkCircled } from 'components/Icons' import Checkbox from 'components/Checkbox'
import Modal from 'components/Modal' import Modal from 'components/Modal'
import { NoIcon, YesIcon } from 'components/Modals/AlertDialog/ButtonIcons' import { NoIcon, YesIcon } from 'components/Modals/AlertDialog/ButtonIcons'
import Text from 'components/Text' import Text from 'components/Text'
import useAlertDialog from 'hooks/useAlertDialog' import useAlertDialog from 'hooks/useAlertDialog'
import useToggle from 'hooks/useToggle'
export default function AlertDialogController() { export default function AlertDialogController() {
const { config, close } = useAlertDialog() const { config, close } = useAlertDialog()
@ -22,19 +22,32 @@ interface Props {
} }
function AlertDialog(props: Props) { function AlertDialog(props: Props) {
const { title, icon, description, negativeButton, positiveButton } = props.config const { title, icon, content, negativeButton, positiveButton, checkbox } = props.config
const [toggle, handleToggle] = useToggle()
const handleButtonClick = (button?: AlertDialogButton) => { const handleButtonClick = (button?: AlertDialogButton) => {
button?.onClick && button.onClick() button?.onClick && button.onClick()
props.close() props.close()
} }
function handleCheckboxClick() {
handleToggle()
checkbox?.onClick(!toggle)
}
return ( return (
<Modal <Modal
onClose={props.close} onClose={props.close}
hideTxLoader hideTxLoader
header={ header={
<div className='grid w-12 h-12 rounded-sm place-items-center bg-white/5'> <div className='flex flex-col'>
{icon ?? <ExclamationMarkCircled width={18} />} {icon && (
<div className='grid w-12 h-12 rounded-sm place-items-center bg-white/5 mb-4'>
{icon}
</div>
)}
<Text size='2xl'>{title}</Text>
</div> </div>
} }
modalClassName='max-w-modal-sm' modalClassName='max-w-modal-sm'
@ -42,24 +55,28 @@ function AlertDialog(props: Props) {
contentClassName='px-8 pb-8' contentClassName='px-8 pb-8'
hideCloseBtn hideCloseBtn
> >
<Text size='xl'>{title}</Text> {typeof content === 'string' ? (
{typeof description === 'string' ? ( <Text className='mt-2 text-white/60'>{content}</Text>
<Text className='mt-2 text-white/60'>{description}</Text>
) : ( ) : (
description content
)} )}
<div <div
className={classNames('mt-10 flex justify-between', positiveButton && 'flex-row-reverse')} className={classNames('mt-10 flex justify-between', positiveButton && 'flex-row-reverse')}
> >
{positiveButton && ( <div className='flex flex-row-reverse gap-4'>
<Button {positiveButton && (
text={positiveButton.text ?? 'Yes'} <Button
color='primary' text={positiveButton.text ?? 'Yes'}
className='px-6' color='tertiary'
rightIcon={positiveButton.icon ?? <YesIcon />} className='px-6'
onClick={() => handleButtonClick(positiveButton)} rightIcon={positiveButton.icon ?? <YesIcon />}
/> onClick={() => handleButtonClick(positiveButton)}
)} />
)}
{checkbox && (
<Checkbox checked={toggle} onChange={handleCheckboxClick} text={checkbox.text} />
)}
</div>
<Button <Button
text={negativeButton?.text ?? 'No'} text={negativeButton?.text ?? 'No'}
color='secondary' color='secondary'

View File

@ -20,6 +20,7 @@ interface Props {
assets: Asset[] | BorrowAsset[] assets: Asset[] | BorrowAsset[]
selectedDenoms: string[] selectedDenoms: string[]
onChangeSelected: (denoms: string[]) => void onChangeSelected: (denoms: string[]) => void
isBorrow: boolean
} }
export default function AssetSelectTable(props: Props) { export default function AssetSelectTable(props: Props) {
@ -39,7 +40,7 @@ export default function AssetSelectTable(props: Props) {
const [sorting, setSorting] = useState<SortingState>([{ id: 'symbol', desc: false }]) const [sorting, setSorting] = useState<SortingState>([{ id: 'symbol', desc: false }])
const [selected, setSelected] = useState<RowSelectionState>(defaultSelected) const [selected, setSelected] = useState<RowSelectionState>(defaultSelected)
const balances = useStore((s) => s.balances) const balances = useStore((s) => s.balances)
const columns = useAssetTableColumns() const columns = useAssetTableColumns(props.isBorrow)
const tableData: AssetTableRow[] = useMemo(() => { const tableData: AssetTableRow[] = useMemo(() => {
return props.assets.map((asset) => { return props.assets.map((asset) => {
const balancesForAsset = balances.find(byDenom(asset.denom)) const balancesForAsset = balances.find(byDenom(asset.denom))
@ -79,7 +80,7 @@ export default function AssetSelectTable(props: Props) {
return ( return (
<table className='w-full'> <table className='w-full'>
<thead className='border-b border-white/5'> <thead className='border-b border-white/10'>
{table.getHeaderGroups().map((headerGroup) => ( {table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}> <tr key={headerGroup.id}>
{headerGroup.headers.map((header) => { {headerGroup.headers.map((header) => {

View File

@ -17,7 +17,7 @@ function showBorrowRate(data: AssetTableRow[]) {
return !!(assetData && assetData?.borrowRate) return !!(assetData && assetData?.borrowRate)
} }
export default function useAssetTableColumns() { export default function useAssetTableColumns(isBorrow: boolean) {
return React.useMemo<ColumnDef<AssetTableRow>[]>( return React.useMemo<ColumnDef<AssetTableRow>[]>(
() => [ () => [
{ {
@ -29,6 +29,9 @@ export default function useAssetTableColumns() {
const market = row.original.market const market = row.original.market
const borrowAsset = row.original.asset as BorrowAsset const borrowAsset = row.original.asset as BorrowAsset
const showRate = !borrowAsset?.borrowRate const showRate = !borrowAsset?.borrowRate
const rate = isBorrow ? market?.borrowRate : market?.liquidityRate
const apy = convertAprToApy((rate ?? 0) * 100, 365)
return ( return (
<div className='flex items-center'> <div className='flex items-center'>
<Checkbox checked={row.getIsSelected()} onChange={row.getToggleSelectedHandler()} /> <Checkbox checked={row.getIsSelected()} onChange={row.getToggleSelectedHandler()} />
@ -39,7 +42,7 @@ export default function useAssetTableColumns() {
</Text> </Text>
{showRate && market ? ( {showRate && market ? (
<AssetRate <AssetRate
rate={convertAprToApy(market.borrowRate * 100, 365)} rate={apy}
isEnabled={market.borrowEnabled} isEnabled={market.borrowEnabled}
className='text-xs' className='text-xs'
type='apy' type='apy'
@ -85,6 +88,6 @@ export default function useAssetTableColumns() {
}, },
}, },
], ],
[], [isBorrow],
) )
} }

View File

@ -24,7 +24,7 @@ import { getDepositAndLendCoinsToSpend } from 'hooks/useUpdatedAccount/functions
import useStore from 'store' import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { byDenom } from 'utils/array' import { byDenom } from 'utils/array'
import { formatPercent, formatValue } from 'utils/formatters' import { formatPercent } from 'utils/formatters'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
import { getDebtAmountWithInterest } from 'utils/tokens' import { getDebtAmountWithInterest } from 'utils/tokens'
@ -204,15 +204,32 @@ function BorrowModal(props: Props) {
title={formatPercent(modal.marketData.borrowRate || '0')} title={formatPercent(modal.marketData.borrowRate || '0')}
sub={'Borrow rate'} sub={'Borrow rate'}
/> />
<div className='h-100 w-[1px] bg-white/10' /> {totalDebt.isGreaterThan(0) && (
<TitleAndSubCell <>
title={formatValue(getDebtAmount(modal), { <div className='h-100 w-[1px] bg-white/10' />
abbreviated: false, <div className='flex flex-col gap-0.5'>
decimals: asset.decimals, <div className='flex gap-2'>
maxDecimals: asset.decimals, <FormattedNumber
})} className='text-xs'
sub={'Borrowed'} amount={totalDebt.toNumber()}
/> options={{
decimals: asset.decimals,
abbreviated: false,
suffix: ` ${asset.symbol}`,
}}
/>
<DisplayCurrency
className='text-xs'
coin={BNCoin.fromDenomAndBigNumber(asset.denom, totalDebt)}
parentheses
/>
</div>
<Text size='xs' className='text-white/50' tag='span'>
Borrowed
</Text>
</div>
</>
)}
<div className='h-100 w-[1px] bg-white/10' /> <div className='h-100 w-[1px] bg-white/10' />
<div className='flex flex-col gap-0.5'> <div className='flex flex-col gap-0.5'>
<div className='flex gap-2'> <div className='flex gap-2'>

View File

@ -33,7 +33,9 @@ export default function FundAndWithdrawModal() {
{modal && currentAccount ? ( {modal && currentAccount ? (
<FundWithdrawModalContent account={currentAccount} isFunding={isFunding} /> <FundWithdrawModalContent account={currentAccount} isFunding={isFunding} />
) : ( ) : (
<CircularProgress /> <div className='flex items-center justify-center w-full h-[380px]'>
<CircularProgress />
</div>
)} )}
</Modal> </Modal>
) )

View File

@ -32,7 +32,7 @@ function LendAndReclaimModal({ currentAccount, config }: Props) {
const { asset } = data const { asset } = data
const isLendAction = action === 'lend' const isLendAction = action === 'lend'
const actionText = isLendAction ? 'Lend' : 'Withdraw' const actionText = isLendAction ? 'Lend' : 'Unlend'
const coinBalances = currentAccount[isLendAction ? 'deposits' : 'lends'] ?? [] const coinBalances = currentAccount[isLendAction ? 'deposits' : 'lends'] ?? []
const handleAmountChange = useCallback( const handleAmountChange = useCallback(

View File

@ -12,20 +12,13 @@ import Select from 'components/Select'
import Text from 'components/Text' import Text from 'components/Text'
import { TextLink } from 'components/TextLink' import { TextLink } from 'components/TextLink'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { import { LocalStorageKeys } from 'constants/localStorageKeys'
DISPLAY_CURRENCY_KEY,
LEND_ASSETS_KEY,
PREFERRED_ASSET_KEY,
REDUCE_MOTION_KEY,
SLIPPAGE_KEY,
TUTORIAL_KEY,
} from 'constants/localStore'
import { BN_ZERO } from 'constants/math' import { BN_ZERO } from 'constants/math'
import useAlertDialog from 'hooks/useAlertDialog' import useAlertDialog from 'hooks/useAlertDialog'
import useAutoLend from 'hooks/useAutoLend' import useAutoLend from 'hooks/useAutoLend'
import useLocalStorage from 'hooks/useLocalStorage' import useLocalStorage from 'hooks/useLocalStorage'
import useStore from 'store' import useStore from 'store'
import { getDisplayCurrencies, getEnabledMarketAssets } from 'utils/assets' import { getDisplayCurrencies } from 'utils/assets'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
const slippages = [0.02, 0.03] const slippages = [0.02, 0.03]
@ -34,29 +27,30 @@ export default function SettingsModal() {
const modal = useStore((s) => s.settingsModal) const modal = useStore((s) => s.settingsModal)
const { open: showResetDialog } = useAlertDialog() const { open: showResetDialog } = useAlertDialog()
const displayCurrencies = getDisplayCurrencies() const displayCurrencies = getDisplayCurrencies()
const assets = getEnabledMarketAssets()
const { setAutoLendOnAllAccounts } = useAutoLend() const { setAutoLendOnAllAccounts } = useAutoLend()
const [customSlippage, setCustomSlippage] = useState<number>(0) const [customSlippage, setCustomSlippage] = useState<number>(0)
const [inputRef, setInputRef] = useState<React.RefObject<HTMLInputElement>>() const [inputRef, setInputRef] = useState<React.RefObject<HTMLInputElement>>()
const [isCustom, setIsCustom] = useState(false) const [isCustom, setIsCustom] = useState(false)
const [displayCurrency, setDisplayCurrency] = useLocalStorage<string>( const [displayCurrency, setDisplayCurrency] = useLocalStorage<string>(
DISPLAY_CURRENCY_KEY, LocalStorageKeys.DISPLAY_CURRENCY,
DEFAULT_SETTINGS.displayCurrency, DEFAULT_SETTINGS.displayCurrency,
) )
const [preferredAsset, setPreferredAsset] = useLocalStorage<string>(
PREFERRED_ASSET_KEY,
DEFAULT_SETTINGS.preferredAsset,
)
const [reduceMotion, setReduceMotion] = useLocalStorage<boolean>( const [reduceMotion, setReduceMotion] = useLocalStorage<boolean>(
REDUCE_MOTION_KEY, LocalStorageKeys.REDUCE_MOTION,
DEFAULT_SETTINGS.reduceMotion, DEFAULT_SETTINGS.reduceMotion,
) )
const [tutorial, setTutorial] = useLocalStorage<boolean>(TUTORIAL_KEY, DEFAULT_SETTINGS.tutorial) const [tutorial, setTutorial] = useLocalStorage<boolean>(
LocalStorageKeys.TUTORIAL,
DEFAULT_SETTINGS.tutorial,
)
const [lendAssets, setLendAssets] = useLocalStorage<boolean>( const [lendAssets, setLendAssets] = useLocalStorage<boolean>(
LEND_ASSETS_KEY, LocalStorageKeys.LEND_ASSETS,
DEFAULT_SETTINGS.lendAssets, DEFAULT_SETTINGS.lendAssets,
) )
const [slippage, setSlippage] = useLocalStorage<number>(SLIPPAGE_KEY, DEFAULT_SETTINGS.slippage) const [slippage, setSlippage] = useLocalStorage<number>(
LocalStorageKeys.SLIPPAGE,
DEFAULT_SETTINGS.slippage,
)
const displayCurrenciesOptions = useMemo( const displayCurrenciesOptions = useMemo(
() => () =>
@ -80,22 +74,6 @@ export default function SettingsModal() {
[displayCurrencies], [displayCurrencies],
) )
const preferredAssetsOptions = useMemo(
() =>
assets.map((asset, index) => ({
label: [
<div className='flex w-full gap-2' key={index}>
<AssetImage asset={asset} size={16} />
<Text size='sm' className='leading-4'>
{asset.symbol}
</Text>
</div>,
],
value: asset.denom,
})),
[assets],
)
const handleReduceMotion = useCallback( const handleReduceMotion = useCallback(
(value: boolean) => { (value: boolean) => {
setReduceMotion(value) setReduceMotion(value)
@ -118,13 +96,6 @@ export default function SettingsModal() {
[setTutorial], [setTutorial],
) )
const handlePreferredAsset = useCallback(
(value: string) => {
setPreferredAsset(value)
},
[setPreferredAsset],
)
const handleDisplayCurrency = useCallback( const handleDisplayCurrency = useCallback(
(value: string) => { (value: string) => {
setDisplayCurrency(value) setDisplayCurrency(value)
@ -175,17 +146,10 @@ export default function SettingsModal() {
const handleResetSettings = useCallback(() => { const handleResetSettings = useCallback(() => {
handleDisplayCurrency(DEFAULT_SETTINGS.displayCurrency) handleDisplayCurrency(DEFAULT_SETTINGS.displayCurrency)
handlePreferredAsset(DEFAULT_SETTINGS.preferredAsset)
handleSlippage(DEFAULT_SETTINGS.slippage) handleSlippage(DEFAULT_SETTINGS.slippage)
handleReduceMotion(DEFAULT_SETTINGS.reduceMotion) handleReduceMotion(DEFAULT_SETTINGS.reduceMotion)
handleLendAssets(DEFAULT_SETTINGS.lendAssets) handleLendAssets(DEFAULT_SETTINGS.lendAssets)
}, [ }, [handleDisplayCurrency, handleReduceMotion, handleLendAssets, handleSlippage])
handleDisplayCurrency,
handleReduceMotion,
handleLendAssets,
handlePreferredAsset,
handleSlippage,
])
const showResetModal = useCallback(() => { const showResetModal = useCallback(() => {
showResetDialog({ showResetDialog({
@ -195,7 +159,7 @@ export default function SettingsModal() {
</div> </div>
), ),
title: 'Are you sure you want to restore to default?', title: 'Are you sure you want to restore to default?',
description: content:
'Once you reset to default settings you cant revert it, and will result in the permanent loss of your current settings', 'Once you reset to default settings you cant revert it, and will result in the permanent loss of your current settings',
positiveButton: { positiveButton: {
text: 'Yes', text: 'Yes',
@ -268,21 +232,11 @@ export default function SettingsModal() {
withStatus withStatus
/> />
<SettingsOptions <SettingsOptions
label='Preferred asset' label='Display Currency'
description='By selecting a different asset you always have the trading pair or asset selector description='Convert all values to the selected asset/currency.'
pre-filled with this asset.'
className='pb-6' className='pb-6'
> >
<Select <Select
label='Global'
options={preferredAssetsOptions}
defaultValue={preferredAsset}
onChange={handlePreferredAsset}
className='relative border w-60 rounded-base border-white/10'
containerClassName='justify-end mb-4'
/>
<Select
label='Display Currency'
options={displayCurrenciesOptions} options={displayCurrenciesOptions}
defaultValue={displayCurrency} defaultValue={displayCurrency}
onChange={handleDisplayCurrency} onChange={handleDisplayCurrency}

View File

@ -8,7 +8,7 @@ import VaultDeposit from 'components/Modals/Vault/VaultDeposits'
import VaultDepositSubTitle from 'components/Modals/Vault/VaultDepositsSubTitle' import VaultDepositSubTitle from 'components/Modals/Vault/VaultDepositsSubTitle'
import Text from 'components/Text' import Text from 'components/Text'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { DISPLAY_CURRENCY_KEY } from 'constants/localStore' import { LocalStorageKeys } from 'constants/localStorageKeys'
import { BN_ZERO } from 'constants/math' import { BN_ZERO } from 'constants/math'
import useDepositVault from 'hooks/broadcast/useDepositVault' import useDepositVault from 'hooks/broadcast/useDepositVault'
import useDisplayAsset from 'hooks/useDisplayAsset' import useDisplayAsset from 'hooks/useDisplayAsset'
@ -36,7 +36,7 @@ export default function VaultModalContent(props: Props) {
const { data: prices } = usePrices() const { data: prices } = usePrices()
const [displayCurrency] = useLocalStorage<string>( const [displayCurrency] = useLocalStorage<string>(
DISPLAY_CURRENCY_KEY, LocalStorageKeys.DISPLAY_CURRENCY,
DEFAULT_SETTINGS.displayCurrency, DEFAULT_SETTINGS.displayCurrency,
) )
const [isOpen, toggleOpen] = useIsOpenArray(2, false) const [isOpen, toggleOpen] = useIsOpenArray(2, false)

View File

@ -34,6 +34,7 @@ export default function WalletAssetsModalContent(props: Props) {
) )
}, [assets, searchString]) }, [assets, searchString])
const isBorrow = useStore((s) => s.walletAssetsModal?.isBorrow ?? false)
const currentSelectedDenom = useStore((s) => s.walletAssetsModal?.selectedDenoms ?? []) const currentSelectedDenom = useStore((s) => s.walletAssetsModal?.selectedDenoms ?? [])
const [selectedDenoms, setSelectedDenoms] = useState<string[]>( const [selectedDenoms, setSelectedDenoms] = useState<string[]>(
currentSelectedDenom.filter((denom) => filteredAssets.findIndex(byDenom(denom)) || []), currentSelectedDenom.filter((denom) => filteredAssets.findIndex(byDenom(denom)) || []),
@ -58,6 +59,7 @@ export default function WalletAssetsModalContent(props: Props) {
</div> </div>
<div className='max-h-[446px] overflow-y-scroll scrollbar-hide'> <div className='max-h-[446px] overflow-y-scroll scrollbar-hide'>
<AssetSelectTable <AssetSelectTable
isBorrow={isBorrow}
assets={filteredAssets} assets={filteredAssets}
onChangeSelected={onChangeSelect} onChangeSelected={onChangeSelect}
selectedDenoms={selectedDenoms} selectedDenoms={selectedDenoms}

Some files were not shown because too many files have changed in this diff Show More