Compare commits

..
Author SHA1 Message Date
Linkie Link 51916683ac feat: added ChartLoading component 2023-11-24 10:32:54 +01:00
Linkie Link 160165aa22 Merge branch 'develop' of https://github.com/mars-protocol/mars-v2-frontend into stats-page
# Conflicts:
#	src/components/Routes.tsx
#	src/utils/route.ts
2023-11-24 09:40:57 +01:00
Linkie Link e3d3d30700 feat: added a first Chart component 2023-11-22 17:01:25 +01:00
Linkie Link ca9e608952 fix: fixed the build 2023-11-22 15:54:44 +01:00
Linkie Link d414e38b88 fix: color adjustments 2023-11-22 15:11:17 +01:00
Linkie Link 31eceeb9fb feat: added additional metric cards 2023-11-22 15:10:25 +01:00
Linkie Link 61c64aa724 feat: first preparations for stats 2023-11-22 12:13:32 +01:00
708 changed files with 9395 additions and 14043 deletions
+33 -12
View File
@@ -1,16 +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/
# MAINNET #
NEXT_PUBLIC_NETWORK=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_OSMOSIS_RPC=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-rpc-front/
NEXT_PUBLIC_OSMOSIS_REST=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-lcd-front/
NEXT_PUBLIC_OSMOSIS_TEST_RPC=https://rpc.devnet.osmosis.zone/
NEXT_PUBLIC_OSMOSIS_TEST_REST=https://lcd.devnet.osmosis.zone/
NEXT_PUBLIC_NEUTRON_TEST_RPC=https://rpc-palvus.pion-1.ntrn.tech/
NEXT_PUBLIC_NEUTRON_TEST_REST=https://rest-palvus.pion-1.ntrn.tech/
# 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_ZAPPER=osmo17qwvc70pzc9mudr8t02t3pl74hhqsgwnskl734p4hug3s8mkerdqzduf7c
NEXT_PUBLIC_PARAMS=osmo1nlmdxt9ctql2jr47qd4fpgzg84cjswxyw6q99u4y4u4q6c2f5ksq7ysent
NEXT_PUBLIC_PYTH_ENDPOINT=https://hermes.pyth.network/api
NEXT_PUBLIC_MAINNET_REST=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-rpc-front/
NEXT_PUBLIC_CANDLES_ENDPOINT_THE_GRAPH=https://osmosis-candles.marsprotocol.io/
NEXT_PUBLIC_CANDLES_ENDPOINT_PYTH=https://benchmarks.pyth.network
NEXT_PUBLIC_WALLET_CONNECT_ID=d93fdffb159bae5ec87d8fee4cdbb045 NEXT_PUBLIC_WALLET_CONNECT_ID=d93fdffb159bae5ec87d8fee4cdbb045
CHARTING_LIBRARY_USERNAME=git_username CHARTING_LIBRARY_REPOSITORY=github.com/tradingview/charting_library
CHARTING_LIBRARY_ACCESS_TOKEN=access_token CHARTING_LIBRARY_ACCESS_TOKEN=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
CHARTING_LIBRARY_REPOSITORY=github.com/tradingview/charting_library/ CHARTING_LIBRARY_USERNAME=mars-git-demo
NEXT_PUBLIC_STRIDE_APRS=https://edge.stride.zone/api/stake-stats
NEXT_PUBLIC_PYTH_API=https://mars.rpc.p2p.world/api
+6
View File
@@ -0,0 +1,6 @@
module.exports = {
src: '/img.jpg',
height: 24,
width: 24,
blurDataURL: 'data:image/png;base64,imagedata',
}
+17
View File
@@ -0,0 +1,17 @@
jest.mock('react-helmet-async', () => {
const React = require('react')
const plugin = jest.requireActual('react-helmet-async')
const mockHelmet = ({ children, ...props }) =>
React.createElement(
'div',
{
...props,
className: 'mock-helmet',
},
children,
)
return {
...plugin,
Helmet: jest.fn().mockImplementation(mockHelmet),
}
})
+23
View File
@@ -0,0 +1,23 @@
jest.mock('store', () => {
let state = {}
const mockUseStore = (selectorFn) => {
return selectorFn(state)
}
mockUseStore.setState = (newState) => {
state = {
...state,
...newState,
}
}
mockUseStore.clearState = () => {
state = {}
}
return {
__esModule: true,
default: mockUseStore,
}
})
+1
View File
@@ -0,0 +1 @@
module.exports = {}
+7
View File
@@ -0,0 +1,7 @@
/* eslint-disable react/display-name */
import React from 'react'
const SvgrMock = React.forwardRef((props, ref) => <svg ref={ref} {...props} />)
export const ReactComponent = SvgrMock
export default SvgrMock
+14
View File
@@ -0,0 +1,14 @@
import { render } from '@testing-library/react'
import Footer from 'components/Footer'
import packageJSON from '../package.json'
describe('<Footer />', () => {
it('should render package version correctly', () => {
const { getByText, container } = render(<Footer />)
const versionText = getByText(`v${packageJSON.version}`)
expect(versionText).toBeInTheDocument()
})
})
@@ -0,0 +1,67 @@
import { render, screen } from '@testing-library/react'
import AccountDetails from 'components/Account/AccountDetails'
import useCurrentAccount from 'hooks/useCurrentAccount'
import useStore from 'store'
jest.mock('react-router', () => ({
...(jest.requireActual('react-router') as {}),
useLocation: jest.fn().mockImplementation(() => {
return { pathname: '/testroute' }
}),
}))
jest.mock('hooks/useCurrentAccount', () => jest.fn(() => null))
jest.mock('hooks/useHealthComputer', () =>
jest.fn(() => ({
health: 0,
})),
)
// AccountBalancesTable component has wallet provider dependency, so we mock it
jest.mock('components/Account/AccountBalancesTable', () => jest.fn(() => null))
const mockedUseCurrentAccount = useCurrentAccount as jest.Mock
const mockedAccounts: Account[] = [
{ id: '1', deposits: [], lends: [], debts: [], vaults: [], kind: 'default' },
{ id: '2', deposits: [], lends: [], debts: [], vaults: [], kind: 'default' },
]
jest.mock('hooks/useAccountId', () => jest.fn(() => '1'))
jest.mock('hooks/useAccounts', () =>
jest.fn(() => ({
data: mockedAccounts,
})),
)
jest.mock('hooks/useAccountIds', () =>
jest.fn(() => ({
data: ['1', '2'],
})),
)
jest.mock('hooks/useCurrentAccount', () => jest.fn(() => mockedAccounts[0]))
describe('<AccountDetails />', () => {
beforeAll(() => {
useStore.setState({
address: 'walletAddress',
accounts: mockedAccounts,
})
})
afterAll(() => {
useStore.clearState()
})
it('renders account details WHEN account is selected', () => {
mockedUseCurrentAccount.mockReturnValue(mockedAccounts)
render(<AccountDetails />)
const container = screen.queryByTestId('account-details')
expect(container).toBeInTheDocument()
})
it('does not render WHEN account is NOT selected', () => {
mockedUseCurrentAccount.mockReturnValue(null)
render(<AccountDetails />)
const container = screen.queryByTestId('account-details')
expect(container).not.toBeInTheDocument()
})
})
+157
View File
@@ -0,0 +1,157 @@
import { cleanup, render } from '@testing-library/react'
import Button from 'components/Button'
import {
buttonColorClasses,
buttonSizeClasses,
buttonVariantClasses,
focusClasses,
} from 'components/Button/constants'
import { parseMockComponentProps } from 'utils/testing'
jest.mock('components/CircularProgress', () => {
return {
CircularProgress: (props: any) =>
require('utils/testing').createMockComponent('circular-progress-component', props),
}
})
describe('<Button />', () => {
afterAll(() => {
jest.unmock('components/CircularProgress')
})
it('should render', () => {
const { container } = render(<Button />)
expect(container).toBeInTheDocument()
})
it('should render `children` when its passed', () => {
const children = <span data-testid='test-id'>Hello World!</span>
const { getByTestId } = render(<Button>{children}</Button>)
expect(getByTestId('test-id')).toBeInTheDocument()
})
it('should handle `className` prop correctly', () => {
const testClass = 'test-class'
const { container } = render(<Button className={testClass} />)
expect(container.querySelector('button')).toHaveClass(testClass)
})
it('should handle `color` prop correctly', () => {
const colors = Object.keys(buttonColorClasses) as [keyof typeof buttonColorClasses]
colors.forEach((color) => {
const { container } = render(<Button color={color} />)
expect(container.querySelector('button')).toHaveClass(buttonColorClasses[color])
})
})
it('should handle `disabled=true` prop correctly', () => {
const testFunction = jest.fn()
const { container } = render(<Button disabled={true} onClick={testFunction} />)
const button = container.querySelector('button')
button?.click()
expect(button).toHaveClass('pointer-events-none')
expect(testFunction).not.toBeCalled()
})
it('should handle `disabled=false` prop correctly', () => {
const testFunction = jest.fn()
const { container } = render(<Button disabled={false} onClick={testFunction} />)
const button = container.querySelector('button')
button?.click()
expect(button).not.toHaveClass('pointer-events-none')
expect(testFunction).toBeCalled()
})
it('should show progress indicator when `showProgressIndicator=true`', () => {
const { getByTestId } = render(<Button showProgressIndicator={true} />)
const circularProgressComponent = getByTestId('circular-progress-component')
expect(circularProgressComponent).toBeInTheDocument()
})
it('should set correct values for progress indicator size', () => {
const sizeValues = { xs: 8, sm: 10, md: 12, lg: 18 }
Object.entries(sizeValues).forEach(([size, value]) => {
const { getByTestId } = render(
<Button showProgressIndicator={true} size={size as keyof typeof buttonSizeClasses} />,
)
const circularProgressComponent = getByTestId('circular-progress-component')
const sizeProp = parseMockComponentProps(circularProgressComponent).size
expect(sizeProp).toBe(value)
cleanup()
})
})
it('should handle `size` prop correctly', () => {
const sizes = Object.keys(buttonSizeClasses) as [keyof typeof buttonSizeClasses]
sizes.forEach((size) => {
const { container } = render(<Button size={size} />)
expect(container.querySelector('button')).toHaveClass(buttonSizeClasses[size])
})
})
it('should show `text` when its passed', () => {
const text = 'Hello!'
const { getByText } = render(<Button text={text} />)
expect(getByText(text)).toBeInTheDocument()
})
it('should handle `variant` prop correctly', () => {
const variants = Object.keys(buttonVariantClasses) as [keyof typeof buttonVariantClasses]
variants.forEach((variant) => {
const { container } = render(<Button variant={variant} />)
expect(container.querySelector('button')).toHaveClass(buttonVariantClasses[variant])
})
})
it('should show left icon when `leftIcon` prop is passed', () => {
const icon = <span data-testid='left-icon'>this is the left icon</span>
const { getByTestId } = render(<Button leftIcon={icon} />)
expect(getByTestId('left-icon')).toBeInTheDocument()
})
it('should show right icon when `rightIcon` prop is passed', () => {
const icon = <span data-testid='right-icon'>this is the right icon</span>
const { getByTestId } = render(<Button rightIcon={icon} />)
expect(getByTestId('right-icon')).toBeInTheDocument()
})
it('should handle `iconClassName` prop correctly', () => {
const icon = <span data-testid='icon'>just an icon</span>
const { getByTestId } = render(<Button rightIcon={icon} iconClassName='test-icon-class' />)
expect(getByTestId('icon').parentElement).toHaveClass('test-icon-class')
})
it('should show submenu indicator when `hasSubmenu=true`', () => {
const { getByTestId } = render(<Button hasSubmenu={true} />)
expect(getByTestId('button-submenu-indicator')).toBeInTheDocument()
})
it('should set focus classes when `hasFocus=true`', () => {
const { container } = render(<Button hasFocus={true} color='primary' />)
const button = container.querySelector('button')
expect(button).toHaveClass(focusClasses['primary'])
})
})
+60
View File
@@ -0,0 +1,60 @@
import { render, screen } from '@testing-library/react'
import Card from 'components/Card'
jest.mock('components/Text', () => {
return {
__esModule: true,
default: (props: any) =>
require('utils/testing').createMockComponent('mock-text-component', props),
}
})
describe('<Card />', () => {
const defaultProps = {
children: <></>,
}
afterAll(() => {
jest.unmock('components/Text')
})
it('should render', () => {
const { container } = render(<Card {...defaultProps} />)
expect(container).toBeInTheDocument()
})
it('should handle `className` prop correctly', () => {
const testClass = 'test-class'
const { container } = render(<Card {...defaultProps} className={testClass} />)
expect(container.querySelector('section')).toHaveClass(testClass)
})
it('should handle `contentClassName` prop correctly', () => {
const testClass = 'test-class'
const { container } = render(<Card {...defaultProps} contentClassName={testClass} />)
expect(container.querySelector('div')).toHaveClass(testClass)
})
it('should handle `title` prop as string correctly', () => {
const testTitle = 'this-is-the-test-title'
const { queryByText } = render(<Card {...defaultProps} title={testTitle} />)
expect(queryByText(testTitle)).toBeInTheDocument()
})
it('should handle `title` prop as element correctly', () => {
const testTitle = <p data-testid='test-title'>Test title</p>
const { queryByTestId } = render(<Card {...defaultProps} title={testTitle} />)
expect(queryByTestId('test-title')).toBeInTheDocument()
expect(queryByTestId('mock-text-component')).not.toBeInTheDocument()
})
it('should handle `id` prop as element correctly', () => {
const testId = 'test-id'
const { container } = render(<Card {...defaultProps} id={testId} />)
expect(container.querySelector(`section#${testId}`)).toBeInTheDocument()
})
})
@@ -0,0 +1,34 @@
import { render } from '@testing-library/react'
import { CircularProgress } from 'components/CircularProgress'
import { LocalStorageKeys } from 'constants/localStorageKeys'
describe('<CircularProgress />', () => {
afterAll(() => {
localStorage.removeItem(LocalStorageKeys.REDUCE_MOTION)
})
it('should render', () => {
const { container } = render(<CircularProgress />)
expect(container).toBeInTheDocument()
})
it('should render `...` when animations disabled', () => {
localStorage.setItem(LocalStorageKeys.REDUCE_MOTION, 'true')
const { getByText } = render(<CircularProgress />)
const threeDots = getByText('...')
expect(threeDots).toBeInTheDocument()
})
it('should render the component with animation classes when animations enabled', () => {
localStorage.setItem(LocalStorageKeys.REDUCE_MOTION, 'false')
const { container } = render(<CircularProgress />)
const progressWithAnimations = container.querySelector('.animate-progress')
expect(progressWithAnimations).toBeInTheDocument()
})
})
@@ -0,0 +1,65 @@
import { render } from '@testing-library/react'
import Modal from 'components/Modal'
import UnlockModal from 'components/Modals/Unlock'
import { BN_ONE, BN_ZERO } from 'constants/math'
import { TESTNET_VAULTS_META_DATA } from 'constants/vaults'
import useStore from 'store'
import { BN } from 'utils/helpers'
jest.mock('components/Modal')
const mockedModal = jest.mocked(Modal).mockImplementation(() => <div>Mock</div>)
const mockedDepositedVault: DepositedVault = {
...TESTNET_VAULTS_META_DATA[0],
status: 'active',
apy: 1,
apr: null,
ltv: {
max: 0.65,
liq: 0.7,
},
amounts: {
primary: BN_ONE,
secondary: BN_ONE,
locked: BN_ONE,
unlocked: BN_ONE,
unlocking: BN_ONE,
},
values: {
primary: BN_ZERO,
secondary: BN_ZERO,
unlocked: BN_ZERO,
unlocking: BN_ZERO,
},
cap: {
denom: 'mock',
max: BN(10),
used: BN_ONE,
},
}
describe('<UnlockModal />', () => {
afterAll(() => {
useStore.clearState()
})
it('should render', () => {
const { container } = render(<UnlockModal />)
expect(container).toBeInTheDocument()
})
describe('should set content correctly', () => {
it('should have no content when no modal is present in store', () => {
useStore.setState({ unlockModal: null })
render(<UnlockModal />)
expect(mockedModal).toHaveBeenCalledTimes(0)
})
it('should have content when modal is present in store', () => {
useStore.setState({ unlockModal: { vault: mockedDepositedVault } })
render(<UnlockModal />)
expect(mockedModal).toHaveBeenCalledTimes(1)
})
})
})
@@ -0,0 +1,32 @@
import { render } from '@testing-library/react'
import * as rrd from 'react-router-dom'
import PageMetadata from 'components/PageMetadata'
import PAGE_METADATA from 'constants/pageMetadata'
jest.mock('react-router-dom')
const mockedUseLocation = rrd.useLocation as jest.Mock
describe('<PageMetadata />', () => {
afterAll(() => {
jest.clearAllMocks()
})
Object.keys(PAGE_METADATA).forEach((page) => {
it(`should render correct ${page} metadata`, () => {
const pageKey = page as keyof typeof PAGE_METADATA
const pageMetadata = PAGE_METADATA[pageKey]
mockedUseLocation.mockReturnValue({ pathname: pageKey })
const { container } = render(<PageMetadata />)
const titleElement = container.querySelector('title')
const descriptionElement = container.querySelector('meta[name="description"]')
const keywordsElement = container.querySelector('meta[name="keywords"]')
expect(titleElement).toHaveTextContent(pageMetadata.title)
expect(descriptionElement).toHaveAttribute('content', pageMetadata.description)
expect(keywordsElement).toHaveAttribute('content', pageMetadata.keywords)
})
})
})
+98
View File
@@ -0,0 +1,98 @@
import { fireEvent, render } from '@testing-library/react'
import BigNumber from 'bignumber.js'
import TokenInput from 'components/TokenInput'
import { ASSETS } from 'constants/assets'
jest.mock('components/DisplayCurrency', () => {
return {
__esModule: true,
default: (props: any) =>
require('utils/testing').createMockComponent('mock-display-currency-component', props),
}
})
describe('<TokenInput />', () => {
const asset = ASSETS[0]
const defaultProps = {
amount: new BigNumber(1),
asset,
max: new BigNumber(100),
onChangeAsset: jest.fn(),
onChange: jest.fn(),
}
afterAll(() => {
jest.unmock('components/DisplayCurrency')
})
it('should render', () => {
const { container } = render(<TokenInput warningMessages={[]} {...defaultProps} />)
expect(container).toBeInTheDocument()
})
it('should handle `className` prop correctly', () => {
const testClass = 'test-class'
const { getByTestId } = render(
<TokenInput warningMessages={[]} {...defaultProps} className={testClass} />,
)
expect(getByTestId('token-input-component')).toHaveClass(testClass)
})
it('should handle `disabled` prop correctly', () => {
const { getByTestId } = render(
<TokenInput warningMessages={[]} {...defaultProps} disabled={true} />,
)
expect(getByTestId('token-input-component')).toHaveClass('pointer-events-none', 'opacity-50')
})
it('should handle `maxText` prop correctly', () => {
const { getByTestId } = render(
<TokenInput warningMessages={[]} {...defaultProps} maxText='Max' />,
)
expect(getByTestId('token-input-max-button')).toBeInTheDocument()
})
describe('should render the max button', () => {
it('when `maxText` prop is defined', () => {
const { getByTestId } = render(
<TokenInput warningMessages={[]} {...defaultProps} maxText='Max' />,
)
expect(getByTestId('token-input-max-button')).toBeInTheDocument()
})
it('not when `maxText` prop is undefined', () => {
const { queryByTestId } = render(<TokenInput warningMessages={[]} {...defaultProps} />)
expect(queryByTestId('token-input-max-button')).not.toBeInTheDocument()
})
})
describe('should render <Select />', () => {
it('when `hasSelect` prop is true and balances is defined', () => {
const { getByTestId } = render(
<TokenInput warningMessages={[]} {...defaultProps} balances={[]} hasSelect />,
)
expect(getByTestId('select-component')).toBeInTheDocument()
})
it('not when `hasSelect` prop is true and balances is not defined', () => {
const { queryByTestId } = render(
<TokenInput warningMessages={[]} {...defaultProps} hasSelect />,
)
expect(queryByTestId('select-component')).not.toBeInTheDocument()
})
it('not when `hasSelect` prop is false and balances is defined', () => {
const { queryByTestId } = render(
<TokenInput warningMessages={[]} {...defaultProps} balances={[]} />,
)
expect(queryByTestId('select-component')).not.toBeInTheDocument()
})
})
it('should call onMaxBtnClick when the user clicks on max button', () => {
const { getByTestId } = render(
<TokenInput warningMessages={[]} {...defaultProps} maxText='max' />,
)
const maxBtn = getByTestId('token-input-max-button')
fireEvent.click(maxBtn)
expect(defaultProps.onChange).toBeCalledWith(defaultProps.max)
})
})
@@ -0,0 +1,45 @@
import { render } from '@testing-library/react'
import { Tooltip } from 'components/Tooltip'
describe('<Tooltip />', () => {
const defaultProps = {
content: <></>,
}
it('should render', () => {
const { container } = render(<Tooltip {...defaultProps} type='info' />)
expect(container).toBeInTheDocument()
})
it('should handle `children` prop correctly', () => {
const { getByTestId } = render(
<Tooltip {...defaultProps} type='info'>
<p data-testid='test-child'>Test text</p>
</Tooltip>,
)
expect(getByTestId('test-child')).toBeInTheDocument()
})
it('should handle `className` prop correctly', () => {
const testClass = 'test-class'
const { container } = render(<Tooltip {...defaultProps} type='info' className={testClass} />)
expect(container.getElementsByClassName(testClass)).toHaveLength(1)
})
describe('should handle `underline` prop correctly', () => {
it('should have border class when children are passed', () => {
const { container } = render(
<Tooltip {...defaultProps} type='info' underline>
<></>
</Tooltip>,
)
expect(container.getElementsByClassName('border-b-1')).toHaveLength(1)
})
it('should not have border class when children are passed', () => {
const { container } = render(<Tooltip {...defaultProps} type='info' underline />)
expect(container.getElementsByClassName('border-b-1')).toHaveLength(0)
})
})
})
@@ -0,0 +1,43 @@
import { render } from '@testing-library/react'
import TooltipContent from 'components/Tooltip/TooltipContent'
describe('<Tooltip />', () => {
const defaultProps = {
content: <></>,
}
it('should render', () => {
const { container } = render(<TooltipContent {...defaultProps} type='info' />)
expect(container).toBeInTheDocument()
})
it('should handle `type` prop correctly', () => {
const types = { info: 'bg-white/20', warning: 'bg-warning', error: 'bg-error' }
Object.entries(types).forEach(([key, value]) => {
const { container } = render(<TooltipContent {...defaultProps} type={key as TooltipType} />)
expect(container.getElementsByClassName(value)).toHaveLength(1)
})
})
describe('should handle `content` props correctly', () => {
it('should render Text component when type is string', () => {
const testText = 'testText'
const { getByTestId } = render(
<TooltipContent {...defaultProps} type='info' content={testText} />,
)
const textComponent = getByTestId('text-component')
expect(textComponent).toHaveTextContent(testText)
})
it('should render content when type is ReactNode', () => {
const testNode = <p data-testid='test-node'>Test node</p>
const { queryByTestId } = render(
<TooltipContent {...defaultProps} type='info' content={testNode} />,
)
expect(queryByTestId('text-component')).not.toBeInTheDocument()
expect(queryByTestId('test-node')).toBeInTheDocument()
})
})
})
+60
View File
@@ -0,0 +1,60 @@
module.exports = {
collectCoverage: true,
coverageProvider: 'v8',
collectCoverageFrom: [
'**/*.{js,jsx,ts,tsx}',
'!**/*.d.ts',
'!**/node_modules/**',
'!<rootDir>/out/**',
'!<rootDir>/.next/**',
'!<rootDir>/*.config.js',
'!<rootDir>/coverage/**',
'!<rootDir>/src/types/**',
'!<rootDir>/src/utils/charting_library/**',
'!<rootDir>/src/utils/datafeeds/**',
'!<rootDir>/public/charting_library/**',
'!<rootDir>/public/datafeeds/**',
'!<rootDir>/src/utils/health_computer/**',
],
moduleNameMapper: {
// Handle CSS imports (with CSS modules)
// https://jestjs.io/docs/webpack#mocking-css-modules
'^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy',
// Handle CSS imports (without CSS modules)
'^.+\\.(css|sass|scss)$': '<rootDir>/__mocks__/styleMock.js',
// Handle image imports
// https://jestjs.io/docs/webpack#handling-static-assets
'^.+\\.(png|jpg|jpeg|gif|webp|avif|ico|bmp)$/i': `<rootDir>/__mocks__/fileMock.js`,
'^.+\\.svg$': `<rootDir>/__mocks__/svgMock.js`,
// Handle module aliases
'^app/(.*)$': '<rootDir>/src/app/$1',
'^api/(.*)$': '<rootDir>/src/api/$1',
'^components/(.*)$': '<rootDir>/src/components/$1',
'^constants/(.*)$': '<rootDir>/src/constants/$1',
'^fonts/(.*)$': '<rootDir>/src/fonts/$1',
'^hooks/(.*)$': '<rootDir>/src/hooks/$1',
'^pages/(.*)$': '<rootDir>/src/pages/$1',
'^store/(.*)$': '<rootDir>/src/store/$1',
'^styles/(.*)$': '<rootDir>/src/styles/$1',
'^types/(.*)$': '<rootDir>/src/types/$1',
'^utils/(.*)$': '<rootDir>/src/utils/$1',
'^store': '<rootDir>/src/store',
},
// Add more setup options before each test is run
setupFilesAfterEnv: [
'<rootDir>/jest.setup.js',
'<rootDir>/__mocks__/store.js',
'<rootDir>/__mocks__/helmet.js',
],
testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/.next/'],
testEnvironment: 'jsdom',
transform: {
// Use babel-jest to transpile tests with the next/babel preset
// https://jestjs.io/docs/configuration#transform-objectstring-pathtotransformer--pathtotransformer-object
'^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { presets: ['next/babel'] }],
},
transformIgnorePatterns: ['/node_modules/', '^.+\\.module\\.(css|sass|scss)$'],
}
+1
View File
@@ -0,0 +1 @@
import '@testing-library/jest-dom/extend-expect'
+25 -19
View File
@@ -1,12 +1,14 @@
{ {
"name": "mars-v2-frontend", "name": "mars-v2-frontend",
"version": "2.2.0", "version": "2.0.6",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "yarn validate-env && next build", "build": "yarn validate-env && next build",
"dev": "next dev", "dev": "next dev",
"test": "jest",
"test:cov": "jest --coverage",
"lint": "eslint ./src/ && yarn prettier-check", "lint": "eslint ./src/ && yarn prettier-check",
"format": "eslint ./src/ --fix && prettier --write ./src/ ", "format": "eslint ./src/ ./__tests__/ --fix && prettier --write ./src/ ./__tests__/",
"prettier-check": "prettier --ignore-path .gitignore --check ./src/", "prettier-check": "prettier --ignore-path .gitignore --check ./src/",
"start": "next start", "start": "next start",
"validate-env": "node ./validate-env", "validate-env": "node ./validate-env",
@@ -15,17 +17,17 @@
}, },
"lint-staged": { "lint-staged": {
"*.ts*": [ "*.ts*": [
"eslint ./src/ --fix", "eslint ./src/ ./__tests__/ --fix",
"prettier --write ./src/" "prettier --write ./src/ ./__tests__/"
] ]
}, },
"dependencies": { "dependencies": {
"@cosmjs/cosmwasm-stargate": "^0.32.2", "@cosmjs/cosmwasm-stargate": "^0.31.1",
"@delphi-labs/shuttle-react": "^3.10.0", "@delphi-labs/shuttle-react": "^3.10.0",
"@keplr-wallet/cosmos": "^0.12.42", "@keplr-wallet/cosmos": "^0.12.42",
"@sentry/nextjs": "^7.94.1", "@sentry/nextjs": "^7.77.0",
"@splinetool/react-spline": "^2.2.6", "@splinetool/react-spline": "^2.2.6",
"@splinetool/runtime": "^0.9.521", "@splinetool/runtime": "^0.9.482",
"@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/container-queries": "^0.1.1",
"@tanstack/react-table": "^8.10.6", "@tanstack/react-table": "^8.10.6",
"@tippyjs/react": "^4.2.6", "@tippyjs/react": "^4.2.6",
@@ -41,37 +43,41 @@
"react-device-detect": "^2.2.3", "react-device-detect": "^2.2.3",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-draggable": "^4.4.6", "react-draggable": "^4.4.6",
"react-helmet-async": "^2.0.3", "react-helmet-async": "^1.3.0",
"react-qr-code": "^2.0.12", "react-qr-code": "^2.0.12",
"react-router-dom": "^6.21.3", "react-router-dom": "^6.17.0",
"react-spring": "^9.7.3", "react-spring": "^9.7.3",
"react-toastify": "^9.1.3", "react-toastify": "^9.1.3",
"react-use-clipboard": "^1.0.9", "react-use-clipboard": "^1.0.9",
"recharts": "^2.10.1", "recharts": "^2.10.1",
"sharp": "^0.33.2",
"swr": "^2.2.4", "swr": "^2.2.4",
"tailwind-scrollbar-hide": "^1.1.7", "tailwind-scrollbar-hide": "^1.1.7",
"zustand": "^4.4.7" "zustand": "^4.4.6"
}, },
"devDependencies": { "devDependencies": {
"@svgr/webpack": "^8.1.0", "@svgr/webpack": "^8.1.0",
"@types/debounce-promise": "^3.1.9", "@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^14.0.0",
"@types/debounce-promise": "^3.1.7",
"@types/lodash.debounce": "^4.0.9", "@types/lodash.debounce": "^4.0.9",
"@types/lodash.throttle": "^4.1.8", "@types/lodash.throttle": "^4.1.8",
"@types/node": "^20.10.6", "@types/node": "^20.8.6",
"@types/react": "18.2.41", "@types/react": "18.2.33",
"@types/react-dom": "18.2.15", "@types/react-dom": "18.2.15",
"@types/react-helmet": "^6.1.11", "@types/react-helmet": "^6.1.9",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.16",
"babel-jest": "^29.7.0",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"dotenv-cli": "^7.3.0", "dotenv-cli": "^7.3.0",
"eslint": "^8.56.0", "eslint": "^8.54.0",
"eslint-config-next": "^14.0.4", "eslint-config-next": "^14.0.0",
"eslint-plugin-import": "^2.29.0", "eslint-plugin-import": "^2.29.0",
"husky": "^8.0.3", "husky": "^8.0.3",
"identity-obj-proxy": "^3.0.0", "identity-obj-proxy": "^3.0.0",
"lint-staged": "^15.2.0", "jest": "^29.7.0",
"prettier": "^3.2.4", "jest-environment-jsdom": "^29.7.0",
"lint-staged": "^15.0.1",
"prettier": "^3.0.3",
"prettier-plugin-tailwindcss": "^0.5.6", "prettier-plugin-tailwindcss": "^0.5.6",
"shelljs": "^0.8.5", "shelljs": "^0.8.5",
"tailwindcss": "^3.3.3", "tailwindcss": "^3.3.3",
-16
View File
@@ -1,16 +0,0 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 24 24">
<path
fill="#FFF"
d="M23.6,14.9C22,21.3,15.5,25.2,9,23.6C2.6,22-1.3,15.5,0.3,9.1S8.4-1.2,14.8,0.4C21.3,2,25.2,8.5,23.6,14.9
L23.6,14.9L23.6,14.9z"
/>
<path
fill="#000"
d="M12,15.1c-1.7,0-3.1-1.4-3.1-3.1c0-0.6,0.2-1.2,0.5-1.6L5.4,7.1v11.5h11.5l-3.3-3.9C13.1,15,12.5,15.1,12,15.1z
"
/>
<path
fill="#000"
d="M6.4,5.4l3.5,4.2c0.5-0.5,1.3-0.8,2-0.8c1.7,0,3.1,1.4,3.1,3.1c0,0.7-0.3,1.4-0.7,2l4.1,3.6V5.4H6.4z"
/>
</svg>

Before

Width:  |  Height:  |  Size: 557 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

-10
View File
@@ -40,16 +40,6 @@
cursor: pointer; cursor: pointer;
} }
.layout__area--center {
background: var(--tv-background) !important;
}
.chart-widget.chart-widget--themed-dark.chart-widget__top--themed-dark.chart-widget__bottom--themed-dark
> table
canvas {
background: transparent !important;
}
/* Floating menu */ /* Floating menu */
.floating-toolbar-react-widgets__button:hover, .floating-toolbar-react-widgets__button:hover,
[class^='button-']:hover:before { [class^='button-']:hover:before {
+4 -9
View File
@@ -3,25 +3,21 @@ import { getCreditManagerQueryClient } from 'api/cosmwasm-client'
import getDepositedVaults from 'api/vaults/getDepositedVaults' import getDepositedVaults from 'api/vaults/getDepositedVaults'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { Positions } from 'types/generated/mars-credit-manager/MarsCreditManager.types' import { Positions } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
import { resolvePerpsPositions } from 'utils/resolvers'
export default async function getAccount( export default async function getAccount(accountId?: string): Promise<Account> {
chainConfig: ChainConfig,
accountId?: string,
): Promise<Account> {
if (!accountId) return new Promise((_, reject) => reject('No account ID found')) if (!accountId) return new Promise((_, reject) => reject('No account ID found'))
const creditManagerQueryClient = await getCreditManagerQueryClient(chainConfig) const creditManagerQueryClient = await getCreditManagerQueryClient()
const accountPosition: Positions = await cacheFn( const accountPosition: Positions = await cacheFn(
() => creditManagerQueryClient.positions({ accountId: accountId }), () => creditManagerQueryClient.positions({ accountId: accountId }),
positionsCache, positionsCache,
`${chainConfig.id}/account/${accountId}`, `account/${accountId}`,
) )
const accountKind = await creditManagerQueryClient.accountKind({ accountId: accountId }) const accountKind = await creditManagerQueryClient.accountKind({ accountId: accountId })
const depositedVaults = await getDepositedVaults(accountId, chainConfig, accountPosition) const depositedVaults = await getDepositedVaults(accountId, accountPosition)
if (accountPosition) { if (accountPosition) {
return { return {
@@ -30,7 +26,6 @@ export default async function getAccount(
lends: accountPosition.lends.map((lend) => new BNCoin(lend)), lends: accountPosition.lends.map((lend) => new BNCoin(lend)),
deposits: accountPosition.deposits.map((deposit) => new BNCoin(deposit)), deposits: accountPosition.deposits.map((deposit) => new BNCoin(deposit)),
vaults: depositedVaults, vaults: depositedVaults,
perps: resolvePerpsPositions(accountPosition.perps),
kind: accountKind, kind: accountKind,
} }
} }
-1
View File
@@ -61,4 +61,3 @@ export const allParamsCache: Cache<AssetParamsBaseForAddr[]> = new Map()
export const underlyingDebtCache: Cache<string> = new Map() export const underlyingDebtCache: Cache<string> = new Map()
export const previewDepositCache: Cache<{ vaultAddress: string; amount: string }> = new Map() export const previewDepositCache: Cache<{ vaultAddress: string; amount: string }> = new Map()
export const stakingAprCache: Cache<StakingApr[]> = new Map() export const stakingAprCache: Cache<StakingApr[]> = new Map()
export const assetParamsCache: Cache<AssetParamsBaseForAddr[]> = new Map()
+59 -104
View File
@@ -1,5 +1,6 @@
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate' import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import { ENV } from 'constants/env'
import { ICNSQueryClient } from 'types/classes/ICNSClient.client' import { ICNSQueryClient } from 'types/classes/ICNSClient.client'
import { MarsAccountNftQueryClient } from 'types/generated/mars-account-nft/MarsAccountNft.client' import { MarsAccountNftQueryClient } from 'types/generated/mars-account-nft/MarsAccountNft.client'
import { MarsCreditManagerQueryClient } from 'types/generated/mars-credit-manager/MarsCreditManager.client' import { MarsCreditManagerQueryClient } from 'types/generated/mars-credit-manager/MarsCreditManager.client'
@@ -7,187 +8,142 @@ import { MarsIncentivesQueryClient } from 'types/generated/mars-incentives/MarsI
import { MarsMockVaultQueryClient } from 'types/generated/mars-mock-vault/MarsMockVault.client' import { MarsMockVaultQueryClient } from 'types/generated/mars-mock-vault/MarsMockVault.client'
import { MarsOracleOsmosisQueryClient } from 'types/generated/mars-oracle-osmosis/MarsOracleOsmosis.client' import { MarsOracleOsmosisQueryClient } from 'types/generated/mars-oracle-osmosis/MarsOracleOsmosis.client'
import { MarsParamsQueryClient } from 'types/generated/mars-params/MarsParams.client' import { MarsParamsQueryClient } from 'types/generated/mars-params/MarsParams.client'
import { MarsPerpsQueryClient } from 'types/generated/mars-perps/MarsPerps.client'
import { MarsRedBankQueryClient } from 'types/generated/mars-red-bank/MarsRedBank.client' import { MarsRedBankQueryClient } from 'types/generated/mars-red-bank/MarsRedBank.client'
import { MarsSwapperOsmosisQueryClient } from 'types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.client' import { MarsSwapperOsmosisQueryClient } from 'types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.client'
let _cosmWasmClient: Map<string, CosmWasmClient> = new Map() let _cosmWasmClient: CosmWasmClient
let _accountNftQueryClient: Map<string, MarsAccountNftQueryClient> = new Map() let _accountNftQueryClient: MarsAccountNftQueryClient
let _creditManagerQueryClient: Map<string, MarsCreditManagerQueryClient> = new Map() let _creditManagerQueryClient: MarsCreditManagerQueryClient
let _oracleQueryClient: Map<string, MarsOracleOsmosisQueryClient> = new Map() let _oracleQueryClient: MarsOracleOsmosisQueryClient
let _redBankQueryClient: Map<string, MarsRedBankQueryClient> = new Map() let _redBankQueryClient: MarsRedBankQueryClient
let _paramsQueryClient: Map<string, MarsParamsQueryClient> = new Map() let _paramsQueryClient: MarsParamsQueryClient
let _incentivesQueryClient: Map<string, MarsIncentivesQueryClient> = new Map() let _incentivesQueryClient: MarsIncentivesQueryClient
let _swapperOsmosisClient: Map<string, MarsSwapperOsmosisQueryClient> = new Map() let _swapperOsmosisClient: MarsSwapperOsmosisQueryClient
let _perpsClient: Map<string, MarsPerpsQueryClient> = new Map() let _ICNSQueryClient: ICNSQueryClient
let _ICNSQueryClient: Map<string, ICNSQueryClient> = new Map()
const getClient = async (rpc: string) => { const getClient = async () => {
try { try {
if (!_cosmWasmClient.get(rpc)) { if (!_cosmWasmClient) {
const client = await CosmWasmClient.connect(rpc) _cosmWasmClient = await CosmWasmClient.connect(ENV.URL_RPC)
_cosmWasmClient.set(rpc, client)
} }
return _cosmWasmClient.get(rpc)! return _cosmWasmClient
} catch (error) { } catch (error) {
throw error throw error
} }
} }
const getAccountNftQueryClient = async (chainConfig: ChainConfig) => { const getAccountNftQueryClient = async () => {
try { try {
const contract = chainConfig.contracts.accountNft if (!_accountNftQueryClient) {
const rpc = chainConfig.endpoints.rpc const client = await getClient()
const key = rpc + contract _accountNftQueryClient = new MarsAccountNftQueryClient(client, ENV.ADDRESS_ACCOUNT_NFT)
if (!_accountNftQueryClient.get(key)) {
const client = await getClient(rpc)
_accountNftQueryClient.set(key, new MarsAccountNftQueryClient(client, contract))
} }
return _accountNftQueryClient.get(key)! return _accountNftQueryClient
} catch (error) { } catch (error) {
throw error throw error
} }
} }
const getCreditManagerQueryClient = async (chainConfig: ChainConfig) => { const getCreditManagerQueryClient = async () => {
try { try {
const contract = chainConfig.contracts.creditManager if (!_creditManagerQueryClient) {
const rpc = chainConfig.endpoints.rpc const client = await getClient()
const key = rpc + contract _creditManagerQueryClient = new MarsCreditManagerQueryClient(
client,
if (!_creditManagerQueryClient.get(key)) { ENV.ADDRESS_CREDIT_MANAGER,
const client = await getClient(rpc) )
_creditManagerQueryClient.set(key, new MarsCreditManagerQueryClient(client, contract))
} }
return _creditManagerQueryClient.get(key)! return _creditManagerQueryClient
} catch (error) { } catch (error) {
throw error throw error
} }
} }
const getParamsQueryClient = async (chainConfig: ChainConfig) => { const getParamsQueryClient = async () => {
try { try {
const contract = chainConfig.contracts.params if (!_paramsQueryClient) {
const rpc = chainConfig.endpoints.rpc const client = await getClient()
const key = rpc + contract _paramsQueryClient = new MarsParamsQueryClient(client, ENV.ADDRESS_PARAMS)
if (!_paramsQueryClient.get(key)) {
const client = await getClient(rpc)
_paramsQueryClient.set(key, new MarsParamsQueryClient(client, contract))
} }
return _paramsQueryClient.get(key)! return _paramsQueryClient
} catch (error) { } catch (error) {
throw error throw error
} }
} }
const getOracleQueryClient = async (chainConfig: ChainConfig) => { const getOracleQueryClient = async () => {
try { try {
const contract = chainConfig.contracts.oracle if (!_oracleQueryClient) {
const rpc = chainConfig.endpoints.rpc const client = await getClient()
const key = rpc + contract _oracleQueryClient = new MarsOracleOsmosisQueryClient(client, ENV.ADDRESS_ORACLE)
if (!_oracleQueryClient.get(key)) {
const client = await getClient(rpc)
_oracleQueryClient.set(key, new MarsOracleOsmosisQueryClient(client, contract))
} }
return _oracleQueryClient.get(key)! return _oracleQueryClient
} catch (error) { } catch (error) {
throw error throw error
} }
} }
const getRedBankQueryClient = async (chainConfig: ChainConfig) => { const getRedBankQueryClient = async () => {
try { try {
const contract = chainConfig.contracts.redBank if (!_redBankQueryClient) {
const rpc = chainConfig.endpoints.rpc const client = await getClient()
const key = rpc + contract _redBankQueryClient = new MarsRedBankQueryClient(client, ENV.ADDRESS_RED_BANK)
if (!_redBankQueryClient.get(key)) {
const client = await getClient(rpc)
_redBankQueryClient.set(key, new MarsRedBankQueryClient(client, contract))
} }
return _redBankQueryClient.get(key)! return _redBankQueryClient
} catch (error) { } catch (error) {
throw error throw error
} }
} }
const getVaultQueryClient = async (chainConfig: ChainConfig, address: string) => { const getVaultQueryClient = async (address: string) => {
try { try {
const client = await getClient(chainConfig.endpoints.rpc) const client = await getClient()
return new MarsMockVaultQueryClient(client, address) return new MarsMockVaultQueryClient(client, address)
} catch (error) { } catch (error) {
throw error throw error
} }
} }
const getIncentivesQueryClient = async (chainConfig: ChainConfig) => { const getIncentivesQueryClient = async () => {
try { try {
const contract = chainConfig.contracts.incentives if (!_incentivesQueryClient) {
const rpc = chainConfig.endpoints.rpc const client = await getClient()
const key = rpc + contract _incentivesQueryClient = new MarsIncentivesQueryClient(client, ENV.ADDRESS_INCENTIVES)
if (!_incentivesQueryClient.get(key)) {
const client = await getClient(rpc)
_incentivesQueryClient.set(key, new MarsIncentivesQueryClient(client, contract))
} }
return _incentivesQueryClient.get(key)! return _incentivesQueryClient
} catch (error) { } catch (error) {
throw error throw error
} }
} }
const getSwapperQueryClient = async (chainConfig: ChainConfig) => { const getSwapperQueryClient = async () => {
try { try {
const contract = chainConfig.contracts.swapper if (!_swapperOsmosisClient) {
const rpc = chainConfig.endpoints.rpc const client = await getClient()
const key = rpc + contract _swapperOsmosisClient = new MarsSwapperOsmosisQueryClient(client, ENV.ADDRESS_SWAPPER)
if (!_swapperOsmosisClient.get(key)) {
const client = await getClient(rpc)
_swapperOsmosisClient.set(key, new MarsSwapperOsmosisQueryClient(client, contract))
} }
return _swapperOsmosisClient.get(key)! return _swapperOsmosisClient
} catch (error) { } catch (error) {
throw error throw error
} }
} }
const getPerpsQueryClient = async (chainConfig: ChainConfig) => { const getICNSQueryClient = async () => {
try { try {
const contract = chainConfig.contracts.perps if (!_ICNSQueryClient) {
const rpc = chainConfig.endpoints.rpc const client = await getClient()
const key = rpc + contract _ICNSQueryClient = new ICNSQueryClient(client)
if (!_perpsClient.get(key)) {
const client = await getClient(rpc)
_perpsClient.set(key, new MarsPerpsQueryClient(client, contract))
} }
return _perpsClient.get(key)! return _ICNSQueryClient
} catch (error) {
throw error
}
}
const getICNSQueryClient = async (chainConfig: ChainConfig) => {
try {
const contract = chainConfig.contracts.params
const rpc = chainConfig.endpoints.rpc
const key = rpc + contract
if (!_ICNSQueryClient.get(key)) {
const client = await getClient(rpc)
_ICNSQueryClient.set(key, new ICNSQueryClient(client))
}
return _ICNSQueryClient.get(key)!
} catch (error) { } catch (error) {
throw error throw error
} }
@@ -204,5 +160,4 @@ export {
getRedBankQueryClient, getRedBankQueryClient,
getSwapperQueryClient, getSwapperQueryClient,
getVaultQueryClient, getVaultQueryClient,
getPerpsQueryClient,
} }
+6 -3
View File
@@ -1,8 +1,9 @@
import { cacheFn, stakingAprCache } from 'api/cache' import { cacheFn, stakingAprCache } from 'api/cache'
import { ENV } from 'constants/env'
export default async function getStakingAprs(url: string) { export default async function getStakingAprs() {
try { try {
return cacheFn(() => fetchAprs(url), stakingAprCache, `stakingAprs`) return cacheFn(() => fetchAprs(), stakingAprCache, `stakingAprs`)
} catch (error) { } catch (error) {
throw error throw error
} }
@@ -12,7 +13,9 @@ interface StakingAprResponse {
stats: StakingApr[] stats: StakingApr[]
} }
async function fetchAprs(url: string): Promise<StakingApr[]> { async function fetchAprs(): Promise<StakingApr[]> {
const url = ENV.STRIDE_APRS
const response = await fetch(url) const response = await fetch(url)
const body = (await response.json()) as StakingAprResponse const body = (await response.json()) as StakingAprResponse
return body.stats return body.stats
+6 -11
View File
@@ -4,29 +4,24 @@ import getAccounts from 'api/wallets/getAccounts'
import { calculateAccountLeverage, getAccountPositionValues, isAccountEmpty } from 'utils/accounts' import { calculateAccountLeverage, getAccountPositionValues, isAccountEmpty } from 'utils/accounts'
export default async function getHLSStakingAccounts( export default async function getHLSStakingAccounts(
chainConfig: ChainConfig,
address?: string, address?: string,
): Promise<HLSAccountWithStrategy[]> { ): Promise<HLSAccountWithStrategy[]> {
const accounts = await getAccounts('high_levered_strategy', chainConfig, address) const accounts = await getAccounts('high_levered_strategy', address)
const activeAccounts = accounts.filter((account) => !isAccountEmpty(account)) const activeAccounts = accounts.filter((account) => !isAccountEmpty(account))
const hlsStrategies = await getHLSStakingAssets(chainConfig) const hlsStrategies = await getHLSStakingAssets()
const prices = await getPrices(chainConfig) const prices = await getPrices()
const hlsAccountsWithStrategy: HLSAccountWithStrategy[] = [] const hlsAccountsWithStrategy: HLSAccountWithStrategy[] = []
activeAccounts.forEach((account) => { activeAccounts.forEach((account) => {
if (account.deposits.length === 0) return if (account.deposits.length === 0) return
const strategy = hlsStrategies.find( const strategy = hlsStrategies.find(
(strategy) => strategy.denoms.deposit === account.deposits[0].denom, (strategy) => strategy.denoms.deposit === account.deposits.at(0).denom,
) )
if (!strategy) return if (!strategy) return
const [deposits, lends, debts, vaults] = getAccountPositionValues( const [deposits, lends, debts, vaults] = getAccountPositionValues(account, prices)
account,
prices,
chainConfig.assets,
)
hlsAccountsWithStrategy.push({ hlsAccountsWithStrategy.push({
...account, ...account,
@@ -36,7 +31,7 @@ export default async function getHLSStakingAccounts(
debt: debts, debt: debts,
total: deposits, total: deposits,
}, },
leverage: calculateAccountLeverage(account, prices, chainConfig.assets).toNumber(), leverage: calculateAccountLeverage(account, prices).toNumber(),
}) })
}) })
+8 -9
View File
@@ -1,29 +1,28 @@
import { getParamsQueryClient } from 'api/cosmwasm-client' import { getParamsQueryClient } from 'api/cosmwasm-client'
import getStakingAprs from 'api/hls/getAprs' import getStakingAprs from 'api/hls/getAprs'
import getAssetParams from 'api/params/getAssetParams' import getAssetParams from 'api/params/getAssetParams'
import { byDenom } from 'utils/array' import { getAssetByDenom, getStakingAssets } from 'utils/assets'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
import { resolveHLSStrategies } from 'utils/resolvers' import { resolveHLSStrategies } from 'utils/resolvers'
export default async function getHLSStakingAssets(chainConfig: ChainConfig) { export default async function getHLSStakingAssets() {
const stakingAssetDenoms = chainConfig.assets const stakingAssetDenoms = getStakingAssets().map((asset) => asset.denom)
.filter((asset) => asset.isStaking) const assetParams = await getAssetParams()
.map((asset) => asset.denom)
const assetParams = await getAssetParams(chainConfig)
const HLSAssets = assetParams const HLSAssets = assetParams
.filter((asset) => stakingAssetDenoms.includes(asset.denom)) .filter((asset) => stakingAssetDenoms.includes(asset.denom))
.filter((asset) => asset.credit_manager.hls) .filter((asset) => asset.credit_manager.hls)
const strategies = resolveHLSStrategies('coin', HLSAssets) const strategies = resolveHLSStrategies('coin', HLSAssets)
const client = await getParamsQueryClient(chainConfig)
const client = await getParamsQueryClient()
const depositCaps$ = strategies.map((strategy) => const depositCaps$ = strategies.map((strategy) =>
client.totalDeposit({ denom: strategy.denoms.deposit }), client.totalDeposit({ denom: strategy.denoms.deposit }),
) )
const aprs = await getStakingAprs(chainConfig.endpoints.aprs.stride) const aprs = await getStakingAprs()
return Promise.all(depositCaps$).then((depositCaps) => { return Promise.all(depositCaps$).then((depositCaps) => {
return depositCaps.map((depositCap, index) => { return depositCaps.map((depositCap, index) => {
const borrowSymbol = chainConfig.assets.find(byDenom(strategies[index].denoms.borrow))?.symbol const borrowSymbol = getAssetByDenom(strategies[index].denoms.borrow)?.symbol
return { return {
...strategies[index], ...strategies[index],
depositCap: { depositCap: {
+4 -4
View File
@@ -4,10 +4,10 @@ import { getVaultConfigs } from 'api/vaults/getVaultConfigs'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
import { resolveHLSStrategies } from 'utils/resolvers' import { resolveHLSStrategies } from 'utils/resolvers'
export default async function getHLSVaults(chainConfig: ChainConfig) { export default async function getHLSVaults() {
const assetParams = await getAssetParams(chainConfig) const assetParams = await getAssetParams()
const client = await getCreditManagerQueryClient(chainConfig) const client = await getCreditManagerQueryClient()
const vaultConfigs = await getVaultConfigs(chainConfig) const vaultConfigs = await getVaultConfigs()
const HLSAssets = assetParams.filter((asset) => asset.credit_manager.hls) const HLSAssets = assetParams.filter((asset) => asset.credit_manager.hls)
const strategies = resolveHLSStrategies('vault', HLSAssets) const strategies = resolveHLSStrategies('vault', HLSAssets)
@@ -1,35 +1,35 @@
import getTotalActiveEmissionValue from 'api/incentives/getTotalActiveEmissionValue'
import getMarket from 'api/markets/getMarket' import getMarket from 'api/markets/getMarket'
import getTotalActiveEmissionValue from 'api/incentives/getTotalActiveEmissionValue'
import getUnderlyingLiquidityAmount from 'api/markets/getMarketUnderlyingLiquidityAmount' import getUnderlyingLiquidityAmount from 'api/markets/getMarketUnderlyingLiquidityAmount'
import getPrice from 'api/prices/getPrice'
import { byDenom } from 'utils/array'
import { SECONDS_IN_A_YEAR } from 'utils/constants'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
import { SECONDS_IN_A_YEAR } from 'utils/constants'
import getPrice from 'api/prices/getPrice'
import { ASSETS } from 'constants/assets'
import { byDenom } from 'utils/array'
export default async function calculateAssetIncentivesApy( export default async function calculateAssetIncentivesApy(
chainConfig: ChainConfig,
denom: string, denom: string,
): Promise<BigNumber | null> { ): Promise<BigNumber | null> {
try { try {
const [totalActiveEmissionValue, market] = await Promise.all([ const [totalActiveEmissionValue, market] = await Promise.all([
getTotalActiveEmissionValue(chainConfig, denom), getTotalActiveEmissionValue(denom),
getMarket(chainConfig, denom), getMarket(denom),
]) ])
if (!totalActiveEmissionValue) return null if (!totalActiveEmissionValue) return null
const [marketLiquidityAmount, assetPrice] = await Promise.all([ const [marketLiquidityAmount, assetPrice] = await Promise.all([
getUnderlyingLiquidityAmount(chainConfig, market), getUnderlyingLiquidityAmount(market),
getPrice(chainConfig, denom), getPrice(denom),
]) ])
const assetDecimals = (chainConfig.assets.find(byDenom(denom)) as Asset).decimals const assetDecimals = (ASSETS.find(byDenom(denom)) as Asset).decimals
const marketLiquidityValue = BN(marketLiquidityAmount) const marketLiquidityValue = BN(marketLiquidityAmount)
.shiftedBy(-assetDecimals) .shiftedBy(-assetDecimals)
.multipliedBy(assetPrice) .multipliedBy(assetPrice)
const marketReturns = BN(market.apy.deposit).multipliedBy(marketLiquidityValue) const marketReturns = BN(market.liquidityRate).multipliedBy(marketLiquidityValue)
const annualEmission = totalActiveEmissionValue.multipliedBy(SECONDS_IN_A_YEAR) const annualEmission = totalActiveEmissionValue.multipliedBy(SECONDS_IN_A_YEAR)
const totalAnnualReturnsValue = annualEmission.plus(marketReturns) const totalAnnualReturnsValue = annualEmission.plus(marketReturns)
@@ -1,16 +1,16 @@
import { cacheFn, emissionsCache } from 'api/cache' import { cacheFn, emissionsCache } from 'api/cache'
import { getIncentivesQueryClient } from 'api/cosmwasm-client' import { getIncentivesQueryClient } from 'api/cosmwasm-client'
import getPrice from 'api/prices/getPrice' import getPrice from 'api/prices/getPrice'
import { ASSETS } from 'constants/assets'
import { BN_ZERO } from 'constants/math' import { BN_ZERO } from 'constants/math'
import { byDenom } from 'utils/array' import { byDenom } from 'utils/array'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
export default async function getTotalActiveEmissionValue( export default async function getTotalActiveEmissionValue(
chainConfig: ChainConfig,
denom: string, denom: string,
): Promise<BigNumber | null> { ): Promise<BigNumber | null> {
try { try {
const client = await getIncentivesQueryClient(chainConfig) const client = await getIncentivesQueryClient()
const activeEmissions = await cacheFn( const activeEmissions = await cacheFn(
() => () =>
client.activeEmissions({ client.activeEmissions({
@@ -26,12 +26,12 @@ export default async function getTotalActiveEmissionValue(
} }
const prices = await Promise.all( const prices = await Promise.all(
activeEmissions.map((activeEmission) => getPrice(chainConfig, activeEmission.denom)), activeEmissions.map((activeEmission) => getPrice(activeEmission.denom)),
) )
return activeEmissions.reduce((accumulation, current, index) => { return activeEmissions.reduce((accumulation, current, index) => {
const price = prices[index] const price = prices[index]
const decimals = chainConfig.assets.find(byDenom(current.denom))?.decimals as number const decimals = ASSETS.find(byDenom(current.denom))?.decimals as number
const emissionValue = BN(current.emission_rate).shiftedBy(-decimals).multipliedBy(price) const emissionValue = BN(current.emission_rate).shiftedBy(-decimals).multipliedBy(price)
return accumulation.plus(emissionValue) return accumulation.plus(emissionValue)
+4 -6
View File
@@ -1,19 +1,17 @@
import { cacheFn, unclaimedRewardsCache } from 'api/cache' import { cacheFn, unclaimedRewardsCache } from 'api/cache'
import { getIncentivesQueryClient } from 'api/cosmwasm-client' import { getIncentivesQueryClient } from 'api/cosmwasm-client'
import { ENV } from 'constants/env'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import iterateContractQuery from 'utils/iterateContractQuery' import iterateContractQuery from 'utils/iterateContractQuery'
export default async function getUnclaimedRewards( export default async function getUnclaimedRewards(accountId: string): Promise<BNCoin[]> {
chainConfig: ChainConfig,
accountId: string,
): Promise<BNCoin[]> {
try { try {
const client = await getIncentivesQueryClient(chainConfig) const client = await getIncentivesQueryClient()
const unclaimedRewards = await cacheFn( const unclaimedRewards = await cacheFn(
() => () =>
iterateContractQuery(() => iterateContractQuery(() =>
client.userUnclaimedRewards({ client.userUnclaimedRewards({
user: chainConfig.contracts.creditManager, user: ENV.ADDRESS_CREDIT_MANAGER,
accountId, accountId,
}), }),
), ),
+5 -5
View File
@@ -2,14 +2,14 @@ import { cacheFn, marketCache } from 'api/cache'
import { getParamsQueryClient, getRedBankQueryClient } from 'api/cosmwasm-client' import { getParamsQueryClient, getRedBankQueryClient } from 'api/cosmwasm-client'
import { resolveMarketResponse } from 'utils/resolvers' import { resolveMarketResponse } from 'utils/resolvers'
export default async function getMarket(chainConfig: ChainConfig, denom: string): Promise<Market> { export default async function getMarket(denom: string): Promise<Market> {
return cacheFn(() => fetchMarket(chainConfig, denom), marketCache, denom, 60) return cacheFn(() => fetchMarket(denom), marketCache, denom, 60)
} }
async function fetchMarket(chainConfig: ChainConfig, denom: string) { async function fetchMarket(denom: string) {
try { try {
const redBankClient = await getRedBankQueryClient(chainConfig) const redBankClient = await getRedBankQueryClient()
const paramsClient = await getParamsQueryClient(chainConfig) const paramsClient = await getParamsQueryClient()
const [market, assetParams, assetCap] = await Promise.all([ const [market, assetParams, assetCap] = await Promise.all([
redBankClient.market({ denom }), redBankClient.market({ denom }),
+8 -10
View File
@@ -1,25 +1,23 @@
import getMarketLiquidities from 'api/markets/getMarketLiquidities' import getMarketLiquidities from 'api/markets/getMarketLiquidities'
import getMarkets from 'api/markets/getMarkets' import getMarkets from 'api/markets/getMarkets'
import getPrices from 'api/prices/getPrices' import getPrices from 'api/prices/getPrices'
import { getEnabledMarketAssets } from 'utils/assets'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
export default async function getMarketBorrowings( export default async function getMarketBorrowings(): Promise<BorrowAsset[]> {
chainConfig: ChainConfig, const liquidities = await getMarketLiquidities()
): Promise<BorrowAsset[]> { const enabledAssets = getEnabledMarketAssets()
const liquidities = await getMarketLiquidities(chainConfig) const borrowEnabledMarkets = (await getMarkets()).filter((market: Market) => market.borrowEnabled)
const borrowEnabledMarkets = (await getMarkets(chainConfig)).filter( const prices = await getPrices()
(market: Market) => market.borrowEnabled,
)
const prices = await getPrices(chainConfig)
const borrow: BorrowAsset[] = borrowEnabledMarkets.map((market) => { const borrow: BorrowAsset[] = borrowEnabledMarkets.map((market) => {
const price = prices.find((coin) => coin.denom === market.denom)?.amount ?? '1' const price = prices.find((coin) => coin.denom === market.denom)?.amount ?? '1'
const amount = liquidities.find((coin) => coin.denom === market.denom)?.amount ?? '0' const amount = liquidities.find((coin) => coin.denom === market.denom)?.amount ?? '0'
const asset = chainConfig.assets.find((asset) => asset.denom === market.denom)! const asset = enabledAssets.find((asset) => asset.denom === market.denom)!
return { return {
...asset, ...asset,
borrowRate: market.apy.borrow ?? 0, borrowRate: market.borrowRate ?? 0,
liquidity: { liquidity: {
amount: BN(amount), amount: BN(amount),
value: BN(amount).multipliedBy(price), value: BN(amount).multipliedBy(price),
+3 -3
View File
@@ -3,10 +3,10 @@ import { getRedBankQueryClient } from 'api/cosmwasm-client'
import getMarkets from 'api/markets/getMarkets' import getMarkets from 'api/markets/getMarkets'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
export default async function getMarketDebts(chainConfig: ChainConfig): Promise<BNCoin[]> { export default async function getMarketDebts(): Promise<BNCoin[]> {
try { try {
const markets: Market[] = await getMarkets(chainConfig) const markets: Market[] = await getMarkets()
const redBankQueryClient = await getRedBankQueryClient(chainConfig) const redBankQueryClient = await getRedBankQueryClient()
const debtQueries = markets.map((asset) => const debtQueries = markets.map((asset) =>
cacheFn( cacheFn(
+3 -5
View File
@@ -2,12 +2,10 @@ import getMarkets from 'api/markets/getMarkets'
import getUnderlyingLiquidityAmount from 'api/markets/getMarketUnderlyingLiquidityAmount' import getUnderlyingLiquidityAmount from 'api/markets/getMarketUnderlyingLiquidityAmount'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
export default async function getMarketDeposits(chainConfig: ChainConfig): Promise<BNCoin[]> { export default async function getMarketDeposits(): Promise<BNCoin[]> {
try { try {
const markets: Market[] = await getMarkets(chainConfig) const markets: Market[] = await getMarkets()
const depositQueries = markets.map((market) => const depositQueries = markets.map(getUnderlyingLiquidityAmount)
getUnderlyingLiquidityAmount(chainConfig, market),
)
const depositsResults = await Promise.all(depositQueries) const depositsResults = await Promise.all(depositQueries)
return depositsResults.map<BNCoin>( return depositsResults.map<BNCoin>(
+5 -4
View File
@@ -1,10 +1,11 @@
import getMarketDebts from 'api/markets/getMarketDebts' import { BN } from 'utils/helpers'
import getMarketDeposits from 'api/markets/getMarketDeposits' import getMarketDeposits from 'api/markets/getMarketDeposits'
import getMarketDebts from 'api/markets/getMarketDebts'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
export default async function getMarketLiquidities(chainConfig: ChainConfig): Promise<BNCoin[]> { export default async function getMarketLiquidities(): Promise<BNCoin[]> {
const deposits = await getMarketDeposits(chainConfig) const deposits = await getMarketDeposits()
const debts = await getMarketDebts(chainConfig) const debts = await getMarketDebts()
const liquidity: BNCoin[] = deposits.map((deposit) => { const liquidity: BNCoin[] = deposits.map((deposit) => {
const debt = debts.find((debt) => debt.denom === deposit.denom) const debt = debts.find((debt) => debt.denom === deposit.denom)
@@ -1,21 +1,18 @@
import { cacheFn, underlyingLiquidityAmountCache } from 'api/cache' import { cacheFn, underlyingLiquidityAmountCache } from 'api/cache'
import { getRedBankQueryClient } from 'api/cosmwasm-client' import { getRedBankQueryClient } from 'api/cosmwasm-client'
export default async function getUnderlyingLiquidityAmount( export default async function getUnderlyingLiquidityAmount(market: Market): Promise<string> {
chainConfig: ChainConfig,
market: Market,
): Promise<string> {
return cacheFn( return cacheFn(
() => fetchUnderlyingLiquidityAmount(chainConfig, market), () => fetchUnderlyingLiquidityAmount(market),
underlyingLiquidityAmountCache, underlyingLiquidityAmountCache,
`underlyingLiquidity/${market.denom}/amount/${market.collateralTotalScaled}`, `underlyingLiquidity/${market.denom}/amount/${market.collateralTotalScaled}`,
60, 60,
) )
} }
async function fetchUnderlyingLiquidityAmount(chainConfig: ChainConfig, market: Market) { async function fetchUnderlyingLiquidityAmount(market: Market) {
try { try {
const client = await getRedBankQueryClient(chainConfig) const client = await getRedBankQueryClient()
return await client.underlyingLiquidityAmount({ return await client.underlyingLiquidityAmount({
denom: market.denom, denom: market.denom,
amountScaled: market.collateralTotalScaled, amountScaled: market.collateralTotalScaled,
+14 -25
View File
@@ -6,46 +6,36 @@ import {
} from 'types/generated/mars-params/MarsParams.types' } from 'types/generated/mars-params/MarsParams.types'
import { Market as RedBankMarket } from 'types/generated/mars-red-bank/MarsRedBank.types' import { Market as RedBankMarket } from 'types/generated/mars-red-bank/MarsRedBank.types'
import { byDenom } from 'utils/array' import { byDenom } from 'utils/array'
import { getEnabledMarketAssets } from 'utils/assets'
import iterateContractQuery from 'utils/iterateContractQuery' import iterateContractQuery from 'utils/iterateContractQuery'
import { resolveMarketResponse } from 'utils/resolvers' import { resolveMarketResponse } from 'utils/resolvers'
export default async function getMarkets(chainConfig: ChainConfig): Promise<Market[]> { export default async function getMarkets(): Promise<Market[]> {
try { try {
const redBankClient = await getRedBankQueryClient(chainConfig) const redBankClient = await getRedBankQueryClient()
const paramsClient = await getParamsQueryClient(chainConfig) const paramsClient = await getParamsQueryClient()
const marketAssets = chainConfig.assets.filter((asset) => asset.isMarket) const enabledAssets = getEnabledMarketAssets()
const capQueries = enabledAssets.map((asset) =>
const capQueries = marketAssets
.filter((asset) => asset.isMarket)
.map((asset) =>
cacheFn(
() => paramsClient.totalDeposit({ denom: asset.denom }),
totalDepositCache,
`chains/${chainConfig.id}/enabledMarkets/${asset.denom}`,
60,
),
)
const caps = await Promise.all(capQueries)
const [markets, assetParams, assetCaps] = await Promise.all([
cacheFn( cacheFn(
() => iterateContractQuery(redBankClient.markets), () => paramsClient.totalDeposit({ denom: asset.denom }),
marketsCache, totalDepositCache,
`chains/${chainConfig.id}/markets`, `enabledMarkets/${asset.denom}`,
60, 60,
), ),
)
const [markets, assetParams, assetCaps] = await Promise.all([
cacheFn(() => iterateContractQuery(redBankClient.markets), marketsCache, 'markets', 60),
cacheFn( cacheFn(
async () => await iterateContractQuery(paramsClient.allAssetParams), async () => await iterateContractQuery(paramsClient.allAssetParams),
allParamsCache, allParamsCache,
`chains/${chainConfig.id}/params`, 'params',
60, 60,
), ),
Promise.all(capQueries), Promise.all(capQueries),
]) ])
return marketAssets.map((asset) => return enabledAssets.map((asset) =>
resolveMarketResponse( resolveMarketResponse(
markets.find(byDenom(asset.denom)) as RedBankMarket, markets.find(byDenom(asset.denom)) as RedBankMarket,
assetParams.find(byDenom(asset.denom)) as AssetParams, assetParams.find(byDenom(asset.denom)) as AssetParams,
@@ -53,7 +43,6 @@ export default async function getMarkets(chainConfig: ChainConfig): Promise<Mark
), ),
) )
} catch (ex) { } catch (ex) {
console.log(ex)
throw ex throw ex
} }
} }
+3 -13
View File
@@ -1,21 +1,11 @@
import { assetParamsCache, cacheFn } from 'api/cache'
import { getParamsQueryClient } from 'api/cosmwasm-client' import { getParamsQueryClient } from 'api/cosmwasm-client'
import { AssetParamsBaseForAddr } from 'types/generated/mars-params/MarsParams.types' import { AssetParamsBaseForAddr } from 'types/generated/mars-params/MarsParams.types'
import iterateContractQuery from 'utils/iterateContractQuery' import iterateContractQuery from 'utils/iterateContractQuery'
export default async function getAssetParams( export default async function getAssetParams(): Promise<AssetParamsBaseForAddr[]> {
chainConfig: ChainConfig,
): Promise<AssetParamsBaseForAddr[]> {
try { try {
return await cacheFn( const paramsQueryClient = await getParamsQueryClient()
async () => { return iterateContractQuery(paramsQueryClient.allAssetParams)
const paramsQueryClient = await getParamsQueryClient(chainConfig)
return iterateContractQuery(paramsQueryClient.allAssetParams)
},
assetParamsCache,
`${chainConfig.id}/assetParams`,
600,
)
} catch (ex) { } catch (ex) {
throw ex throw ex
} }
-14
View File
@@ -1,14 +0,0 @@
import { getPerpsQueryClient } from 'api/cosmwasm-client'
import { BNCoin } from 'types/classes/BNCoin'
export default async function getOpeningFee(
chainConfig: ChainConfig,
denom: string,
amount: string,
) {
const perpsClient = await getPerpsQueryClient(chainConfig)
return perpsClient
.openingFee({ denom, size: amount as any })
.then((resp) => BNCoin.fromCoin(resp.fee))
}
+4 -3
View File
@@ -1,10 +1,11 @@
import getPoolPrice from 'api/prices/getPoolPrice' import getPoolPrice from 'api/prices/getPoolPrice'
import { ASSETS } from 'constants/assets'
import { bySymbol } from 'utils/array' import { bySymbol } from 'utils/array'
async function getMarsPrice(chainConfig: ChainConfig) { async function getMarsPrice() {
const marsAsset = chainConfig.assets.find(bySymbol('MARS')) const marsAsset = ASSETS.find(bySymbol('MARS'))
if (!marsAsset) return 0 if (!marsAsset) return 0
return await getPoolPrice(chainConfig, marsAsset) return await getPoolPrice(marsAsset)
} }
export default getMarsPrice export default getMarsPrice
+4 -8
View File
@@ -1,6 +1,5 @@
import { cacheFn, oraclePriceCache } from 'api/cache' import { cacheFn, oraclePriceCache } from 'api/cache'
import { getOracleQueryClient } from 'api/cosmwasm-client' import { getOracleQueryClient } from 'api/cosmwasm-client'
import { BN_ZERO } from 'constants/math'
import { PRICE_ORACLE_DECIMALS } from 'constants/query' import { PRICE_ORACLE_DECIMALS } from 'constants/query'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { PriceResponse } from 'types/generated/mars-oracle-osmosis/MarsOracleOsmosis.types' import { PriceResponse } from 'types/generated/mars-oracle-osmosis/MarsOracleOsmosis.types'
@@ -8,18 +7,15 @@ import { byDenom } from 'utils/array'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
import iterateContractQuery from 'utils/iterateContractQuery' import iterateContractQuery from 'utils/iterateContractQuery'
export default async function getOraclePrices( export default async function getOraclePrices(...assets: Asset[]): Promise<BNCoin[]> {
chainConfig: ChainConfig,
assets: Asset[],
): Promise<BNCoin[]> {
try { try {
if (!assets.length) return [] if (!assets.length) return []
const oracleQueryClient = await getOracleQueryClient(chainConfig) const oracleQueryClient = await getOracleQueryClient()
const priceResults = await cacheFn( const priceResults = await cacheFn(
() => iterateContractQuery(oracleQueryClient.prices), () => iterateContractQuery(oracleQueryClient.prices),
oraclePriceCache, oraclePriceCache,
`${chainConfig.id}/oraclePrices`, 'oraclePrices',
60, 60,
) )
@@ -28,7 +24,7 @@ export default async function getOraclePrices(
const decimalDiff = asset.decimals - PRICE_ORACLE_DECIMALS const decimalDiff = asset.decimals - PRICE_ORACLE_DECIMALS
return BNCoin.fromDenomAndBigNumber( return BNCoin.fromDenomAndBigNumber(
asset.denom, asset.denom,
BN(priceResponse?.price ?? BN_ZERO).shiftedBy(decimalDiff), BN(priceResponse.price).shiftedBy(decimalDiff),
) )
}) })
} catch (ex) { } catch (ex) {
+6 -6
View File
@@ -1,5 +1,6 @@
import { cacheFn, poolPriceCache } from 'api/cache' import { cacheFn, poolPriceCache } from 'api/cache'
import getPrice from 'api/prices/getPrice' import getPrice from 'api/prices/getPrice'
import { ENV } from 'constants/env'
import { BN_ONE } from 'constants/math' import { BN_ONE } from 'constants/math'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { byDenom, byTokenDenom, partition } from 'utils/array' import { byDenom, byTokenDenom, partition } from 'utils/array'
@@ -16,29 +17,28 @@ interface PoolAsset {
} }
export default async function getPoolPrice( export default async function getPoolPrice(
chainConfig: ChainConfig,
asset: Asset, asset: Asset,
lookupPricesForBaseAsset?: BNCoin[], lookupPricesForBaseAsset?: BNCoin[],
): Promise<BigNumber> { ): Promise<BigNumber> {
if (!asset.poolId) throw 'given asset should have a poolId to fetch the price' if (!asset.poolId) throw 'given asset should have a poolId to fetch the price'
const [assetRate, baseAsset] = await getAssetRate(chainConfig, asset) const [assetRate, baseAsset] = await getAssetRate(asset)
const baseAssetPrice = const baseAssetPrice =
(lookupPricesForBaseAsset && (lookupPricesForBaseAsset &&
lookupPricesForBaseAsset.find(byDenom(baseAsset.token.denom))?.amount) || lookupPricesForBaseAsset.find(byDenom(baseAsset.token.denom))?.amount) ||
(await getPrice(chainConfig, baseAsset.token.denom)) (await getPrice(baseAsset.token.denom))
if (!baseAssetPrice) throw 'base asset price must be available on Pyth or in Oracle contract' if (!baseAssetPrice) throw 'base asset price must be available on Pyth or in Oracle contract'
return assetRate.multipliedBy(baseAssetPrice) return assetRate.multipliedBy(baseAssetPrice)
} }
const getAssetRate = async (chainConfig: ChainConfig, asset: Asset) => { const getAssetRate = async (asset: Asset) => {
const url = chainConfig.endpoints.pools.replace('POOL_ID', asset.poolId!.toString()) const url = `${ENV.URL_REST}osmosis/gamm/v1beta1/pools/${asset.poolId}`
const response = await cacheFn( const response = await cacheFn(
() => fetch(url).then((res) => res.json()), () => fetch(url).then((res) => res.json()),
poolPriceCache, poolPriceCache,
`${chainConfig.id}/poolPrices/${(asset.poolId || 0).toString()}`, `poolPrices/${(asset.poolId || 0).toString()}`,
60, 60,
) )
const pool = response.pool const pool = response.pool
+27 -10
View File
@@ -1,20 +1,37 @@
import { cacheFn, priceCache } from 'api/cache' import { cacheFn, priceCache } from 'api/cache'
import getPrices from 'api/prices/getPrices' import { getOracleQueryClient } from 'api/cosmwasm-client'
import { BN_ZERO } from 'constants/math' import getPoolPrice from 'api/prices/getPoolPrice'
import getPythPrice from 'api/prices/getPythPrices'
import { ASSETS } from 'constants/assets'
import { PRICE_ORACLE_DECIMALS } from 'constants/query'
import { byDenom } from 'utils/array' import { byDenom } from 'utils/array'
import { BN } from 'utils/helpers'
export default async function getPrice( export default async function getPrice(denom: string): Promise<BigNumber> {
chainConfig: ChainConfig, return cacheFn(() => fetchPrice(denom), priceCache, `price/${denom}`, 60)
denom: string,
): Promise<BigNumber> {
return cacheFn(() => fetchPrice(chainConfig, denom), priceCache, `price/${denom}`, 60)
} }
async function fetchPrice(chainConfig: ChainConfig, denom: string) { async function fetchPrice(denom: string) {
try { try {
const prices = await getPrices(chainConfig) const asset = ASSETS.find(byDenom(denom)) as Asset
return prices.find(byDenom(denom))?.amount ?? BN_ZERO if (asset.pythPriceFeedId) {
return (await getPythPrice(asset.pythPriceFeedId))[0]
}
if (asset.hasOraclePrice) {
const oracleQueryClient = await getOracleQueryClient()
const priceResponse = await oracleQueryClient.price({ denom: asset.denom })
const decimalDiff = asset.decimals - PRICE_ORACLE_DECIMALS
return BN(priceResponse.price).shiftedBy(decimalDiff)
}
if (asset.poolId) {
return await getPoolPrice(asset)
}
throw `could not fetch the price info for the given denom: ${denom}`
} catch (ex) { } catch (ex) {
throw ex throw ex
} }
+20
View File
@@ -0,0 +1,20 @@
import fetchPythPriceData from 'api/prices/getPythPriceData'
import { getPythAssets } from 'utils/assets'
export default async function getPricesData(): Promise<string[]> {
try {
const assetsWithPythPriceFeedId = getPythAssets()
const pythAndOraclePriceData = await requestPythPriceData(assetsWithPythPriceFeedId)
return pythAndOraclePriceData
} catch (ex) {
console.error(ex)
throw ex
}
}
async function requestPythPriceData(assets: Asset[]): Promise<string[]> {
if (!assets.length) return []
const priceFeedIds = assets.map((a) => a.pythPriceFeedId) as string[]
return await fetchPythPriceData(...priceFeedIds)
}
+28 -36
View File
@@ -1,35 +1,29 @@
import getOraclePrices from 'api/prices/getOraclePrices' import getOraclePrices from 'api/prices/getOraclePrices'
import getPoolPrice from 'api/prices/getPoolPrice' import getPoolPrice from 'api/prices/getPoolPrice'
import fetchPythPrices from 'api/prices/getPythPrices' import fetchPythPrices from 'api/prices/getPythPrices'
import chains from 'configs/chains'
import useStore from 'store' import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { partition } from 'utils/array' import { partition } from 'utils/array'
import { getAllAssetsWithPythId } from 'utils/assets' import { getAssetsMustHavePriceInfo } from 'utils/assets'
export default async function getPrices(chainConfig: ChainConfig): Promise<BNCoin[]> { export default async function getPrices(): Promise<BNCoin[]> {
const usdPrice = new BNCoin({ denom: 'usd', amount: '1' }) const usdPrice = new BNCoin({ denom: 'usd', amount: '1' })
const pythAndOraclePrices = []
const assetsToFetchPrices = useStore
.getState()
.chainConfig.assets.filter(
(asset) => (asset.isEnabled && asset.isMarket) || asset.forceFetchPrice,
)
const assetsWithPythPriceFeedId = getAllAssetsWithPythId(chains)
const pythPrices = await requestPythPrices(assetsWithPythPriceFeedId)
pythAndOraclePrices.push(...pythPrices)
try { try {
const [assetsWithOraclePrices, assetsWithPoolIds] = const assetsToFetchPrices = getAssetsMustHavePriceInfo()
const [assetsWithPythPriceFeedId, assetsWithOraclePrices, assetsWithPoolIds] =
separateAssetsByPriceSources(assetsToFetchPrices) separateAssetsByPriceSources(assetsToFetchPrices)
const oraclePrices = await getOraclePrices(chainConfig, assetsWithOraclePrices)
const poolPrices = await requestPoolPrices(chainConfig, assetsWithPoolIds, pythAndOraclePrices)
if (oraclePrices) useStore.setState({ isOracleStale: false }) const pythAndOraclePrices = (
await Promise.all([
requestPythPrices(assetsWithPythPriceFeedId),
getOraclePrices(...assetsWithOraclePrices),
])
).flat()
const poolPrices = await requestPoolPrices(assetsWithPoolIds, pythAndOraclePrices)
return [...pythAndOraclePrices, ...oraclePrices, ...poolPrices, usdPrice] useStore.setState({ isOracleStale: false })
return [...pythAndOraclePrices, ...poolPrices, usdPrice]
} catch (ex) { } catch (ex) {
console.error(ex) console.error(ex)
let message = 'Unknown Error' let message = 'Unknown Error'
@@ -37,43 +31,41 @@ export default async function getPrices(chainConfig: ChainConfig): Promise<BNCoi
if (message.includes('price publish time is too old')) if (message.includes('price publish time is too old'))
useStore.setState({ isOracleStale: true }) useStore.setState({ isOracleStale: true })
return [...pythAndOraclePrices, usdPrice] throw ex
} }
} }
async function requestPythPrices(assets: Asset[]): Promise<BNCoin[]> { async function requestPythPrices(assets: Asset[]): Promise<BNCoin[]> {
if (!assets.length) return [] if (!assets.length) return []
const priceFeedIds = assets const priceFeedIds = assets.map((a) => a.pythPriceFeedId) as string[]
.map((a) => a.pythPriceFeedId) return await fetchPythPrices(...priceFeedIds).then(mapResponseToBnCoin(assets))
.filter((priceFeedId, index, array) => array.indexOf(priceFeedId) === index) as string[]
return await fetchPythPrices(priceFeedIds, assets)
} }
async function requestPoolPrices( async function requestPoolPrices(assets: Asset[], lookupPrices: BNCoin[]): Promise<BNCoin[]> {
chainConfig: ChainConfig, const requests = assets.map((asset) => getPoolPrice(asset, lookupPrices))
assets: Asset[],
lookupPrices: BNCoin[],
): Promise<BNCoin[]> {
const requests = assets.map((asset) => getPoolPrice(chainConfig, asset, lookupPrices))
return await Promise.all(requests).then(mapResponseToBnCoin(assets)) return await Promise.all(requests).then(mapResponseToBnCoin(assets))
} }
const mapResponseToBnCoin = (assets: Asset[]) => (prices: BigNumber[]) => { const mapResponseToBnCoin = (assets: Asset[]) => (prices: BigNumber[]) =>
return prices.map((price: BigNumber, index: number) => prices.map((price: BigNumber, index: number) =>
BNCoin.fromDenomAndBigNumber(assets[index].denom, price), BNCoin.fromDenomAndBigNumber(assets[index].denom, price),
) )
}
function separateAssetsByPriceSources(assets: Asset[]) { function separateAssetsByPriceSources(assets: Asset[]) {
const assetsWithoutPythPriceFeedId = assets.filter((asset) => !asset.pythPriceFeedId) // Only fetch Pyth prices for mainnet
const [assetsWithPythPriceFeedId, assetsWithoutPythPriceFeedId] = partition(
assets,
(asset) => !!asset.pythPriceFeedId,
)
// Don't get oracle price if it's not mainnet and there is a poolId
const [assetsWithOraclePrice, assetsWithoutOraclePrice] = partition( const [assetsWithOraclePrice, assetsWithoutOraclePrice] = partition(
assetsWithoutPythPriceFeedId, assetsWithoutPythPriceFeedId,
(asset) => asset.hasOraclePrice || !asset.poolId, (asset) => asset.hasOraclePrice || !asset.poolId,
) )
const assetsWithPoolId = assetsWithoutOraclePrice.filter((asset) => !!asset.poolId) const assetsWithPoolId = assetsWithoutOraclePrice.filter((asset) => !!asset.poolId)
return [assetsWithOraclePrice, assetsWithPoolId] return [assetsWithPythPriceFeedId, assetsWithOraclePrice, assetsWithPoolId]
} }
+4 -5
View File
@@ -1,9 +1,9 @@
import { cacheFn, pythPriceCache } from 'api/cache' import { cacheFn, pythPriceCache } from 'api/cache'
import { pythEndpoints } from 'constants/pyth' import { ENV } from 'constants/env'
export default async function getPythPriceData(priceFeedIds: string[]) { export default async function fetchPythPriceData(...priceFeedIds: string[]) {
try { try {
const pricesUrl = new URL(`${pythEndpoints.api}/latest_vaas`) const pricesUrl = new URL(`${ENV.PYTH_ENDPOINT}/latest_vaas`)
priceFeedIds.forEach((id) => pricesUrl.searchParams.append('ids[]', id)) priceFeedIds.forEach((id) => pricesUrl.searchParams.append('ids[]', id))
const pythDataResponse: string[] = await cacheFn( const pythDataResponse: string[] = await cacheFn(
@@ -14,7 +14,6 @@ export default async function getPythPriceData(priceFeedIds: string[]) {
) )
return pythDataResponse return pythDataResponse
} catch (ex) { } catch (ex) {
console.log(ex) throw ex
return []
} }
} }
+4 -16
View File
@@ -1,11 +1,10 @@
import { cacheFn, pythPriceCache } from 'api/cache' import { cacheFn, pythPriceCache } from 'api/cache'
import { pythEndpoints } from 'constants/pyth' import { ENV } from 'constants/env'
import { BNCoin } from 'types/classes/BNCoin'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
export default async function fetchPythPrices(priceFeedIds: string[], assets: Asset[]) { export default async function fetchPythPrices(...priceFeedIds: string[]) {
try { try {
const pricesUrl = new URL(`${pythEndpoints.api}/latest_price_feeds`) const pricesUrl = new URL(`${ENV.PYTH_ENDPOINT}/latest_price_feeds`)
priceFeedIds.forEach((id) => pricesUrl.searchParams.append('ids[]', id)) priceFeedIds.forEach((id) => pricesUrl.searchParams.append('ids[]', id))
const pythResponse: PythPriceData[] = await cacheFn( const pythResponse: PythPriceData[] = await cacheFn(
@@ -15,18 +14,7 @@ export default async function fetchPythPrices(priceFeedIds: string[], assets: As
30, 30,
) )
const mappedPriceData = [] as BNCoin[] return pythResponse.map(({ price }) => BN(price.price).shiftedBy(price.expo))
assets.forEach((asset) => {
const price = pythResponse.find((pythPrice) => asset.pythPriceFeedId === pythPrice.id)?.price
if (price)
mappedPriceData.push(
BNCoin.fromDenomAndBigNumber(asset.denom, BN(price.price).shiftedBy(price.expo)),
)
return
})
return mappedPriceData
} catch (ex) { } catch (ex) {
throw ex throw ex
} }
+2 -6
View File
@@ -2,13 +2,9 @@ import { getSwapperQueryClient } from 'api/cosmwasm-client'
import { BN_ZERO } from 'constants/math' import { BN_ZERO } from 'constants/math'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
export default async function estimateExactIn( export default async function estimateExactIn(coinIn: Coin, denomOut: string) {
chainConfig: ChainConfig,
coinIn: Coin,
denomOut: string,
) {
try { try {
const swapperClient = await getSwapperQueryClient(chainConfig) const swapperClient = await getSwapperQueryClient()
const estimatedAmount = (await swapperClient.estimateExactInSwap({ coinIn, denomOut })).amount const estimatedAmount = (await swapperClient.estimateExactInSwap({ coinIn, denomOut })).amount
return BN(estimatedAmount) return BN(estimatedAmount)
-53
View File
@@ -1,53 +0,0 @@
import { BN_ZERO } from 'constants/math'
import { STANDARD_SWAP_FEE } from 'utils/constants'
export default async function getOsmosisSwapFee(
chainConfig: ChainConfig,
poolIds: string[],
): Promise<number> {
const promises = poolIds.map((poolId) =>
fetch(chainConfig.endpoints.pools.replace('POOL_ID', poolId)),
)
const responses = await Promise.all(promises)
const pools = await Promise.all(responses.map(async (pool) => (await pool.json()).pool as Pool))
if (!pools?.length) return STANDARD_SWAP_FEE
return pools
.reduce((acc, pool) => acc.plus(pool?.pool_params?.swap_fee || STANDARD_SWAP_FEE), BN_ZERO)
.toNumber()
}
interface Pool {
'@type': string
address: string
future_pool_governor: string
id: string
pool_assets?: PoolAsset[]
pool_liquidity?: PoolLiquidity[]
pool_params: PoolParams
total_shares: TotalShares
total_weight: string
}
interface PoolAsset {
token: TotalShares
weight: string
}
interface PoolLiquidity {
amount: string
denom: string
}
interface TotalShares {
amount: string
denom: string
}
interface PoolParams {
exit_fee: string
smooth_weight_change_params: null
swap_fee: string
}
+2 -6
View File
@@ -1,12 +1,8 @@
import { getSwapperQueryClient } from 'api/cosmwasm-client' import { getSwapperQueryClient } from 'api/cosmwasm-client'
export default async function getSwapRoute( export default async function getSwapRoute(denomIn: string, denomOut: string): Promise<Route[]> {
chainConfig: ChainConfig,
denomIn: string,
denomOut: string,
): Promise<Route[]> {
try { try {
const swapperClient = await getSwapperQueryClient(chainConfig) const swapperClient = await getSwapperQueryClient()
const routes = await swapperClient.route({ const routes = await swapperClient.route({
denomIn, denomIn,
denomOut, denomOut,
+27 -47
View File
@@ -21,13 +21,9 @@ import {
import { getCoinValue } from 'utils/formatters' import { getCoinValue } from 'utils/formatters'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
async function getUnlocksAtTimestamp( async function getUnlocksAtTimestamp(unlockingId: number, vaultAddress: string) {
chainConfig: ChainConfig,
unlockingId: number,
vaultAddress: string,
) {
try { try {
const client = await getClient(chainConfig.endpoints.rpc) const client = await getClient()
const vaultExtension = (await cacheFn( const vaultExtension = (await cacheFn(
() => () =>
@@ -46,7 +42,6 @@ async function getUnlocksAtTimestamp(
} }
async function getVaultPositionStatusAndUnlockIdAndUnlockTime( async function getVaultPositionStatusAndUnlockIdAndUnlockTime(
chainConfig: ChainConfig,
vaultPosition: VaultPosition, vaultPosition: VaultPosition,
): Promise<[VaultStatus, number | undefined, number | undefined]> { ): Promise<[VaultStatus, number | undefined, number | undefined]> {
const amount = vaultPosition.amount const amount = vaultPosition.amount
@@ -55,11 +50,7 @@ async function getVaultPositionStatusAndUnlockIdAndUnlockTime(
if (amount.locking.unlocking.length) { if (amount.locking.unlocking.length) {
const unlockId = amount.locking.unlocking[0].id const unlockId = amount.locking.unlocking[0].id
const unlocksAtTimestamp = await getUnlocksAtTimestamp( const unlocksAtTimestamp = await getUnlocksAtTimestamp(unlockId, vaultPosition.vault.address)
chainConfig,
unlockId,
vaultPosition.vault.address,
)
if (moment(unlocksAtTimestamp).isBefore(new Date())) { if (moment(unlocksAtTimestamp).isBefore(new Date())) {
return [VaultStatus.UNLOCKED, unlockId, unlocksAtTimestamp] return [VaultStatus.UNLOCKED, unlockId, unlocksAtTimestamp]
@@ -71,7 +62,7 @@ async function getVaultPositionStatusAndUnlockIdAndUnlockTime(
} }
} }
export function flatVaultPositionAmount( function flatVaultPositionAmount(
vaultPositionAmount: VaultPositionAmount, vaultPositionAmount: VaultPositionAmount,
): VaultPositionFlatAmounts { ): VaultPositionFlatAmounts {
const amounts = { const amounts = {
@@ -91,14 +82,13 @@ export function flatVaultPositionAmount(
return amounts return amounts
} }
export async function getLpTokensForVaultPosition( async function getLpTokensForVaultPosition(
chainConfig: ChainConfig,
vault: Vault, vault: Vault,
vaultPosition: VaultPosition, vaultPosition: VaultPosition,
): Promise<Coin[]> { ): Promise<Coin[]> {
try { try {
const vaultQueryClient = await getVaultQueryClient(chainConfig, vault.address) const vaultQueryClient = await getVaultQueryClient(vault.address)
const creditManagerQueryClient = await getCreditManagerQueryClient(chainConfig) const creditManagerQueryClient = await getCreditManagerQueryClient()
const amounts = flatVaultPositionAmount(vaultPosition.amount) const amounts = flatVaultPositionAmount(vaultPosition.amount)
const totalAmount = amounts.locked.plus(amounts.unlocked).plus(amounts.unlocking).toString() const totalAmount = amounts.locked.plus(amounts.unlocked).plus(amounts.unlocking).toString()
@@ -143,16 +133,15 @@ export async function getLpTokensForVaultPosition(
async function getVaultValuesAndAmounts( async function getVaultValuesAndAmounts(
vault: Vault, vault: Vault,
vaultPosition: VaultPosition, vaultPosition: VaultPosition,
chainConfig: ChainConfig,
): Promise<VaultValuesAndAmounts> { ): Promise<VaultValuesAndAmounts> {
try { try {
const pricesQueries = Promise.all([ const pricesQueries = Promise.all([
getPrice(chainConfig, vault.denoms.primary), getPrice(vault.denoms.primary),
getPrice(chainConfig, vault.denoms.secondary), getPrice(vault.denoms.secondary),
getPrice(chainConfig, vault.denoms.lp), getPrice(vault.denoms.lp),
]) ])
const lpTokensQuery = getLpTokensForVaultPosition(chainConfig, vault, vaultPosition) const lpTokensQuery = getLpTokensForVaultPosition(vault, vaultPosition)
const amounts = flatVaultPositionAmount(vaultPosition.amount) const amounts = flatVaultPositionAmount(vaultPosition.amount)
const [[primaryLpToken, secondaryLpToken], [primaryPrice, secondaryPrice, lpPrice]] = const [[primaryLpToken, secondaryLpToken], [primaryPrice, secondaryPrice, lpPrice]] =
@@ -165,26 +154,18 @@ async function getVaultValuesAndAmounts(
secondary: BN(secondaryLpToken.amount), secondary: BN(secondaryLpToken.amount),
}, },
values: { values: {
primary: getCoinValue( primary: getCoinValue(new BNCoin(primaryLpToken), [
new BNCoin(primaryLpToken), BNCoin.fromDenomAndBigNumber(primaryLpToken.denom, primaryPrice),
[BNCoin.fromDenomAndBigNumber(primaryLpToken.denom, primaryPrice)], ]),
chainConfig.assets, secondary: getCoinValue(new BNCoin(secondaryLpToken), [
), BNCoin.fromDenomAndBigNumber(secondaryLpToken.denom, secondaryPrice),
secondary: getCoinValue( ]),
new BNCoin(secondaryLpToken), unlocking: getCoinValue(BNCoin.fromDenomAndBigNumber(vault.denoms.lp, amounts.unlocking), [
[BNCoin.fromDenomAndBigNumber(secondaryLpToken.denom, secondaryPrice)], BNCoin.fromDenomAndBigNumber(vault.denoms.lp, lpPrice),
chainConfig.assets, ]),
), unlocked: getCoinValue(BNCoin.fromDenomAndBigNumber(vault.denoms.lp, amounts.unlocked), [
unlocking: getCoinValue( BNCoin.fromDenomAndBigNumber(vault.denoms.lp, lpPrice),
BNCoin.fromDenomAndBigNumber(vault.denoms.lp, amounts.unlocking), ]),
[BNCoin.fromDenomAndBigNumber(vault.denoms.lp, lpPrice)],
chainConfig.assets,
),
unlocked: getCoinValue(
BNCoin.fromDenomAndBigNumber(vault.denoms.lp, amounts.unlocked),
[BNCoin.fromDenomAndBigNumber(vault.denoms.lp, lpPrice)],
chainConfig.assets,
),
}, },
} }
} catch (ex) { } catch (ex) {
@@ -194,11 +175,10 @@ async function getVaultValuesAndAmounts(
async function getDepositedVaults( async function getDepositedVaults(
accountId: string, accountId: string,
chainConfig: ChainConfig,
positions?: Positions, positions?: Positions,
): Promise<DepositedVault[]> { ): Promise<DepositedVault[]> {
try { try {
const creditManagerQueryClient = await getCreditManagerQueryClient(chainConfig) const creditManagerQueryClient = await getCreditManagerQueryClient()
if (!positions) if (!positions)
positions = await cacheFn( positions = await cacheFn(
@@ -209,7 +189,7 @@ async function getDepositedVaults(
if (!positions.vaults.length) return [] if (!positions.vaults.length) return []
const [allVaults] = await Promise.all([getVaults(chainConfig)]) const [allVaults] = await Promise.all([getVaults()])
const depositedVaults = positions.vaults.map(async (vaultPosition) => { const depositedVaults = positions.vaults.map(async (vaultPosition) => {
const vault = allVaults.find((v) => v.address === vaultPosition.vault.address) const vault = allVaults.find((v) => v.address === vaultPosition.vault.address)
@@ -219,8 +199,8 @@ async function getDepositedVaults(
} }
const [[status, unlockId, unlocksAt], valuesAndAmounts] = await Promise.all([ const [[status, unlockId, unlocksAt], valuesAndAmounts] = await Promise.all([
getVaultPositionStatusAndUnlockIdAndUnlockTime(chainConfig, vaultPosition), getVaultPositionStatusAndUnlockIdAndUnlockTime(vaultPosition),
getVaultValuesAndAmounts(vault, vaultPosition, chainConfig), getVaultValuesAndAmounts(vault, vaultPosition),
]) ])
return { return {
+5 -10
View File
@@ -1,22 +1,17 @@
import { aprsCache, aprsCacheResponse, cacheFn } from 'api/cache' import { aprsCache, aprsCacheResponse, cacheFn } from 'api/cache'
import { ENV } from 'constants/env'
export default async function getAprs(chainConfig: ChainConfig) { export default async function getAprs() {
if (!chainConfig.farm) return []
try { try {
const response = await cacheFn( const response = await cacheFn(
() => fetch(chainConfig.endpoints.aprs.vaults), () => fetch(ENV.URL_VAULT_APR),
aprsCacheResponse, aprsCacheResponse,
`${chainConfig.id}/aprsResponse`, 'aprsResponse',
60, 60,
) )
if (response.ok) { if (response.ok) {
const data: AprResponse = await cacheFn( const data: AprResponse = await cacheFn(() => response.json(), aprsCache, 'aprs', 60)
() => response.json(),
aprsCache,
`${chainConfig.id}/aprs`,
60,
)
return data.vaults.map((aprData) => { return data.vaults.map((aprData) => {
const finalApr = aprData.apr.projected_apr * 100 const finalApr = aprData.apr.projected_apr * 100
+3 -5
View File
@@ -3,15 +3,13 @@ import { getParamsQueryClient } from 'api/cosmwasm-client'
import { VaultConfigBaseForAddr } from 'types/generated/mars-params/MarsParams.types' import { VaultConfigBaseForAddr } from 'types/generated/mars-params/MarsParams.types'
import iterateContractQuery from 'utils/iterateContractQuery' import iterateContractQuery from 'utils/iterateContractQuery'
export const getVaultConfigs = async ( export const getVaultConfigs = async (): Promise<VaultConfigBaseForAddr[]> => {
chainConfig: ChainConfig,
): Promise<VaultConfigBaseForAddr[]> => {
try { try {
const paramsQueryClient = await getParamsQueryClient(chainConfig) const paramsQueryClient = await getParamsQueryClient()
return await cacheFn( return await cacheFn(
() => iterateContractQuery(paramsQueryClient.allVaultConfigs, 'addr'), () => iterateContractQuery(paramsQueryClient.allVaultConfigs, 'addr'),
vaultConfigsCache, vaultConfigsCache,
`${chainConfig.id}/vaultConfigs`, 'vaultConfigs',
600, 600,
) )
} catch (ex) { } catch (ex) {
+21
View File
@@ -0,0 +1,21 @@
import { cacheFn, previewDepositCache } from 'api/cache'
import { getVaultQueryClient } from 'api/cosmwasm-client'
export async function getVaultTokenFromLp(
vaultAddress: string,
lpAmount: string,
): Promise<{ vaultAddress: string; amount: string }> {
try {
const client = await getVaultQueryClient(vaultAddress)
return cacheFn(
() =>
client.previewDeposit({ amount: lpAmount }).then((amount) => ({ vaultAddress, amount })),
previewDepositCache,
`vaults/${vaultAddress}/amounts/${lpAmount}`,
30,
)
} catch (ex) {
throw ex
}
}
+3 -2
View File
@@ -1,13 +1,14 @@
import { cacheFn, vaultUtilizationCache } from 'api/cache' import { cacheFn, vaultUtilizationCache } from 'api/cache'
import { getCreditManagerQueryClient } from 'api/cosmwasm-client' import { getCreditManagerQueryClient } from 'api/cosmwasm-client'
import { ENV } from 'constants/env'
import { VaultUtilizationResponse } from 'types/generated/mars-credit-manager/MarsCreditManager.types' import { VaultUtilizationResponse } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
import { VaultConfigBaseForString } from 'types/generated/mars-params/MarsParams.types' import { VaultConfigBaseForString } from 'types/generated/mars-params/MarsParams.types'
export const getVaultUtilizations = async ( export const getVaultUtilizations = async (
chainConfig: ChainConfig,
vaultConfigs: VaultConfigBaseForString[], vaultConfigs: VaultConfigBaseForString[],
): Promise<VaultUtilizationResponse[]> => { ): Promise<VaultUtilizationResponse[]> => {
const creditManagerQueryClient = await getCreditManagerQueryClient(chainConfig) if (!ENV.ADDRESS_PARAMS) return []
const creditManagerQueryClient = await getCreditManagerQueryClient()
try { try {
const vaultUtilizations$ = vaultConfigs.map((vaultConfig) => { const vaultUtilizations$ = vaultConfigs.map((vaultConfig) => {
return cacheFn( return cacheFn(
+10 -6
View File
@@ -2,16 +2,20 @@ import getAssetParams from 'api/params/getAssetParams'
import getAprs from 'api/vaults/getVaultAprs' import getAprs from 'api/vaults/getVaultAprs'
import { getVaultConfigs } from 'api/vaults/getVaultConfigs' import { getVaultConfigs } from 'api/vaults/getVaultConfigs'
import { getVaultUtilizations } from 'api/vaults/getVaultUtilizations' import { getVaultUtilizations } from 'api/vaults/getVaultUtilizations'
import { ENV } from 'constants/env'
import { TESTNET_VAULTS_META_DATA, VAULTS_META_DATA } from 'constants/vaults'
import { NETWORK } from 'types/enums/network'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
import { convertAprToApy } from 'utils/parsers' import { convertAprToApy } from 'utils/parsers'
import { resolveHLSStrategies } from 'utils/resolvers' import { resolveHLSStrategies } from 'utils/resolvers'
export default async function getVaults(chainConfig: ChainConfig): Promise<Vault[]> { export default async function getVaults(): Promise<Vault[]> {
const assetParams = await getAssetParams(chainConfig) const assetParams = await getAssetParams()
const vaultConfigs = await getVaultConfigs(chainConfig) const vaultConfigs = await getVaultConfigs()
const $vaultUtilizations = getVaultUtilizations(chainConfig, vaultConfigs) const $vaultUtilizations = getVaultUtilizations(vaultConfigs)
const $aprs = getAprs(chainConfig) const $aprs = getAprs()
const vaultMetaDatas = chainConfig.vaults const vaultMetaDatas =
ENV.NETWORK === NETWORK.TESTNET ? TESTNET_VAULTS_META_DATA : VAULTS_META_DATA
const HLSAssets = assetParams.filter((asset) => asset.credit_manager.hls) const HLSAssets = assetParams.filter((asset) => asset.credit_manager.hls)
const hlsStrategies = resolveHLSStrategies('vault', HLSAssets) const hlsStrategies = resolveHLSStrategies('vault', HLSAssets)
+2 -3
View File
@@ -2,13 +2,12 @@ import { getCreditManagerQueryClient } from 'api/cosmwasm-client'
import { ITEM_LIMIT_PER_QUERY } from 'constants/query' import { ITEM_LIMIT_PER_QUERY } from 'constants/query'
export default async function getAccountIds( export default async function getAccountIds(
chainConfig: ChainConfig,
address?: string, address?: string,
previousResults?: AccountIdAndKind[], previousResults?: AccountIdAndKind[],
): Promise<AccountIdAndKind[]> { ): Promise<AccountIdAndKind[]> {
if (!address) return [] if (!address) return []
try { try {
const client = await getCreditManagerQueryClient(chainConfig) const client = await getCreditManagerQueryClient()
const lastItem = previousResults && previousResults.at(-1) const lastItem = previousResults && previousResults.at(-1)
const accounts = ( const accounts = (
@@ -25,7 +24,7 @@ export default async function getAccountIds(
return accumulated.sort((a, b) => parseInt(a.id) - parseInt(b.id)) return accumulated.sort((a, b) => parseInt(a.id) - parseInt(b.id))
} }
return await getAccountIds(chainConfig, address, accumulated) return await getAccountIds(address, accumulated)
} catch { } catch {
return new Promise((_, reject) => reject('No data')) return new Promise((_, reject) => reject('No data'))
} }
+4 -7
View File
@@ -2,19 +2,16 @@ import getAccount from 'api/accounts/getAccount'
import getWalletAccountIds from 'api/wallets/getAccountIds' import getWalletAccountIds from 'api/wallets/getAccountIds'
import { AccountKind } from 'types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types' import { AccountKind } from 'types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types'
export default async function getAccounts( export default async function getAccounts(kind: AccountKind, address?: string): Promise<Account[]> {
kind: AccountKind,
chainConfig: ChainConfig,
address?: string,
): Promise<Account[]> {
if (!address) return new Promise((_, reject) => reject('No address')) if (!address) return new Promise((_, reject) => reject('No address'))
const accountIdsAndKinds = await getWalletAccountIds(chainConfig, address) const accountIdsAndKinds = await getWalletAccountIds(address)
const $accounts = accountIdsAndKinds const $accounts = accountIdsAndKinds
.filter((a) => a.kind === kind) .filter((a) => a.kind === kind)
.map((account) => getAccount(chainConfig, account.id)) .map((account) => getAccount(account.id))
const accounts = await Promise.all($accounts).then((accounts) => accounts) const accounts = await Promise.all($accounts).then((accounts) => accounts)
if (accounts) { if (accounts) {
return accounts.sort((a, b) => Number(a.id) - Number(b.id)) return accounts.sort((a, b) => Number(a.id) - Number(b.id))
} }
+4 -6
View File
@@ -1,13 +1,11 @@
import { getICNSQueryClient } from 'api/cosmwasm-client' import { getICNSQueryClient } from 'api/cosmwasm-client'
import { ENV } from 'constants/env'
import { ChainInfoID } from 'types/enums/wallet' import { ChainInfoID } from 'types/enums/wallet'
export default async function getICNS( export default async function getICNS(address?: string): Promise<ICNSResult | undefined> {
chainConfig: ChainConfig, if (!address || ENV.CHAIN_ID !== ChainInfoID.Osmosis1) return
address?: string,
): Promise<ICNSResult | undefined> {
if (!address || chainConfig.id !== ChainInfoID.Osmosis1) return
try { try {
const icnsQueryClient = await getICNSQueryClient(chainConfig) const icnsQueryClient = await getICNSQueryClient()
return icnsQueryClient.primaryName({ address }) return icnsQueryClient.primaryName({ address })
} catch (ex) { } catch (ex) {
throw ex throw ex
+4 -5
View File
@@ -1,10 +1,9 @@
export default async function getWalletBalances( import { ENV } from 'constants/env'
chainConfig: ChainConfig,
address: string, export default async function getWalletBalances(address: string): Promise<Coin[]> {
): Promise<Coin[]> {
const uri = '/cosmos/bank/v1beta1/balances/' const uri = '/cosmos/bank/v1beta1/balances/'
const response = await fetch(`${chainConfig.endpoints.rest}${uri}${address}`) const response = await fetch(`${ENV.URL_REST}${uri}${address}`)
if (response.ok) { if (response.ok) {
const data = await response.json() const data = await response.json()
@@ -1,7 +1,7 @@
import classNames from 'classnames' import classNames from 'classnames'
import AccordionContent, { Item } from 'components/common/AccordionContent' import AccordionContent, { Item } from 'components/AccordionContent'
import Card from 'components/common/Card' import Card from 'components/Card'
interface Props { interface Props {
items: Item[] items: Item[]
@@ -1,7 +1,7 @@
import classNames from 'classnames' import classNames from 'classnames'
import { ChevronDown, ChevronRight } from 'components/common/Icons' import { ChevronDown, ChevronRight } from 'components/Icons'
import Text from 'components/common/Text' import Text from 'components/Text'
interface Props { interface Props {
item: Item item: Item
@@ -1,13 +1,13 @@
import AssetRate from 'components/common/assets/AssetRate' import AssetRate from 'components/Asset/AssetRate'
import { byDenom } from 'utils/array' import { byDenom } from 'utils/array'
export const APY_META = { accessorKey: 'apy', header: 'APY', meta: { className: 'w-30' } } export const APY_META = { accessorKey: 'apy', header: 'APY' }
interface Props { interface Props {
apy: number apy: number
markets: Market[] markets: Market[]
denom: string denom: string
type: PositionType type: 'deposits' | 'borrowing' | 'lending' | 'vault'
} }
export default function Apr(props: Props) { export default function Apr(props: Props) {
@@ -20,7 +20,7 @@ export default function Apr(props: Props) {
<AssetRate <AssetRate
className='justify-end text-xs' className='justify-end text-xs'
rate={apy} rate={apy}
isEnabled={type !== 'lend' || isEnabled} isEnabled={type !== 'lending' || isEnabled}
type='apy' type='apy'
orientation='ltr' orientation='ltr'
/> />
@@ -0,0 +1,22 @@
import Text from 'components/Text'
export const ASSET_META = { accessorKey: 'symbol', header: 'Asset', id: 'symbol' }
interface Props {
symbol: string
type: 'deposits' | 'borrowing' | 'lending' | 'vault'
}
export const borderColor = (type: Props['type']): string =>
type === 'borrowing' ? 'border-loss' : 'border-profit'
export default function Asset(props: Props) {
const { symbol, type } = props
return (
<Text size='xs'>
{symbol}
{type === 'borrowing' && <span className='ml-1 text-loss'>(debt)</span>}
{type === 'lending' && <span className='ml-1 text-profit'>(lent)</span>}
{type === 'vault' && <span className='ml-1 text-profit'>(farm)</span>}
</Text>
)
}
@@ -1,18 +1,18 @@
import { Row } from '@tanstack/react-table' import { Row } from '@tanstack/react-table'
import classNames from 'classnames' import classNames from 'classnames'
import { getAmountChangeColor } from 'components/account/AccountBalancesTable/functions' import { getAmountChangeColor } from 'components/Account/AccountBalancesTable/functions'
import { FormattedNumber } from 'components/common/FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
import { MAX_AMOUNT_DECIMALS, MIN_AMOUNT } from 'constants/math' import { MAX_AMOUNT_DECIMALS, MIN_AMOUNT } from 'constants/math'
import { formatAmountToPrecision } from 'utils/formatters' import { formatAmountToPrecision } from 'utils/formatters'
export const SIZE_META = { accessorKey: 'size', header: 'Size', meta: { className: 'w-40' } } export const SIZE_META = { accessorKey: 'size', header: 'Size' }
interface Props { interface Props {
size: number size: number
amountChange: BigNumber amountChange: BigNumber
denom: string denom: string
type: PositionType type: 'deposits' | 'borrowing' | 'lending' | 'vault'
} }
export const sizeSortingFn = (a: Row<AccountBalanceRow>, b: Row<AccountBalanceRow>): number => { export const sizeSortingFn = (a: Row<AccountBalanceRow>, b: Row<AccountBalanceRow>): number => {
@@ -1,8 +1,8 @@
import { Row } from '@tanstack/react-table' import { Row } from '@tanstack/react-table'
import classNames from 'classnames' import classNames from 'classnames'
import { getAmountChangeColor } from 'components/account/AccountBalancesTable/functions' import { getAmountChangeColor } from 'components/Account/AccountBalancesTable/functions'
import DisplayCurrency from 'components/common/DisplayCurrency' import DisplayCurrency from 'components/DisplayCurrency'
import { ORACLE_DENOM } from 'constants/oracle' import { ORACLE_DENOM } from 'constants/oracle'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
@@ -12,19 +12,10 @@ export const VALUE_META = { accessorKey: 'value', header: 'Value' }
interface Props { interface Props {
amountChange: BigNumber amountChange: BigNumber
value: string value: string
type: PositionType type: 'deposits' | 'borrowing' | 'lending' | 'vault'
} }
export const valueBalancesSortingFn = ( export const valueSortingFn = (a: Row<AccountBalanceRow>, b: Row<AccountBalanceRow>): number => {
a: Row<AccountBalanceRow>,
b: Row<AccountBalanceRow>,
): number => {
const valueA = BN(a.original.value)
const valueB = BN(b.original.value)
return valueA.minus(valueB).toNumber()
}
export const valuePerpSortingFn = (a: Row<AccountPerpRow>, b: Row<AccountPerpRow>): number => {
const valueA = BN(a.original.value) const valueA = BN(a.original.value)
const valueB = BN(b.original.value) const valueB = BN(b.original.value)
return valueA.minus(valueB).toNumber() return valueA.minus(valueB).toNumber()
@@ -33,12 +24,17 @@ export const valuePerpSortingFn = (a: Row<AccountPerpRow>, b: Row<AccountPerpRow
export default function Value(props: Props) { export default function Value(props: Props) {
const { amountChange, type, value } = props const { amountChange, type, value } = props
const color = getAmountChangeColor(type, amountChange) const color = getAmountChangeColor(type, amountChange)
const allowZero = !amountChange.isZero()
const coin = new BNCoin({ const coin = new BNCoin({
denom: ORACLE_DENOM, denom: ORACLE_DENOM,
amount: value, amount: value,
}) })
return ( return (
<DisplayCurrency coin={coin} className={classNames('text-xs text-right', color)} showZero /> <DisplayCurrency
coin={coin}
className={classNames('text-xs text-right', color)}
showZero={!allowZero}
/>
) )
} }
@@ -0,0 +1,62 @@
import { ColumnDef } from '@tanstack/react-table'
import { useMemo } from 'react'
import Apy, { APY_META } from 'components/Account/AccountBalancesTable/Columns/Apy'
import Asset, { ASSET_META } from 'components/Account/AccountBalancesTable/Columns/Asset'
import Size, {
SIZE_META,
sizeSortingFn,
} from 'components/Account/AccountBalancesTable/Columns/Size'
import Value, {
VALUE_META,
valueSortingFn,
} from 'components/Account/AccountBalancesTable/Columns/Value'
import useMarketAssets from 'hooks/useMarketAssets'
export default function useAccountBalancesColumns() {
const { data: markets } = useMarketAssets()
return useMemo<ColumnDef<AccountBalanceRow>[]>(() => {
return [
{
...ASSET_META,
cell: ({ row }) => <Asset type={row.original.type} symbol={row.original.symbol} />,
},
{
...VALUE_META,
cell: ({ row }) => (
<Value
amountChange={row.original.amountChange}
value={row.original.value}
type={row.original.type}
/>
),
sortingFn: valueSortingFn,
},
{
...SIZE_META,
cell: ({ row }) => (
<Size
size={row.original.size}
amountChange={row.original.amountChange}
denom={row.original.denom}
type={row.original.type}
/>
),
sortingFn: sizeSortingFn,
},
{
...APY_META,
cell: ({ row }) => (
<Apy
apy={row.original.apy}
markets={markets}
denom={row.original.denom}
type={row.original.type}
/>
),
},
]
}, [markets])
}
@@ -3,10 +3,9 @@ import { BNCoin } from 'types/classes/BNCoin'
import { demagnify, getCoinValue } from 'utils/formatters' import { demagnify, getCoinValue } from 'utils/formatters'
export function getAssetAccountBalanceRow( export function getAssetAccountBalanceRow(
type: 'deposit' | 'borrow' | 'lend', type: 'deposits' | 'borrowing' | 'lending',
asset: Asset, asset: Asset,
prices: BNCoin[], prices: BNCoin[],
assets: Asset[],
position: BNCoin, position: BNCoin,
apy: number, apy: number,
prev?: BNCoin, prev?: BNCoin,
@@ -18,7 +17,7 @@ export function getAssetAccountBalanceRow(
type, type,
symbol: asset.symbol, symbol: asset.symbol,
size: demagnify(amount, asset), size: demagnify(amount, asset),
value: getCoinValue(BNCoin.fromDenomAndBigNumber(denom, amount), prices, assets).toString(), value: getCoinValue(BNCoin.fromDenomAndBigNumber(denom, amount), prices).toString(),
denom, denom,
amount, amount,
apy, apy,
@@ -57,9 +56,11 @@ export function getVaultAccountBalanceRow(
} }
} }
export function getAmountChangeColor(type: PositionType, amount: BigNumber) { export function getAmountChangeColor(
if (type === 'perp') return '' type: 'deposits' | 'borrowing' | 'lending' | 'vault',
if (type === 'borrow') { amount: BigNumber,
) {
if (type === 'borrowing') {
if (amount.isGreaterThan(0)) return 'text-loss' if (amount.isGreaterThan(0)) return 'text-loss'
if (amount.isLessThan(0)) return 'text-profit' if (amount.isLessThan(0)) return 'text-profit'
} }
@@ -1,14 +1,14 @@
import classNames from 'classnames' import classNames from 'classnames'
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
import useAccountBalancesColumns from 'components/account/AccountBalancesTable/Columns/useAccountBalancesColumns' import useAccountBalancesColumns from 'components/Account/AccountBalancesTable/Columns/useAccountBalancesColumns'
import useAccountBalanceData from 'components/account/AccountBalancesTable/useAccountBalanceData' import useAccountBalanceData from 'components/Account/AccountBalancesTable/useAccountBalanceData'
import AccountFundFullPage from 'components/account/AccountFund/AccountFundFullPage' import AccountFundFullPage from 'components/Account/AccountFund/AccountFundFullPage'
import ActionButton from 'components/common/Button/ActionButton' import ActionButton from 'components/Button/ActionButton'
import Card from 'components/common/Card' import Card from 'components/Card'
import Table from 'components/common/Table' import Table from 'components/Table'
import ConditionalWrapper from 'hocs/ConditionalWrapper' import ConditionalWrapper from 'hocs/ConditionalWrapper'
import useCurrentAccount from 'hooks/accounts/useCurrentAccount' import useCurrentAccount from 'hooks/useCurrentAccount'
import useStore from 'store' import useStore from 'store'
import { getPage, getRoute } from 'utils/route' import { getPage, getRoute } from 'utils/route'
@@ -19,19 +19,10 @@ interface Props {
borrowingData: BorrowMarketTableData[] borrowingData: BorrowMarketTableData[]
hideCard?: boolean hideCard?: boolean
tableBodyClassName?: string tableBodyClassName?: string
showLiquidationPrice?: boolean
} }
export default function AccountBalancesTable(props: Props) { export default function AccountBalancesTable(props: Props) {
const [searchParams] = useSearchParams() const { account, lendingData, borrowingData, tableBodyClassName, hideCard } = props
const {
account,
lendingData,
borrowingData,
tableBodyClassName,
hideCard,
showLiquidationPrice,
} = props
const currentAccount = useCurrentAccount() const currentAccount = useCurrentAccount()
const navigate = useNavigate() const navigate = useNavigate()
const { pathname } = useLocation() const { pathname } = useLocation()
@@ -45,7 +36,7 @@ export default function AccountBalancesTable(props: Props) {
isHls: props.isHls, isHls: props.isHls,
}) })
const columns = useAccountBalancesColumns(account, showLiquidationPrice) const columns = useAccountBalancesColumns()
if (accountBalanceData.length === 0) if (accountBalanceData.length === 0)
return ( return (
@@ -64,7 +55,7 @@ export default function AccountBalancesTable(props: Props) {
color='tertiary' color='tertiary'
onClick={() => { onClick={() => {
if (currentAccount?.id !== account.id) { if (currentAccount?.id !== account.id) {
navigate(getRoute(getPage(pathname), searchParams, address, account.id)) navigate(getRoute(getPage(pathname), address, account.id))
} }
useStore.setState({ useStore.setState({
focusComponent: { focusComponent: {
@@ -89,7 +80,7 @@ export default function AccountBalancesTable(props: Props) {
initialSorting={[]} initialSorting={[]}
spacingClassName='p-2' spacingClassName='p-2'
hideCard={hideCard} hideCard={hideCard}
type='balances' isBalancesTable
/> />
) )
} }
@@ -3,11 +3,13 @@ import { useMemo } from 'react'
import { import {
getAssetAccountBalanceRow, getAssetAccountBalanceRow,
getVaultAccountBalanceRow, getVaultAccountBalanceRow,
} from 'components/account/AccountBalancesTable/functions' } from 'components/Account/AccountBalancesTable/functions'
import useAllAssets from 'hooks/assets/useAllAssets' import { ASSETS } from 'constants/assets'
import useHLSStakingAssets from 'hooks/useHLSStakingAssets' import useHLSStakingAssets from 'hooks/useHLSStakingAssets'
import usePrices from 'hooks/usePrices' import usePrices from 'hooks/usePrices'
import { byDenom } from 'utils/array' import { byDenom } from 'utils/array'
import { convertLiquidityRateToAPR } from 'utils/formatters'
import { convertAprToApy } from 'utils/parsers'
interface Props { interface Props {
account: Account account: Account
@@ -19,9 +21,10 @@ interface Props {
export default function useAccountBalanceData(props: Props) { export default function useAccountBalanceData(props: Props) {
const { account, updatedAccount, lendingData, borrowingData } = props const { account, updatedAccount, lendingData, borrowingData } = props
const { data: hlsStrategies } = useHLSStakingAssets() const { data: hlsStrategies } = useHLSStakingAssets()
const { data: prices } = usePrices() const { data: prices } = usePrices()
const assets = useAllAssets()
return useMemo<AccountBalanceRow[]>(() => { return useMemo<AccountBalanceRow[]>(() => {
const usedAccount = updatedAccount ?? account const usedAccount = updatedAccount ?? account
const accountDeposits = usedAccount?.deposits ?? [] const accountDeposits = usedAccount?.deposits ?? []
@@ -31,26 +34,26 @@ export default function useAccountBalanceData(props: Props) {
const deposits: AccountBalanceRow[] = [] const deposits: AccountBalanceRow[] = []
accountDeposits.forEach((deposit) => { accountDeposits.forEach((deposit) => {
const asset = assets.find(byDenom(deposit.denom)) const asset = ASSETS.find(byDenom(deposit.denom))
if (!asset) return if (!asset) return
const apy = props.isHls const apy = props.isHls
? hlsStrategies.find((strategy) => strategy.denoms.deposit === asset.denom)?.apy ?? 0 ? hlsStrategies.find((strategy) => strategy.denoms.deposit === asset.denom)?.apy ?? 0
: 0 : 0
const prevDeposit = updatedAccount ? account?.deposits.find(byDenom(deposit.denom)) : deposit const prevDeposit = updatedAccount ? account?.deposits.find(byDenom(deposit.denom)) : deposit
deposits.push( deposits.push(getAssetAccountBalanceRow('deposits', asset, prices, deposit, apy, prevDeposit))
getAssetAccountBalanceRow('deposit', asset, prices, assets, deposit, apy, prevDeposit),
)
}) })
const lends = accountLends.map((lending) => { const lends = accountLends.map((lending) => {
const asset = assets.find(byDenom(lending.denom)) ?? assets[0] const asset = ASSETS.find(byDenom(lending.denom)) ?? ASSETS[0]
const apy = const apr = convertLiquidityRateToAPR(
lendingData.find((market) => market.asset.denom === lending.denom)?.apy.deposit ?? 0 lendingData.find((market) => market.asset.denom === lending.denom)?.marketLiquidityRate ??
0,
)
const apy = convertAprToApy(apr, 365)
const prevLending = updatedAccount const prevLending = updatedAccount
? account?.lends.find((position) => position.denom === lending.denom) ? account?.lends.find((position) => position.denom === lending.denom)
: lending : lending
return getAssetAccountBalanceRow('lend', asset, prices, assets, lending, apy, prevLending) return getAssetAccountBalanceRow('lending', asset, prices, lending, apy, prevLending)
}) })
const vaults = accountVaults.map((vault) => { const vaults = accountVaults.map((vault) => {
@@ -62,22 +65,13 @@ export default function useAccountBalanceData(props: Props) {
}) })
const debts = accountDebts.map((debt) => { const debts = accountDebts.map((debt) => {
const asset = assets.find(byDenom(debt.denom)) ?? assets[0] const asset = ASSETS.find(byDenom(debt.denom)) ?? ASSETS[0]
const apy = borrowingData.find((market) => market.asset.denom === debt.denom)?.apy.borrow ?? 0 const apy = borrowingData.find((market) => market.asset.denom === debt.denom)?.borrowRate ?? 0
const prevDebt = updatedAccount const prevDebt = updatedAccount
? account?.debts.find((position) => position.denom === debt.denom) ? account?.debts.find((position) => position.denom === debt.denom)
: debt : debt
return getAssetAccountBalanceRow('borrow', asset, prices, assets, debt, apy, prevDebt) return getAssetAccountBalanceRow('borrowing', asset, prices, debt, apy, prevDebt)
}) })
return [...deposits, ...lends, ...vaults, ...debts] return [...deposits, ...lends, ...vaults, ...debts]
}, [ }, [updatedAccount, account, props.isHls, hlsStrategies, prices, lendingData, borrowingData])
updatedAccount,
account,
props.isHls,
hlsStrategies,
prices,
assets,
lendingData,
borrowingData,
])
} }
@@ -2,13 +2,12 @@ import BigNumber from 'bignumber.js'
import classNames from 'classnames' import classNames from 'classnames'
import { useMemo } from 'react' import { useMemo } from 'react'
import DisplayCurrency from 'components/common/DisplayCurrency' import DisplayCurrency from 'components/DisplayCurrency'
import { FormattedNumber } from 'components/common/FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
import { ArrowRight } from 'components/common/Icons' import { ArrowRight } from 'components/Icons'
import Text from 'components/common/Text' import Text from 'components/Text'
import { BN_ZERO, MAX_AMOUNT_DECIMALS } from 'constants/math' import { BN_ZERO, MAX_AMOUNT_DECIMALS } from 'constants/math'
import { ORACLE_DENOM } from 'constants/oracle' import { ORACLE_DENOM } from 'constants/oracle'
import useAllAssets from 'hooks/assets/useAllAssets'
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData' import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
import useHLSStakingAssets from 'hooks/useHLSStakingAssets' import useHLSStakingAssets from 'hooks/useHLSStakingAssets'
import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData' import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData'
@@ -41,8 +40,7 @@ export default function AccountComposition(props: Props) {
const hasChanged = !!updatedAccount const hasChanged = !!updatedAccount
const { data: prices } = usePrices() const { data: prices } = usePrices()
const { data: hlsStrategies } = useHLSStakingAssets() const { data: hlsStrategies } = useHLSStakingAssets()
const assets = useAllAssets() const { data } = useBorrowMarketAssetsTableData(false)
const data = useBorrowMarketAssetsTableData()
const borrowAssetsData = useMemo(() => data?.allAssets || [], [data]) const borrowAssetsData = useMemo(() => data?.allAssets || [], [data])
const { availableAssets: lendingAvailableAssets, accountLentAssets } = const { availableAssets: lendingAvailableAssets, accountLentAssets } =
useLendingMarketAssetsTableData() useLendingMarketAssetsTableData()
@@ -52,15 +50,15 @@ export default function AccountComposition(props: Props) {
) )
const [depositsBalance, lendsBalance, debtsBalance, vaultsBalance] = useMemo( const [depositsBalance, lendsBalance, debtsBalance, vaultsBalance] = useMemo(
() => getAccountPositionValues(account, prices, assets), () => getAccountPositionValues(account, prices),
[account, assets, prices], [account, prices],
) )
const totalBalance = depositsBalance.plus(lendsBalance).plus(vaultsBalance) const totalBalance = depositsBalance.plus(lendsBalance).plus(vaultsBalance)
const [updatedPositionValue, updatedDebtsBalance] = useMemo(() => { const [updatedPositionValue, updatedDebtsBalance] = useMemo(() => {
const [updatedDepositsBalance, updatedLendsBalance, updatedDebtsBalance, updatedVaultsBalance] = const [updatedDepositsBalance, updatedLendsBalance, updatedDebtsBalance, updatedVaultsBalance] =
updatedAccount updatedAccount
? getAccountPositionValues(updatedAccount, prices, assets) ? getAccountPositionValues(updatedAccount, prices)
: [BN_ZERO, BN_ZERO, BN_ZERO] : [BN_ZERO, BN_ZERO, BN_ZERO]
const updatedPositionValue = updatedDepositsBalance const updatedPositionValue = updatedDepositsBalance
@@ -68,15 +66,12 @@ export default function AccountComposition(props: Props) {
.plus(updatedVaultsBalance) .plus(updatedVaultsBalance)
return [updatedPositionValue, updatedDebtsBalance] return [updatedPositionValue, updatedDebtsBalance]
}, [updatedAccount, prices, assets]) }, [updatedAccount, prices])
const netWorth = useMemo( const netWorth = useMemo(() => calculateAccountBalanceValue(account, prices), [account, prices])
() => calculateAccountBalanceValue(account, prices, assets),
[account, assets, prices],
)
const updatedTotalBalance = useMemo( const updatedTotalBalance = useMemo(
() => (updatedAccount ? calculateAccountBalanceValue(updatedAccount, prices, assets) : BN_ZERO), () => (updatedAccount ? calculateAccountBalanceValue(updatedAccount, prices) : BN_ZERO),
[updatedAccount, prices, assets], [updatedAccount, prices],
) )
const apr = useMemo( const apr = useMemo(
@@ -87,10 +82,9 @@ export default function AccountComposition(props: Props) {
lendingAssetsData, lendingAssetsData,
prices, prices,
hlsStrategies, hlsStrategies,
assets,
props.isHls, props.isHls,
), ),
[account, assets, borrowAssetsData, hlsStrategies, lendingAssetsData, prices, props.isHls], [account, borrowAssetsData, hlsStrategies, lendingAssetsData, prices, props.isHls],
) )
const updatedApr = useMemo( const updatedApr = useMemo(
() => () =>
@@ -101,19 +95,10 @@ export default function AccountComposition(props: Props) {
lendingAssetsData, lendingAssetsData,
prices, prices,
hlsStrategies, hlsStrategies,
assets,
props.isHls, props.isHls,
) )
: BN_ZERO, : BN_ZERO,
[ [updatedAccount, borrowAssetsData, lendingAssetsData, prices, hlsStrategies, props.isHls],
updatedAccount,
borrowAssetsData,
lendingAssetsData,
prices,
hlsStrategies,
assets,
props.isHls,
],
) )
return ( return (
@@ -1,8 +1,8 @@
import { useCallback, useEffect } from 'react' import { useCallback, useEffect } from 'react'
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
import AccountFundFullPage from 'components/account/AccountFund/AccountFundFullPage' import AccountFundFullPage from 'components/Account/AccountFund/AccountFundFullPage'
import FullOverlayContent from 'components/common/FullOverlayContent' import FullOverlayContent from 'components/FullOverlayContent'
import WalletSelect from 'components/Wallet/WalletSelect' import WalletSelect from 'components/Wallet/WalletSelect'
import useToggle from 'hooks/useToggle' import useToggle from 'hooks/useToggle'
import useStore from 'store' import useStore from 'store'
@@ -14,7 +14,6 @@ export default function AccountCreateFirst() {
const address = useStore((s) => s.address) const address = useStore((s) => s.address)
const createAccount = useStore((s) => s.createAccount) const createAccount = useStore((s) => s.createAccount)
const [isCreating, setIsCreating] = useToggle(false) const [isCreating, setIsCreating] = useToggle(false)
const [searchParams] = useSearchParams()
useEffect(() => { useEffect(() => {
if (!address) useStore.setState({ focusComponent: { component: <WalletSelect /> } }) if (!address) useStore.setState({ focusComponent: { component: <WalletSelect /> } })
@@ -25,7 +24,7 @@ export default function AccountCreateFirst() {
const accountId = await createAccount('default') const accountId = await createAccount('default')
setIsCreating(false) setIsCreating(false)
if (accountId) { if (accountId) {
navigate(getRoute(getPage(pathname), searchParams, address, accountId)) navigate(getRoute(getPage(pathname), address, accountId))
useStore.setState({ useStore.setState({
focusComponent: { focusComponent: {
component: <AccountFundFullPage />, component: <AccountFundFullPage />,
@@ -35,7 +34,7 @@ export default function AccountCreateFirst() {
}, },
}) })
} }
}, [setIsCreating, createAccount, navigate, pathname, searchParams, address]) }, [createAccount, navigate, pathname, address, setIsCreating])
return ( return (
<FullOverlayContent <FullOverlayContent
@@ -1,7 +1,7 @@
import classNames from 'classnames' import classNames from 'classnames'
import { FormattedNumber } from 'components/common/FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
import { ArrowRight } from 'components/common/Icons' import { ArrowRight } from 'components/Icons'
interface Props { interface Props {
leverage: number leverage: number
@@ -1,6 +1,6 @@
import { HealthGauge } from 'components/account/Health/HealthGauge' import { HealthGauge } from 'components/Account/Health/HealthGauge'
import Loading from 'components/common/Loading' import Loading from 'components/Loading'
import Text from 'components/common/Text' import Text from 'components/Text'
export default function Skeleton() { export default function Skeleton() {
return ( return (
@@ -2,32 +2,30 @@ import classNames from 'classnames'
import { useCallback, useMemo } from 'react' import { useCallback, useMemo } from 'react'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import AccountBalancesTable from 'components/account/AccountBalancesTable' import AccountBalancesTable from 'components/Account/AccountBalancesTable'
import AccountComposition from 'components/account/AccountComposition' import AccountComposition from 'components/Account/AccountComposition'
import AccountDetailsLeverage from 'components/account/AccountDetails/AccountDetailsLeverage' import AccountDetailsLeverage from 'components/Account/AccountDetails/AccountDetailsLeverage'
import Skeleton from 'components/account/AccountDetails/Skeleton' import Skeleton from 'components/Account/AccountDetails/Skeleton'
import AccountPerpPositionTable from 'components/account/AccountPerpPositionTable' import { HealthGauge } from 'components/Account/Health/HealthGauge'
import { HealthGauge } from 'components/account/Health/HealthGauge' import EscButton from 'components/Button/EscButton'
import EscButton from 'components/common/Button/EscButton' import { glowElement } from 'components/Button/utils'
import { glowElement } from 'components/common/Button/utils' import Card from 'components/Card'
import Card from 'components/common/Card' import DisplayCurrency from 'components/DisplayCurrency'
import DisplayCurrency from 'components/common/DisplayCurrency' import { FormattedNumber } from 'components/FormattedNumber'
import { FormattedNumber } from 'components/common/FormattedNumber' import { ThreeDots } from 'components/Icons'
import { ThreeDots } from 'components/common/Icons' import Text from 'components/Text'
import Text from 'components/common/Text'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { LocalStorageKeys } from 'constants/localStorageKeys' import { LocalStorageKeys } from 'constants/localStorageKeys'
import { ORACLE_DENOM } from 'constants/oracle' import { ORACLE_DENOM } from 'constants/oracle'
import useAccountIds from 'hooks/accounts/useAccountIds'
import useAccounts from 'hooks/accounts/useAccounts'
import useCurrentAccount from 'hooks/accounts/useCurrentAccount'
import useAllAssets from 'hooks/assets/useAllAssets'
import useLocalStorage from 'hooks/localStorage/useLocalStorage'
import useAccountId from 'hooks/useAccountId' import useAccountId from 'hooks/useAccountId'
import useAccountIds from 'hooks/useAccountIds'
import useAccounts from 'hooks/useAccounts'
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData' import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
import useHLSStakingAssets from 'hooks/useHLSStakingAssets' import useCurrentAccount from 'hooks/useCurrentAccount'
import useHealthComputer from 'hooks/useHealthComputer' import useHealthComputer from 'hooks/useHealthComputer'
import useHLSStakingAssets from 'hooks/useHLSStakingAssets'
import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData' import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData'
import useLocalStorage from 'hooks/useLocalStorage'
import usePrices from 'hooks/usePrices' import usePrices from 'hooks/usePrices'
import useStore from 'store' import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
@@ -39,16 +37,16 @@ import {
export default function AccountDetailsController() { export default function AccountDetailsController() {
const address = useStore((s) => s.address) const address = useStore((s) => s.address)
const isHLS = useStore((s) => s.isHLS) const currentAppSection = useStore((s) => s.currentAppSection)
const { data: accounts, isLoading } = useAccounts('default', address) const { data: accounts, isLoading } = useAccounts('default', address)
const { data: accountIds } = useAccountIds(address, false, true) const { data: accountIds } = useAccountIds(address, false)
const accountId = useAccountId() const accountId = useAccountId()
const account = useCurrentAccount() const account = useCurrentAccount()
const focusComponent = useStore((s) => s.focusComponent) const focusComponent = useStore((s) => s.focusComponent)
const isOwnAccount = accountId && accountIds?.includes(accountId) const isOwnAccount = accountId && accountIds?.includes(accountId)
if (!address || focusComponent || !isOwnAccount || isHLS) return null if (!address || focusComponent || !isOwnAccount || currentAppSection !== 'app') return null
if ((isLoading && accountId && !focusComponent) || !account) return <Skeleton /> if ((isLoading && accountId && !focusComponent) || !account) return <Skeleton />
@@ -74,25 +72,21 @@ function AccountDetails(props: Props) {
updatedAccount || account, updatedAccount || account,
) )
const { data: prices } = usePrices() const { data: prices } = usePrices()
const assets = useAllAssets()
const accountBalanceValue = useMemo( const accountBalanceValue = useMemo(
() => calculateAccountBalanceValue(updatedAccount ?? account, prices, assets), () => calculateAccountBalanceValue(updatedAccount ?? account, prices),
[updatedAccount, account, prices, assets], [updatedAccount, account, prices],
) )
const coin = BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, accountBalanceValue) const coin = BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, accountBalanceValue)
const leverage = useMemo( const leverage = useMemo(() => calculateAccountLeverage(account, prices), [account, prices])
() => calculateAccountLeverage(account, prices, assets),
[account, assets, prices],
)
const updatedLeverage = useMemo(() => { const updatedLeverage = useMemo(() => {
if (!updatedAccount) return null if (!updatedAccount) return null
const updatedLeverage = calculateAccountLeverage(updatedAccount, prices, assets) const updatedLeverage = calculateAccountLeverage(updatedAccount, prices)
if (updatedLeverage.eq(leverage)) return null if (updatedLeverage.eq(leverage)) return null
return updatedLeverage return updatedLeverage
}, [updatedAccount, prices, leverage, assets]) }, [updatedAccount, prices, leverage])
const data = useBorrowMarketAssetsTableData() const { data } = useBorrowMarketAssetsTableData(false)
const borrowAssetsData = useMemo(() => data?.allAssets || [], [data]) const borrowAssetsData = useMemo(() => data?.allAssets || [], [data])
const { availableAssets: lendingAvailableAssets, accountLentAssets } = const { availableAssets: lendingAvailableAssets, accountLentAssets } =
@@ -110,15 +104,11 @@ function AccountDetails(props: Props) {
lendingAssetsData, lendingAssetsData,
prices, prices,
hlsStrategies, hlsStrategies,
assets,
account.kind === 'high_levered_strategy', account.kind === 'high_levered_strategy',
), ),
[account, assets, borrowAssetsData, hlsStrategies, lendingAssetsData, prices, updatedAccount], [account, borrowAssetsData, hlsStrategies, lendingAssetsData, prices, updatedAccount],
) )
const isFullWidth = const isFullWidth = location.pathname.includes('trade') || location.pathname === '/'
location.pathname.includes('trade') ||
location.pathname === '/' ||
location.pathname.includes('perps')
function AccountDetailsHeader() { function AccountDetailsHeader() {
const onClose = useCallback(() => useStore.setState({ accountDetailsExpanded: false }), []) const onClose = useCallback(() => useStore.setState({ accountDetailsExpanded: false }), [])
@@ -219,12 +209,6 @@ function AccountDetails(props: Props) {
lendingData={lendingAssetsData} lendingData={lendingAssetsData}
hideCard hideCard
/> />
{account.perps.length > 0 && (
<>
<Text className='w-full px-4 py-2 text-white bg-white/10'>Perp Positions</Text>
<AccountPerpPositionTable account={account} hideCard />
</>
)}
</Card> </Card>
</div> </div>
</div> </div>
@@ -1,22 +1,26 @@
import classNames from 'classnames' import classNames from 'classnames'
import { useCallback, useEffect, useMemo, useState } from 'react' import { useCallback, useEffect, useMemo, useState } from 'react'
import AccountFundRow from 'components/account/AccountFund/AccountFundRow' import Button from 'components/Button'
import Button from 'components/common/Button' import DepositCapMessage from 'components/DepositCapMessage'
import DepositCapMessage from 'components/common/DepositCapMessage' import { ArrowRight, Plus } from 'components/Icons'
import { ArrowRight, Plus } from 'components/common/Icons' import SwitchAutoLend from 'components/Switch/SwitchAutoLend'
import SwitchAutoLend from 'components/common/Switch/SwitchAutoLend' import Text from 'components/Text'
import Text from 'components/common/Text' import TokenInputWithSlider from 'components/TokenInput/TokenInputWithSlider'
import WalletBridges from 'components/Wallet/WalletBridges' import WalletBridges from 'components/Wallet/WalletBridges'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { LocalStorageKeys } from 'constants/localStorageKeys'
import { BN_ZERO } from 'constants/math' import { BN_ZERO } from 'constants/math'
import useBaseAsset from 'hooks/assets/useBasetAsset'
import useMarketAssets from 'hooks/markets/useMarketAssets'
import useAutoLend from 'hooks/useAutoLend' import useAutoLend from 'hooks/useAutoLend'
import useLocalStorage from 'hooks/useLocalStorage'
import useMarketAssets from 'hooks/useMarketAssets'
import useToggle from 'hooks/useToggle'
import { useUpdatedAccount } from 'hooks/useUpdatedAccount' import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
import useWalletBalances from 'hooks/useWalletBalances' import useWalletBalances from 'hooks/useWalletBalances'
import useStore from 'store' import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { byDenom } from 'utils/array' import { byDenom } from 'utils/array'
import { getAssetByDenom, getBaseAsset } from 'utils/assets'
import { defaultFee } from 'utils/constants' import { defaultFee } from 'utils/constants'
import { getCapLeftWithBuffer } from 'utils/generic' import { getCapLeftWithBuffer } from 'utils/generic'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
@@ -30,15 +34,21 @@ interface Props {
export default function AccountFundContent(props: Props) { export default function AccountFundContent(props: Props) {
const deposit = useStore((s) => s.deposit) const deposit = useStore((s) => s.deposit)
const accounts = useStore((s) => s.accounts)
const walletAssetModal = useStore((s) => s.walletAssetsModal) const walletAssetModal = useStore((s) => s.walletAssetsModal)
const [isConfirming, setIsConfirming] = useState(false) const [isConfirming, setIsConfirming] = useState(false)
const { autoLendEnabledAccountIds } = useAutoLend() const [lendAssets, setLendAssets] = useLocalStorage<boolean>(
const isLending = autoLendEnabledAccountIds.includes(props.accountId) LocalStorageKeys.LEND_ASSETS,
DEFAULT_SETTINGS.lendAssets,
)
const [fundingAssets, setFundingAssets] = useState<BNCoin[]>([]) const [fundingAssets, setFundingAssets] = useState<BNCoin[]>([])
const { data: marketAssets } = useMarketAssets() const { data: marketAssets } = useMarketAssets()
const { data: walletBalances } = useWalletBalances(props.address) const { data: walletBalances } = useWalletBalances(props.address)
const { autoLendEnabledAccountIds } = useAutoLend()
const [isLending, toggleIsLending] = useToggle(lendAssets)
const { simulateDeposits } = useUpdatedAccount(props.account) const { simulateDeposits } = useUpdatedAccount(props.account)
const baseAsset = useBaseAsset() const autoLendEnabled = autoLendEnabledAccountIds.includes(props.accountId)
const baseAsset = getBaseAsset()
const hasAssetSelected = fundingAssets.length > 0 const hasAssetSelected = fundingAssets.length > 0
const hasFundingAssets = const hasFundingAssets =
@@ -124,6 +134,14 @@ export default function AccountFundContent(props: Props) {
}) })
}, []) }, [])
useEffect(() => {
toggleIsLending(autoLendEnabled)
}, [autoLendEnabled, toggleIsLending])
useEffect(() => {
if (accounts?.length === 1 && isLending) setLendAssets(true)
}, [isLending, accounts, lendAssets, setLendAssets])
const depositCapReachedCoins = useMemo(() => { const depositCapReachedCoins = useMemo(() => {
const depositCapReachedCoins: BNCoin[] = [] const depositCapReachedCoins: BNCoin[] = []
fundingAssets.forEach((asset) => { fundingAssets.forEach((asset) => {
@@ -144,20 +162,26 @@ export default function AccountFundContent(props: Props) {
<div> <div>
{!hasAssetSelected && <Text>Please select an asset.</Text>} {!hasAssetSelected && <Text>Please select an asset.</Text>}
{fundingAssets.map((coin) => { {fundingAssets.map((coin) => {
const asset = getAssetByDenom(coin.denom) as Asset
const balance = balances.find(byDenom(coin.denom))?.amount ?? BN_ZERO
return ( return (
<div <div
key={coin.denom} key={asset.symbol}
className={classNames( className={classNames(
'w-full mb-4', 'w-full mb-4',
props.isFullPage && 'w-full p-4 border rounded-base border-white/20 bg-white/5', props.isFullPage && 'w-full p-4 border rounded-base border-white/20 bg-white/5',
)} )}
> >
<AccountFundRow <TokenInputWithSlider
denom={coin.denom} asset={asset}
balances={balances} onChange={(amount) => updateFundingAssets(amount, asset.denom)}
amount={coin.amount ?? BN_ZERO} amount={coin.amount ?? BN_ZERO}
isConfirming={isConfirming} max={balance}
updateFundingAssets={updateFundingAssets} balances={balances}
maxText='Max'
disabled={isConfirming}
warningMessages={[]}
/> />
</div> </div>
) )
@@ -165,7 +189,7 @@ export default function AccountFundContent(props: Props) {
<Button <Button
className='w-full mt-4' className='w-full mt-4'
text='Select Assets' text='Select assets'
color='tertiary' color='tertiary'
rightIcon={<Plus />} rightIcon={<Plus />}
iconClassName='w-3' iconClassName='w-3'
@@ -181,6 +205,8 @@ export default function AccountFundContent(props: Props) {
<SwitchAutoLend <SwitchAutoLend
className='pt-4 mt-4 border border-transparent border-t-white/10' className='pt-4 mt-4 border border-transparent border-t-white/10'
accountId={props.accountId} accountId={props.accountId}
value={!props.isFullPage ? isLending : undefined}
onChange={!props.isFullPage ? toggleIsLending : undefined}
/> />
</div> </div>
<Button <Button
@@ -1,12 +1,12 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import AccountFundContent from 'components/account/AccountFund/AccountFundContent' import AccountFundContent from 'components/Account/AccountFund/AccountFundContent'
import Card from 'components/common/Card' import Card from 'components/Card'
import { CircularProgress } from 'components/common/CircularProgress' import { CircularProgress } from 'components/CircularProgress'
import FullOverlayContent from 'components/common/FullOverlayContent' import FullOverlayContent from 'components/FullOverlayContent'
import useAccounts from 'hooks/accounts/useAccounts'
import useCurrentAccount from 'hooks/accounts/useCurrentAccount'
import useAccountId from 'hooks/useAccountId' import useAccountId from 'hooks/useAccountId'
import useAccounts from 'hooks/useAccounts'
import useCurrentAccount from 'hooks/useCurrentAccount'
import useStore from 'store' import useStore from 'store'
export default function AccountFundFullPage() { export default function AccountFundFullPage() {
@@ -1,12 +1,11 @@
import { useCallback, useMemo } from 'react' import { useCallback, useMemo } from 'react'
import AccountFundFullPage from 'components/account/AccountFund/AccountFundFullPage' import AccountFundFullPage from 'components/Account/AccountFund/AccountFundFullPage'
import Skeleton from 'components/account/AccountList/Skeleton' import Skeleton from 'components/Account/AccountList/Skeleton'
import Button from 'components/common/Button' import Button from 'components/Button'
import { ArrowDownLine, ArrowUpLine, TrashBin } from 'components/common/Icons' import { ArrowDownLine, ArrowUpLine, TrashBin } from 'components/Icons'
import SwitchAutoLend from 'components/common/Switch/SwitchAutoLend' import SwitchAutoLend from 'components/Switch/SwitchAutoLend'
import useAccount from 'hooks/accounts/useAccount' import useAccount from 'hooks/useAccount'
import useAllAssets from 'hooks/assets/useAllAssets'
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData' import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
import useHealthComputer from 'hooks/useHealthComputer' import useHealthComputer from 'hooks/useHealthComputer'
import useHLSStakingAssets from 'hooks/useHLSStakingAssets' import useHLSStakingAssets from 'hooks/useHLSStakingAssets'
@@ -23,17 +22,16 @@ interface Props {
export default function AccountStats(props: Props) { export default function AccountStats(props: Props) {
const { accountId, isActive, setShowMenu } = props const { accountId, isActive, setShowMenu } = props
const assets = useAllAssets()
const { data: account } = useAccount(accountId) const { data: account } = useAccount(accountId)
const { data: prices } = usePrices() const { data: prices } = usePrices()
const { data: hlsStrategies } = useHLSStakingAssets() const { data: hlsStrategies } = useHLSStakingAssets()
const positionBalance = useMemo( const positionBalance = useMemo(
() => (!account ? null : calculateAccountBalanceValue(account, prices, assets)), () => (!account ? null : calculateAccountBalanceValue(account, prices)),
[account, assets, prices], [account, prices],
) )
const { health, healthFactor } = useHealthComputer(account) const { health, healthFactor } = useHealthComputer(account)
const data = useBorrowMarketAssetsTableData() const { data } = useBorrowMarketAssetsTableData(false)
const borrowAssetsData = useMemo(() => data?.allAssets || [], [data]) const borrowAssetsData = useMemo(() => data?.allAssets || [], [data])
const { availableAssets: lendingAvailableAssets, accountLentAssets } = const { availableAssets: lendingAvailableAssets, accountLentAssets } =
useLendingMarketAssetsTableData() useLendingMarketAssetsTableData()
@@ -51,10 +49,9 @@ export default function AccountStats(props: Props) {
lendingAssetsData, lendingAssetsData,
prices, prices,
hlsStrategies, hlsStrategies,
assets,
account.kind === 'high_levered_strategy', account.kind === 'high_levered_strategy',
), ),
[account, assets, borrowAssetsData, hlsStrategies, lendingAssetsData, prices], [account, borrowAssetsData, hlsStrategies, lendingAssetsData, prices],
) )
const deleteAccountHandler = useCallback(() => { const deleteAccountHandler = useCallback(() => {
@@ -1,10 +1,10 @@
import HealthBar from 'components/account/Health/HealthBar' import HealthBar from 'components/Account/Health/HealthBar'
import HealthIcon from 'components/account/Health/HealthIcon' import HealthIcon from 'components/Account/Health/HealthIcon'
import DisplayCurrency from 'components/common/DisplayCurrency' import DisplayCurrency from 'components/DisplayCurrency'
import { FormattedNumber } from 'components/common/FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
import { ArrowChartLineUp } from 'components/common/Icons' import { ArrowChartLineUp } from 'components/Icons'
import Loading from 'components/common/Loading' import Loading from 'components/Loading'
import Text from 'components/common/Text' import Text from 'components/Text'
import { ORACLE_DENOM } from 'constants/oracle' import { ORACLE_DENOM } from 'constants/oracle'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
@@ -1,13 +1,13 @@
import classNames from 'classnames' import classNames from 'classnames'
import { useEffect } from 'react' import { useEffect } from 'react'
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
import AccountStats from 'components/account/AccountList/AccountStats' import AccountStats from 'components/Account/AccountList/AccountStats'
import Card from 'components/common/Card' import Card from 'components/Card'
import Radio from 'components/common/Radio' import Radio from 'components/Radio'
import Text from 'components/common/Text' import Text from 'components/Text'
import useAccountIds from 'hooks/accounts/useAccountIds'
import useAccountId from 'hooks/useAccountId' import useAccountId from 'hooks/useAccountId'
import useAccountIds from 'hooks/useAccountIds'
import useStore from 'store' import useStore from 'store'
import { getPage, getRoute } from 'utils/route' import { getPage, getRoute } from 'utils/route'
@@ -28,7 +28,6 @@ export default function AccountList(props: Props) {
const currentAccountId = useAccountId() const currentAccountId = useAccountId()
const address = useStore((s) => s.address) const address = useStore((s) => s.address)
const { data: accountIds } = useAccountIds(address, true, true) const { data: accountIds } = useAccountIds(address, true, true)
const [searchParams] = useSearchParams()
useEffect(() => { useEffect(() => {
if (!currentAccountId) return if (!currentAccountId) return
@@ -55,7 +54,7 @@ export default function AccountList(props: Props) {
onClick={() => { onClick={() => {
if (isActive) return if (isActive) return
useStore.setState({ accountDeleteModal: null }) useStore.setState({ accountDeleteModal: null })
navigate(getRoute(getPage(pathname), searchParams, address, accountId)) navigate(getRoute(getPage(pathname), address, accountId))
}} }}
title={ title={
<div className={accountCardHeaderClasses} role={!isActive ? 'button' : undefined}> <div className={accountCardHeaderClasses} role={!isActive ? 'button' : undefined}>
@@ -1,8 +1,8 @@
import { Suspense } from 'react' import { Suspense } from 'react'
import AccountMenuContent from 'components/account/AccountMenuContent' import AccountMenuContent from 'components/Account/AccountMenuContent'
import Loading from 'components/common/Loading' import Loading from 'components/Loading'
import useAccountIds from 'hooks/accounts/useAccountIds' import useAccountIds from 'hooks/useAccountIds'
import useStore from 'store' import useStore from 'store'
function Content() { function Content() {
@@ -1,21 +1,22 @@
import classNames from 'classnames' import classNames from 'classnames'
import { useCallback } from 'react' import { useCallback } from 'react'
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
import AccountCreateFirst from 'components/account/AccountCreateFirst' import AccountCreateFirst from 'components/Account/AccountCreateFirst'
import AccountFund from 'components/account/AccountFund/AccountFundFullPage' import AccountFund from 'components/Account/AccountFund/AccountFundFullPage'
import AccountList from 'components/account/AccountList' import AccountList from 'components/Account/AccountList'
import Button from 'components/common/Button' import Button from 'components/Button'
import { Account, Plus, PlusCircled } from 'components/common/Icons' import { Account, Plus, PlusCircled } from 'components/Icons'
import Overlay from 'components/common/Overlay' import Overlay from 'components/Overlay'
import Text from 'components/common/Text' import Text from 'components/Text'
import WalletBridges from 'components/Wallet/WalletBridges' import WalletBridges from 'components/Wallet/WalletBridges'
import useAccountIds from 'hooks/accounts/useAccountIds' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import useBaseAsset from 'hooks/assets/useBasetAsset' import { LocalStorageKeys } from 'constants/localStorageKeys'
import useEnableAutoLendGlobal from 'hooks/localStorage/useEnableAutoLendGlobal'
import useAccountId from 'hooks/useAccountId' import useAccountId from 'hooks/useAccountId'
import useAccountIds from 'hooks/useAccountIds'
import useAutoLend from 'hooks/useAutoLend' import useAutoLend from 'hooks/useAutoLend'
import useCurrentWalletBalance from 'hooks/useCurrentWalletBalance' import useCurrentWalletBalance from 'hooks/useCurrentWalletBalance'
import useLocalStorage from 'hooks/useLocalStorage'
import useToggle from 'hooks/useToggle' import useToggle from 'hooks/useToggle'
import useStore from 'store' import useStore from 'store'
import { defaultFee } from 'utils/constants' import { defaultFee } from 'utils/constants'
@@ -30,16 +31,18 @@ export default function AccountMenuContent() {
const navigate = useNavigate() const navigate = useNavigate()
const { pathname } = useLocation() const { pathname } = useLocation()
const address = useStore((s) => s.address) const address = useStore((s) => s.address)
const { data: accountIds } = useAccountIds(address, true, true) const { data: accountIds } = useAccountIds(address)
const accountId = useAccountId() const accountId = useAccountId()
const [searchParams] = useSearchParams()
const createAccount = useStore((s) => s.createAccount) const createAccount = useStore((s) => s.createAccount)
const baseAsset = useBaseAsset() const baseCurrency = useStore((s) => s.baseCurrency)
const [showMenu, setShowMenu] = useToggle() const [showMenu, setShowMenu] = useToggle()
const [isCreating, setIsCreating] = useToggle() const [isCreating, setIsCreating] = useToggle()
const transactionFeeCoinBalance = useCurrentWalletBalance(baseAsset.denom) const transactionFeeCoinBalance = useCurrentWalletBalance(baseCurrency.denom)
const [enableAutoLendGlobal] = useEnableAutoLendGlobal() const [lendAssets] = useLocalStorage<boolean>(
LocalStorageKeys.LEND_ASSETS,
DEFAULT_SETTINGS.lendAssets,
)
const { enableAutoLendAccountId } = useAutoLend() const { enableAutoLendAccountId } = useAutoLend()
const hasCreditAccounts = !!accountIds?.length const hasCreditAccounts = !!accountIds?.length
@@ -60,8 +63,8 @@ export default function AccountMenuContent() {
setIsCreating(false) setIsCreating(false)
if (accountId) { if (accountId) {
navigate(getRoute(getPage(pathname), searchParams, address, accountId)) navigate(getRoute(getPage(pathname), address, accountId))
if (enableAutoLendGlobal) enableAutoLendAccountId(accountId) if (lendAssets) enableAutoLendAccountId(accountId)
useStore.setState({ useStore.setState({
focusComponent: { focusComponent: {
component: <AccountFund />, component: <AccountFund />,
@@ -77,9 +80,8 @@ export default function AccountMenuContent() {
createAccount, createAccount,
navigate, navigate,
pathname, pathname,
searchParams,
address, address,
enableAutoLendGlobal, lendAssets,
enableAutoLendAccountId, enableAutoLendAccountId,
]) ])
@@ -1,25 +1,23 @@
import classNames from 'classnames' import classNames from 'classnames'
import { HTMLAttributes, useCallback, useMemo } from 'react' import { HTMLAttributes, useCallback, useMemo } from 'react'
import AccountBalancesTable from 'components/account/AccountBalancesTable' import Accordion from 'components/Accordion'
import AccountComposition from 'components/account/AccountComposition' import AccountBalancesTable from 'components/Account/AccountBalancesTable'
import AccountPerpPositionTable from 'components/account/AccountPerpPositionTable' import AccountComposition from 'components/Account/AccountComposition'
import HealthBar from 'components/account/Health/HealthBar' import HealthBar from 'components/Account/Health/HealthBar'
import Accordion from 'components/common/Accordion' import Card from 'components/Card'
import Card from 'components/common/Card' import DisplayCurrency from 'components/DisplayCurrency'
import DisplayCurrency from 'components/common/DisplayCurrency' import { FormattedNumber } from 'components/FormattedNumber'
import { FormattedNumber } from 'components/common/FormattedNumber' import { ArrowRight } from 'components/Icons'
import { ArrowRight } from 'components/common/Icons' import Text from 'components/Text'
import Text from 'components/common/Text'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { LocalStorageKeys } from 'constants/localStorageKeys' import { LocalStorageKeys } from 'constants/localStorageKeys'
import { BN_ZERO } from 'constants/math' import { BN_ZERO } from 'constants/math'
import { ORACLE_DENOM } from 'constants/oracle' import { ORACLE_DENOM } from 'constants/oracle'
import useAllAssets from 'hooks/assets/useAllAssets'
import useLocalStorage from 'hooks/localStorage/useLocalStorage'
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData' import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
import useHealthComputer from 'hooks/useHealthComputer' import useHealthComputer from 'hooks/useHealthComputer'
import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData' import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData'
import useLocalStorage from 'hooks/useLocalStorage'
import usePrices from 'hooks/usePrices' import usePrices from 'hooks/usePrices'
import useStore from 'store' import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
@@ -36,17 +34,15 @@ export default function AccountSummary(props: Props) {
DEFAULT_SETTINGS.accountSummaryTabs, DEFAULT_SETTINGS.accountSummaryTabs,
) )
const { data: prices } = usePrices() const { data: prices } = usePrices()
const assets = useAllAssets()
const updatedAccount = useStore((s) => s.updatedAccount) const updatedAccount = useStore((s) => s.updatedAccount)
const chainConfig = useStore((s) => s.chainConfig)
const accountBalance = useMemo( const accountBalance = useMemo(
() => () =>
props.account props.account
? calculateAccountBalanceValue(updatedAccount ?? props.account, prices, assets) ? calculateAccountBalanceValue(updatedAccount ?? props.account, prices)
: BN_ZERO, : BN_ZERO,
[props.account, updatedAccount, prices, assets], [props.account, updatedAccount, prices],
) )
const data = useBorrowMarketAssetsTableData() const { data } = useBorrowMarketAssetsTableData(false)
const borrowAssetsData = useMemo(() => data?.allAssets || [], [data]) const borrowAssetsData = useMemo(() => data?.allAssets || [], [data])
const { availableAssets: lendingAvailableAssets, accountLentAssets } = const { availableAssets: lendingAvailableAssets, accountLentAssets } =
useLendingMarketAssetsTableData() useLendingMarketAssetsTableData()
@@ -59,16 +55,16 @@ export default function AccountSummary(props: Props) {
const { health: updatedHealth, healthFactor: updatedHealthFactor } = const { health: updatedHealth, healthFactor: updatedHealthFactor } =
useHealthComputer(updatedAccount) useHealthComputer(updatedAccount)
const leverage = useMemo( const leverage = useMemo(
() => (props.account ? calculateAccountLeverage(props.account, prices, assets) : BN_ZERO), () => (props.account ? calculateAccountLeverage(props.account, prices) : BN_ZERO),
[props.account, prices, assets], [props.account, prices],
) )
const updatedLeverage = useMemo(() => { const updatedLeverage = useMemo(() => {
if (!updatedAccount) return null if (!updatedAccount) return null
const updatedLeverage = calculateAccountLeverage(updatedAccount, prices, assets) const updatedLeverage = calculateAccountLeverage(updatedAccount, prices)
if (updatedLeverage.eq(leverage)) return null if (updatedLeverage.eq(leverage)) return null
return updatedLeverage return updatedLeverage
}, [updatedAccount, prices, assets, leverage]) }, [updatedAccount, prices, leverage])
const handleToggle = useCallback( const handleToggle = useCallback(
(index: number) => { (index: number) => {
@@ -77,56 +73,6 @@ export default function AccountSummary(props: Props) {
[accountSummaryTabs, setAccountSummaryTabs], [accountSummaryTabs, setAccountSummaryTabs],
) )
const items = useMemo(() => {
const itemsArray = [
{
title: `Credit Account ${props.account.id} Composition`,
renderContent: () =>
props.account ? <AccountComposition account={props.account} isHls={props.isHls} /> : null,
isOpen: accountSummaryTabs[0],
toggleOpen: (index: number) => handleToggle(index),
renderSubTitle: () => <></>,
},
{
title: 'Balances',
renderContent: () =>
props.account ? (
<AccountBalancesTable
account={props.account}
borrowingData={borrowAssetsData}
lendingData={lendingAssetsData}
hideCard
isHls={props.isHls}
/>
) : null,
isOpen: accountSummaryTabs[1],
toggleOpen: (index: number) => handleToggle(index),
renderSubTitle: () => <></>,
},
]
if (chainConfig.perps)
itemsArray.push({
title: 'Perp Positions',
renderContent: () =>
props.account && props.account.perps.length > 0 ? (
<AccountPerpPositionTable account={props.account} hideCard />
) : null,
isOpen: accountSummaryTabs[2] ?? false,
toggleOpen: (index: number) => handleToggle(index),
renderSubTitle: () => <></>,
})
return itemsArray
}, [
props.account,
borrowAssetsData,
lendingAssetsData,
props.isHls,
chainConfig.perps,
handleToggle,
accountSummaryTabs,
])
if (!props.account) return null if (!props.account) return null
return ( return (
<div className='h-[546px] max-w-screen overflow-y-scroll scrollbar-hide w-93.5'> <div className='h-[546px] max-w-screen overflow-y-scroll scrollbar-hide w-93.5'>
@@ -174,7 +120,37 @@ export default function AccountSummary(props: Props) {
/> />
</Item> </Item>
</Card> </Card>
<Accordion items={items} allowMultipleOpen /> <Accordion
items={[
{
title: `Credit Account ${props.account.id} Composition`,
renderContent: () =>
props.account ? (
<AccountComposition account={props.account} isHls={props.isHls} />
) : null,
isOpen: accountSummaryTabs[0],
toggleOpen: (index: number) => handleToggle(index),
renderSubTitle: () => <></>,
},
{
title: 'Balances',
renderContent: () =>
props.account ? (
<AccountBalancesTable
account={props.account}
borrowingData={borrowAssetsData}
lendingData={lendingAssetsData}
hideCard
isHls={props.isHls}
/>
) : null,
isOpen: accountSummaryTabs[1],
toggleOpen: (index: number) => handleToggle(index),
renderSubTitle: () => <></>,
},
]}
allowMultipleOpen
/>
</div> </div>
) )
} }
@@ -1,5 +1,5 @@
import AccountSummary from 'components/account/AccountSummary' import AccountSummary from 'components/Account/AccountSummary'
import useCurrentAccount from 'hooks/accounts/useCurrentAccount' import useCurrentAccount from 'hooks/useCurrentAccount'
function CurrentAccountSummary() { function CurrentAccountSummary() {
const account = useCurrentAccount() const account = useCurrentAccount()
@@ -1,12 +1,12 @@
import classNames from 'classnames' import classNames from 'classnames'
import { useMemo } from 'react' import { useMemo } from 'react'
import HealthIcon from 'components/account/Health/HealthIcon' import HealthIcon from 'components/Account/Health/HealthIcon'
import HealthTooltip from 'components/account/Health/HealthTooltip' import HealthTooltip from 'components/Account/Health/HealthTooltip'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { LocalStorageKeys } from 'constants/localStorageKeys' import { LocalStorageKeys } from 'constants/localStorageKeys'
import useLocalStorage from 'hooks/localStorage/useLocalStorage'
import useHealthColor from 'hooks/useHealthColor' import useHealthColor from 'hooks/useHealthColor'
import useLocalStorage from 'hooks/useLocalStorage'
import { getHealthIndicatorColors } from 'utils/healthIndicator' import { getHealthIndicatorColors } from 'utils/healthIndicator'
interface Props { interface Props {
@@ -1,12 +1,12 @@
import classNames from 'classnames' import classNames from 'classnames'
import { useMemo } from 'react' import { useMemo } from 'react'
import HealthIcon from 'components/account/Health/HealthIcon' import HealthIcon from 'components/Account/Health/HealthIcon'
import HealthTooltip from 'components/account/Health/HealthTooltip' import HealthTooltip from 'components/Account/Health/HealthTooltip'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { LocalStorageKeys } from 'constants/localStorageKeys' import { LocalStorageKeys } from 'constants/localStorageKeys'
import useLocalStorage from 'hooks/localStorage/useLocalStorage'
import useHealthColorAndLabel from 'hooks/useHealthColor' import useHealthColorAndLabel from 'hooks/useHealthColor'
import useLocalStorage from 'hooks/useLocalStorage'
import { computeHealthGaugePercentage } from 'utils/accounts' import { computeHealthGaugePercentage } from 'utils/accounts'
import { getHealthIndicatorColors } from 'utils/healthIndicator' import { getHealthIndicatorColors } from 'utils/healthIndicator'
@@ -1,6 +1,6 @@
import classNames from 'classnames' import classNames from 'classnames'
import { ExclamationMarkCircled, Heart } from 'components/common/Icons' import { ExclamationMarkCircled, Heart } from 'components/Icons'
interface Props { interface Props {
isLoading: boolean isLoading: boolean
@@ -1,8 +1,8 @@
import { ReactElement, useMemo } from 'react' import React, { ReactElement, useMemo } from 'react'
import { CircularProgress } from 'components/common/CircularProgress' import { CircularProgress } from 'components/CircularProgress'
import Text from 'components/common/Text' import Text from 'components/Text'
import { Tooltip } from 'components/common/Tooltip' import { Tooltip } from 'components/Tooltip'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
interface Props { interface Props {
@@ -9,12 +9,12 @@ import {
YAxis, YAxis,
} from 'recharts' } from 'recharts'
import { FormattedNumber } from 'components/common/FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
import Text from 'components/common/Text' import Text from 'components/Text'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { LocalStorageKeys } from 'constants/localStorageKeys' import { LocalStorageKeys } from 'constants/localStorageKeys'
import { BN_ZERO } from 'constants/math' import { BN_ZERO } from 'constants/math'
import useLocalStorage from 'hooks/localStorage/useLocalStorage' import useLocalStorage from 'hooks/useLocalStorage'
import { formatValue } from 'utils/formatters' import { formatValue } from 'utils/formatters'
export const RiskChart = ({ data }: RiskChartProps) => { export const RiskChart = ({ data }: RiskChartProps) => {
@@ -1,5 +1,5 @@
import DisplayCurrency from 'components/common/DisplayCurrency' import DisplayCurrency from 'components/DisplayCurrency'
import { FormattedNumber } from 'components/common/FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
import { MAX_AMOUNT_DECIMALS, MIN_AMOUNT } from 'constants/math' import { MAX_AMOUNT_DECIMALS, MIN_AMOUNT } from 'constants/math'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { demagnify } from 'utils/formatters' import { demagnify } from 'utils/formatters'
@@ -1,7 +1,7 @@
import AssetImage from 'components/common/assets/AssetImage' import AssetImage from 'components/Asset/AssetImage'
import DisplayCurrency from 'components/common/DisplayCurrency' import DisplayCurrency from 'components/DisplayCurrency'
import { FormattedNumber } from 'components/common/FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
import Text from 'components/common/Text' import Text from 'components/Text'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { demagnify } from 'utils/formatters' import { demagnify } from 'utils/formatters'
@@ -1,9 +1,9 @@
import classNames from 'classnames' import classNames from 'classnames'
import { FormattedNumber } from 'components/common/FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
import { InfoCircle } from 'components/common/Icons' import { InfoCircle } from 'components/Icons'
import Text from 'components/common/Text' import Text from 'components/Text'
import { Tooltip } from 'components/common/Tooltip' import { Tooltip } from 'components/Tooltip'
interface Props { interface Props {
rate: number rate: number
@@ -1,5 +1,5 @@
import { FormattedNumber } from 'components/common/FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
import Text from 'components/common/Text' import Text from 'components/Text'
interface Props { interface Props {
availableLiquidity: BigNumber availableLiquidity: BigNumber
@@ -1,10 +1,10 @@
import classNames from 'classnames' import classNames from 'classnames'
import { useEffect, useMemo } from 'react' import { useEffect, useMemo, useState } from 'react'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { LocalStorageKeys } from 'constants/localStorageKeys' import { LocalStorageKeys } from 'constants/localStorageKeys'
import useLocalStorage from 'hooks/localStorage/useLocalStorage' import useLocalStorage from 'hooks/useLocalStorage'
import useStore from 'store' import useStore from 'store'
import { getPage } from 'utils/route' import { getPage } from 'utils/route'
@@ -13,13 +13,41 @@ export default function Background() {
LocalStorageKeys.REDUCE_MOTION, LocalStorageKeys.REDUCE_MOTION,
DEFAULT_SETTINGS.reduceMotion, DEFAULT_SETTINGS.reduceMotion,
) )
const [backgroundClasses, setBackgroundClasses] = useState<string[]>([
'bg-body',
'bg-orb-primary',
'bg-orb-secondary',
'bg-orb-tertiary',
])
const { pathname } = useLocation() const { pathname } = useLocation()
const page = getPage(pathname) const page = getPage(pathname)
const isHLS = useMemo(() => page.split('-')[0] === 'hls', [page]) const currentAppSection = useMemo(() => {
switch (page.split('-')[0]) {
case 'hls':
setBackgroundClasses([
'bg-body-hls',
'bg-orb-primary-hls',
'bg-orb-secondary-hls',
'bg-orb-tertiary-hls',
])
return 'hls'
case 'stats':
setBackgroundClasses([
'bg-body',
'bg-orb-primary-stats',
'bg-orb-secondary-stats',
'bg-orb-tertiary-stats',
])
return 'stats'
default:
setBackgroundClasses(['bg-body', 'bg-orb-primary', 'bg-orb-secondary', 'bg-orb-tertiary'])
return 'app'
}
}, [page])
useEffect(() => { useEffect(() => {
useStore.setState({ isHLS }) useStore.setState({ currentAppSection })
}, [isHLS]) }, [currentAppSection])
return ( return (
<div <div
@@ -27,7 +55,7 @@ export default function Background() {
'fixed inset-0', 'fixed inset-0',
'w-full h-full', 'w-full h-full',
'overflow-hidden pointer-events-none background ', 'overflow-hidden pointer-events-none background ',
isHLS ? 'bg-body-hls' : 'bg-body', backgroundClasses[0],
!reduceMotion && 'transition-bg duration-1000 delay-300', !reduceMotion && 'transition-bg duration-1000 delay-300',
)} )}
> >
@@ -39,7 +67,7 @@ export default function Background() {
'max-h-[500px] max-w-[500px]', 'max-h-[500px] max-w-[500px]',
'left-[-10vw] top-[-10vw]', 'left-[-10vw] top-[-10vw]',
'blur-orb-primary', 'blur-orb-primary',
isHLS ? ' bg-orb-primary-hls' : 'bg-orb-primary', backgroundClasses[1],
'translate-x-0 translate-y-0 rounded-full opacity-20', 'translate-x-0 translate-y-0 rounded-full opacity-20',
!reduceMotion && 'animate-[float_120s_ease-in-out_infinite_2s]', !reduceMotion && 'animate-[float_120s_ease-in-out_infinite_2s]',
!reduceMotion && 'transition-bg duration-1000 delay-300', !reduceMotion && 'transition-bg duration-1000 delay-300',
@@ -53,7 +81,7 @@ export default function Background() {
'max-h-[1000px] max-w-[1000px]', 'max-h-[1000px] max-w-[1000px]',
'bottom-[-20vw] right-[-10vw]', 'bottom-[-20vw] right-[-10vw]',
'blur-orb-secondary', 'blur-orb-secondary',
isHLS ? ' bg-orb-secondary-hls' : 'bg-orb-secondary', backgroundClasses[2],
'translate-x-0 translate-y-0 rounded-full opacity-30', 'translate-x-0 translate-y-0 rounded-full opacity-30',
!reduceMotion && 'transition-bg duration-1000 delay-300', !reduceMotion && 'transition-bg duration-1000 delay-300',
)} )}
@@ -66,7 +94,7 @@ export default function Background() {
'max-h-[600px] max-w-[600px]', 'max-h-[600px] max-w-[600px]',
'right-[-4vw] top-[-10vw]', 'right-[-4vw] top-[-10vw]',
'blur-orb-tertiary ', 'blur-orb-tertiary ',
isHLS ? ' bg-orb-tertiary-hls' : 'bg-orb-tertiary', backgroundClasses[3],
'translate-x-0 translate-y-0 rounded-full opacity-20', 'translate-x-0 translate-y-0 rounded-full opacity-20',
!reduceMotion && 'animate-[float_180s_ease-in_infinite]', !reduceMotion && 'animate-[float_180s_ease-in_infinite]',
!reduceMotion && 'transition-bg duration-1000 delay-300', !reduceMotion && 'transition-bg duration-1000 delay-300',
@@ -1,10 +1,10 @@
import { useCallback } from 'react' import { useCallback } from 'react'
import Button from 'components/common/Button' import Button from 'components/Button'
import ActionButton from 'components/common/Button/ActionButton' import ActionButton from 'components/Button/ActionButton'
import { HandCoins, Plus } from 'components/common/Icons' import { HandCoins, Plus } from 'components/Icons'
import useMarketEnabledAssets from 'hooks/assets/useMarketEnabledAssets'
import useStore from 'store' import useStore from 'store'
import { getEnabledMarketAssets } from 'utils/assets'
interface Props { interface Props {
data: BorrowMarketTableData data: BorrowMarketTableData
@@ -12,7 +12,7 @@ interface Props {
export default function BorrowActionButtons(props: Props) { export default function BorrowActionButtons(props: Props) {
const { asset, debt } = props.data const { asset, debt } = props.data
const marketAssets = useMarketEnabledAssets() const marketAssets = getEnabledMarketAssets()
const currentAsset = marketAssets.find((a) => a.denom === asset.denom) const currentAsset = marketAssets.find((a) => a.denom === asset.denom)
const borrowHandler = useCallback(() => { const borrowHandler = useCallback(() => {
@@ -1,4 +1,4 @@
import Intro from 'components/common/Intro' import Intro from 'components/Intro'
export default function BorrowIntro() { export default function BorrowIntro() {
return ( return (

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