* 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_CHAIN_ID=devnet
NEXT_PUBLIC_RPC=https://rpc.devnet.osmosis.zone/
NEXT_PUBLIC_GQL=https://devnet-osmosis-gql.marsprotocol.io/graphql
NEXT_PUBLIC_REST=https://lcd.devnet.osmosis.zone/
NEXT_PUBLIC_SWAP=https://testnet.osmosis.zone
NEXT_PUBLIC_VAULT_APR=https://api.marsprotocol.io/v1/vaults/osmosis
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
NEXT_PUBLIC_ZAPPER=osmo1yhh8mhthj5jn5c6ty59z3tpsk554qxmlkrkcderw6jls0pcg8zxsdjdj94
NEXT_PUBLIC_PARAMS=osmo1aye5qcer5n52crrkaf35jprsad2807q6kg3eeeu7k79h4slxfausfqhc9y
# MAINNET #
NEXT_PUBLIC_NETWORK=mainnet
NEXT_PUBLIC_CHAIN_ID=osmosis-1
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_REST=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-lcd-front/
NEXT_PUBLIC_ZAPPER=osmo17qwvc70pzc9mudr8t02t3pl74hhqsgwnskl734p4hug3s8mkerdqzduf7c
NEXT_PUBLIC_PARAMS=osmo1nlmdxt9ctql2jr47qd4fpgzg84cjswxyw6q99u4y4u4q6c2f5ksq7ysent
# 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
# MAINNET #
# NEXT_PUBLIC_NETWORK=mainnet
# NEXT_PUBLIC_CHAIN_ID=osmosis-1
# 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_REST=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-lcd-front/
# 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_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

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/datafeeds
src/types/generated
.husky

View File

@ -1,11 +1,11 @@
import { render } from '@testing-library/react'
import { CircularProgress } from 'components/CircularProgress'
import { REDUCE_MOTION_KEY } from 'constants/localStore'
import { LocalStorageKeys } from 'constants/localStorageKeys'
describe('<CircularProgress />', () => {
afterAll(() => {
localStorage.removeItem(REDUCE_MOTION_KEY)
localStorage.removeItem(LocalStorageKeys.REDUCE_MOTION)
})
it('should render', () => {
@ -15,7 +15,7 @@ describe('<CircularProgress />', () => {
})
it('should render `...` when animations disabled', () => {
localStorage.setItem(REDUCE_MOTION_KEY, 'true')
localStorage.setItem(LocalStorageKeys.REDUCE_MOTION, 'true')
const { getByText } = render(<CircularProgress />)
const threeDots = getByText('...')
@ -24,7 +24,7 @@ describe('<CircularProgress />', () => {
})
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 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],
status: 'active',
apy: 1,
apr: null,
ltv: {
max: 0.65,
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 { TooltipType } from 'components/Tooltip'
import TooltipContent from 'components/Tooltip/TooltipContent'
describe('<Tooltip />', () => {

View File

@ -1,6 +1,6 @@
{
"name": "mars-v2-frontend",
"version": "2.0.0",
"version": "2.0.1",
"private": true,
"scripts": {
"build": "yarn validate-env && next build",
@ -12,15 +12,22 @@
"prettier-check": "prettier --ignore-path .gitignore --check ./src/",
"start": "next start",
"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": {
"@cosmjs/cosmwasm-stargate": "^0.31.1",
"@delphi-labs/shuttle-react": "^3.9.0",
"@keplr-wallet/cosmos": "^0.12.32",
"@sentry/nextjs": "^7.73.0",
"@delphi-labs/shuttle-react": "^3.9.1",
"@keplr-wallet/cosmos": "^0.12.35",
"@sentry/nextjs": "^7.74.0",
"@splinetool/react-spline": "^2.2.6",
"@splinetool/runtime": "^0.9.477",
"@splinetool/runtime": "^0.9.482",
"@tanstack/react-table": "^8.10.6",
"@tippyjs/react": "^4.2.6",
"bignumber.js": "^9.1.2",
@ -36,7 +43,7 @@
"react-draggable": "^4.4.6",
"react-helmet-async": "^1.3.0",
"react-qr-code": "^2.0.12",
"react-router-dom": "^6.16.0",
"react-router-dom": "^6.17.0",
"react-spring": "^9.7.3",
"react-toastify": "^9.1.3",
"react-use-clipboard": "^1.0.9",
@ -52,7 +59,7 @@
"@types/debounce-promise": "^3.1.7",
"@types/lodash.debounce": "^4.0.7",
"@types/lodash.throttle": "^4.1.7",
"@types/node": "^20.7.0",
"@types/node": "^20.8.6",
"@types/react": "18.2.28",
"@types/react-dom": "18.2.13",
"@types/react-helmet": "^6.1.7",
@ -63,11 +70,13 @@
"eslint": "^8.51.0",
"eslint-config-next": "^13.5.4",
"eslint-plugin-import": "^2.28.1",
"husky": "^8.0.3",
"identity-obj-proxy": "^3.0.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"lint-staged": "^15.0.1",
"prettier": "^3.0.3",
"prettier-plugin-tailwindcss": "^0.5.5",
"prettier-plugin-tailwindcss": "^0.5.6",
"shelljs": "^0.8.5",
"tailwindcss": "^3.3.3",
"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 { getVaultConfigs } from 'api/vaults/getVaultConfigs'
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 { BN } from 'utils/helpers'
import { convertAprToApy } from 'utils/parsers'
import { resolveHLSStrategies } from 'utils/resolvers'
export default async function getVaults(): Promise<Vault[]> {
const assetParams = await getAssetParams()
const vaultConfigs = await getVaultConfigs()
const $vaultUtilizations = getVaultUtilizations(vaultConfigs)
const $aprs = getAprs()
const vaultMetaDatas =
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[] = []
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)
})
})

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@ import { useMemo } from 'react'
import { Tooltip } from 'components/Tooltip'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { REDUCE_MOTION_KEY } from 'constants/localStore'
import { LocalStorageKeys } from 'constants/localStorageKeys'
import useHealthColorAndLabel from 'hooks/useHealthColorAndLabel'
import useLocalStorage from 'hooks/useLocalStorage'
import { getHealthIndicatorColors } from 'utils/healthIndicator'
@ -30,7 +30,10 @@ function calculateHealth(health: number): number {
export default function HealthBar(props: 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 updatedWidth = calculateHealth(updatedHealth ?? 0)
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' />
<path fill='#FFFFFF' d='M95.5,0H182c1.1,0,2,0.9,2,2s-0.9,2-2,2H95.5V0z' />
</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={classNames(backgroundColor, !reduceMotion && 'transition-all duration-500')}
width={isUpdated && isIncrease ? updatedWidth : width}
height='4'
mask='url(#healthBarMask)'
mask={isUpdated ? 'url(#backgroundHealthBarMask)' : 'url(#healthBarMask)'}
/>
{isUpdated && (
<rect

View File

@ -12,13 +12,16 @@ import {
import { FormattedNumber } from 'components/FormattedNumber'
import Text from 'components/Text'
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 { formatValue } from 'utils/formatters'
import { BN_ZERO } from 'constants/math'
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
return (

View File

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

View File

@ -1,11 +1,14 @@
import classNames from 'classnames'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { REDUCE_MOTION_KEY } from 'constants/localStore'
import { LocalStorageKeys } from 'constants/localStorageKeys'
import useLocalStorage from 'hooks/useLocalStorage'
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 (
<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 { Tooltip } from 'components/Tooltip'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { REDUCE_MOTION_KEY } from 'constants/localStore'
import { LocalStorageKeys } from 'constants/localStorageKeys'
import useLocalStorage from 'hooks/useLocalStorage'
interface Props {
@ -31,7 +31,10 @@ export const BorrowCapacity = ({
hideValues,
decimals = 2,
}: 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 [percentOfMaxRange, setPercentOfMaxRange] = 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 { ChevronDown } from 'components/Icons'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { REDUCE_MOTION_KEY } from 'constants/localStore'
import { LocalStorageKeys } from 'constants/localStorageKeys'
import useLocalStorage from 'hooks/useLocalStorage'
const Button = React.forwardRef(function Button(
@ -44,7 +44,10 @@ const Button = React.forwardRef(function Button(
}: ButtonProps,
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 shouldShowText = text && !children
const shouldShowGlowElement = variant === 'solid' && !isDisabled && !reduceMotion

View File

@ -2,7 +2,7 @@ import classNames from 'classnames'
import { CheckCircled } from 'components/Icons'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { REDUCE_MOTION_KEY } from 'constants/localStore'
import { LocalStorageKeys } from 'constants/localStorageKeys'
import useLocalStorage from 'hooks/useLocalStorage'
interface Props {
@ -12,7 +12,10 @@ interface 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)
if (reduceMotion)

View File

@ -1,18 +1,24 @@
import classNames from 'classnames'
import { Check } from 'components/Icons'
import Text from 'components/Text'
interface Props {
checked: boolean
onChange: (checked: boolean) => void
text?: string
}
export default function Checkbox(props: Props) {
return (
<button
onClick={() => props.onChange(props.checked)}
role='checkbox'
aria-checked={props.checked}
<label className='flex gap-2 items-center cursor-pointer'>
<input
onChange={() => props.onChange(props.checked)}
checked={props.checked}
type='checkbox'
className='opacity-0 absolute'
/>
<div
className={classNames(
'h-5 w-5 rounded-sm p-0.5',
props.checked && 'relative isolate overflow-hidden rounded-sm',
@ -22,6 +28,12 @@ export default function Checkbox(props: Props) {
)}
>
{props.checked && <Check />}
</button>
</div>
{props.text && (
<Text size='xs' className='text-white/60'>
{props.text}
</Text>
)}
</label>
)
}

View File

@ -1,7 +1,7 @@
import classNames from 'classnames'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { REDUCE_MOTION_KEY } from 'constants/localStore'
import { LocalStorageKeys } from 'constants/localStorageKeys'
import useLocalStorage from 'hooks/useLocalStorage'
interface Props {
@ -11,7 +11,10 @@ interface 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 borderColor = `${color} transparent transparent transparent`
const loaderClasses = classNames('inline-block relative', className)

View File

@ -3,7 +3,7 @@ import { useMemo } from 'react'
import { FormattedNumber } from 'components/FormattedNumber'
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 useLocalStorage from 'hooks/useLocalStorage'
import usePrices from 'hooks/usePrices'
@ -22,7 +22,7 @@ interface Props {
export default function DisplayCurrency(props: Props) {
const displayCurrencies = getDisplayCurrencies()
const [displayCurrency] = useLocalStorage<string>(
DISPLAY_CURRENCY_KEY,
LocalStorageKeys.DISPLAY_CURRENCY,
DEFAULT_SETTINGS.displayCurrency,
)
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 { Tooltip } from 'components/Tooltip'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { SLIPPAGE_KEY } from 'constants/localStore'
import { LocalStorageKeys } from 'constants/localStorageKeys'
import useAccountId from 'hooks/useAccountId'
import useLocalStorage from 'hooks/useLocalStorage'
import useStore from 'store'
import { VaultStatus } from 'types/enums/vault'
interface Props {
row: Row<Vault | DepositedVault>
row: Row<DepositedVault>
resetExpanded: (defaultState?: boolean | undefined) => void
}
export default function VaultExpanded(props: Props) {
const vault = props.row.original as DepositedVault
const vault = props.row.original
const accountId = useAccountId()
const [isConfirming, setIsConfirming] = useState(false)
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() {
useStore.setState({
@ -105,7 +105,7 @@ export default function VaultExpanded(props: Props) {
return (
<tr
key={props.row.id}
className='hover:cursor-pointer bg-black/20 transition-colors'
className='transition-colors hover:cursor-pointer bg-black/20'
onClick={(e) => {
e.preventDefault()
const isExpanded = props.row.getIsExpanded()
@ -113,7 +113,7 @@ export default function VaultExpanded(props: Props) {
!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'>
{status && <DepositMoreButton />}
{status === VaultStatus.ACTIVE && <UnlockButton />}

View File

@ -12,9 +12,9 @@ export const VaultRow = (props: AssetRowProps) => {
<tr
key={props.row.id}
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',
props.row.getIsExpanded() && 'is-expanded',
props.row.getIsExpanded() ? 'is-expanded bg-black/20' : 'border-b',
)}
onClick={(e) => {
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 NotificationBanner from 'components/NotificationBanner'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { SLIPPAGE_KEY } from 'constants/localStore'
import { LocalStorageKeys } from 'constants/localStorageKeys'
import useAccountId from 'hooks/useAccountId'
import useLocalStorage from 'hooks/useLocalStorage'
import useStore from 'store'
@ -17,7 +17,7 @@ export default function VaultUnlockBanner(props: Props) {
const accountId = useAccountId()
const [isConfirming, setIsConfirming] = useState(false)
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() {
if (!accountId) return

View File

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

View File

@ -3,7 +3,7 @@ import { useCallback } from 'react'
import { ACCOUNT_MENU_BUTTON_ID } from 'components/Account/AccountMenuContent'
import Button from 'components/Button'
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 { Tooltip } from 'components/Tooltip'
import ConditionalWrapper from 'hocs/ConditionalWrapper'
@ -33,12 +33,13 @@ export default function LendingActionButtons(props: Props) {
const accountId = useAccountId()
const hasNoDeposit = !!(!assetDepositAmount && address && accountId)
const handleWithdraw = useCallback(() => {
const handleUnlend = useCallback(() => {
if (isAutoLendEnabledForCurrentAccount) {
showAlertDialog({
icon: <ExclamationMarkCircled width={18} />,
title: 'Disable Automatically Lend Assets',
description:
"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.",
content:
"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: {
onClick: () => document.getElementById(ACCOUNT_MENU_BUTTON_ID)?.click(),
text: 'Continue to Account Settings',
@ -62,10 +63,10 @@ export default function LendingActionButtons(props: Props) {
leftIcon={<ArrowDownLine />}
iconClassName={iconClassnames}
color='secondary'
onClick={handleWithdraw}
onClick={handleUnlend}
className={buttonClassnames}
>
Withdraw
Unlend
</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 { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { REDUCE_MOTION_KEY } from 'constants/localStore'
import { LocalStorageKeys } from 'constants/localStorageKeys'
import useLocalStorage from 'hooks/useLocalStorage'
import { formatValue } from 'utils/formatters'
@ -19,7 +19,7 @@ interface Props {
export const FormattedNumber = React.memo(
(props: Props) => {
const [reduceMotion] = useLocalStorage<boolean>(
REDUCE_MOTION_KEY,
LocalStorageKeys.REDUCE_MOTION,
DEFAULT_SETTINGS.reduceMotion,
)
const prevAmountRef = useRef<number>(0)

View File

@ -3,7 +3,7 @@ import { ReactElement, ReactNode } from 'react'
import { Tooltip } from 'components/Tooltip'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { REDUCE_MOTION_KEY } from 'constants/localStore'
import { LocalStorageKeys } from 'constants/localStorageKeys'
import useLocalStorage from 'hooks/useLocalStorage'
interface Props {
@ -27,7 +27,10 @@ export const Gauge = ({
icon,
labelClassName,
}: 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 percentageValue = percentage > 100 ? 100 : percentage < 0 ? 0 : percentage
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 { Tooltip } from 'components/Tooltip'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { REDUCE_MOTION_KEY } from 'constants/localStore'
import { LocalStorageKeys } from 'constants/localStorageKeys'
import useHealthColorAndLabel from 'hooks/useHealthColorAndLabel'
import useLocalStorage from 'hooks/useLocalStorage'
import { computeHealthGaugePercentage } from 'utils/accounts'
@ -25,7 +25,10 @@ const ROTATION = {
export const HealthGauge = ({ diameter = 40, health = 0, updatedHealth = 0 }: Props) => {
const [color, label] = useHealthColorAndLabel(health, '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 updatedPercentage = useMemo(
() => computeHealthGaugePercentage(updatedHealth),
@ -77,6 +80,140 @@ export const HealthGauge = ({ diameter = 40, health = 0, updatedHealth = 0 }: Pr
/>
</g>
</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
r={RADIUS}
cx={RADIUS}
@ -97,7 +234,7 @@ export const HealthGauge = ({ diameter = 40, health = 0, updatedHealth = 0 }: Pr
strokeDashoffset={isUpdated && isIncrease ? updatedPercentage : percentage}
strokeLinecap='round'
style={ROTATION}
mask='url(#mask)'
mask={isUpdated ? 'url(#backgroundMask)' : 'url(#mask)'}
className={classNames(
backgroundColor,
'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 { FormattedNumber } from 'components/FormattedNumber'
@ -5,7 +6,7 @@ import TitleAndSubCell from 'components/TitleAndSubCell'
import useDisplayCurrencyPrice from 'hooks/useDisplayCurrencyPrice'
interface Props {
data: BorrowMarketTableData | LendingMarketTableData
row: Row<BorrowMarketTableData | LendingMarketTableData>
type: 'borrow' | 'lend'
}
@ -15,7 +16,7 @@ interface Detail {
title: string
}
export default function MarketDetails({ data, type }: Props) {
export default function MarketDetails({ row, type }: Props) {
const {
convertAmount,
getConversionRate,
@ -28,7 +29,7 @@ export default function MarketDetails({ data, type }: Props) {
marketDepositAmount,
marketLiquidityAmount,
marketLiquidationThreshold,
} = data
} = row.original
const totalBorrowed = marketDepositAmount.minus(marketLiquidityAmount)
@ -36,7 +37,6 @@ export default function MarketDetails({ data, type }: Props) {
const isDollar = displayCurrencySymbol === '$'
function getLendingMarketDetails() {
const depositCap = (data as LendingMarketTableData).marketDepositCap
return [
{
amount: convertAmount(asset, marketDepositAmount).toNumber(),
@ -107,7 +107,6 @@ export default function MarketDetails({ data, type }: Props) {
if (type === 'lend') return getLendingMarketDetails()
return getBorrowMarketDetails()
}, [
data,
type,
asset,
marketDepositAmount,
@ -120,7 +119,9 @@ export default function MarketDetails({ data, type }: Props) {
])
return (
<div className='flex justify-between flex-1 bg-white rounded-md bg-opacity-5'>
<tr>
<td colSpan={row.getAllCells().length} className='p-4 border-b bg-black/20 border-white/10'>
<div className='flex justify-between flex-1 rounded-md bg-white/5'>
{details.map((detail, index) => (
<TitleAndSubCell
key={index}
@ -138,5 +139,7 @@ export default function MarketDetails({ data, type }: Props) {
/>
))}
</div>
</td>
</tr>
)
}

View File

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

View File

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

View File

@ -2,7 +2,7 @@ import { useCallback, useMemo } from 'react'
import { useLocation, useNavigate, useParams } from 'react-router-dom'
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 Text from 'components/Text'
import useStore from 'store'
@ -51,7 +51,8 @@ function AccountDeleteModal(props: Props) {
return (
<AccountDeleteAlertDialog
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}
positiveButton={{
text: 'Repay Debts',
@ -68,7 +69,7 @@ function AccountDeleteModal(props: Props) {
return (
<AccountDeleteAlertDialog
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}
positiveButton={{
text: 'Close Positions',
@ -85,7 +86,7 @@ function AccountDeleteModal(props: Props) {
return (
<AccountDeleteAlertDialog
title={`Delete Credit Account ${accountId}`}
description='Deleting your Credit Account is irreversible.'
content='Deleting your Credit Account is irreversible.'
closeHandler={closeDeleteAccountModal}
positiveButton={{
text: 'Delete Account',
@ -98,7 +99,7 @@ function AccountDeleteModal(props: Props) {
return (
<AccountDeleteAlertDialog
title={`Delete Credit Account ${accountId}`}
description={
content={
<>
<Text className='mt-2 text-white/50' size='sm'>
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 (
<>
<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
value={searchString}
placeholder={`Search for e.g. "ETH" or "Ethereum"`}
@ -90,6 +90,7 @@ export default function AddVaultAssetsModalContent(props: Props) {
</Text>
</div>
<AssetSelectTable
isBorrow={true}
assets={poolAssets}
onChangeSelected={onChangePoolDenoms}
selectedDenoms={selectedPoolDenoms}
@ -102,6 +103,7 @@ export default function AddVaultAssetsModalContent(props: Props) {
</Text>
</div>
<AssetSelectTable
isBorrow={true}
assets={stableAssets}
onChangeSelected={onChangeOtherDenoms}
selectedDenoms={selectedOtherDenoms}

View File

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

View File

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

View File

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

View File

@ -17,7 +17,7 @@ function showBorrowRate(data: AssetTableRow[]) {
return !!(assetData && assetData?.borrowRate)
}
export default function useAssetTableColumns() {
export default function useAssetTableColumns(isBorrow: boolean) {
return React.useMemo<ColumnDef<AssetTableRow>[]>(
() => [
{
@ -29,6 +29,9 @@ export default function useAssetTableColumns() {
const market = row.original.market
const borrowAsset = row.original.asset as BorrowAsset
const showRate = !borrowAsset?.borrowRate
const rate = isBorrow ? market?.borrowRate : market?.liquidityRate
const apy = convertAprToApy((rate ?? 0) * 100, 365)
return (
<div className='flex items-center'>
<Checkbox checked={row.getIsSelected()} onChange={row.getToggleSelectedHandler()} />
@ -39,7 +42,7 @@ export default function useAssetTableColumns() {
</Text>
{showRate && market ? (
<AssetRate
rate={convertAprToApy(market.borrowRate * 100, 365)}
rate={apy}
isEnabled={market.borrowEnabled}
className='text-xs'
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 { BNCoin } from 'types/classes/BNCoin'
import { byDenom } from 'utils/array'
import { formatPercent, formatValue } from 'utils/formatters'
import { formatPercent } from 'utils/formatters'
import { BN } from 'utils/helpers'
import { getDebtAmountWithInterest } from 'utils/tokens'
@ -204,15 +204,32 @@ function BorrowModal(props: Props) {
title={formatPercent(modal.marketData.borrowRate || '0')}
sub={'Borrow rate'}
/>
{totalDebt.isGreaterThan(0) && (
<>
<div className='h-100 w-[1px] bg-white/10' />
<TitleAndSubCell
title={formatValue(getDebtAmount(modal), {
abbreviated: false,
<div className='flex flex-col gap-0.5'>
<div className='flex gap-2'>
<FormattedNumber
className='text-xs'
amount={totalDebt.toNumber()}
options={{
decimals: asset.decimals,
maxDecimals: asset.decimals,
})}
sub={'Borrowed'}
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='flex flex-col gap-0.5'>
<div className='flex gap-2'>

View File

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

View File

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

View File

@ -12,20 +12,13 @@ import Select from 'components/Select'
import Text from 'components/Text'
import { TextLink } from 'components/TextLink'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import {
DISPLAY_CURRENCY_KEY,
LEND_ASSETS_KEY,
PREFERRED_ASSET_KEY,
REDUCE_MOTION_KEY,
SLIPPAGE_KEY,
TUTORIAL_KEY,
} from 'constants/localStore'
import { LocalStorageKeys } from 'constants/localStorageKeys'
import { BN_ZERO } from 'constants/math'
import useAlertDialog from 'hooks/useAlertDialog'
import useAutoLend from 'hooks/useAutoLend'
import useLocalStorage from 'hooks/useLocalStorage'
import useStore from 'store'
import { getDisplayCurrencies, getEnabledMarketAssets } from 'utils/assets'
import { getDisplayCurrencies } from 'utils/assets'
import { BN } from 'utils/helpers'
const slippages = [0.02, 0.03]
@ -34,29 +27,30 @@ export default function SettingsModal() {
const modal = useStore((s) => s.settingsModal)
const { open: showResetDialog } = useAlertDialog()
const displayCurrencies = getDisplayCurrencies()
const assets = getEnabledMarketAssets()
const { setAutoLendOnAllAccounts } = useAutoLend()
const [customSlippage, setCustomSlippage] = useState<number>(0)
const [inputRef, setInputRef] = useState<React.RefObject<HTMLInputElement>>()
const [isCustom, setIsCustom] = useState(false)
const [displayCurrency, setDisplayCurrency] = useLocalStorage<string>(
DISPLAY_CURRENCY_KEY,
LocalStorageKeys.DISPLAY_CURRENCY,
DEFAULT_SETTINGS.displayCurrency,
)
const [preferredAsset, setPreferredAsset] = useLocalStorage<string>(
PREFERRED_ASSET_KEY,
DEFAULT_SETTINGS.preferredAsset,
)
const [reduceMotion, setReduceMotion] = useLocalStorage<boolean>(
REDUCE_MOTION_KEY,
LocalStorageKeys.REDUCE_MOTION,
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>(
LEND_ASSETS_KEY,
LocalStorageKeys.LEND_ASSETS,
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(
() =>
@ -80,22 +74,6 @@ export default function SettingsModal() {
[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(
(value: boolean) => {
setReduceMotion(value)
@ -118,13 +96,6 @@ export default function SettingsModal() {
[setTutorial],
)
const handlePreferredAsset = useCallback(
(value: string) => {
setPreferredAsset(value)
},
[setPreferredAsset],
)
const handleDisplayCurrency = useCallback(
(value: string) => {
setDisplayCurrency(value)
@ -175,17 +146,10 @@ export default function SettingsModal() {
const handleResetSettings = useCallback(() => {
handleDisplayCurrency(DEFAULT_SETTINGS.displayCurrency)
handlePreferredAsset(DEFAULT_SETTINGS.preferredAsset)
handleSlippage(DEFAULT_SETTINGS.slippage)
handleReduceMotion(DEFAULT_SETTINGS.reduceMotion)
handleLendAssets(DEFAULT_SETTINGS.lendAssets)
}, [
handleDisplayCurrency,
handleReduceMotion,
handleLendAssets,
handlePreferredAsset,
handleSlippage,
])
}, [handleDisplayCurrency, handleReduceMotion, handleLendAssets, handleSlippage])
const showResetModal = useCallback(() => {
showResetDialog({
@ -195,7 +159,7 @@ export default function SettingsModal() {
</div>
),
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',
positiveButton: {
text: 'Yes',
@ -268,21 +232,11 @@ export default function SettingsModal() {
withStatus
/>
<SettingsOptions
label='Preferred asset'
description='By selecting a different asset you always have the trading pair or asset selector
pre-filled with this asset.'
label='Display Currency'
description='Convert all values to the selected asset/currency.'
className='pb-6'
>
<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}
defaultValue={displayCurrency}
onChange={handleDisplayCurrency}

View File

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

View File

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

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