Added chain agnostic v2 (#710)

* update assets config and chains

* make clients dynamic

* feat: formatted ChainSelect

* fix infinite rerender on trade page

* feat: added NTRN icon

* fix: fixed ChainInfoID

* fix: fixed autoLendEnabled for NTRN

* fix: fixed the navigation and dependencies

* fix: fixed the pricefeed id

* fix: fixed the header menu

* fix: fixed the trading charts

* fix: fixed the healthbars

* fix: fixed naming of pion-1

* feast: updated xdefi image

* env: updated contracts

* make localStorage chain agnostic

* fix: made the selected chain persistant

* fix: fixed the wallet providers

* fix: updated auto connect

* fix: fixed auto connecting

* fix: added ChainSelect to focusMode

* store raw strings in localstorage

* 🔥 remnove tests

* update caching keys + disconnect wallet on change chain

* fix: fixed the chain select

* env: bumped version

---------

Co-authored-by: Bob van der Helm <34470358+bobthebuidlr@users.noreply.github.com>
This commit is contained in:
Linkie Link 2024-01-03 15:50:38 +01:00 committed by GitHub
parent a4cdede670
commit fb830c08cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
298 changed files with 2786 additions and 2779 deletions

View File

@ -1,37 +1,11 @@
# DEVNET # NEXT_PUBLIC_OSMOSIS1_RPC='https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-rpc-front/'
NEXT_PUBLIC_NETWORK=devnet NEXT_PUBLIC_OSMOSIS1_REST='https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-lcd-front/'
NEXT_PUBLIC_CHAIN_ID=devnet NEXT_PUBLIC_OSMOSIS_DEVNET_RPC='https://rpc.devnet.osmosis.zone/'
NEXT_PUBLIC_RPC=https://rpc.devnet.osmosis.zone/ NEXT_PUBLIC_OSMOSIS_DEVNET_REST='https://lcd.devnet.osmosis.zone/'
NEXT_PUBLIC_GQL=https://devnet-osmosis-gql.marsprotocol.io/graphql NEXT_PUBLIC_PION1_RPC='https://rpc-palvus.pion-1.ntrn.tech/'
NEXT_PUBLIC_REST=https://lcd.devnet.osmosis.zone/ NEXT_PUBLIC_PION1_REST='https://rest-palvus.pion-1.ntrn.tech/'
# 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/
# 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_REPOSITORY=github.com/tradingview/charting_library CHARTING_LIBRARY_USERNAME="git_username"
CHARTING_LIBRARY_ACCESS_TOKEN=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 CHARTING_LIBRARY_ACCESS_TOKEN="access_token"
CHARTING_LIBRARY_USERNAME=mars-git-demo CHARTING_LIBRARY_REPOSITORY="github.com/tradingview/charting_library/"
NEXT_PUBLIC_STRIDE_APRS=https://edge.stride.zone/api/stake-stats

View File

@ -1,6 +0,0 @@
module.exports = {
src: '/img.jpg',
height: 24,
width: 24,
blurDataURL: 'data:image/png;base64,imagedata',
}

View File

@ -1,17 +0,0 @@
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),
}
})

View File

@ -1,23 +0,0 @@
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,
}
})

View File

@ -1 +0,0 @@
module.exports = {}

View File

@ -1,7 +0,0 @@
/* 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

View File

@ -1,14 +0,0 @@
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()
})
})

View File

@ -1,67 +0,0 @@
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()
})
})

View File

@ -1,157 +0,0 @@
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'])
})
})

View File

@ -1,60 +0,0 @@
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()
})
})

View File

@ -1,34 +0,0 @@
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()
})
})

View File

@ -1,65 +0,0 @@
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)
})
})
})

View File

@ -1,32 +0,0 @@
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)
})
})
})

View File

@ -1,98 +0,0 @@
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)
})
})

View File

@ -1,45 +0,0 @@
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)
})
})
})

View File

@ -1,43 +0,0 @@
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()
})
})
})

View File

@ -1,60 +0,0 @@
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)$'],
}

View File

@ -1 +0,0 @@
import '@testing-library/jest-dom/extend-expect'

View File

@ -1,14 +1,12 @@
{ {
"name": "mars-v2-frontend", "name": "mars-v2-frontend",
"version": "2.1.2", "version": "2.2.0",
"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/ ./__tests__/ --fix && prettier --write ./src/ ./__tests__/", "format": "eslint ./src/ --fix && prettier --write ./src/ ",
"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",
@ -17,8 +15,8 @@
}, },
"lint-staged": { "lint-staged": {
"*.ts*": [ "*.ts*": [
"eslint ./src/ ./__tests__/ --fix", "eslint ./src/ --fix",
"prettier --write ./src/ ./__tests__/" "prettier --write ./src/"
] ]
}, },
"dependencies": { "dependencies": {
@ -57,7 +55,6 @@
}, },
"devDependencies": { "devDependencies": {
"@svgr/webpack": "^8.1.0", "@svgr/webpack": "^8.1.0",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^14.0.0", "@testing-library/react": "^14.0.0",
"@types/debounce-promise": "^3.1.9", "@types/debounce-promise": "^3.1.9",
"@types/lodash.debounce": "^4.0.9", "@types/lodash.debounce": "^4.0.9",
@ -67,7 +64,6 @@
"@types/react-dom": "18.2.15", "@types/react-dom": "18.2.15",
"@types/react-helmet": "^6.1.11", "@types/react-helmet": "^6.1.11",
"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.54.0", "eslint": "^8.54.0",
@ -75,8 +71,6 @@
"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",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"lint-staged": "^15.2.0", "lint-staged": "^15.2.0",
"prettier": "^3.0.3", "prettier": "^3.0.3",
"prettier-plugin-tailwindcss": "^0.5.6", "prettier-plugin-tailwindcss": "^0.5.6",

View File

@ -0,0 +1,16 @@
<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>

After

Width:  |  Height:  |  Size: 557 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -4,10 +4,13 @@ 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'
export default async function getAccount(accountId?: string): Promise<Account> { export default async function getAccount(
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() const creditManagerQueryClient = await getCreditManagerQueryClient(chainConfig)
const accountPosition: Positions = await cacheFn( const accountPosition: Positions = await cacheFn(
() => creditManagerQueryClient.positions({ accountId: accountId }), () => creditManagerQueryClient.positions({ accountId: accountId }),
@ -17,7 +20,7 @@ export default async function getAccount(accountId?: string): Promise<Account> {
const accountKind = await creditManagerQueryClient.accountKind({ accountId: accountId }) const accountKind = await creditManagerQueryClient.accountKind({ accountId: accountId })
const depositedVaults = await getDepositedVaults(accountId, accountPosition) const depositedVaults = await getDepositedVaults(accountId, chainConfig, accountPosition)
if (accountPosition) { if (accountPosition) {
return { return {

View File

@ -1,6 +1,5 @@
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'
@ -11,139 +10,166 @@ import { MarsParamsQueryClient } from 'types/generated/mars-params/MarsParams.cl
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: CosmWasmClient let _cosmWasmClient: Map<string, CosmWasmClient> = new Map()
let _accountNftQueryClient: MarsAccountNftQueryClient let _accountNftQueryClient: Map<string, MarsAccountNftQueryClient> = new Map()
let _creditManagerQueryClient: MarsCreditManagerQueryClient let _creditManagerQueryClient: Map<string, MarsCreditManagerQueryClient> = new Map()
let _oracleQueryClient: MarsOracleOsmosisQueryClient let _oracleQueryClient: Map<string, MarsOracleOsmosisQueryClient> = new Map()
let _redBankQueryClient: MarsRedBankQueryClient let _redBankQueryClient: Map<string, MarsRedBankQueryClient> = new Map()
let _paramsQueryClient: MarsParamsQueryClient let _paramsQueryClient: Map<string, MarsParamsQueryClient> = new Map()
let _incentivesQueryClient: MarsIncentivesQueryClient let _incentivesQueryClient: Map<string, MarsIncentivesQueryClient> = new Map()
let _swapperOsmosisClient: MarsSwapperOsmosisQueryClient let _swapperOsmosisClient: Map<string, MarsSwapperOsmosisQueryClient> = new Map()
let _ICNSQueryClient: ICNSQueryClient let _ICNSQueryClient: Map<string, ICNSQueryClient> = new Map()
const getClient = async () => { const getClient = async (rpc: string) => {
try { try {
if (!_cosmWasmClient) { if (!_cosmWasmClient.get(rpc)) {
_cosmWasmClient = await CosmWasmClient.connect(ENV.URL_RPC) const client = await CosmWasmClient.connect(rpc)
_cosmWasmClient.set(rpc, client)
} }
return _cosmWasmClient return _cosmWasmClient.get(rpc)!
} catch (error) { } catch (error) {
throw error throw error
} }
} }
const getAccountNftQueryClient = async () => { const getAccountNftQueryClient = async (chainConfig: ChainConfig) => {
try { try {
if (!_accountNftQueryClient) { const contract = chainConfig.contracts.accountNft
const client = await getClient() const rpc = chainConfig.endpoints.rpc
_accountNftQueryClient = new MarsAccountNftQueryClient(client, ENV.ADDRESS_ACCOUNT_NFT) const key = rpc + contract
if (!_accountNftQueryClient.get(key)) {
const client = await getClient(rpc)
_accountNftQueryClient.set(key, new MarsAccountNftQueryClient(client, contract))
} }
return _accountNftQueryClient return _accountNftQueryClient.get(key)!
} catch (error) { } catch (error) {
throw error throw error
} }
} }
const getCreditManagerQueryClient = async () => { const getCreditManagerQueryClient = async (chainConfig: ChainConfig) => {
try { try {
if (!_creditManagerQueryClient) { const contract = chainConfig.contracts.creditManager
const client = await getClient() const rpc = chainConfig.endpoints.rpc
_creditManagerQueryClient = new MarsCreditManagerQueryClient( const key = rpc + contract
client,
ENV.ADDRESS_CREDIT_MANAGER, if (!_creditManagerQueryClient.get(key)) {
) const client = await getClient(rpc)
_creditManagerQueryClient.set(key, new MarsCreditManagerQueryClient(client, contract))
} }
return _creditManagerQueryClient return _creditManagerQueryClient.get(key)!
} catch (error) { } catch (error) {
throw error throw error
} }
} }
const getParamsQueryClient = async () => { const getParamsQueryClient = async (chainConfig: ChainConfig) => {
try { try {
if (!_paramsQueryClient) { const contract = chainConfig.contracts.params
const client = await getClient() const rpc = chainConfig.endpoints.rpc
_paramsQueryClient = new MarsParamsQueryClient(client, ENV.ADDRESS_PARAMS) const key = rpc + contract
if (!_paramsQueryClient.get(key)) {
const client = await getClient(rpc)
_paramsQueryClient.set(key, new MarsParamsQueryClient(client, contract))
} }
return _paramsQueryClient return _paramsQueryClient.get(key)!
} catch (error) { } catch (error) {
throw error throw error
} }
} }
const getOracleQueryClient = async () => { const getOracleQueryClient = async (chainConfig: ChainConfig) => {
try { try {
if (!_oracleQueryClient) { const contract = chainConfig.contracts.oracle
const client = await getClient() const rpc = chainConfig.endpoints.rpc
_oracleQueryClient = new MarsOracleOsmosisQueryClient(client, ENV.ADDRESS_ORACLE) const key = rpc + contract
if (!_oracleQueryClient.get(key)) {
const client = await getClient(rpc)
_oracleQueryClient.set(key, new MarsOracleOsmosisQueryClient(client, contract))
} }
return _oracleQueryClient return _oracleQueryClient.get(key)!
} catch (error) { } catch (error) {
throw error throw error
} }
} }
const getRedBankQueryClient = async () => { const getRedBankQueryClient = async (chainConfig: ChainConfig) => {
try { try {
if (!_redBankQueryClient) { const contract = chainConfig.contracts.redBank
const client = await getClient() const rpc = chainConfig.endpoints.rpc
_redBankQueryClient = new MarsRedBankQueryClient(client, ENV.ADDRESS_RED_BANK) const key = rpc + contract
if (!_redBankQueryClient.get(key)) {
const client = await getClient(rpc)
_redBankQueryClient.set(key, new MarsRedBankQueryClient(client, contract))
} }
return _redBankQueryClient return _redBankQueryClient.get(key)!
} catch (error) { } catch (error) {
throw error throw error
} }
} }
const getVaultQueryClient = async (address: string) => { const getVaultQueryClient = async (chainConfig: ChainConfig, address: string) => {
try { try {
const client = await getClient() const client = await getClient(chainConfig.endpoints.rpc)
return new MarsMockVaultQueryClient(client, address) return new MarsMockVaultQueryClient(client, address)
} catch (error) { } catch (error) {
throw error throw error
} }
} }
const getIncentivesQueryClient = async () => { const getIncentivesQueryClient = async (chainConfig: ChainConfig) => {
try { try {
if (!_incentivesQueryClient) { const contract = chainConfig.contracts.incentives
const client = await getClient() const rpc = chainConfig.endpoints.rpc
_incentivesQueryClient = new MarsIncentivesQueryClient(client, ENV.ADDRESS_INCENTIVES) const key = rpc + contract
if (!_incentivesQueryClient.get(key)) {
const client = await getClient(rpc)
_incentivesQueryClient.set(key, new MarsIncentivesQueryClient(client, contract))
} }
return _incentivesQueryClient return _incentivesQueryClient.get(key)!
} catch (error) { } catch (error) {
throw error throw error
} }
} }
const getSwapperQueryClient = async () => { const getSwapperQueryClient = async (chainConfig: ChainConfig) => {
try { try {
if (!_swapperOsmosisClient) { const contract = chainConfig.contracts.swapper
const client = await getClient() const rpc = chainConfig.endpoints.rpc
_swapperOsmosisClient = new MarsSwapperOsmosisQueryClient(client, ENV.ADDRESS_SWAPPER) const key = rpc + contract
if (!_swapperOsmosisClient.get(key)) {
const client = await getClient(rpc)
_swapperOsmosisClient.set(key, new MarsSwapperOsmosisQueryClient(client, contract))
} }
return _swapperOsmosisClient return _swapperOsmosisClient.get(key)!
} catch (error) { } catch (error) {
throw error throw error
} }
} }
const getICNSQueryClient = async () => { const getICNSQueryClient = async (chainConfig: ChainConfig) => {
try { try {
if (!_ICNSQueryClient) { const contract = chainConfig.contracts.params
const client = await getClient() const rpc = chainConfig.endpoints.rpc
_ICNSQueryClient = new ICNSQueryClient(client) const key = rpc + contract
if (!_ICNSQueryClient.get(key)) {
const client = await getClient(rpc)
_ICNSQueryClient.set(key, new ICNSQueryClient(client))
} }
return _ICNSQueryClient return _ICNSQueryClient.get(key)!
} catch (error) { } catch (error) {
throw error throw error
} }

View File

@ -1,9 +1,8 @@
import { cacheFn, stakingAprCache } from 'api/cache' import { cacheFn, stakingAprCache } from 'api/cache'
import { ENV } from 'constants/env'
export default async function getStakingAprs() { export default async function getStakingAprs(url: string) {
try { try {
return cacheFn(() => fetchAprs(), stakingAprCache, `stakingAprs`) return cacheFn(() => fetchAprs(url), stakingAprCache, `stakingAprs`)
} catch (error) { } catch (error) {
throw error throw error
} }
@ -13,9 +12,7 @@ interface StakingAprResponse {
stats: StakingApr[] stats: StakingApr[]
} }
async function fetchAprs(): Promise<StakingApr[]> { async function fetchAprs(url: string): 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

View File

@ -4,12 +4,13 @@ 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', address) const accounts = await getAccounts('high_levered_strategy', chainConfig, address)
const activeAccounts = accounts.filter((account) => !isAccountEmpty(account)) const activeAccounts = accounts.filter((account) => !isAccountEmpty(account))
const hlsStrategies = await getHLSStakingAssets() const hlsStrategies = await getHLSStakingAssets(chainConfig)
const prices = await getPrices() const prices = await getPrices(chainConfig)
const hlsAccountsWithStrategy: HLSAccountWithStrategy[] = [] const hlsAccountsWithStrategy: HLSAccountWithStrategy[] = []
activeAccounts.forEach((account) => { activeAccounts.forEach((account) => {
@ -21,7 +22,11 @@ export default async function getHLSStakingAccounts(
if (!strategy) return if (!strategy) return
const [deposits, lends, debts, vaults] = getAccountPositionValues(account, prices) const [deposits, lends, debts, vaults] = getAccountPositionValues(
account,
prices,
chainConfig.assets,
)
hlsAccountsWithStrategy.push({ hlsAccountsWithStrategy.push({
...account, ...account,
@ -31,7 +36,7 @@ export default async function getHLSStakingAccounts(
debt: debts, debt: debts,
total: deposits, total: deposits,
}, },
leverage: calculateAccountLeverage(account, prices).toNumber(), leverage: calculateAccountLeverage(account, prices, chainConfig.assets).toNumber(),
}) })
}) })

View File

@ -1,28 +1,29 @@
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 { getAssetByDenom, getStakingAssets } from 'utils/assets' import { byDenom } from 'utils/array'
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() { export default async function getHLSStakingAssets(chainConfig: ChainConfig) {
const stakingAssetDenoms = getStakingAssets().map((asset) => asset.denom) const stakingAssetDenoms = chainConfig.assets
const assetParams = await getAssetParams() .filter((asset) => asset.isStaking)
.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() const aprs = await getStakingAprs(chainConfig.endpoints.aprs.stride)
return Promise.all(depositCaps$).then((depositCaps) => { return Promise.all(depositCaps$).then((depositCaps) => {
return depositCaps.map((depositCap, index) => { return depositCaps.map((depositCap, index) => {
const borrowSymbol = getAssetByDenom(strategies[index].denoms.borrow)?.symbol const borrowSymbol = chainConfig.assets.find(byDenom(strategies[index].denoms.borrow))?.symbol
return { return {
...strategies[index], ...strategies[index],
depositCap: { depositCap: {

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() { export default async function getHLSVaults(chainConfig: ChainConfig) {
const assetParams = await getAssetParams() const assetParams = await getAssetParams(chainConfig)
const client = await getCreditManagerQueryClient() const client = await getCreditManagerQueryClient(chainConfig)
const vaultConfigs = await getVaultConfigs() const vaultConfigs = await getVaultConfigs(chainConfig)
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)

View File

@ -2,28 +2,28 @@ import getTotalActiveEmissionValue from 'api/incentives/getTotalActiveEmissionVa
import getMarket from 'api/markets/getMarket' import getMarket from 'api/markets/getMarket'
import getUnderlyingLiquidityAmount from 'api/markets/getMarketUnderlyingLiquidityAmount' import getUnderlyingLiquidityAmount from 'api/markets/getMarketUnderlyingLiquidityAmount'
import getPrice from 'api/prices/getPrice' import getPrice from 'api/prices/getPrice'
import { ASSETS } from 'constants/assets'
import { byDenom } from 'utils/array' import { byDenom } from 'utils/array'
import { SECONDS_IN_A_YEAR } from 'utils/constants' import { SECONDS_IN_A_YEAR } from 'utils/constants'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
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(denom), getTotalActiveEmissionValue(chainConfig, denom),
getMarket(denom), getMarket(chainConfig, denom),
]) ])
if (!totalActiveEmissionValue) return null if (!totalActiveEmissionValue) return null
const [marketLiquidityAmount, assetPrice] = await Promise.all([ const [marketLiquidityAmount, assetPrice] = await Promise.all([
getUnderlyingLiquidityAmount(market), getUnderlyingLiquidityAmount(chainConfig, market),
getPrice(denom), getPrice(chainConfig, denom),
]) ])
const assetDecimals = (ASSETS.find(byDenom(denom)) as Asset).decimals const assetDecimals = (chainConfig.assets.find(byDenom(denom)) as Asset).decimals
const marketLiquidityValue = BN(marketLiquidityAmount) const marketLiquidityValue = BN(marketLiquidityAmount)
.shiftedBy(-assetDecimals) .shiftedBy(-assetDecimals)

View File

@ -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() const client = await getIncentivesQueryClient(chainConfig)
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(activeEmission.denom)), activeEmissions.map((activeEmission) => getPrice(chainConfig, activeEmission.denom)),
) )
return activeEmissions.reduce((accumulation, current, index) => { return activeEmissions.reduce((accumulation, current, index) => {
const price = prices[index] const price = prices[index]
const decimals = ASSETS.find(byDenom(current.denom))?.decimals as number const decimals = chainConfig.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)

View File

@ -1,17 +1,19 @@
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(accountId: string): Promise<BNCoin[]> { export default async function getUnclaimedRewards(
chainConfig: ChainConfig,
accountId: string,
): Promise<BNCoin[]> {
try { try {
const client = await getIncentivesQueryClient() const client = await getIncentivesQueryClient(chainConfig)
const unclaimedRewards = await cacheFn( const unclaimedRewards = await cacheFn(
() => () =>
iterateContractQuery(() => iterateContractQuery(() =>
client.userUnclaimedRewards({ client.userUnclaimedRewards({
user: ENV.ADDRESS_CREDIT_MANAGER, user: chainConfig.contracts.creditManager,
accountId, accountId,
}), }),
), ),

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(denom: string): Promise<Market> { export default async function getMarket(chainConfig: ChainConfig, denom: string): Promise<Market> {
return cacheFn(() => fetchMarket(denom), marketCache, denom, 60) return cacheFn(() => fetchMarket(chainConfig, denom), marketCache, denom, 60)
} }
async function fetchMarket(denom: string) { async function fetchMarket(chainConfig: ChainConfig, denom: string) {
try { try {
const redBankClient = await getRedBankQueryClient() const redBankClient = await getRedBankQueryClient(chainConfig)
const paramsClient = await getParamsQueryClient() const paramsClient = await getParamsQueryClient(chainConfig)
const [market, assetParams, assetCap] = await Promise.all([ const [market, assetParams, assetCap] = await Promise.all([
redBankClient.market({ denom }), redBankClient.market({ denom }),

View File

@ -1,19 +1,21 @@
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(): Promise<BorrowAsset[]> { export default async function getMarketBorrowings(
const liquidities = await getMarketLiquidities() chainConfig: ChainConfig,
const enabledAssets = getEnabledMarketAssets() ): Promise<BorrowAsset[]> {
const borrowEnabledMarkets = (await getMarkets()).filter((market: Market) => market.borrowEnabled) const liquidities = await getMarketLiquidities(chainConfig)
const prices = await getPrices() const borrowEnabledMarkets = (await getMarkets(chainConfig)).filter(
(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 = enabledAssets.find((asset) => asset.denom === market.denom)! const asset = chainConfig.assets.find((asset) => asset.denom === market.denom)!
return { return {
...asset, ...asset,

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(): Promise<BNCoin[]> { export default async function getMarketDebts(chainConfig: ChainConfig): Promise<BNCoin[]> {
try { try {
const markets: Market[] = await getMarkets() const markets: Market[] = await getMarkets(chainConfig)
const redBankQueryClient = await getRedBankQueryClient() const redBankQueryClient = await getRedBankQueryClient(chainConfig)
const debtQueries = markets.map((asset) => const debtQueries = markets.map((asset) =>
cacheFn( cacheFn(

View File

@ -2,10 +2,12 @@ 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(): Promise<BNCoin[]> { export default async function getMarketDeposits(chainConfig: ChainConfig): Promise<BNCoin[]> {
try { try {
const markets: Market[] = await getMarkets() const markets: Market[] = await getMarkets(chainConfig)
const depositQueries = markets.map(getUnderlyingLiquidityAmount) const depositQueries = markets.map((market) =>
getUnderlyingLiquidityAmount(chainConfig, market),
)
const depositsResults = await Promise.all(depositQueries) const depositsResults = await Promise.all(depositQueries)
return depositsResults.map<BNCoin>( return depositsResults.map<BNCoin>(

View File

@ -1,11 +1,10 @@
import { BN } from 'utils/helpers'
import getMarketDeposits from 'api/markets/getMarketDeposits'
import getMarketDebts from 'api/markets/getMarketDebts' import getMarketDebts from 'api/markets/getMarketDebts'
import getMarketDeposits from 'api/markets/getMarketDeposits'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
export default async function getMarketLiquidities(): Promise<BNCoin[]> { export default async function getMarketLiquidities(chainConfig: ChainConfig): Promise<BNCoin[]> {
const deposits = await getMarketDeposits() const deposits = await getMarketDeposits(chainConfig)
const debts = await getMarketDebts() const debts = await getMarketDebts(chainConfig)
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)

View File

@ -1,18 +1,21 @@
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(market: Market): Promise<string> { export default async function getUnderlyingLiquidityAmount(
chainConfig: ChainConfig,
market: Market,
): Promise<string> {
return cacheFn( return cacheFn(
() => fetchUnderlyingLiquidityAmount(market), () => fetchUnderlyingLiquidityAmount(chainConfig, market),
underlyingLiquidityAmountCache, underlyingLiquidityAmountCache,
`underlyingLiquidity/${market.denom}/amount/${market.collateralTotalScaled}`, `underlyingLiquidity/${market.denom}/amount/${market.collateralTotalScaled}`,
60, 60,
) )
} }
async function fetchUnderlyingLiquidityAmount(market: Market) { async function fetchUnderlyingLiquidityAmount(chainConfig: ChainConfig, market: Market) {
try { try {
const client = await getRedBankQueryClient() const client = await getRedBankQueryClient(chainConfig)
return await client.underlyingLiquidityAmount({ return await client.underlyingLiquidityAmount({
denom: market.denom, denom: market.denom,
amountScaled: market.collateralTotalScaled, amountScaled: market.collateralTotalScaled,

View File

@ -6,36 +6,46 @@ 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(): Promise<Market[]> { export default async function getMarkets(chainConfig: ChainConfig): Promise<Market[]> {
try { try {
const redBankClient = await getRedBankQueryClient() const redBankClient = await getRedBankQueryClient(chainConfig)
const paramsClient = await getParamsQueryClient() const paramsClient = await getParamsQueryClient(chainConfig)
const enabledAssets = getEnabledMarketAssets() const marketAssets = chainConfig.assets.filter((asset) => asset.isMarket)
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(
() => paramsClient.totalDeposit({ denom: asset.denom }), () => iterateContractQuery(redBankClient.markets),
totalDepositCache, marketsCache,
`enabledMarkets/${asset.denom}`, `chains/${chainConfig.id}/markets`,
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,
'params', `chains/${chainConfig.id}/params`,
60, 60,
), ),
Promise.all(capQueries), Promise.all(capQueries),
]) ])
return enabledAssets.map((asset) => return marketAssets.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,
@ -43,6 +53,7 @@ export default async function getMarkets(): Promise<Market[]> {
), ),
) )
} catch (ex) { } catch (ex) {
console.log(ex)
throw ex throw ex
} }
} }

View File

@ -3,11 +3,13 @@ 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(): Promise<AssetParamsBaseForAddr[]> { export default async function getAssetParams(
chainConfig: ChainConfig,
): Promise<AssetParamsBaseForAddr[]> {
try { try {
return await cacheFn( return await cacheFn(
async () => { async () => {
const paramsQueryClient = await getParamsQueryClient() const paramsQueryClient = await getParamsQueryClient(chainConfig)
return iterateContractQuery(paramsQueryClient.allAssetParams) return iterateContractQuery(paramsQueryClient.allAssetParams)
}, },
assetParamsCache, assetParamsCache,

View File

@ -1,11 +1,10 @@
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() { async function getMarsPrice(chainConfig: ChainConfig) {
const marsAsset = ASSETS.find(bySymbol('MARS')) const marsAsset = chainConfig.assets.find(bySymbol('MARS'))
if (!marsAsset) return 0 if (!marsAsset) return 0
return await getPoolPrice(marsAsset) return await getPoolPrice(chainConfig, marsAsset)
} }
export default getMarsPrice export default getMarsPrice

View File

@ -7,11 +7,14 @@ 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(...assets: Asset[]): Promise<BNCoin[]> { export default async function getOraclePrices(
chainConfig: ChainConfig,
assets: Asset[],
): Promise<BNCoin[]> {
try { try {
if (!assets.length) return [] if (!assets.length) return []
const oracleQueryClient = await getOracleQueryClient() const oracleQueryClient = await getOracleQueryClient(chainConfig)
const priceResults = await cacheFn( const priceResults = await cacheFn(
() => iterateContractQuery(oracleQueryClient.prices), () => iterateContractQuery(oracleQueryClient.prices),
oraclePriceCache, oraclePriceCache,

View File

@ -1,6 +1,5 @@
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'
@ -17,24 +16,25 @@ 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(asset) const [assetRate, baseAsset] = await getAssetRate(chainConfig, asset)
const baseAssetPrice = const baseAssetPrice =
(lookupPricesForBaseAsset && (lookupPricesForBaseAsset &&
lookupPricesForBaseAsset.find(byDenom(baseAsset.token.denom))?.amount) || lookupPricesForBaseAsset.find(byDenom(baseAsset.token.denom))?.amount) ||
(await getPrice(baseAsset.token.denom)) (await getPrice(chainConfig, 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 (asset: Asset) => { const getAssetRate = async (chainConfig: ChainConfig, asset: Asset) => {
const url = `${ENV.URL_REST}osmosis/gamm/v1beta1/pools/${asset.poolId}` const url = chainConfig.endpoints.pools.replace('POOL_ID', asset.poolId!.toString())
const response = await cacheFn( const response = await cacheFn(
() => fetch(url).then((res) => res.json()), () => fetch(url).then((res) => res.json()),
poolPriceCache, poolPriceCache,

View File

@ -2,25 +2,27 @@ import { cacheFn, priceCache } from 'api/cache'
import { getOracleQueryClient } from 'api/cosmwasm-client' import { getOracleQueryClient } from 'api/cosmwasm-client'
import getPoolPrice from 'api/prices/getPoolPrice' import getPoolPrice from 'api/prices/getPoolPrice'
import getPythPrice from 'api/prices/getPythPrices' import getPythPrice from 'api/prices/getPythPrices'
import { ASSETS } from 'constants/assets'
import { PRICE_ORACLE_DECIMALS } from 'constants/query' import { PRICE_ORACLE_DECIMALS } from 'constants/query'
import { byDenom } from 'utils/array' import { byDenom } from 'utils/array'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
export default async function getPrice(denom: string): Promise<BigNumber> { export default async function getPrice(
return cacheFn(() => fetchPrice(denom), priceCache, `price/${denom}`, 60) chainConfig: ChainConfig,
denom: string,
): Promise<BigNumber> {
return cacheFn(() => fetchPrice(chainConfig, denom), priceCache, `price/${denom}`, 60)
} }
async function fetchPrice(denom: string) { async function fetchPrice(chainConfig: ChainConfig, denom: string) {
try { try {
const asset = ASSETS.find(byDenom(denom)) as Asset const asset = chainConfig.assets.find(byDenom(denom)) as Asset
if (asset.pythPriceFeedId) { if (asset.pythPriceFeedId) {
return (await getPythPrice(asset.pythPriceFeedId))[0] return (await getPythPrice(chainConfig, [asset.pythPriceFeedId]))[0]
} }
if (asset.hasOraclePrice) { if (asset.hasOraclePrice) {
const oracleQueryClient = await getOracleQueryClient() const oracleQueryClient = await getOracleQueryClient(chainConfig)
const priceResponse = await oracleQueryClient.price({ denom: asset.denom }) const priceResponse = await oracleQueryClient.price({ denom: asset.denom })
const decimalDiff = asset.decimals - PRICE_ORACLE_DECIMALS const decimalDiff = asset.decimals - PRICE_ORACLE_DECIMALS
@ -28,7 +30,7 @@ async function fetchPrice(denom: string) {
} }
if (asset.poolId) { if (asset.poolId) {
return await getPoolPrice(asset) return await getPoolPrice(chainConfig, asset)
} }
throw `could not fetch the price info for the given denom: ${denom}` throw `could not fetch the price info for the given denom: ${denom}`

View File

@ -1,20 +1,21 @@
import fetchPythPriceData from 'api/prices/getPythPriceData' import fetchPythPriceData from 'api/prices/getPythPriceData'
import { getPythAssets } from 'utils/assets'
export default async function getPricesData(): Promise<string[]> { export default async function getPricesData(
chainConfig: ChainConfig,
assets: Asset[],
): Promise<string[]> {
try { try {
const assetsWithPythPriceFeedId = getPythAssets() const assetsWithPythPriceFeedId = assets.filter((asset) => !!asset.pythPriceFeedId)
const pythAndOraclePriceData = await requestPythPriceData(assetsWithPythPriceFeedId) return await requestPythPriceData(chainConfig, assetsWithPythPriceFeedId)
return pythAndOraclePriceData
} catch (ex) { } catch (ex) {
console.error(ex) console.error(ex)
throw ex throw ex
} }
} }
async function requestPythPriceData(assets: Asset[]): Promise<string[]> { async function requestPythPriceData(chainConfig: ChainConfig, assets: Asset[]): Promise<string[]> {
if (!assets.length) return [] if (!assets.length) return []
const priceFeedIds = assets.map((a) => a.pythPriceFeedId) as string[] const priceFeedIds = assets.map((a) => a.pythPriceFeedId) as string[]
return await fetchPythPriceData(...priceFeedIds) return await fetchPythPriceData(chainConfig, priceFeedIds)
} }

View File

@ -4,22 +4,25 @@ import fetchPythPrices from 'api/prices/getPythPrices'
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 { getAssetsMustHavePriceInfo } from 'utils/assets'
export default async function getPrices(): Promise<BNCoin[]> { export default async function getPrices(chainConfig: ChainConfig): Promise<BNCoin[]> {
const usdPrice = new BNCoin({ denom: 'usd', amount: '1' }) const usdPrice = new BNCoin({ denom: 'usd', amount: '1' })
try { try {
const assetsToFetchPrices = getAssetsMustHavePriceInfo() const assetsToFetchPrices = useStore
.getState()
.chainConfig.assets.filter(
(asset) => (asset.isEnabled && asset.isMarket) || asset.forceFetchPrice,
)
const [assetsWithPythPriceFeedId, assetsWithOraclePrices, assetsWithPoolIds] = const [assetsWithPythPriceFeedId, assetsWithOraclePrices, assetsWithPoolIds] =
separateAssetsByPriceSources(assetsToFetchPrices) separateAssetsByPriceSources(assetsToFetchPrices)
const pythAndOraclePrices = ( const pythAndOraclePrices = (
await Promise.all([ await Promise.all([
requestPythPrices(assetsWithPythPriceFeedId), requestPythPrices(chainConfig, assetsWithPythPriceFeedId),
getOraclePrices(...assetsWithOraclePrices), getOraclePrices(chainConfig, assetsWithOraclePrices),
]) ])
).flat() ).flat()
const poolPrices = await requestPoolPrices(assetsWithPoolIds, pythAndOraclePrices) const poolPrices = await requestPoolPrices(chainConfig, assetsWithPoolIds, pythAndOraclePrices)
useStore.setState({ isOracleStale: false }) useStore.setState({ isOracleStale: false })
@ -35,15 +38,19 @@ export default async function getPrices(): Promise<BNCoin[]> {
} }
} }
async function requestPythPrices(assets: Asset[]): Promise<BNCoin[]> { async function requestPythPrices(chainConfig: ChainConfig, assets: Asset[]): Promise<BNCoin[]> {
if (!assets.length) return [] if (!assets.length) return []
const priceFeedIds = assets.map((a) => a.pythPriceFeedId) as string[] const priceFeedIds = assets.map((a) => a.pythPriceFeedId) as string[]
return await fetchPythPrices(...priceFeedIds).then(mapResponseToBnCoin(assets)) return await fetchPythPrices(chainConfig, priceFeedIds).then(mapResponseToBnCoin(assets))
} }
async function requestPoolPrices(assets: Asset[], lookupPrices: BNCoin[]): Promise<BNCoin[]> { async function requestPoolPrices(
const requests = assets.map((asset) => getPoolPrice(asset, lookupPrices)) chainConfig: ChainConfig,
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))
} }

View File

@ -1,9 +1,8 @@
import { cacheFn, pythPriceCache } from 'api/cache' import { cacheFn, pythPriceCache } from 'api/cache'
import { ENV } from 'constants/env'
export default async function fetchPythPriceData(...priceFeedIds: string[]) { export default async function fetchPythPriceData(chainConfig: ChainConfig, priceFeedIds: string[]) {
try { try {
const pricesUrl = new URL(`${ENV.PYTH_ENDPOINT}/latest_vaas`) const pricesUrl = new URL(`${chainConfig.endpoints.pyth}/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(

View File

@ -1,10 +1,9 @@
import { cacheFn, pythPriceCache } from 'api/cache' import { cacheFn, pythPriceCache } from 'api/cache'
import { ENV } from 'constants/env'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
export default async function fetchPythPrices(...priceFeedIds: string[]) { export default async function fetchPythPrices(chainConfig: ChainConfig, priceFeedIds: string[]) {
try { try {
const pricesUrl = new URL(`${ENV.PYTH_ENDPOINT}/latest_price_feeds`) const pricesUrl = new URL(`${chainConfig.endpoints.pyth}/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(

View File

@ -2,9 +2,13 @@ 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(coinIn: Coin, denomOut: string) { export default async function estimateExactIn(
chainConfig: ChainConfig,
coinIn: Coin,
denomOut: string,
) {
try { try {
const swapperClient = await getSwapperQueryClient() const swapperClient = await getSwapperQueryClient(chainConfig)
const estimatedAmount = (await swapperClient.estimateExactInSwap({ coinIn, denomOut })).amount const estimatedAmount = (await swapperClient.estimateExactInSwap({ coinIn, denomOut })).amount
return BN(estimatedAmount) return BN(estimatedAmount)

View File

@ -1,8 +1,10 @@
import { ENV } from 'constants/env' export default async function getPools(
chainConfig: ChainConfig,
const url = `${ENV.URL_REST}osmosis/gamm/v1beta1/pools/` poolIds: string[],
export default async function getPools(poolIds: string[]): Promise<Pool[]> { ): Promise<Pool[]> {
const promises = poolIds.map((poolId) => fetch(url + poolId)) const promises = poolIds.map((poolId) =>
fetch(chainConfig.endpoints.pools.replace('POOL_ID', poolId)),
)
const responses = await Promise.all(promises) const responses = await Promise.all(promises)

View File

@ -1,8 +1,12 @@
import { getSwapperQueryClient } from 'api/cosmwasm-client' import { getSwapperQueryClient } from 'api/cosmwasm-client'
export default async function getSwapRoute(denomIn: string, denomOut: string): Promise<Route[]> { export default async function getSwapRoute(
chainConfig: ChainConfig,
denomIn: string,
denomOut: string,
): Promise<Route[]> {
try { try {
const swapperClient = await getSwapperQueryClient() const swapperClient = await getSwapperQueryClient(chainConfig)
const routes = await swapperClient.route({ const routes = await swapperClient.route({
denomIn, denomIn,
denomOut, denomOut,

View File

@ -21,9 +21,13 @@ import {
import { getCoinValue } from 'utils/formatters' import { getCoinValue } from 'utils/formatters'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
async function getUnlocksAtTimestamp(unlockingId: number, vaultAddress: string) { async function getUnlocksAtTimestamp(
chainConfig: ChainConfig,
unlockingId: number,
vaultAddress: string,
) {
try { try {
const client = await getClient() const client = await getClient(chainConfig.endpoints.rpc)
const vaultExtension = (await cacheFn( const vaultExtension = (await cacheFn(
() => () =>
@ -42,6 +46,7 @@ async function getUnlocksAtTimestamp(unlockingId: number, vaultAddress: string)
} }
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
@ -50,7 +55,11 @@ 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(unlockId, vaultPosition.vault.address) const unlocksAtTimestamp = await getUnlocksAtTimestamp(
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]
@ -83,12 +92,13 @@ export function flatVaultPositionAmount(
} }
export async function getLpTokensForVaultPosition( export async function getLpTokensForVaultPosition(
chainConfig: ChainConfig,
vault: Vault, vault: Vault,
vaultPosition: VaultPosition, vaultPosition: VaultPosition,
): Promise<Coin[]> { ): Promise<Coin[]> {
try { try {
const vaultQueryClient = await getVaultQueryClient(vault.address) const vaultQueryClient = await getVaultQueryClient(chainConfig, vault.address)
const creditManagerQueryClient = await getCreditManagerQueryClient() const creditManagerQueryClient = await getCreditManagerQueryClient(chainConfig)
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()
@ -133,15 +143,16 @@ 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(vault.denoms.primary), getPrice(chainConfig, vault.denoms.primary),
getPrice(vault.denoms.secondary), getPrice(chainConfig, vault.denoms.secondary),
getPrice(vault.denoms.lp), getPrice(chainConfig, vault.denoms.lp),
]) ])
const lpTokensQuery = getLpTokensForVaultPosition(vault, vaultPosition) const lpTokensQuery = getLpTokensForVaultPosition(chainConfig, vault, vaultPosition)
const amounts = flatVaultPositionAmount(vaultPosition.amount) const amounts = flatVaultPositionAmount(vaultPosition.amount)
const [[primaryLpToken, secondaryLpToken], [primaryPrice, secondaryPrice, lpPrice]] = const [[primaryLpToken, secondaryLpToken], [primaryPrice, secondaryPrice, lpPrice]] =
@ -154,18 +165,26 @@ async function getVaultValuesAndAmounts(
secondary: BN(secondaryLpToken.amount), secondary: BN(secondaryLpToken.amount),
}, },
values: { values: {
primary: getCoinValue(new BNCoin(primaryLpToken), [ primary: getCoinValue(
BNCoin.fromDenomAndBigNumber(primaryLpToken.denom, primaryPrice), new BNCoin(primaryLpToken),
]), [BNCoin.fromDenomAndBigNumber(primaryLpToken.denom, primaryPrice)],
secondary: getCoinValue(new BNCoin(secondaryLpToken), [ chainConfig.assets,
BNCoin.fromDenomAndBigNumber(secondaryLpToken.denom, secondaryPrice), ),
]), secondary: getCoinValue(
unlocking: getCoinValue(BNCoin.fromDenomAndBigNumber(vault.denoms.lp, amounts.unlocking), [ new BNCoin(secondaryLpToken),
BNCoin.fromDenomAndBigNumber(vault.denoms.lp, lpPrice), [BNCoin.fromDenomAndBigNumber(secondaryLpToken.denom, secondaryPrice)],
]), chainConfig.assets,
unlocked: getCoinValue(BNCoin.fromDenomAndBigNumber(vault.denoms.lp, amounts.unlocked), [ ),
BNCoin.fromDenomAndBigNumber(vault.denoms.lp, lpPrice), unlocking: getCoinValue(
]), 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) {
@ -175,10 +194,11 @@ 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() const creditManagerQueryClient = await getCreditManagerQueryClient(chainConfig)
if (!positions) if (!positions)
positions = await cacheFn( positions = await cacheFn(
@ -189,7 +209,7 @@ async function getDepositedVaults(
if (!positions.vaults.length) return [] if (!positions.vaults.length) return []
const [allVaults] = await Promise.all([getVaults()]) const [allVaults] = await Promise.all([getVaults(chainConfig)])
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)
@ -199,8 +219,8 @@ async function getDepositedVaults(
} }
const [[status, unlockId, unlocksAt], valuesAndAmounts] = await Promise.all([ const [[status, unlockId, unlocksAt], valuesAndAmounts] = await Promise.all([
getVaultPositionStatusAndUnlockIdAndUnlockTime(vaultPosition), getVaultPositionStatusAndUnlockIdAndUnlockTime(chainConfig, vaultPosition),
getVaultValuesAndAmounts(vault, vaultPosition), getVaultValuesAndAmounts(vault, vaultPosition, chainConfig),
]) ])
return { return {

View File

@ -1,10 +1,9 @@
import { aprsCache, aprsCacheResponse, cacheFn } from 'api/cache' import { aprsCache, aprsCacheResponse, cacheFn } from 'api/cache'
import { ENV } from 'constants/env'
export default async function getAprs() { export default async function getAprs(chainConfig: ChainConfig) {
try { try {
const response = await cacheFn( const response = await cacheFn(
() => fetch(ENV.URL_VAULT_APR), () => fetch(chainConfig.endpoints.aprs.vaults),
aprsCacheResponse, aprsCacheResponse,
'aprsResponse', 'aprsResponse',
60, 60,

View File

@ -3,9 +3,11 @@ 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 (): Promise<VaultConfigBaseForAddr[]> => { export const getVaultConfigs = async (
chainConfig: ChainConfig,
): Promise<VaultConfigBaseForAddr[]> => {
try { try {
const paramsQueryClient = await getParamsQueryClient() const paramsQueryClient = await getParamsQueryClient(chainConfig)
return await cacheFn( return await cacheFn(
() => iterateContractQuery(paramsQueryClient.allVaultConfigs, 'addr'), () => iterateContractQuery(paramsQueryClient.allVaultConfigs, 'addr'),
vaultConfigsCache, vaultConfigsCache,

View File

@ -1,21 +0,0 @@
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
}
}

View File

@ -1,14 +1,13 @@
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[]> => {
if (!ENV.ADDRESS_PARAMS) return [] const creditManagerQueryClient = await getCreditManagerQueryClient(chainConfig)
const creditManagerQueryClient = await getCreditManagerQueryClient()
try { try {
const vaultUtilizations$ = vaultConfigs.map((vaultConfig) => { const vaultUtilizations$ = vaultConfigs.map((vaultConfig) => {
return cacheFn( return cacheFn(

View File

@ -2,20 +2,16 @@ 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(): Promise<Vault[]> { export default async function getVaults(chainConfig: ChainConfig): Promise<Vault[]> {
const assetParams = await getAssetParams() const assetParams = await getAssetParams(chainConfig)
const vaultConfigs = await getVaultConfigs() const vaultConfigs = await getVaultConfigs(chainConfig)
const $vaultUtilizations = getVaultUtilizations(vaultConfigs) const $vaultUtilizations = getVaultUtilizations(chainConfig, vaultConfigs)
const $aprs = getAprs() const $aprs = getAprs(chainConfig)
const vaultMetaDatas = const vaultMetaDatas = chainConfig.vaults
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)

View File

@ -2,12 +2,13 @@ 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() const client = await getCreditManagerQueryClient(chainConfig)
const lastItem = previousResults && previousResults.at(-1) const lastItem = previousResults && previousResults.at(-1)
const accounts = ( const accounts = (
@ -24,7 +25,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(address, accumulated) return await getAccountIds(chainConfig, address, accumulated)
} catch { } catch {
return new Promise((_, reject) => reject('No data')) return new Promise((_, reject) => reject('No data'))
} }

View File

@ -2,13 +2,17 @@ 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(kind: AccountKind, address?: string): Promise<Account[]> { export default async function getAccounts(
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(address) const accountIdsAndKinds = await getWalletAccountIds(chainConfig, address)
const $accounts = accountIdsAndKinds const $accounts = accountIdsAndKinds
.filter((a) => a.kind === kind) .filter((a) => a.kind === kind)
.map((account) => getAccount(account.id)) .map((account) => getAccount(chainConfig, account.id))
const accounts = await Promise.all($accounts).then((accounts) => accounts) const accounts = await Promise.all($accounts).then((accounts) => accounts)

View File

@ -1,11 +1,14 @@
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(address?: string): Promise<ICNSResult | undefined> { export default async function getICNS(
if (!address || ENV.CHAIN_ID !== ChainInfoID.Osmosis1) return chainConfig: ChainConfig,
address?: string,
): Promise<ICNSResult | undefined> {
// TODO: Make this also work for different chains?
if (!address || chainConfig.id !== ChainInfoID.Osmosis1) return
try { try {
const icnsQueryClient = await getICNSQueryClient() const icnsQueryClient = await getICNSQueryClient(chainConfig)
return icnsQueryClient.primaryName({ address }) return icnsQueryClient.primaryName({ address })
} catch (ex) { } catch (ex) {
throw ex throw ex

View File

@ -1,9 +1,10 @@
import { ENV } from 'constants/env' export default async function getWalletBalances(
chainConfig: ChainConfig,
export default async function getWalletBalances(address: string): Promise<Coin[]> { address: string,
): Promise<Coin[]> {
const uri = '/cosmos/bank/v1beta1/balances/' const uri = '/cosmos/bank/v1beta1/balances/'
const response = await fetch(`${ENV.URL_REST}${uri}${address}`) const response = await fetch(`${chainConfig.endpoints.rest}${uri}${address}`)
if (response.ok) { if (response.ok) {
const data = await response.json() const data = await response.json()

View File

@ -13,8 +13,8 @@ import Value, {
VALUE_META, VALUE_META,
valueSortingFn, valueSortingFn,
} from 'components/Account/AccountBalancesTable/Columns/Value' } from 'components/Account/AccountBalancesTable/Columns/Value'
import useMarketAssets from 'hooks/markets/useMarketAssets'
import useHealthComputer from 'hooks/useHealthComputer' import useHealthComputer from 'hooks/useHealthComputer'
import useMarketAssets from 'hooks/useMarketAssets'
import useStore from 'store' import useStore from 'store'
export default function useAccountBalancesColumns( export default function useAccountBalancesColumns(

View File

@ -6,6 +6,7 @@ export function getAssetAccountBalanceRow(
type: 'deposits' | 'borrowing' | 'lending', 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,
@ -17,7 +18,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).toString(), value: getCoinValue(BNCoin.fromDenomAndBigNumber(denom, amount), prices, assets).toString(),
denom, denom,
amount, amount,
apy, apy,

View File

@ -4,7 +4,7 @@ import {
getAssetAccountBalanceRow, getAssetAccountBalanceRow,
getVaultAccountBalanceRow, getVaultAccountBalanceRow,
} from 'components/Account/AccountBalancesTable/functions' } from 'components/Account/AccountBalancesTable/functions'
import { ASSETS } from 'constants/assets' import useAllAssets from 'hooks/assets/useAllAssets'
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'
@ -19,10 +19,9 @@ 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 ?? []
@ -32,24 +31,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(getAssetAccountBalanceRow('deposits', asset, prices, deposit, apy, prevDeposit)) deposits.push(
getAssetAccountBalanceRow('deposits', 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 apy =
lendingData.find((market) => market.asset.denom === lending.denom)?.apy.deposit ?? 0 lendingData.find((market) => market.asset.denom === lending.denom)?.apy.deposit ?? 0
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('lending', asset, prices, lending, apy, prevLending) return getAssetAccountBalanceRow('lending', asset, prices, assets, lending, apy, prevLending)
}) })
const vaults = accountVaults.map((vault) => { const vaults = accountVaults.map((vault) => {
@ -61,13 +62,22 @@ 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)?.apy.borrow ?? 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('borrowing', asset, prices, debt, apy, prevDebt) return getAssetAccountBalanceRow('borrowing', asset, prices, assets, 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,
])
} }

View File

@ -8,6 +8,7 @@ import { ArrowRight } from 'components/Icons'
import Text from 'components/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'
@ -40,6 +41,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(false)
const borrowAssetsData = useMemo(() => data?.allAssets || [], [data]) const borrowAssetsData = useMemo(() => data?.allAssets || [], [data])
const { availableAssets: lendingAvailableAssets, accountLentAssets } = const { availableAssets: lendingAvailableAssets, accountLentAssets } =
@ -50,15 +52,15 @@ export default function AccountComposition(props: Props) {
) )
const [depositsBalance, lendsBalance, debtsBalance, vaultsBalance] = useMemo( const [depositsBalance, lendsBalance, debtsBalance, vaultsBalance] = useMemo(
() => getAccountPositionValues(account, prices), () => getAccountPositionValues(account, prices, assets),
[account, prices], [account, assets, 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) ? getAccountPositionValues(updatedAccount, prices, assets)
: [BN_ZERO, BN_ZERO, BN_ZERO] : [BN_ZERO, BN_ZERO, BN_ZERO]
const updatedPositionValue = updatedDepositsBalance const updatedPositionValue = updatedDepositsBalance
@ -66,12 +68,15 @@ export default function AccountComposition(props: Props) {
.plus(updatedVaultsBalance) .plus(updatedVaultsBalance)
return [updatedPositionValue, updatedDebtsBalance] return [updatedPositionValue, updatedDebtsBalance]
}, [updatedAccount, prices]) }, [updatedAccount, prices, assets])
const netWorth = useMemo(() => calculateAccountBalanceValue(account, prices), [account, prices]) const netWorth = useMemo(
() => calculateAccountBalanceValue(account, prices, assets),
[account, assets, prices],
)
const updatedTotalBalance = useMemo( const updatedTotalBalance = useMemo(
() => (updatedAccount ? calculateAccountBalanceValue(updatedAccount, prices) : BN_ZERO), () => (updatedAccount ? calculateAccountBalanceValue(updatedAccount, prices, assets) : BN_ZERO),
[updatedAccount, prices], [updatedAccount, prices, assets],
) )
const apr = useMemo( const apr = useMemo(
@ -82,9 +87,10 @@ export default function AccountComposition(props: Props) {
lendingAssetsData, lendingAssetsData,
prices, prices,
hlsStrategies, hlsStrategies,
assets,
props.isHls, props.isHls,
), ),
[account, borrowAssetsData, hlsStrategies, lendingAssetsData, prices, props.isHls], [account, assets, borrowAssetsData, hlsStrategies, lendingAssetsData, prices, props.isHls],
) )
const updatedApr = useMemo( const updatedApr = useMemo(
() => () =>
@ -95,10 +101,19 @@ 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 (

View File

@ -17,15 +17,16 @@ 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 { ORACLE_DENOM } from 'constants/oracle' import { ORACLE_DENOM } from 'constants/oracle'
import useAccountIds from 'hooks/accounts/useAccountIds'
import useAccounts from 'hooks/accounts/useAccounts'
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 useCurrentAccount from 'hooks/useCurrentAccount' import useCurrentAccount from 'hooks/useCurrentAccount'
import useHealthComputer from 'hooks/useHealthComputer' import useHealthComputer from 'hooks/useHealthComputer'
import useHLSStakingAssets from 'hooks/useHLSStakingAssets' 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'
@ -72,19 +73,23 @@ 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), () => calculateAccountBalanceValue(updatedAccount ?? account, prices, assets),
[updatedAccount, account, prices], [updatedAccount, account, prices, assets],
) )
const coin = BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, accountBalanceValue) const coin = BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, accountBalanceValue)
const leverage = useMemo(() => calculateAccountLeverage(account, prices), [account, prices]) const leverage = useMemo(
() => 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) const updatedLeverage = calculateAccountLeverage(updatedAccount, prices, assets)
if (updatedLeverage.eq(leverage)) return null if (updatedLeverage.eq(leverage)) return null
return updatedLeverage return updatedLeverage
}, [updatedAccount, prices, leverage]) }, [updatedAccount, prices, leverage, assets])
const { data } = useBorrowMarketAssetsTableData(false) const { data } = useBorrowMarketAssetsTableData(false)
const borrowAssetsData = useMemo(() => data?.allAssets || [], [data]) const borrowAssetsData = useMemo(() => data?.allAssets || [], [data])
@ -104,9 +109,10 @@ function AccountDetails(props: Props) {
lendingAssetsData, lendingAssetsData,
prices, prices,
hlsStrategies, hlsStrategies,
assets,
account.kind === 'high_levered_strategy', account.kind === 'high_levered_strategy',
), ),
[account, borrowAssetsData, hlsStrategies, lendingAssetsData, prices, updatedAccount], [account, assets, borrowAssetsData, hlsStrategies, lendingAssetsData, prices, updatedAccount],
) )
const isFullWidth = location.pathname.includes('trade') || location.pathname === '/' const isFullWidth = location.pathname.includes('trade') || location.pathname === '/'

View File

@ -1,25 +1,23 @@
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/Button'
import DepositCapMessage from 'components/DepositCapMessage' import DepositCapMessage from 'components/DepositCapMessage'
import { ArrowRight, Plus } from 'components/Icons' import { ArrowRight, Plus } from 'components/Icons'
import SwitchAutoLend from 'components/Switch/SwitchAutoLend' import SwitchAutoLend from 'components/Switch/SwitchAutoLend'
import Text from 'components/Text' import Text from 'components/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 useLocalStorage from 'hooks/useLocalStorage' import useBaseAsset from 'hooks/assets/useBasetAsset'
import useMarketAssets from 'hooks/useMarketAssets' import useEnableAutoLendGlobal from 'hooks/localStorage/useEnableAutoLendGlobal'
import useMarketAssets from 'hooks/markets/useMarketAssets'
import useToggle from 'hooks/useToggle' 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'
@ -35,16 +33,13 @@ export default function AccountFundContent(props: Props) {
const deposit = useStore((s) => s.deposit) const deposit = useStore((s) => s.deposit)
const walletAssetModal = useStore((s) => s.walletAssetsModal) const walletAssetModal = useStore((s) => s.walletAssetsModal)
const [isConfirming, setIsConfirming] = useState(false) const [isConfirming, setIsConfirming] = useState(false)
const [lendAssets] = useLocalStorage<boolean>( const [enableAutoLendGlobal] = useEnableAutoLendGlobal()
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 [isLending, toggleIsLending] = useToggle(lendAssets) const [isLending, toggleIsLending] = useToggle(enableAutoLendGlobal)
const { simulateDeposits } = useUpdatedAccount(props.account) const { simulateDeposits } = useUpdatedAccount(props.account)
const baseAsset = getBaseAsset() const baseAsset = useBaseAsset()
const hasAssetSelected = fundingAssets.length > 0 const hasAssetSelected = fundingAssets.length > 0
const hasFundingAssets = const hasFundingAssets =
@ -150,26 +145,20 @@ 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={asset.symbol} key={coin.denom}
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',
)} )}
> >
<TokenInputWithSlider <AccountFundRow
asset={asset} denom={coin.denom}
onChange={(amount) => updateFundingAssets(amount, asset.denom)}
amount={coin.amount ?? BN_ZERO}
max={balance}
balances={balances} balances={balances}
maxText='Max' amount={coin.amount ?? BN_ZERO}
disabled={isConfirming} isConfirming={isConfirming}
warningMessages={[]} updateFundingAssets={updateFundingAssets}
/> />
</div> </div>
) )

View File

@ -4,8 +4,8 @@ import AccountFundContent from 'components/Account/AccountFund/AccountFundConten
import Card from 'components/Card' import Card from 'components/Card'
import { CircularProgress } from 'components/CircularProgress' import { CircularProgress } from 'components/CircularProgress'
import FullOverlayContent from 'components/FullOverlayContent' import FullOverlayContent from 'components/FullOverlayContent'
import useAccounts from 'hooks/accounts/useAccounts'
import useAccountId from 'hooks/useAccountId' import useAccountId from 'hooks/useAccountId'
import useAccounts from 'hooks/useAccounts'
import useCurrentAccount from 'hooks/useCurrentAccount' import useCurrentAccount from 'hooks/useCurrentAccount'
import useStore from 'store' import useStore from 'store'

View File

@ -0,0 +1,34 @@
import TokenInputWithSlider from 'components/TokenInput/TokenInputWithSlider'
import { BN_ZERO } from 'constants/math'
import useAsset from 'hooks/assets/useAsset'
import { BNCoin } from 'types/classes/BNCoin'
import { byDenom } from 'utils/array'
interface Props {
amount: BigNumber
balances: BNCoin[]
denom: string
isConfirming: boolean
updateFundingAssets: (amount: BigNumber, denom: string) => void
}
export default function AccountFundRow(props: Props) {
const asset = useAsset(props.denom)
if (!asset) return null
const balance = props.balances.find(byDenom(props.denom))?.amount ?? BN_ZERO
return (
<TokenInputWithSlider
asset={asset}
onChange={(amount) => props.updateFundingAssets(amount, asset.denom)}
amount={props.amount}
max={balance}
balances={props.balances}
maxText='Max'
disabled={props.isConfirming}
warningMessages={[]}
/>
)
}

View File

@ -5,7 +5,8 @@ import Skeleton from 'components/Account/AccountList/Skeleton'
import Button from 'components/Button' import Button from 'components/Button'
import { ArrowDownLine, ArrowUpLine, TrashBin } from 'components/Icons' import { ArrowDownLine, ArrowUpLine, TrashBin } from 'components/Icons'
import SwitchAutoLend from 'components/Switch/SwitchAutoLend' import SwitchAutoLend from 'components/Switch/SwitchAutoLend'
import useAccount from 'hooks/useAccount' import useAccount from 'hooks/accounts/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'
@ -22,13 +23,14 @@ 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)), () => (!account ? null : calculateAccountBalanceValue(account, prices, assets)),
[account, prices], [account, assets, prices],
) )
const { health, healthFactor } = useHealthComputer(account) const { health, healthFactor } = useHealthComputer(account)
const { data } = useBorrowMarketAssetsTableData(false) const { data } = useBorrowMarketAssetsTableData(false)
@ -49,9 +51,10 @@ export default function AccountStats(props: Props) {
lendingAssetsData, lendingAssetsData,
prices, prices,
hlsStrategies, hlsStrategies,
assets,
account.kind === 'high_levered_strategy', account.kind === 'high_levered_strategy',
), ),
[account, borrowAssetsData, hlsStrategies, lendingAssetsData, prices], [account, assets, borrowAssetsData, hlsStrategies, lendingAssetsData, prices],
) )
const deleteAccountHandler = useCallback(() => { const deleteAccountHandler = useCallback(() => {

View File

@ -6,8 +6,8 @@ import AccountStats from 'components/Account/AccountList/AccountStats'
import Card from 'components/Card' import Card from 'components/Card'
import Radio from 'components/Radio' import Radio from 'components/Radio'
import Text from 'components/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'

View File

@ -2,7 +2,7 @@ import { Suspense } from 'react'
import AccountMenuContent from 'components/Account/AccountMenuContent' import AccountMenuContent from 'components/Account/AccountMenuContent'
import Loading from 'components/Loading' import Loading from 'components/Loading'
import useAccountIds from 'hooks/useAccountIds' import useAccountIds from 'hooks/accounts/useAccountIds'
import useStore from 'store' import useStore from 'store'
function Content() { function Content() {

View File

@ -10,13 +10,12 @@ import { Account, Plus, PlusCircled } from 'components/Icons'
import Overlay from 'components/Overlay' import Overlay from 'components/Overlay'
import Text from 'components/Text' import Text from 'components/Text'
import WalletBridges from 'components/Wallet/WalletBridges' import WalletBridges from 'components/Wallet/WalletBridges'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import useAccountIds from 'hooks/accounts/useAccountIds'
import { LocalStorageKeys } from 'constants/localStorageKeys' import useBaseAsset from 'hooks/assets/useBasetAsset'
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'
@ -36,14 +35,11 @@ export default function AccountMenuContent() {
const [searchParams] = useSearchParams() const [searchParams] = useSearchParams()
const createAccount = useStore((s) => s.createAccount) const createAccount = useStore((s) => s.createAccount)
const baseCurrency = useStore((s) => s.baseCurrency) const baseAsset = useBaseAsset()
const [showMenu, setShowMenu] = useToggle() const [showMenu, setShowMenu] = useToggle()
const [isCreating, setIsCreating] = useToggle() const [isCreating, setIsCreating] = useToggle()
const transactionFeeCoinBalance = useCurrentWalletBalance(baseCurrency.denom) const transactionFeeCoinBalance = useCurrentWalletBalance(baseAsset.denom)
const [lendAssets] = useLocalStorage<boolean>( const [enableAutoLendGlobal] = useEnableAutoLendGlobal()
LocalStorageKeys.LEND_ASSETS,
DEFAULT_SETTINGS.lendAssets,
)
const { enableAutoLendAccountId } = useAutoLend() const { enableAutoLendAccountId } = useAutoLend()
const hasCreditAccounts = !!accountIds?.length const hasCreditAccounts = !!accountIds?.length
@ -65,7 +61,7 @@ export default function AccountMenuContent() {
if (accountId) { if (accountId) {
navigate(getRoute(getPage(pathname), searchParams, address, accountId)) navigate(getRoute(getPage(pathname), searchParams, address, accountId))
if (lendAssets) enableAutoLendAccountId(accountId) if (enableAutoLendGlobal) enableAutoLendAccountId(accountId)
useStore.setState({ useStore.setState({
focusComponent: { focusComponent: {
component: <AccountFund />, component: <AccountFund />,
@ -83,7 +79,7 @@ export default function AccountMenuContent() {
pathname, pathname,
searchParams, searchParams,
address, address,
lendAssets, enableAutoLendGlobal,
enableAutoLendAccountId, enableAutoLendAccountId,
]) ])

View File

@ -14,10 +14,11 @@ 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'
@ -34,13 +35,14 @@ 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 accountBalance = useMemo( const accountBalance = useMemo(
() => () =>
props.account props.account
? calculateAccountBalanceValue(updatedAccount ?? props.account, prices) ? calculateAccountBalanceValue(updatedAccount ?? props.account, prices, assets)
: BN_ZERO, : BN_ZERO,
[props.account, updatedAccount, prices], [props.account, updatedAccount, prices, assets],
) )
const { data } = useBorrowMarketAssetsTableData(false) const { data } = useBorrowMarketAssetsTableData(false)
const borrowAssetsData = useMemo(() => data?.allAssets || [], [data]) const borrowAssetsData = useMemo(() => data?.allAssets || [], [data])
@ -55,16 +57,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) : BN_ZERO), () => (props.account ? calculateAccountLeverage(props.account, prices, assets) : BN_ZERO),
[props.account, prices], [props.account, prices, assets],
) )
const updatedLeverage = useMemo(() => { const updatedLeverage = useMemo(() => {
if (!updatedAccount) return null if (!updatedAccount) return null
const updatedLeverage = calculateAccountLeverage(updatedAccount, prices) const updatedLeverage = calculateAccountLeverage(updatedAccount, prices, assets)
if (updatedLeverage.eq(leverage)) return null if (updatedLeverage.eq(leverage)) return null
return updatedLeverage return updatedLeverage
}, [updatedAccount, prices, leverage]) }, [updatedAccount, prices, assets, leverage])
const handleToggle = useCallback( const handleToggle = useCallback(
(index: number) => { (index: number) => {

View File

@ -5,8 +5,8 @@ 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 {

View File

@ -5,8 +5,8 @@ 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'

View File

@ -1,4 +1,4 @@
import React, { ReactElement, useMemo } from 'react' import { ReactElement, useMemo } from 'react'
import { CircularProgress } from 'components/CircularProgress' import { CircularProgress } from 'components/CircularProgress'
import Text from 'components/Text' import Text from 'components/Text'

View File

@ -14,7 +14,7 @@ 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/useLocalStorage' import useLocalStorage from 'hooks/localStorage/useLocalStorage'
import { formatValue } from 'utils/formatters' import { formatValue } from 'utils/formatters'
export const RiskChart = ({ data }: RiskChartProps) => { export const RiskChart = ({ data }: RiskChartProps) => {

View File

@ -1,10 +1,10 @@
import classNames from 'classnames' import classNames from 'classnames'
import { useLocation } from 'react-router-dom'
import { useEffect, useMemo } from 'react' import { useEffect, useMemo } from 'react'
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/useLocalStorage' import useLocalStorage from 'hooks/localStorage/useLocalStorage'
import useStore from 'store' import useStore from 'store'
import { getPage } from 'utils/route' import { getPage } from 'utils/route'

View File

@ -3,8 +3,8 @@ import { useCallback } from 'react'
import Button from 'components/Button' import Button from 'components/Button'
import ActionButton from 'components/Button/ActionButton' import ActionButton from 'components/Button/ActionButton'
import { HandCoins, Plus } from 'components/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 = getEnabledMarketAssets() const marketAssets = useMarketEnabledAssets()
const currentAsset = marketAssets.find((a) => a.denom === asset.denom) const currentAsset = marketAssets.find((a) => a.denom === asset.denom)
const borrowHandler = useCallback(() => { const borrowHandler = useCallback(() => {

View File

@ -1,8 +1,8 @@
import AvailableBorrowingsTable from 'components/Borrow/Table/AvailableBorrowingsTable' import AvailableBorrowingsTable from 'components/Borrow/Table/AvailableBorrowingsTable'
import DepositedBorrowingsTable from 'components/Borrow/Table/DepositedBorrowingsTable' import DepositedBorrowingsTable from 'components/Borrow/Table/DepositedBorrowingsTable'
import { BN_ZERO } from 'constants/math' import { BN_ZERO } from 'constants/math'
import useBorrowEnabledAssets from 'hooks/assets/useBorrowEnabledAssets'
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData' import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
import { getBorrowEnabledAssets } from 'utils/assets'
export default function Borrowings() { export default function Borrowings() {
const { data } = useBorrowMarketAssetsTableData() const { data } = useBorrowMarketAssetsTableData()
@ -19,7 +19,7 @@ export default function Borrowings() {
} }
function Fallback() { function Fallback() {
const assets = getBorrowEnabledAssets() const assets = useBorrowEnabledAssets()
const data: BorrowMarketTableData[] = assets.map((asset) => ({ const data: BorrowMarketTableData[] = assets.map((asset) => ({
asset, asset,
apy: { apy: {

View File

@ -2,8 +2,8 @@ import { Row } from '@tanstack/react-table'
import AmountAndValue from 'components/AmountAndValue' import AmountAndValue from 'components/AmountAndValue'
import { BN_ZERO } from 'constants/math' import { BN_ZERO } from 'constants/math'
import useMarketEnabledAssets from 'hooks/assets/useMarketEnabledAssets'
import { byDenom } from 'utils/array' import { byDenom } from 'utils/array'
import { getEnabledMarketAssets } from 'utils/assets'
export const DEBT_META = { export const DEBT_META = {
accessorKey: 'debt', accessorKey: 'debt',
@ -33,7 +33,7 @@ interface Props {
} }
export default function Debt(props: Props) { export default function Debt(props: Props) {
const marketAssets = getEnabledMarketAssets() const marketAssets = useMarketEnabledAssets()
const asset = marketAssets.find(byDenom(props.data.asset.denom)) const asset = marketAssets.find(byDenom(props.data.asset.denom))
if (!asset) return null if (!asset) return null

View File

@ -3,8 +3,7 @@ import { Row } from '@tanstack/react-table'
import AmountAndValue from 'components/AmountAndValue' import AmountAndValue from 'components/AmountAndValue'
import Loading from 'components/Loading' import Loading from 'components/Loading'
import { BN_ZERO } from 'constants/math' import { BN_ZERO } from 'constants/math'
import { byDenom } from 'utils/array' import useAsset from 'hooks/assets/useAsset'
import { getEnabledMarketAssets } from 'utils/assets'
import { demagnify } from 'utils/formatters' import { demagnify } from 'utils/formatters'
export const LIQUIDITY_META = { export const LIQUIDITY_META = {
@ -32,8 +31,7 @@ interface Props {
export default function Liquidity(props: Props) { export default function Liquidity(props: Props) {
const { liquidity, asset: borrowAsset } = props.data const { liquidity, asset: borrowAsset } = props.data
const marketAssets = getEnabledMarketAssets() const asset = useAsset(borrowAsset.denom)
const asset = marketAssets.find(byDenom(borrowAsset.denom))
if (!asset) return null if (!asset) return null

View File

@ -6,7 +6,7 @@ import Text from 'components/Text'
import { Tooltip } from 'components/Tooltip' import { Tooltip } from 'components/Tooltip'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { LocalStorageKeys } from 'constants/localStorageKeys' import { LocalStorageKeys } from 'constants/localStorageKeys'
import useLocalStorage from 'hooks/useLocalStorage' import useLocalStorage from 'hooks/localStorage/useLocalStorage'
interface Props { interface Props {
balance: number balance: number

View File

@ -5,8 +5,8 @@ import { ACCOUNT_MENU_BUTTON_ID } from 'components/Account/AccountMenuContent'
import Button from 'components/Button' import Button from 'components/Button'
import { Account, PlusCircled } from 'components/Icons' import { Account, PlusCircled } from 'components/Icons'
import WalletConnectButton from 'components/Wallet/WalletConnectButton' import WalletConnectButton from 'components/Wallet/WalletConnectButton'
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'
export default function ActionButton(props: ButtonProps) { export default function ActionButton(props: ButtonProps) {

View File

@ -18,7 +18,7 @@ import { CircularProgress } from 'components/CircularProgress'
import { ChevronDown } from 'components/Icons' import { ChevronDown } from 'components/Icons'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { LocalStorageKeys } from 'constants/localStorageKeys' import { LocalStorageKeys } from 'constants/localStorageKeys'
import useLocalStorage from 'hooks/useLocalStorage' import useLocalStorage from 'hooks/localStorage/useLocalStorage'
const Button = React.forwardRef(function Button( const Button = React.forwardRef(function Button(
{ {
@ -33,6 +33,7 @@ const Button = React.forwardRef(function Button(
text, text,
variant = 'solid', variant = 'solid',
onClick, onClick,
onMouseOver,
leftIcon, leftIcon,
rightIcon, rightIcon,
iconClassName, iconClassName,
@ -97,6 +98,7 @@ const Button = React.forwardRef(function Button(
id={id} id={id}
ref={ref as LegacyRef<HTMLButtonElement>} ref={ref as LegacyRef<HTMLButtonElement>}
onClick={isDisabled ? () => {} : onClick} onClick={isDisabled ? () => {} : onClick}
onMouseOver={isDisabled ? () => {} : onMouseOver}
tabIndex={tabIndex} tabIndex={tabIndex}
autoFocus={autoFocus} autoFocus={autoFocus}
> >

View File

@ -0,0 +1,19 @@
import { Neutron, Osmo } from 'components/Icons'
import { ChainInfoID } from 'types/enums/wallet'
interface Props {
chainID: ChainInfoID
className?: string
}
export default function ChainLogo(props: Props) {
const { chainID, className } = props
switch (chainID) {
case ChainInfoID.Pion1:
return <Neutron className={className} />
default:
return <Osmo className={className} />
}
}

View File

@ -3,7 +3,7 @@ import classNames from 'classnames'
import { CheckCircled } from 'components/Icons' import { CheckCircled } from 'components/Icons'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { LocalStorageKeys } from 'constants/localStorageKeys' import { LocalStorageKeys } from 'constants/localStorageKeys'
import useLocalStorage from 'hooks/useLocalStorage' import useLocalStorage from 'hooks/localStorage/useLocalStorage'
interface Props { interface Props {
color?: string color?: string

View File

@ -2,7 +2,7 @@ import classNames from 'classnames'
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/useLocalStorage' import useLocalStorage from 'hooks/localStorage/useLocalStorage'
interface Props { interface Props {
color?: string color?: string

View File

@ -1,7 +1,7 @@
import { FormattedNumber } from 'components/FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
import TitleAndSubCell from 'components/TitleAndSubCell' import TitleAndSubCell from 'components/TitleAndSubCell'
import { VAULT_DEPOSIT_BUFFER } from 'constants/vaults' import { VAULT_DEPOSIT_BUFFER } from 'constants/vaults'
import { getAssetByDenom } from 'utils/assets' import useAsset from 'hooks/assets/useAsset'
interface Props { interface Props {
depositCap: DepositCap depositCap: DepositCap
@ -13,7 +13,7 @@ export default function DepositCapCell(props: Props) {
.multipliedBy(100) .multipliedBy(100)
.integerValue() .integerValue()
const depositCapUsed = Math.min(percent.toNumber(), 100) const depositCapUsed = Math.min(percent.toNumber(), 100)
const decimals = getAssetByDenom(props.depositCap.denom)?.decimals ?? 6 const decimals = useAsset(props.depositCap.denom)?.decimals ?? 6
return ( return (
<TitleAndSubCell <TitleAndSubCell

View File

@ -4,8 +4,8 @@ import { HTMLAttributes } from 'react'
import { FormattedNumber } from 'components/FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
import { InfoCircle } from 'components/Icons' import { InfoCircle } from 'components/Icons'
import Text from 'components/Text' import Text from 'components/Text'
import useAsset from 'hooks/assets/useAsset'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { getAssetByDenom } from 'utils/assets'
interface Props extends HTMLAttributes<HTMLDivElement> { interface Props extends HTMLAttributes<HTMLDivElement> {
action: 'buy' | 'deposit' | 'fund' action: 'buy' | 'deposit' | 'fund'
@ -24,27 +24,32 @@ export default function DepositCapMessage(props: Props) {
<Text size='xs' className='text-white/40'>{`Unfortunately you're not able to ${ <Text size='xs' className='text-white/40'>{`Unfortunately you're not able to ${
props.action props.action
} more than the following amount${props.coins.length > 1 ? 's' : ''}:`}</Text> } more than the following amount${props.coins.length > 1 ? 's' : ''}:`}</Text>
{props.coins.map((coin) => { {props.coins.map((coin) => (
const asset = getAssetByDenom(coin.denom) <AmountMessage key={coin.denom} coin={coin} />
))}
if (!asset) return null
return (
<div key={coin.denom} className='flex gap-1'>
<Text size='xs'>Cap Left:</Text>
<FormattedNumber
amount={Math.max(coin.amount.toNumber(), 0)}
options={{
decimals: asset.decimals,
maxDecimals: asset.decimals,
suffix: ` ${asset.symbol}`,
}}
className='text-xs text-white/60'
/>
</div>
)
})}
</div> </div>
</div> </div>
) )
} }
interface AmountMessageProps {
coin: BNCoin
}
function AmountMessage(props: AmountMessageProps) {
const asset = useAsset(props.coin.denom)
if (!asset) return null
return (
<div key={props.coin.denom} className='flex gap-1'>
<Text size='xs'>Cap Left:</Text>
<FormattedNumber
amount={Math.max(0, props.coin.amount.toNumber())}
options={{
decimals: asset.decimals,
suffix: ` ${asset.symbol}`,
}}
className='text-xs text-white/60'
/>
</div>
)
}

View File

@ -2,13 +2,12 @@ import classNames from 'classnames'
import { useMemo } from 'react' import { useMemo } from 'react'
import { FormattedNumber } from 'components/FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { LocalStorageKeys } from 'constants/localStorageKeys'
import { ORACLE_DENOM } from 'constants/oracle' import { ORACLE_DENOM } from 'constants/oracle'
import useLocalStorage from 'hooks/useLocalStorage' import useAllAssets from 'hooks/assets/useAllAssets'
import useDisplayCurrencyAssets from 'hooks/assets/useDisplayCurrencyAssets'
import useDisplayCurrency from 'hooks/localStorage/useDisplayCurrency'
import usePrices from 'hooks/usePrices' import usePrices from 'hooks/usePrices'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { getDisplayCurrencies } from 'utils/assets'
import { getCoinValue } from 'utils/formatters' import { getCoinValue } from 'utils/formatters'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
@ -22,11 +21,9 @@ interface Props {
} }
export default function DisplayCurrency(props: Props) { export default function DisplayCurrency(props: Props) {
const displayCurrencies = getDisplayCurrencies() const displayCurrencies = useDisplayCurrencyAssets()
const [displayCurrency] = useLocalStorage<string>( const assets = useAllAssets()
LocalStorageKeys.DISPLAY_CURRENCY, const [displayCurrency] = useDisplayCurrency()
DEFAULT_SETTINGS.displayCurrency,
)
const { data: prices } = usePrices() const { data: prices } = usePrices()
const displayCurrencyAsset = useMemo( const displayCurrencyAsset = useMemo(
@ -38,7 +35,7 @@ export default function DisplayCurrency(props: Props) {
const isUSD = displayCurrencyAsset.id === 'USD' const isUSD = displayCurrencyAsset.id === 'USD'
const amount = useMemo(() => { const amount = useMemo(() => {
const coinValue = getCoinValue(props.coin, prices) const coinValue = getCoinValue(props.coin, prices, assets)
if (displayCurrency === ORACLE_DENOM) return coinValue.toNumber() if (displayCurrency === ORACLE_DENOM) return coinValue.toNumber()
@ -46,10 +43,11 @@ export default function DisplayCurrency(props: Props) {
const displayPrice = getCoinValue( const displayPrice = getCoinValue(
BNCoin.fromDenomAndBigNumber(displayCurrency, BN(1).shiftedBy(displayDecimals)), BNCoin.fromDenomAndBigNumber(displayCurrency, BN(1).shiftedBy(displayDecimals)),
prices, prices,
assets,
) )
return coinValue.div(displayPrice).toNumber() return coinValue.div(displayPrice).toNumber()
}, [displayCurrency, displayCurrencyAsset.decimals, prices, props.coin]) }, [assets, displayCurrency, displayCurrencyAsset.decimals, prices, props.coin])
const isLessThanACent = (isUSD && amount < 0.01 && amount > 0) || (amount === 0 && props.showZero) const isLessThanACent = (isUSD && amount < 0.01 && amount > 0) || (amount === 0 && props.showZero)
const smallerThanPrefix = isLessThanACent ? '< ' : '' const smallerThanPrefix = isLessThanACent ? '< ' : ''

View File

@ -1,5 +1,5 @@
import AssetImage from 'components/Asset/AssetImage' import AssetImage from 'components/Asset/AssetImage'
import { getAssetByDenom } from 'utils/assets' import useAsset from 'hooks/assets/useAsset'
interface Props { interface Props {
primaryDenom: string primaryDenom: string
@ -7,8 +7,8 @@ interface Props {
} }
export default function DoubleLogo(props: Props) { export default function DoubleLogo(props: Props) {
const primaryAsset = getAssetByDenom(props.primaryDenom) const primaryAsset = useAsset(props.primaryDenom)
const secondaryAsset = getAssetByDenom(props.secondaryDenom) const secondaryAsset = useAsset(props.secondaryDenom)
if (!primaryAsset || !secondaryAsset) return null if (!primaryAsset || !secondaryAsset) return null

View File

@ -1,39 +0,0 @@
import { Suspense } from 'react'
import Card from 'components/Card'
import VaultCard from 'components/Earn/Farm/VaultCard'
import useVaults from 'hooks/useVaults'
function Content() {
const { data: vaults } = useVaults()
if (!vaults) return null
const featuredVaults = vaults.filter((vault) => vault.isFeatured)
if (!featuredVaults.length) return null
return (
<Card
title='Featured vaults'
className='w-full mb-4 h-fit bg-white/5'
contentClassName='grid grid-cols-3'
>
{featuredVaults.map((vault) => (
<VaultCard
key={vault.address}
vault={vault}
title={vault.name}
subtitle='Hot off the presses'
provider={vault.provider}
/>
))}
</Card>
)
}
export default function FeaturedVaults() {
return (
<Suspense fallback={null}>
<Content />
</Suspense>
)
}

View File

@ -1,83 +0,0 @@
import ActionButton from 'components/Button/ActionButton'
import DoubleLogo from 'components/DoubleLogo'
import Text from 'components/Text'
import TitleAndSubCell from 'components/TitleAndSubCell'
import useCurrentAccount from 'hooks/useCurrentAccount'
import useStore from 'store'
import { getAssetByDenom } from 'utils/assets'
import { formatPercent, formatValue } from 'utils/formatters'
interface Props {
vault: Vault
title: string
subtitle: string
provider?: string
unbondingPeriod?: number
}
export default function VaultCard(props: Props) {
const currentAccount = useCurrentAccount()
function openVaultModal() {
useStore.setState({
vaultModal: {
vault: props.vault,
selectedBorrowDenoms: [props.vault.denoms.secondary],
isCreate: true,
},
})
}
return (
<div className='border-r-[1px] border-r-white/10 p-4'>
<div className='flex justify-between mb-8 align-center'>
<div>
<Text size='xs' className='mb-2 text-white/60'>
{props.subtitle}
</Text>
<span className='flex'>
<Text className='mr-2 font-bold'>{props.title}</Text>
{props.provider && (
<Text size='sm' className='text-white/60'>
via {props.provider}
</Text>
)}
</span>
</div>
<DoubleLogo
primaryDenom={props.vault.denoms.primary}
secondaryDenom={props.vault.denoms.secondary}
/>
</div>
<div className='flex justify-between mb-6'>
<TitleAndSubCell
className='text-xs'
title={props.vault.apy ? formatPercent(props.vault.apy, 2) : '-'}
sub={'APY'}
/>
<TitleAndSubCell
className='text-xs'
title={`${props.vault.lockup.duration} ${props.vault.lockup.timeframe}`}
sub={'Lockup'}
/>
<TitleAndSubCell
className='text-xs'
title={formatValue(props.vault.cap.used.integerValue().toNumber() || '0', {
abbreviated: true,
decimals: getAssetByDenom(props.vault.cap.denom)?.decimals,
})}
sub={'TVL'}
/>
<TitleAndSubCell
className='text-xs'
title={formatValue(props.vault.cap.max.integerValue().toNumber() || '0', {
abbreviated: true,
decimals: getAssetByDenom(props.vault.cap.denom)?.decimals,
})}
sub={'Deposit Cap'}
/>
</div>
<ActionButton onClick={openVaultModal} color='secondary' text='Deposit' className='w-full' />
</div>
)
}

View File

@ -7,8 +7,8 @@ import { AccountArrowDown, LockLocked, LockUnlocked, Plus } from 'components/Ico
import { Tooltip } from 'components/Tooltip' import { Tooltip } from 'components/Tooltip'
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 useAccountId from 'hooks/useAccountId' import useAccountId from 'hooks/useAccountId'
import useLocalStorage from 'hooks/useLocalStorage'
import useStore from 'store' import useStore from 'store'
import { VaultStatus } from 'types/enums/vault' import { VaultStatus } from 'types/enums/vault'

View File

@ -5,8 +5,8 @@ import { ChevronRight } from 'components/Icons'
import NotificationBanner from 'components/NotificationBanner' import NotificationBanner from 'components/NotificationBanner'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { LocalStorageKeys } from 'constants/localStorageKeys' import { LocalStorageKeys } from 'constants/localStorageKeys'
import useLocalStorage from 'hooks/localStorage/useLocalStorage'
import useAccountId from 'hooks/useAccountId' import useAccountId from 'hooks/useAccountId'
import useLocalStorage from 'hooks/useLocalStorage'
import useStore from 'store' import useStore from 'store'
interface Props { interface Props {

View File

@ -3,13 +3,11 @@ import { Suspense, useMemo } from 'react'
import AvailableVaultsTable from 'components/Earn/Farm/Table/AvailableVaultsTable' import AvailableVaultsTable from 'components/Earn/Farm/Table/AvailableVaultsTable'
import DepositedVaultsTable from 'components/Earn/Farm/Table/DepositedVaultsTable' import DepositedVaultsTable from 'components/Earn/Farm/Table/DepositedVaultsTable'
import VaultUnlockBanner from 'components/Earn/Farm/VaultUnlockBanner' import VaultUnlockBanner from 'components/Earn/Farm/VaultUnlockBanner'
import { ENV } from 'constants/env'
import { BN_ZERO } from 'constants/math' import { BN_ZERO } from 'constants/math'
import { TESTNET_VAULTS_META_DATA, VAULTS_META_DATA } from 'constants/vaults'
import useAccountId from 'hooks/useAccountId' import useAccountId from 'hooks/useAccountId'
import useDepositedVaults from 'hooks/useDepositedVaults' import useDepositedVaults from 'hooks/useDepositedVaults'
import useVaults from 'hooks/useVaults' import useVaults from 'hooks/useVaults'
import { NETWORK } from 'types/enums/network' import useStore from 'store'
import { VaultStatus } from 'types/enums/vault' import { VaultStatus } from 'types/enums/vault'
function Content() { function Content() {
@ -17,11 +15,10 @@ function Content() {
const { data: vaults } = useVaults() const { data: vaults } = useVaults()
const { data: depositedVaults } = useDepositedVaults(accountId || '') const { data: depositedVaults } = useDepositedVaults(accountId || '')
const vaultsMetaData = const vaultMetaData = useStore((s) => s.chainConfig.vaults)
ENV.NETWORK === NETWORK.TESTNET ? TESTNET_VAULTS_META_DATA : VAULTS_META_DATA
const { deposited, available } = useMemo(() => { const { deposited, available } = useMemo(() => {
return vaultsMetaData.reduce( return vaultMetaData.reduce(
(prev: { deposited: DepositedVault[]; available: Vault[] }, curr) => { (prev: { deposited: DepositedVault[]; available: Vault[] }, curr) => {
if (!vaults) return prev if (!vaults) return prev
const vault = vaults.find((vault) => vault.address === curr.address) const vault = vaults.find((vault) => vault.address === curr.address)
@ -37,7 +34,7 @@ function Content() {
}, },
{ deposited: [], available: [] }, { deposited: [], available: [] },
) )
}, [vaults, depositedVaults, vaultsMetaData]) }, [vaults, depositedVaults, vaultMetaData])
const unlockedVaults: DepositedVault[] = [] const unlockedVaults: DepositedVault[] = []
@ -63,7 +60,7 @@ function Content() {
} }
function Fallback() { function Fallback() {
const vaults = ENV.NETWORK === NETWORK.TESTNET ? TESTNET_VAULTS_META_DATA : VAULTS_META_DATA const vaults = useStore((s) => s.chainConfig.vaults)
const mockVaults: Vault[] = vaults.map((vault) => ({ const mockVaults: Vault[] = vaults.map((vault) => ({
...vault, ...vault,
apy: null, apy: null,

View File

@ -1,8 +1,8 @@
import AvailableLendsTable from 'components/Earn/Lend/Table/AvailableLendsTable' import AvailableLendsTable from 'components/Earn/Lend/Table/AvailableLendsTable'
import DepositedLendsTable from 'components/Earn/Lend/Table/DepositedLendsTable' import DepositedLendsTable from 'components/Earn/Lend/Table/DepositedLendsTable'
import { BN_ZERO } from 'constants/math' import { BN_ZERO } from 'constants/math'
import useLendEnabledAssets from 'hooks/assets/useLendEnabledAssets'
import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData' import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData'
import { getLendEnabledAssets } from 'utils/assets'
export default function Lends() { export default function Lends() {
const { accountLentAssets, availableAssets, allAssets } = useLendingMarketAssetsTableData() const { accountLentAssets, availableAssets, allAssets } = useLendingMarketAssetsTableData()
@ -19,7 +19,8 @@ export default function Lends() {
} }
function Fallback() { function Fallback() {
const assets = getLendEnabledAssets() const assets = useLendEnabledAssets()
const data: LendingMarketTableData[] = assets.map((asset) => ({ const data: LendingMarketTableData[] = assets.map((asset) => ({
asset, asset,
marketDepositCap: BN_ZERO, marketDepositCap: BN_ZERO,

View File

@ -4,7 +4,7 @@ import { animated, useSpring } from 'react-spring'
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/useLocalStorage' import useLocalStorage from 'hooks/localStorage/useLocalStorage'
import { formatValue } from 'utils/formatters' import { formatValue } from 'utils/formatters'
interface Props { interface Props {

View File

@ -4,7 +4,7 @@ import { ReactElement, ReactNode } from 'react'
import { Tooltip } from 'components/Tooltip' import { Tooltip } from 'components/Tooltip'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { LocalStorageKeys } from 'constants/localStorageKeys' import { LocalStorageKeys } from 'constants/localStorageKeys'
import useLocalStorage from 'hooks/useLocalStorage' import useLocalStorage from 'hooks/localStorage/useLocalStorage'
interface Props { interface Props {
tooltip: string | ReactNode tooltip: string | ReactNode

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