This commit is contained in:
Linkie Link 2024-01-25 14:30:01 +01:00 committed by GitHub
parent 68c60c29df
commit f6f46d3372
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
678 changed files with 11366 additions and 8062 deletions

View File

@ -1,37 +1,16 @@
# DEVNET #
NEXT_PUBLIC_NETWORK=devnet
NEXT_PUBLIC_CHAIN_ID=devnet
NEXT_PUBLIC_RPC=https://rpc.devnet.osmosis.zone/
NEXT_PUBLIC_GQL=https://devnet-osmosis-gql.marsprotocol.io/graphql
NEXT_PUBLIC_REST=https://lcd.devnet.osmosis.zone/
# MAINNET #
NEXT_PUBLIC_NETWORK=mainnet NEXT_PUBLIC_NETWORK=mainnet
NEXT_PUBLIC_CHAIN_ID=osmosis-1
NEXT_PUBLIC_RPC=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-rpc-front/
NEXT_PUBLIC_GQL=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-hive-front/graphql
NEXT_PUBLIC_REST=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-lcd-front/
NEXT_PUBLIC_OSMOSIS_RPC=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-rpc-front/
NEXT_PUBLIC_OSMOSIS_REST=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-lcd-front/
NEXT_PUBLIC_OSMOSIS_TEST_RPC=https://rpc.devnet.osmosis.zone/
NEXT_PUBLIC_OSMOSIS_TEST_REST=https://lcd.devnet.osmosis.zone/
NEXT_PUBLIC_NEUTRON_TEST_RPC=https://rpc-palvus.pion-1.ntrn.tech/
NEXT_PUBLIC_NEUTRON_TEST_REST=https://rest-palvus.pion-1.ntrn.tech/
# COMMON #
NEXT_PUBLIC_SWAP=https://app.osmosis.zone
NEXT_PUBLIC_VAULT_APR=https://api.marsprotocol.io/v1/vaults/osmosis
NEXT_PUBLIC_ACCOUNT_NFT=osmo1450hrg6dv2l58c0rvdwx8ec2a0r6dd50hn4frk370tpvqjhy8khqw7sw09
NEXT_PUBLIC_ORACLE=osmo1mhznfr60vjdp2gejhyv2gax9nvyyzhd3z0qcwseyetkfustjauzqycsy2g
NEXT_PUBLIC_RED_BANK=osmo1c3ljch9dfw5kf52nfwpxd2zmj2ese7agnx0p9tenkrryasrle5sqf3ftpg
NEXT_PUBLIC_CREDIT_MANAGER=osmo1f2m24wktq0sw3c0lexlg7fv4kngwyttvzws3a3r3al9ld2s2pvds87jqvf
NEXT_PUBLIC_INCENTIVES=osmo1nkahswfr8shg8rlxqwup0vgahp0dk4x8w6tkv3rra8rratnut36sk22vrm
NEXT_PUBLIC_SWAPPER=osmo1wee0z8c7tcawyl647eapqs4a88q8jpa7ddy6nn2nrs7t47p2zhxswetwla
NEXT_PUBLIC_PYTH=osmo13ge29x4e2s63a8ytz2px8gurtyznmue4a69n5275692v3qn3ks8q7cwck7
NEXT_PUBLIC_ZAPPER=osmo17qwvc70pzc9mudr8t02t3pl74hhqsgwnskl734p4hug3s8mkerdqzduf7c
NEXT_PUBLIC_PARAMS=osmo1nlmdxt9ctql2jr47qd4fpgzg84cjswxyw6q99u4y4u4q6c2f5ksq7ysent
NEXT_PUBLIC_PYTH_ENDPOINT=https://hermes.pyth.network/api
NEXT_PUBLIC_MAINNET_REST=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-rpc-front/
NEXT_PUBLIC_CANDLES_ENDPOINT_THE_GRAPH=https://osmosis-candles.marsprotocol.io/
NEXT_PUBLIC_CANDLES_ENDPOINT_PYTH=https://benchmarks.pyth.network
NEXT_PUBLIC_WALLET_CONNECT_ID=d93fdffb159bae5ec87d8fee4cdbb045 NEXT_PUBLIC_WALLET_CONNECT_ID=d93fdffb159bae5ec87d8fee4cdbb045
CHARTING_LIBRARY_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
NEXT_PUBLIC_PYTH_API=https://mars.rpc.p2p.world/api

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,15 +15,15 @@
}, },
"lint-staged": { "lint-staged": {
"*.ts*": [ "*.ts*": [
"eslint ./src/ ./__tests__/ --fix", "eslint ./src/ --fix",
"prettier --write ./src/ ./__tests__/" "prettier --write ./src/"
] ]
}, },
"dependencies": { "dependencies": {
"@cosmjs/cosmwasm-stargate": "^0.31.1", "@cosmjs/cosmwasm-stargate": "^0.32.2",
"@delphi-labs/shuttle-react": "^3.10.0", "@delphi-labs/shuttle-react": "^3.10.0",
"@keplr-wallet/cosmos": "^0.12.42", "@keplr-wallet/cosmos": "^0.12.42",
"@sentry/nextjs": "^7.84.0", "@sentry/nextjs": "^7.94.1",
"@splinetool/react-spline": "^2.2.6", "@splinetool/react-spline": "^2.2.6",
"@splinetool/runtime": "^0.9.521", "@splinetool/runtime": "^0.9.521",
"@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/container-queries": "^0.1.1",
@ -45,40 +43,35 @@
"react-draggable": "^4.4.6", "react-draggable": "^4.4.6",
"react-helmet-async": "^2.0.3", "react-helmet-async": "^2.0.3",
"react-qr-code": "^2.0.12", "react-qr-code": "^2.0.12",
"react-router-dom": "^6.17.0", "react-router-dom": "^6.21.3",
"react-spring": "^9.7.3", "react-spring": "^9.7.3",
"react-toastify": "^9.1.3", "react-toastify": "^9.1.3",
"react-use-clipboard": "^1.0.9", "react-use-clipboard": "^1.0.9",
"recharts": "^2.10.1", "recharts": "^2.10.1",
"sharp": "^0.33.0", "sharp": "^0.33.2",
"swr": "^2.2.4", "swr": "^2.2.4",
"tailwind-scrollbar-hide": "^1.1.7", "tailwind-scrollbar-hide": "^1.1.7",
"zustand": "^4.4.6" "zustand": "^4.4.7"
}, },
"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",
"@types/debounce-promise": "^3.1.9", "@types/debounce-promise": "^3.1.9",
"@types/lodash.debounce": "^4.0.9", "@types/lodash.debounce": "^4.0.9",
"@types/lodash.throttle": "^4.1.8", "@types/lodash.throttle": "^4.1.8",
"@types/node": "^20.8.6", "@types/node": "^20.10.6",
"@types/react": "18.2.41", "@types/react": "18.2.41",
"@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.56.0",
"eslint-config-next": "^14.0.4", "eslint-config-next": "^14.0.4",
"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.2.4",
"prettier-plugin-tailwindcss": "^0.5.6", "prettier-plugin-tailwindcss": "^0.5.6",
"shelljs": "^0.8.5", "shelljs": "^0.8.5",
"tailwindcss": "^3.3.3", "tailwindcss": "^3.3.3",

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

@ -40,6 +40,16 @@
cursor: pointer; cursor: pointer;
} }
.layout__area--center {
background: var(--tv-background) !important;
}
.chart-widget.chart-widget--themed-dark.chart-widget__top--themed-dark.chart-widget__bottom--themed-dark
> table
canvas {
background: transparent !important;
}
/* Floating menu */ /* Floating menu */
.floating-toolbar-react-widgets__button:hover, .floating-toolbar-react-widgets__button:hover,
[class^='button-']:hover:before { [class^='button-']:hover:before {

View File

@ -3,21 +3,25 @@ import { getCreditManagerQueryClient } from 'api/cosmwasm-client'
import getDepositedVaults from 'api/vaults/getDepositedVaults' import getDepositedVaults from 'api/vaults/getDepositedVaults'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { Positions } from 'types/generated/mars-credit-manager/MarsCreditManager.types' import { Positions } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
import { resolvePerpsPositions } from 'utils/resolvers'
export default async function getAccount(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 }),
positionsCache, positionsCache,
`account/${accountId}`, `${chainConfig.id}/account/${accountId}`,
) )
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 {
@ -26,6 +30,7 @@ export default async function getAccount(accountId?: string): Promise<Account> {
lends: accountPosition.lends.map((lend) => new BNCoin(lend)), lends: accountPosition.lends.map((lend) => new BNCoin(lend)),
deposits: accountPosition.deposits.map((deposit) => new BNCoin(deposit)), deposits: accountPosition.deposits.map((deposit) => new BNCoin(deposit)),
vaults: depositedVaults, vaults: depositedVaults,
perps: resolvePerpsPositions(accountPosition.perps),
kind: accountKind, kind: accountKind,
} }
} }

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'
@ -8,142 +7,187 @@ import { MarsIncentivesQueryClient } from 'types/generated/mars-incentives/MarsI
import { MarsMockVaultQueryClient } from 'types/generated/mars-mock-vault/MarsMockVault.client' import { MarsMockVaultQueryClient } from 'types/generated/mars-mock-vault/MarsMockVault.client'
import { MarsOracleOsmosisQueryClient } from 'types/generated/mars-oracle-osmosis/MarsOracleOsmosis.client' import { MarsOracleOsmosisQueryClient } from 'types/generated/mars-oracle-osmosis/MarsOracleOsmosis.client'
import { MarsParamsQueryClient } from 'types/generated/mars-params/MarsParams.client' import { MarsParamsQueryClient } from 'types/generated/mars-params/MarsParams.client'
import { MarsPerpsQueryClient } from 'types/generated/mars-perps/MarsPerps.client'
import { MarsRedBankQueryClient } from 'types/generated/mars-red-bank/MarsRedBank.client' import { MarsRedBankQueryClient } from 'types/generated/mars-red-bank/MarsRedBank.client'
import { MarsSwapperOsmosisQueryClient } from 'types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.client' import { MarsSwapperOsmosisQueryClient } from 'types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.client'
let _cosmWasmClient: 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 _perpsClient: Map<string, MarsPerpsQueryClient> = new Map()
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 getPerpsQueryClient = async (chainConfig: ChainConfig) => {
try { try {
if (!_ICNSQueryClient) { const contract = chainConfig.contracts.perps
const client = await getClient() const rpc = chainConfig.endpoints.rpc
_ICNSQueryClient = new ICNSQueryClient(client) const key = rpc + contract
if (!_perpsClient.get(key)) {
const client = await getClient(rpc)
_perpsClient.set(key, new MarsPerpsQueryClient(client, contract))
} }
return _ICNSQueryClient return _perpsClient.get(key)!
} catch (error) {
throw error
}
}
const getICNSQueryClient = async (chainConfig: ChainConfig) => {
try {
const contract = chainConfig.contracts.params
const rpc = chainConfig.endpoints.rpc
const key = rpc + contract
if (!_ICNSQueryClient.get(key)) {
const client = await getClient(rpc)
_ICNSQueryClient.set(key, new ICNSQueryClient(client))
}
return _ICNSQueryClient.get(key)!
} catch (error) { } catch (error) {
throw error throw error
} }
@ -160,4 +204,5 @@ export {
getRedBankQueryClient, getRedBankQueryClient,
getSwapperQueryClient, getSwapperQueryClient,
getVaultQueryClient, getVaultQueryClient,
getPerpsQueryClient,
} }

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,24 +4,29 @@ 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) => {
if (account.deposits.length === 0) return if (account.deposits.length === 0) return
const strategy = hlsStrategies.find( const strategy = hlsStrategies.find(
(strategy) => strategy.denoms.deposit === account.deposits.at(0).denom, (strategy) => strategy.denoms.deposit === account.deposits[0].denom,
) )
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( cacheFn(
() => paramsClient.totalDeposit({ denom: asset.denom }), () => paramsClient.totalDeposit({ denom: asset.denom }),
totalDepositCache, totalDepositCache,
`enabledMarkets/${asset.denom}`, `chains/${chainConfig.id}/enabledMarkets/${asset.denom}`,
60, 60,
), ),
) )
const caps = await Promise.all(capQueries)
const [markets, assetParams, assetCaps] = await Promise.all([ const [markets, assetParams, assetCaps] = await Promise.all([
cacheFn(() => iterateContractQuery(redBankClient.markets), marketsCache, 'markets', 60), cacheFn(
() => iterateContractQuery(redBankClient.markets),
marketsCache,
`chains/${chainConfig.id}/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,15 +3,17 @@ 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,
'assetParams', `${chainConfig.id}/assetParams`,
600, 600,
) )
} catch (ex) { } catch (ex) {

View File

@ -0,0 +1,14 @@
import { getPerpsQueryClient } from 'api/cosmwasm-client'
import { BNCoin } from 'types/classes/BNCoin'
export default async function getOpeningFee(
chainConfig: ChainConfig,
denom: string,
amount: string,
) {
const perpsClient = await getPerpsQueryClient(chainConfig)
return perpsClient
.openingFee({ denom, size: amount as any })
.then((resp) => BNCoin.fromCoin(resp.fee))
}

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

@ -1,5 +1,6 @@
import { cacheFn, oraclePriceCache } from 'api/cache' import { cacheFn, oraclePriceCache } from 'api/cache'
import { getOracleQueryClient } from 'api/cosmwasm-client' import { getOracleQueryClient } from 'api/cosmwasm-client'
import { BN_ZERO } from 'constants/math'
import { PRICE_ORACLE_DECIMALS } from 'constants/query' import { PRICE_ORACLE_DECIMALS } from 'constants/query'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { PriceResponse } from 'types/generated/mars-oracle-osmosis/MarsOracleOsmosis.types' import { PriceResponse } from 'types/generated/mars-oracle-osmosis/MarsOracleOsmosis.types'
@ -7,15 +8,18 @@ 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,
'oraclePrices', `${chainConfig.id}/oraclePrices`,
60, 60,
) )
@ -24,7 +28,7 @@ export default async function getOraclePrices(...assets: Asset[]): Promise<BNCoi
const decimalDiff = asset.decimals - PRICE_ORACLE_DECIMALS const decimalDiff = asset.decimals - PRICE_ORACLE_DECIMALS
return BNCoin.fromDenomAndBigNumber( return BNCoin.fromDenomAndBigNumber(
asset.denom, asset.denom,
BN(priceResponse.price).shiftedBy(decimalDiff), BN(priceResponse?.price ?? BN_ZERO).shiftedBy(decimalDiff),
) )
}) })
} catch (ex) { } catch (ex) {

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,28 +16,29 @@ 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,
`poolPrices/${(asset.poolId || 0).toString()}`, `${chainConfig.id}/poolPrices/${(asset.poolId || 0).toString()}`,
60, 60,
) )
const pool = response.pool const pool = response.pool

View File

@ -1,37 +1,20 @@
import { cacheFn, priceCache } from 'api/cache' import { cacheFn, priceCache } from 'api/cache'
import { getOracleQueryClient } from 'api/cosmwasm-client' import getPrices from 'api/prices/getPrices'
import getPoolPrice from 'api/prices/getPoolPrice' import { BN_ZERO } from 'constants/math'
import getPythPrice from 'api/prices/getPythPrices'
import { ASSETS } from 'constants/assets'
import { PRICE_ORACLE_DECIMALS } from 'constants/query'
import { byDenom } from 'utils/array' import { byDenom } from 'utils/array'
import { BN } from 'utils/helpers'
export default async function getPrice(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 prices = await getPrices(chainConfig)
if (asset.pythPriceFeedId) { return prices.find(byDenom(denom))?.amount ?? BN_ZERO
return (await getPythPrice(asset.pythPriceFeedId))[0]
}
if (asset.hasOraclePrice) {
const oracleQueryClient = await getOracleQueryClient()
const priceResponse = await oracleQueryClient.price({ denom: asset.denom })
const decimalDiff = asset.decimals - PRICE_ORACLE_DECIMALS
return BN(priceResponse.price).shiftedBy(decimalDiff)
}
if (asset.poolId) {
return await getPoolPrice(asset)
}
throw `could not fetch the price info for the given denom: ${denom}`
} catch (ex) { } catch (ex) {
throw ex throw ex
} }

View File

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

View File

@ -1,29 +1,35 @@
import getOraclePrices from 'api/prices/getOraclePrices' import getOraclePrices from 'api/prices/getOraclePrices'
import getPoolPrice from 'api/prices/getPoolPrice' import getPoolPrice from 'api/prices/getPoolPrice'
import fetchPythPrices from 'api/prices/getPythPrices' import fetchPythPrices from 'api/prices/getPythPrices'
import chains from 'configs/chains'
import useStore from 'store' import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { partition } from 'utils/array' import { partition } from 'utils/array'
import { getAssetsMustHavePriceInfo } from 'utils/assets' import { getAllAssetsWithPythId } 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' })
const pythAndOraclePrices = []
const assetsToFetchPrices = useStore
.getState()
.chainConfig.assets.filter(
(asset) => (asset.isEnabled && asset.isMarket) || asset.forceFetchPrice,
)
const assetsWithPythPriceFeedId = getAllAssetsWithPythId(chains)
const pythPrices = await requestPythPrices(assetsWithPythPriceFeedId)
pythAndOraclePrices.push(...pythPrices)
try { try {
const assetsToFetchPrices = getAssetsMustHavePriceInfo() const [assetsWithOraclePrices, assetsWithPoolIds] =
const [assetsWithPythPriceFeedId, assetsWithOraclePrices, assetsWithPoolIds] =
separateAssetsByPriceSources(assetsToFetchPrices) separateAssetsByPriceSources(assetsToFetchPrices)
const oraclePrices = await getOraclePrices(chainConfig, assetsWithOraclePrices)
const poolPrices = await requestPoolPrices(chainConfig, assetsWithPoolIds, pythAndOraclePrices)
const pythAndOraclePrices = ( if (oraclePrices) useStore.setState({ isOracleStale: false })
await Promise.all([
requestPythPrices(assetsWithPythPriceFeedId),
getOraclePrices(...assetsWithOraclePrices),
])
).flat()
const poolPrices = await requestPoolPrices(assetsWithPoolIds, pythAndOraclePrices)
useStore.setState({ isOracleStale: false }) return [...pythAndOraclePrices, ...oraclePrices, ...poolPrices, usdPrice]
return [...pythAndOraclePrices, ...poolPrices, usdPrice]
} catch (ex) { } catch (ex) {
console.error(ex) console.error(ex)
let message = 'Unknown Error' let message = 'Unknown Error'
@ -31,41 +37,43 @@ export default async function getPrices(): Promise<BNCoin[]> {
if (message.includes('price publish time is too old')) if (message.includes('price publish time is too old'))
useStore.setState({ isOracleStale: true }) useStore.setState({ isOracleStale: true })
throw ex return [...pythAndOraclePrices, usdPrice]
} }
} }
async function requestPythPrices(assets: Asset[]): Promise<BNCoin[]> { async function requestPythPrices(assets: Asset[]): Promise<BNCoin[]> {
if (!assets.length) return [] if (!assets.length) return []
const priceFeedIds = assets.map((a) => a.pythPriceFeedId) as string[] const priceFeedIds = assets
return await fetchPythPrices(...priceFeedIds).then(mapResponseToBnCoin(assets)) .map((a) => a.pythPriceFeedId)
.filter((priceFeedId, index, array) => array.indexOf(priceFeedId) === index) as string[]
return await fetchPythPrices(priceFeedIds, 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))
} }
const mapResponseToBnCoin = (assets: Asset[]) => (prices: BigNumber[]) => const mapResponseToBnCoin = (assets: Asset[]) => (prices: BigNumber[]) => {
prices.map((price: BigNumber, index: number) => return prices.map((price: BigNumber, index: number) =>
BNCoin.fromDenomAndBigNumber(assets[index].denom, price), BNCoin.fromDenomAndBigNumber(assets[index].denom, price),
) )
}
function separateAssetsByPriceSources(assets: Asset[]) { function separateAssetsByPriceSources(assets: Asset[]) {
// Only fetch Pyth prices for mainnet const assetsWithoutPythPriceFeedId = assets.filter((asset) => !asset.pythPriceFeedId)
const [assetsWithPythPriceFeedId, assetsWithoutPythPriceFeedId] = partition(
assets,
(asset) => !!asset.pythPriceFeedId,
)
// Don't get oracle price if it's not mainnet and there is a poolId
const [assetsWithOraclePrice, assetsWithoutOraclePrice] = partition( const [assetsWithOraclePrice, assetsWithoutOraclePrice] = partition(
assetsWithoutPythPriceFeedId, assetsWithoutPythPriceFeedId,
(asset) => asset.hasOraclePrice || !asset.poolId, (asset) => asset.hasOraclePrice || !asset.poolId,
) )
const assetsWithPoolId = assetsWithoutOraclePrice.filter((asset) => !!asset.poolId) const assetsWithPoolId = assetsWithoutOraclePrice.filter((asset) => !!asset.poolId)
return [assetsWithPythPriceFeedId, assetsWithOraclePrice, assetsWithPoolId] return [assetsWithOraclePrice, assetsWithPoolId]
} }

View File

@ -1,9 +1,9 @@
import { cacheFn, pythPriceCache } from 'api/cache' import { cacheFn, pythPriceCache } from 'api/cache'
import { ENV } from 'constants/env' import { pythEndpoints } from 'constants/pyth'
export default async function fetchPythPriceData(...priceFeedIds: string[]) { export default async function getPythPriceData(priceFeedIds: string[]) {
try { try {
const pricesUrl = new URL(`${ENV.PYTH_ENDPOINT}/latest_vaas`) const pricesUrl = new URL(`${pythEndpoints.api}/latest_vaas`)
priceFeedIds.forEach((id) => pricesUrl.searchParams.append('ids[]', id)) priceFeedIds.forEach((id) => pricesUrl.searchParams.append('ids[]', id))
const pythDataResponse: string[] = await cacheFn( const pythDataResponse: string[] = await cacheFn(
@ -14,6 +14,7 @@ export default async function fetchPythPriceData(...priceFeedIds: string[]) {
) )
return pythDataResponse return pythDataResponse
} catch (ex) { } catch (ex) {
throw ex console.log(ex)
return []
} }
} }

View File

@ -1,10 +1,11 @@
import { cacheFn, pythPriceCache } from 'api/cache' import { cacheFn, pythPriceCache } from 'api/cache'
import { ENV } from 'constants/env' import { pythEndpoints } from 'constants/pyth'
import { BNCoin } from 'types/classes/BNCoin'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
export default async function fetchPythPrices(...priceFeedIds: string[]) { export default async function fetchPythPrices(priceFeedIds: string[], assets: Asset[]) {
try { try {
const pricesUrl = new URL(`${ENV.PYTH_ENDPOINT}/latest_price_feeds`) const pricesUrl = new URL(`${pythEndpoints.api}/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(
@ -14,7 +15,18 @@ export default async function fetchPythPrices(...priceFeedIds: string[]) {
30, 30,
) )
return pythResponse.map(({ price }) => BN(price.price).shiftedBy(price.expo)) const mappedPriceData = [] as BNCoin[]
assets.forEach((asset) => {
const price = pythResponse.find((pythPrice) => asset.pythPriceFeedId === pythPrice.id)?.price
if (price)
mappedPriceData.push(
BNCoin.fromDenomAndBigNumber(asset.denom, BN(price.price).shiftedBy(price.expo)),
)
return
})
return mappedPriceData
} catch (ex) { } catch (ex) {
throw ex throw ex
} }

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

@ -0,0 +1,53 @@
import { BN_ZERO } from 'constants/math'
import { STANDARD_SWAP_FEE } from 'utils/constants'
export default async function getOsmosisSwapFee(
chainConfig: ChainConfig,
poolIds: string[],
): Promise<number> {
const promises = poolIds.map((poolId) =>
fetch(chainConfig.endpoints.pools.replace('POOL_ID', poolId)),
)
const responses = await Promise.all(promises)
const pools = await Promise.all(responses.map(async (pool) => (await pool.json()).pool as Pool))
if (!pools?.length) return STANDARD_SWAP_FEE
return pools
.reduce((acc, pool) => acc.plus(pool?.pool_params?.swap_fee || STANDARD_SWAP_FEE), BN_ZERO)
.toNumber()
}
interface Pool {
'@type': string
address: string
future_pool_governor: string
id: string
pool_assets?: PoolAsset[]
pool_liquidity?: PoolLiquidity[]
pool_params: PoolParams
total_shares: TotalShares
total_weight: string
}
interface PoolAsset {
token: TotalShares
weight: string
}
interface PoolLiquidity {
amount: string
denom: string
}
interface TotalShares {
amount: string
denom: string
}
interface PoolParams {
exit_fee: string
smooth_weight_change_params: null
swap_fee: string
}

View File

@ -1,42 +0,0 @@
import { ENV } from 'constants/env'
const url = `${ENV.URL_REST}osmosis/gamm/v1beta1/pools/`
export default async function getPools(poolIds: string[]): Promise<Pool[]> {
const promises = poolIds.map((poolId) => fetch(url + poolId))
const responses = await Promise.all(promises)
return await Promise.all(responses.map(async (pool) => (await pool.json()).pool as Pool))
}
interface Pool {
'@type': string
address: string
future_pool_governor: string
id: string
pool_assets?: PoolAsset[]
pool_liquidity?: PoolLiquidity[]
pool_params: PoolParams
total_shares: TotalShares
total_weight: string
}
interface PoolAsset {
token: TotalShares
weight: string
}
interface PoolLiquidity {
amount: string
denom: string
}
interface TotalShares {
amount: string
denom: string
}
interface PoolParams {
exit_fee: string
smooth_weight_change_params: null
swap_fee: string
}

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,17 +1,22 @@
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) {
if (!chainConfig.farm) return []
try { try {
const response = await cacheFn( const response = await cacheFn(
() => fetch(ENV.URL_VAULT_APR), () => fetch(chainConfig.endpoints.aprs.vaults),
aprsCacheResponse, aprsCacheResponse,
'aprsResponse', `${chainConfig.id}/aprsResponse`,
60, 60,
) )
if (response.ok) { if (response.ok) {
const data: AprResponse = await cacheFn(() => response.json(), aprsCache, 'aprs', 60) const data: AprResponse = await cacheFn(
() => response.json(),
aprsCache,
`${chainConfig.id}/aprs`,
60,
)
return data.vaults.map((aprData) => { return data.vaults.map((aprData) => {
const finalApr = aprData.apr.projected_apr * 100 const finalApr = aprData.apr.projected_apr * 100

View File

@ -3,13 +3,15 @@ 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,
'vaultConfigs', `${chainConfig.id}/vaultConfigs`,
600, 600,
) )
} catch (ex) { } catch (ex) {

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,16 +2,19 @@ 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)
if (accounts) { if (accounts) {
return accounts.sort((a, b) => Number(a.id) - Number(b.id)) return accounts.sort((a, b) => Number(a.id) - Number(b.id))
} }

View File

@ -1,11 +1,13 @@
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> {
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

@ -1,22 +0,0 @@
import Text from 'components/Text'
export const ASSET_META = { accessorKey: 'symbol', header: 'Asset', id: 'symbol' }
interface Props {
symbol: string
type: 'deposits' | 'borrowing' | 'lending' | 'vault'
}
export const borderColor = (type: Props['type']): string =>
type === 'borrowing' ? 'border-loss' : 'border-profit'
export default function Asset(props: Props) {
const { symbol, type } = props
return (
<Text size='xs'>
{symbol}
{type === 'borrowing' && <span className='ml-1 text-loss'>(debt)</span>}
{type === 'lending' && <span className='ml-1 text-profit'>(lent)</span>}
{type === 'vault' && <span className='ml-1 text-profit'>(farm)</span>}
</Text>
)
}

View File

@ -1,50 +0,0 @@
import classNames from 'classnames'
import { HTMLAttributes } from 'react'
import { FormattedNumber } from 'components/FormattedNumber'
import { InfoCircle } from 'components/Icons'
import Text from 'components/Text'
import { BNCoin } from 'types/classes/BNCoin'
import { getAssetByDenom } from 'utils/assets'
interface Props extends HTMLAttributes<HTMLDivElement> {
action: 'buy' | 'deposit' | 'fund'
coins: BNCoin[]
showIcon?: boolean
}
export default function DepositCapMessage(props: Props) {
if (!props.coins.length) return null
return (
<div className={classNames('flex items-start', props.className)}>
{props.showIcon && <InfoCircle width={26} className='mr-5' />}
<div className='flex flex-col gap-2'>
<Text size='sm'>Deposit Cap Reached!</Text>
<Text size='xs' className='text-white/40'>{`Unfortunately you're not able to ${
props.action
} more than the following amount${props.coins.length > 1 ? 's' : ''}:`}</Text>
{props.coins.map((coin) => {
const asset = getAssetByDenom(coin.denom)
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>
)
}

View File

@ -1,59 +0,0 @@
import classNames from 'classnames'
import Text from 'components/Text'
interface Props {
direction: OrderDirection
onChangeDirection: (direction: OrderDirection) => void
asset?: Asset
}
export function DirectionSelect(props: Props) {
const hasAsset = props.asset
const directions: OrderDirection[] = hasAsset ? ['buy', 'sell'] : ['long', 'short']
return (
<div className='flex rounded-sm bg-black/20'>
<Direction
onClick={() => props.onChangeDirection(directions[0])}
direction={directions[0]}
isActive={props.direction === directions[0]}
asset={props.asset}
/>
<Direction
onClick={() => props.onChangeDirection(directions[1])}
direction={directions[1]}
isActive={props.direction === directions[1]}
asset={props.asset}
/>
</div>
)
}
interface DirectionProps {
direction: 'long' | 'short' | 'buy' | 'sell'
isActive: boolean
onClick: () => void
asset?: Asset
}
function Direction(props: DirectionProps) {
const classString = props.direction === 'long' || props.direction === 'buy' ? 'success' : 'error'
return (
<button
className={classNames(
'px-4 py-3 rounded-sm flex-1',
props.isActive && 'border bg-white/10',
`border-${classString}`,
)}
onClick={props.onClick}
>
<Text
className={classNames(
'text-center first-letter:uppercase',
props.isActive ? `text-${classString}` : 'text-white/20',
)}
>
{props.asset ? `${props.direction} ${props.asset.symbol}` : props.direction}
</Text>
</button>
)
}

View File

@ -1,82 +0,0 @@
import classNames from 'classnames'
import { useMemo } from 'react'
import { FormattedNumber } from 'components/FormattedNumber'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { LocalStorageKeys } from 'constants/localStorageKeys'
import { ORACLE_DENOM } from 'constants/oracle'
import useLocalStorage from 'hooks/useLocalStorage'
import usePrices from 'hooks/usePrices'
import { BNCoin } from 'types/classes/BNCoin'
import { getDisplayCurrencies } from 'utils/assets'
import { getCoinValue } from 'utils/formatters'
import { BN } from 'utils/helpers'
interface Props {
coin: BNCoin
className?: string
isApproximation?: boolean
parentheses?: boolean
showZero?: boolean
options?: FormatOptions
}
export default function DisplayCurrency(props: Props) {
const displayCurrencies = getDisplayCurrencies()
const [displayCurrency] = useLocalStorage<string>(
LocalStorageKeys.DISPLAY_CURRENCY,
DEFAULT_SETTINGS.displayCurrency,
)
const { data: prices } = usePrices()
const displayCurrencyAsset = useMemo(
() =>
displayCurrencies.find((asset) => asset.denom === displayCurrency) ?? displayCurrencies[0],
[displayCurrency, displayCurrencies],
)
const isUSD = displayCurrencyAsset.id === 'USD'
const amount = useMemo(() => {
const coinValue = getCoinValue(props.coin, prices)
if (displayCurrency === ORACLE_DENOM) return coinValue.toNumber()
const displayDecimals = displayCurrencyAsset.decimals
const displayPrice = getCoinValue(
BNCoin.fromDenomAndBigNumber(displayCurrency, BN(1).shiftedBy(displayDecimals)),
prices,
)
return coinValue.div(displayPrice).toNumber()
}, [displayCurrency, displayCurrencyAsset.decimals, prices, props.coin])
const isLessThanACent = (isUSD && amount < 0.01 && amount > 0) || (amount === 0 && props.showZero)
const smallerThanPrefix = isLessThanACent ? '< ' : ''
const prefix = isUSD
? `${props.isApproximation ? '~ ' : smallerThanPrefix}$`
: `${props.isApproximation ? '~ ' : ''}`
const suffix = isUSD
? ''
: ` ${displayCurrencyAsset.symbol ? ` ${displayCurrencyAsset.symbol}` : ''}`
return (
<FormattedNumber
className={classNames(
props.className,
props.parentheses && 'before:content-["("] after:content-[")"]',
)}
amount={isLessThanACent ? 0.01 : amount}
options={{
minDecimals: isUSD ? 2 : 0,
maxDecimals: 2,
abbreviated: true,
prefix,
suffix,
...props.options,
}}
animate
/>
)
}

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

@ -1,9 +0,0 @@
<svg viewBox="0 0 13 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M1.8335 6.00065H11.1668M11.1668 6.00065L6.50016 1.33398M11.1668 6.00065L6.50016 10.6673"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>

Before

Width:  |  Height:  |  Size: 291 B

View File

@ -1,68 +0,0 @@
// @index(['./*.svg'], f => `export { default as ${f.name} } from 'components/Icons/${f.name}.svg'`)
export { default as Account } from 'components/Icons/Account.svg'
export { default as AccountArrowDown } from 'components/Icons/AccountArrowDown.svg'
export { default as ArrowChartLineUp } from 'components/Icons/ArrowChartLineUp.svg'
export { default as ArrowCircle } from 'components/Icons/ArrowCircle.svg'
export { default as ArrowCircledTopRight } from 'components/Icons/ArrowCircledTopRight.svg'
export { default as ArrowDownLine } from 'components/Icons/ArrowDownLine.svg'
export { default as ArrowRight } from 'components/Icons/ArrowRight.svg'
export { default as ArrowUpLine } from 'components/Icons/ArrowUpLine.svg'
export { default as Chain } from 'components/Icons/Chain.svg'
export { default as Check } from 'components/Icons/Check.svg'
export { default as CheckCircled } from 'components/Icons/CheckCircled.svg'
export { default as ChevronDown } from 'components/Icons/ChevronDown.svg'
export { default as ChevronLeft } from 'components/Icons/ChevronLeft.svg'
export { default as ChevronRight } from 'components/Icons/ChevronRight.svg'
export { default as ChevronUp } from 'components/Icons/ChevronUp.svg'
export { default as Circle } from 'components/Icons/Circle.svg'
export { default as Coins } from 'components/Icons/Coins.svg'
export { default as CoinsSwap } from 'components/Icons/CoinsSwap.svg'
export { default as Compass } from 'components/Icons/Compass.svg'
export { default as Copy } from 'components/Icons/Copy.svg'
export { default as Cross } from 'components/Icons/Cross.svg'
export { default as CrossCircled } from 'components/Icons/CrossCircled.svg'
export { default as Enter } from 'components/Icons/Enter.svg'
export { default as ExclamationMarkCircled } from 'components/Icons/ExclamationMarkCircled.svg'
export { default as ExclamationMarkTriangle } from 'components/Icons/ExclamationMarkTriangle.svg'
export { default as ExternalLink } from 'components/Icons/ExternalLink.svg'
export { default as Flag } from 'components/Icons/Flag.svg'
export { default as Gear } from 'components/Icons/Gear.svg'
export { default as GridGlobe } from 'components/Icons/GridGlobe.svg'
export { default as GridHole } from 'components/Icons/GridHole.svg'
export { default as GridLandscape } from 'components/Icons/GridLandscape.svg'
export { default as GridPlanet } from 'components/Icons/GridPlanet.svg'
export { default as GridTire } from 'components/Icons/GridTire.svg'
export { default as GridWeb } from 'components/Icons/GridWeb.svg'
export { default as HandCoins } from 'components/Icons/HandCoins.svg'
export { default as Heart } from 'components/Icons/Heart.svg'
export { default as InfoCircle } from 'components/Icons/InfoCircle.svg'
export { default as LockLocked } from 'components/Icons/LockLocked.svg'
export { default as LockUnlocked } from 'components/Icons/LockUnlocked.svg'
export { default as Logo } from 'components/Icons/Logo.svg'
export { default as Luggage } from 'components/Icons/Luggage.svg'
export { default as MarsProtocol } from 'components/Icons/MarsProtocol.svg'
export { default as Osmo } from 'components/Icons/Osmo.svg'
export { default as OverlayMark } from 'components/Icons/OverlayMark.svg'
export { default as Plus } from 'components/Icons/Plus.svg'
export { default as PlusCircled } from 'components/Icons/PlusCircled.svg'
export { default as PlusSquared } from 'components/Icons/PlusSquared.svg'
export { default as PythLogoType } from 'components/Icons/PythLogoType.svg'
export { default as Questionmark } from 'components/Icons/Questionmark.svg'
export { default as ReceiptCheck } from 'components/Icons/ReceiptCheck.svg'
export { default as Scale } from 'components/Icons/Scale.svg'
export { default as Search } from 'components/Icons/Search.svg'
export { default as Shield } from 'components/Icons/Shield.svg'
export { default as SortAsc } from 'components/Icons/SortAsc.svg'
export { default as SortDesc } from 'components/Icons/SortDesc.svg'
export { default as SortNone } from 'components/Icons/SortNone.svg'
export { default as StarFilled } from 'components/Icons/StarFilled.svg'
export { default as StarOutlined } from 'components/Icons/StarOutlined.svg'
export { default as Subtract } from 'components/Icons/Subtract.svg'
export { default as SwapIcon } from 'components/Icons/SwapIcon.svg'
export { default as ThreeDots } from 'components/Icons/ThreeDots.svg'
export { default as TooltipArrow } from 'components/Icons/TooltipArrow.svg'
export { default as TrashBin } from 'components/Icons/TrashBin.svg'
export { default as Twitter } from 'components/Icons/Twitter.svg'
export { default as VerticalThreeLine } from 'components/Icons/VerticalThreeLine.svg'
export { default as Wallet } from 'components/Icons/Wallet.svg'
// @endindex

View File

@ -1,6 +1,6 @@
import { useEffect } from 'react' import { useEffect } from 'react'
import { Enter, InfoCircle } from 'components/Icons' import { Enter, InfoCircle } from 'components/common/Icons'
import useAlertDialog from 'hooks/useAlertDialog' import useAlertDialog from 'hooks/useAlertDialog'
interface Props { interface Props {

View File

@ -1,13 +1,14 @@
import { useCallback, useMemo } from 'react' import { useCallback, useMemo } from 'react'
import { useLocation, useNavigate, useParams, useSearchParams } from 'react-router-dom' import { useLocation, useNavigate, useParams, useSearchParams } from 'react-router-dom'
import AssetBalanceRow from 'components/Asset/AssetBalanceRow' import AssetBalanceRow from 'components/common/assets/AssetBalanceRow'
import { ArrowRight, ExclamationMarkCircled } from 'components/Icons' import { ArrowRight, ExclamationMarkCircled } from 'components/common/Icons'
import AccountDeleteAlertDialog from 'components/Modals/Account/AccountDeleteAlertDialog' import AccountDeleteAlertDialog from 'components/Modals/Account/AccountDeleteAlertDialog'
import Text from 'components/Text' import Text from 'components/common/Text'
import useAllAssets from 'hooks/assets/useAllAssets'
import useStore from 'store' import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { getAssetByDenom } from 'utils/assets' import { byDenom } from 'utils/array'
import { combineBNCoins } from 'utils/parsers' import { combineBNCoins } from 'utils/parsers'
import { getPage, getRoute } from 'utils/route' import { getPage, getRoute } from 'utils/route'
@ -31,7 +32,7 @@ function AccountDeleteModal(props: Props) {
const { address } = useParams() const { address } = useParams()
const { debts, vaults, id: accountId } = modal || {} const { debts, vaults, id: accountId } = modal || {}
const [searchParams] = useSearchParams() const [searchParams] = useSearchParams()
const assets = useAllAssets()
const closeDeleteAccountModal = useCallback(() => { const closeDeleteAccountModal = useCallback(() => {
useStore.setState({ accountDeleteModal: null }) useStore.setState({ accountDeleteModal: null })
}, []) }, [])
@ -117,7 +118,7 @@ function AccountDeleteModal(props: Props) {
<div className='flex flex-col w-full gap-4 py-4 overflow-y-scroll max-h-100 scrollbar-hide'> <div className='flex flex-col w-full gap-4 py-4 overflow-y-scroll max-h-100 scrollbar-hide'>
{depositsAndLends.map((position, index) => { {depositsAndLends.map((position, index) => {
const coin = BNCoin.fromDenomAndBigNumber(position.denom, position.amount) const coin = BNCoin.fromDenomAndBigNumber(position.denom, position.amount)
const asset = getAssetByDenom(position.denom) const asset = assets.find(byDenom(position.denom))
if (!asset) return null if (!asset) return null
return <AssetBalanceRow key={index} asset={asset} coin={coin} /> return <AssetBalanceRow key={index} asset={asset} coin={coin} />
})} })}

View File

@ -1,9 +1,9 @@
import { useCallback, useMemo, useState } from 'react' import { useCallback, useMemo, useState } from 'react'
import AssetSelectTable from 'components/Modals/AssetsSelect/AssetSelectTable' import AssetsSelect from 'components/Modals/AssetsSelect'
import SearchBar from 'components/SearchBar' import SearchBar from 'components/common/SearchBar'
import Text from 'components/Text' import Text from 'components/common/Text'
import useMarketBorrowings from 'hooks/useMarketBorrowings' import useMarketBorrowings from 'hooks/markets/useMarketBorrowings'
import useStore from 'store' import useStore from 'store'
interface Props { interface Props {
@ -89,11 +89,11 @@ export default function AddVaultAssetsModalContent(props: Props) {
Leverage will be set at 50% for both assets by default Leverage will be set at 50% for both assets by default
</Text> </Text>
</div> </div>
<AssetSelectTable <AssetsSelect
isBorrow={true}
assets={poolAssets} assets={poolAssets}
onChangeSelected={onChangePoolDenoms} onChangeSelected={onChangePoolDenoms}
selectedDenoms={selectedPoolDenoms} selectedDenoms={selectedPoolDenoms}
isBorrow
/> />
<div className='p-4'> <div className='p-4'>
<Text>Assets not in the liquidity pool</Text> <Text>Assets not in the liquidity pool</Text>
@ -102,11 +102,11 @@ export default function AddVaultAssetsModalContent(props: Props) {
these assets below. these assets below.
</Text> </Text>
</div> </div>
<AssetSelectTable <AssetsSelect
isBorrow={true}
assets={stableAssets} assets={stableAssets}
onChangeSelected={onChangeOtherDenoms} onChangeSelected={onChangeOtherDenoms}
selectedDenoms={selectedOtherDenoms} selectedDenoms={selectedOtherDenoms}
isBorrow
/> />
</div> </div>
</> </>

View File

@ -1,10 +1,10 @@
import { useCallback, useState } from 'react' import { useCallback, useState } from 'react'
import Button from 'components/Button' import Button from 'components/common/Button'
import { CircularProgress } from 'components/CircularProgress' import { CircularProgress } from 'components/common/CircularProgress'
import Modal from 'components/Modal' import Modal from 'components/Modals/Modal'
import AddVaultAssetsModalContent from 'components/Modals/AddVaultAssets/AddVaultBorrowAssetsModalContent' import AddVaultAssetsModalContent from 'components/Modals/AddVaultAssets/AddVaultBorrowAssetsModalContent'
import Text from 'components/Text' import Text from 'components/common/Text'
import useStore from 'store' import useStore from 'store'
export default function AddVaultBorrowAssetsModal() { export default function AddVaultBorrowAssetsModal() {

View File

@ -1,4 +1,4 @@
import { Enter } from 'components/Icons' import { Enter } from 'components/common/Icons'
export function NoIcon() { export function NoIcon() {
return ( return (

View File

@ -1,10 +1,10 @@
import classNames from 'classnames' import classNames from 'classnames'
import Button from 'components/Button' import Button from 'components/common/Button'
import Checkbox from 'components/Checkbox' import Checkbox from 'components/common/Checkbox'
import Modal from 'components/Modal' import Modal from 'components/Modals/Modal'
import { NoIcon, YesIcon } from 'components/Modals/AlertDialog/ButtonIcons' import { NoIcon, YesIcon } from 'components/Modals/AlertDialog/ButtonIcons'
import Text from 'components/Text' import Text from 'components/common/Text'
import useAlertDialog from 'hooks/useAlertDialog' import useAlertDialog from 'hooks/useAlertDialog'
import useToggle from 'hooks/useToggle' import useToggle from 'hooks/useToggle'

View File

@ -1,22 +1,23 @@
import { useCallback, useState } from 'react' import { useCallback, useState } from 'react'
import CurrentAccountSummary from 'components/Account/CurrentAccountSummary' import Modal from 'components/Modals/Modal'
import AssetImage from 'components/Asset/AssetImage' import CurrentAccountSummary from 'components/account/CurrentAccountSummary'
import Button from 'components/Button' import Button from 'components/common/Button'
import Card from 'components/Card' import Card from 'components/common/Card'
import Divider from 'components/Divider' import Divider from 'components/common/Divider'
import { ArrowRight } from 'components/Icons' import { ArrowRight } from 'components/common/Icons'
import Modal from 'components/Modal' import Text from 'components/common/Text'
import Text from 'components/Text' import TokenInputWithSlider from 'components/common/TokenInput/TokenInputWithSlider'
import TokenInputWithSlider from 'components/TokenInput/TokenInputWithSlider' import AssetImage from 'components/common/assets/AssetImage'
import { BN_ZERO } from 'constants/math' import { BN_ZERO } from 'constants/math'
import { BNCoin } from 'types/classes/BNCoin'
import { byDenom } from 'utils/array' import { byDenom } from 'utils/array'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
interface Props { interface Props {
asset: Asset asset: Asset
title: string title: string
coinBalances: Coin[] coinBalances: BNCoin[]
actionButtonText: string actionButtonText: string
contentHeader?: JSX.Element contentHeader?: JSX.Element
onClose: () => void onClose: () => void

View File

@ -1,154 +0,0 @@
import {
flexRender,
getCoreRowModel,
getSortedRowModel,
RowSelectionState,
SortingState,
useReactTable,
} from '@tanstack/react-table'
import classNames from 'classnames'
import { useEffect, useMemo, useState } from 'react'
import { SortAsc, SortDesc, SortNone } from 'components/Icons'
import useAssetTableColumns from 'components/Modals/AssetsSelect/useAssetTableColumns'
import Text from 'components/Text'
import useMarketAssets from 'hooks/useMarketAssets'
import useStore from 'store'
import { byDenom } from 'utils/array'
interface Props {
assets: Asset[] | BorrowAsset[]
selectedDenoms: string[]
onChangeSelected: (denoms: string[]) => void
isBorrow: boolean
}
export default function AssetSelectTable(props: Props) {
const { data: markets } = useMarketAssets()
const defaultSelected = useMemo(() => {
const assets = props.assets as BorrowAsset[]
return assets.reduce(
(acc, asset, index) => {
if (props.selectedDenoms?.includes(asset.denom)) {
acc[index] = true
}
return acc
},
{} as { [key: number]: boolean },
)
}, [props.selectedDenoms, props.assets])
const [sorting, setSorting] = useState<SortingState>([{ id: 'symbol', desc: false }])
const [selected, setSelected] = useState<RowSelectionState>(defaultSelected)
const balances = useStore((s) => s.balances)
const columns = useAssetTableColumns(props.isBorrow)
const tableData: AssetTableRow[] = useMemo(() => {
return props.assets.map((asset) => {
const balancesForAsset = balances.find(byDenom(asset.denom))
return {
asset,
balance: balancesForAsset?.amount ?? '0',
market: markets.find((market) => market.denom === asset.denom),
}
})
}, [balances, props.assets, markets])
const table = useReactTable({
data: tableData,
columns,
state: {
sorting,
rowSelection: selected,
},
onRowSelectionChange: setSelected,
onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
})
useEffect(() => {
const newSelectedDenoms = props.assets
.filter((_, index) => selected[index])
.map((asset) => asset.denom)
if (
props.selectedDenoms.length === newSelectedDenoms.length &&
newSelectedDenoms.every((denom) => props.selectedDenoms.includes(denom))
)
return
props.onChangeSelected(newSelectedDenoms)
}, [selected, props])
return (
<table className='w-full'>
<thead className='border-b border-white/10'>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<th
key={header.id}
onClick={header.column.getToggleSortingHandler()}
className={classNames(
'p-2',
header.column.getCanSort() && 'hover:cursor-pointer',
header.id === 'symbol' ? 'text-left' : 'text-right',
)}
>
<div
className={classNames(
'flex',
header.id === 'symbol' ? 'justify-start' : 'justify-end',
'align-center',
)}
>
<span className='w-6 h-6 text-white'>
{header.column.getCanSort()
? {
asc: <SortAsc />,
desc: <SortDesc />,
false: <SortNone />,
}[header.column.getIsSorted() as string] ?? null
: null}
</span>
<Text
tag='span'
size='sm'
className='flex items-center font-normal text-white/40'
>
{flexRender(header.column.columnDef.header, header.getContext())}
</Text>
</div>
</th>
)
})}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map((row) => {
return (
<tr
key={row.id}
className='hover:cursor-pointer text-white/60'
onClick={() => row.toggleSelected()}
>
{row.getVisibleCells().map((cell) => {
return (
<td
key={cell.id}
className={classNames(
cell.column.id === 'select' ? `` : 'text-right',
'px-4 py-3',
)}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
)
})}
</tr>
)
})}
</tbody>
</table>
)
}

View File

@ -0,0 +1,55 @@
import { Row } from '@tanstack/react-table'
import Checkbox from 'components/common/Checkbox'
import Text from 'components/common/Text'
import AssetImage from 'components/common/assets/AssetImage'
import AssetRate from 'components/common/assets/AssetRate'
export const ASSET_META = { id: 'name', header: 'Asset', accessorKey: 'asset.symbol' }
interface Props {
row: Row<AssetTableRow>
}
function isBorrowAsset(object?: any): object is BorrowAsset {
if (!object) return false
return 'borrowRate' in object
}
export default function Asset(props: Props) {
const { row } = props
const asset = row.original.asset
const market = row.original.market
const isBorrow = isBorrowAsset(asset)
const showRate = !isBorrow && market?.borrowEnabled
const apy = isBorrow ? market?.apy.borrow : market?.apy.deposit
return (
<div className='flex items-center'>
<Checkbox
name={`asset-${asset.id.toLowerCase()}`}
checked={row.getIsSelected()}
onChange={row.getToggleSelectedHandler()}
noMouseEvents
/>
<AssetImage asset={asset} size={24} className='ml-4' />
<div className='ml-2 text-left'>
<Text size='sm' className='mb-0.5 text-white'>
{asset.symbol}
</Text>
{showRate && market ? (
<AssetRate
rate={apy ?? 0}
isEnabled={market.borrowEnabled}
className='text-xs'
type='apy'
orientation='rtl'
suffix
/>
) : (
<Text size='xs'>{asset.name}</Text>
)}
</div>
</div>
)
}

View File

@ -0,0 +1,38 @@
import { Row } from '@tanstack/react-table'
import DisplayCurrency from 'components/common/DisplayCurrency'
import { FormattedNumber } from 'components/common/FormattedNumber'
import { BN_ZERO } from 'constants/math'
import { BNCoin } from 'types/classes/BNCoin'
import { demagnify } from 'utils/formatters'
import { BN } from 'utils/helpers'
export const BALANCE_META = { id: 'value', header: 'Balance', accessorKey: 'value' }
interface Props {
row: Row<AssetTableRow>
}
export const valueSortingFn = (a: Row<AssetTableRow>, b: Row<AssetTableRow>): number => {
const valueA = a.original.value ?? BN_ZERO
const valueB = b.original.value ?? BN_ZERO
return valueA.minus(valueB).toNumber()
}
export default function Balance(props: Props) {
const { row } = props
const asset = row.original.asset
const balance = BN(row.original.balance ?? '0')
const coin = BNCoin.fromDenomAndBigNumber(asset.denom, balance)
return (
<div className='flex flex-wrap items-center'>
<DisplayCurrency coin={coin} className='mb-0.5 w-full text-white' />
<FormattedNumber
className='w-full text-xs'
options={{ minDecimals: 2, maxDecimals: asset.decimals }}
amount={demagnify(balance, asset)}
/>
</div>
)
}

View File

@ -0,0 +1,25 @@
import { Row } from '@tanstack/react-table'
import Text from 'components/common/Text'
import { formatPercent } from 'utils/formatters'
export const BORROW_RATE_META = {
id: 'asset.borrowRate',
header: 'BorrowRate',
accessorKey: 'asset.borrowRate',
}
interface Props {
row: Row<AssetTableRow>
}
export default function BorrowRate(props: Props) {
const { row } = props
const asset = row.original.asset as BorrowAsset
return (
<Text size='sm' className='mb-0.5 text-white'>
{formatPercent(asset.borrowRate ?? 0)}
</Text>
)
}

View File

@ -0,0 +1,30 @@
import { ColumnDef } from '@tanstack/react-table'
import { useMemo } from 'react'
import Asset, { ASSET_META } from 'components/Modals/AssetsSelect/Columns/Asset'
import Balance, {
BALANCE_META,
valueSortingFn,
} from 'components/Modals/AssetsSelect/Columns/Balance'
import BorrowRate, { BORROW_RATE_META } from 'components/Modals/AssetsSelect/Columns/BorrowRate'
export default function useAssetSelectColumns(isBorrow?: boolean) {
return useMemo<ColumnDef<AssetTableRow>[]>(() => {
return [
{
...ASSET_META,
cell: ({ row }) => <Asset row={row} />,
},
isBorrow
? {
...BORROW_RATE_META,
cell: ({ row }) => <BorrowRate row={row} />,
}
: {
...BALANCE_META,
cell: ({ row }) => <Balance row={row} />,
sortingFn: valueSortingFn,
},
]
}, [isBorrow])
}

View File

@ -0,0 +1,81 @@
import { RowSelectionState } from '@tanstack/react-table'
import { useEffect, useMemo, useState } from 'react'
import useAssetSelectColumns from 'components/Modals/AssetsSelect/Columns/useAssetSelectColumns'
import Table from 'components/common/Table'
import useGetCoinValue from 'hooks/assets/useGetCoinValue'
import useMarketAssets from 'hooks/markets/useMarketAssets'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { byDenom } from 'utils/array'
import { BN } from 'utils/helpers'
interface Props {
assets: Asset[]
onChangeSelected: (selected: string[]) => void
selectedDenoms: string[]
isBorrow?: boolean
}
export default function AssetsSelect(props: Props) {
const { assets, onChangeSelected, selectedDenoms, isBorrow } = props
const columns = useAssetSelectColumns(isBorrow)
const { data: markets } = useMarketAssets()
const getCoinValue = useGetCoinValue()
const defaultSelected = useMemo(() => {
const selectableAssets = assets
return selectableAssets.reduce(
(acc, asset, index) => {
if (selectedDenoms?.includes(asset.denom)) {
acc[index] = true
}
return acc
},
{} as { [key: number]: boolean },
)
}, [selectedDenoms, assets])
const [selected, setSelected] = useState<RowSelectionState>(defaultSelected)
const balances = useStore((s) => s.balances)
const tableData: AssetTableRow[] = useMemo(() => {
return assets.map((asset) => {
const balancesForAsset = balances.find(byDenom(asset.denom))
const coin = BNCoin.fromDenomAndBigNumber(asset.denom, BN(balancesForAsset?.amount ?? '0'))
const value = getCoinValue(coin)
return {
asset,
balance: balancesForAsset?.amount ?? '0',
value,
market: markets.find((market) => market.denom === asset.denom),
}
})
}, [balances, assets, markets, getCoinValue])
useEffect(() => {
const selectedAssets = assets.filter((_, index) => selected[index])
const newSelectedDenoms = selectedAssets
.sort((a, b) => a.symbol.localeCompare(b.symbol))
.map((asset) => asset.denom)
if (
selectedDenoms.length === newSelectedDenoms.length &&
newSelectedDenoms.every((denom) => selectedDenoms.includes(denom))
)
return
onChangeSelected(newSelectedDenoms)
}, [selected, props, assets, selectedDenoms, onChangeSelected])
return (
<Table
title='Assets'
hideCard={true}
columns={columns}
data={tableData}
initialSorting={[{ id: isBorrow ? 'asset.borrowRate' : 'value', desc: !isBorrow }]}
setRowSelection={setSelected}
selectedRows={selected}
/>
)
}

View File

@ -1,96 +0,0 @@
import { ColumnDef } from '@tanstack/react-table'
import React from 'react'
import AssetImage from 'components/Asset/AssetImage'
import AssetRate from 'components/Asset/AssetRate'
import Checkbox from 'components/Checkbox'
import DisplayCurrency from 'components/DisplayCurrency'
import { FormattedNumber } from 'components/FormattedNumber'
import Text from 'components/Text'
import { BNCoin } from 'types/classes/BNCoin'
import { getAssetByDenom } from 'utils/assets'
import { demagnify, formatPercent } from 'utils/formatters'
function showBorrowRate(data: AssetTableRow[]) {
const assetData = data.length && (data[0].asset as BorrowAsset)
return !!(assetData && assetData?.borrowRate)
}
export default function useAssetTableColumns(isBorrow: boolean) {
return React.useMemo<ColumnDef<AssetTableRow>[]>(
() => [
{
header: 'Asset',
accessorKey: 'symbol',
id: 'symbol',
cell: ({ row }) => {
const asset = getAssetByDenom(row.original.asset.denom) as Asset
const market = row.original.market
const borrowAsset = row.original.asset as BorrowAsset
const showRate = !borrowAsset?.borrowRate
const apy = isBorrow ? market?.apy.borrow : market?.apy.deposit
return (
<div className='flex items-center'>
<Checkbox
name={`asset-${asset.id.toLowerCase()}`}
checked={row.getIsSelected()}
onChange={row.getToggleSelectedHandler()}
noMouseEvents
/>
<AssetImage asset={asset} size={24} className='ml-4' />
<div className='ml-2 text-left'>
<Text size='sm' className='mb-0.5 text-white'>
{asset.symbol}
</Text>
{showRate && market ? (
<AssetRate
rate={apy ?? 0}
isEnabled={market.borrowEnabled}
className='text-xs'
type='apy'
orientation='rtl'
suffix
/>
) : (
<Text size='xs'>{asset.name}</Text>
)}
</div>
</div>
)
},
},
{
id: 'details',
header: (data) => {
const tableData = data.table.options.data as AssetTableRow[]
if (showBorrowRate(tableData)) return 'Borrow Rate'
return 'Balance'
},
cell: ({ row }) => {
const asset = row.original.asset as BorrowAsset
const balance = row.original.balance
if (asset?.borrowRate)
return (
<Text size='sm' className='mb-0.5 text-white'>
{formatPercent(asset.borrowRate ?? 0)}
</Text>
)
if (!balance) return null
const coin = new BNCoin({ denom: row.original.asset.denom, amount: balance })
return (
<div className='flex flex-wrap items-center'>
<DisplayCurrency coin={coin} className='mb-0.5 w-full text-white' />
<FormattedNumber
className='w-full text-xs'
options={{ minDecimals: 2, maxDecimals: asset.decimals }}
amount={demagnify(balance, asset)}
/>
</div>
)
},
},
],
[isBorrow],
)
}

View File

@ -1,24 +1,24 @@
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { useCallback, useEffect, useMemo, useState } from 'react' import { useCallback, useEffect, useMemo, useState } from 'react'
import AccountSummary from 'components/Account/AccountSummary' import AccountSummary from 'components/account/AccountSummary'
import AssetImage from 'components/Asset/AssetImage' import AssetImage from 'components/common/assets/AssetImage'
import Button from 'components/Button' import Button from 'components/common/Button'
import Card from 'components/Card' import Card from 'components/common/Card'
import DisplayCurrency from 'components/DisplayCurrency' import DisplayCurrency from 'components/common/DisplayCurrency'
import Divider from 'components/Divider' import Divider from 'components/common/Divider'
import { FormattedNumber } from 'components/FormattedNumber' import { FormattedNumber } from 'components/common/FormattedNumber'
import { ArrowRight, InfoCircle } from 'components/Icons' import { ArrowRight, InfoCircle } from 'components/common/Icons'
import Modal from 'components/Modal' import Switch from 'components/common/Switch'
import Switch from 'components/Switch' import Text from 'components/common/Text'
import Text from 'components/Text' import TitleAndSubCell from 'components/common/TitleAndSubCell'
import TitleAndSubCell from 'components/TitleAndSubCell' import TokenInputWithSlider from 'components/common/TokenInput/TokenInputWithSlider'
import TokenInputWithSlider from 'components/TokenInput/TokenInputWithSlider' import Modal from 'components/Modals/Modal'
import { BN_ZERO } from 'constants/math' import { BN_ZERO } from 'constants/math'
import useCurrentAccount from 'hooks/accounts/useCurrentAccount'
import useMarketAssets from 'hooks/markets/useMarketAssets'
import useAutoLend from 'hooks/useAutoLend' import useAutoLend from 'hooks/useAutoLend'
import useCurrentAccount from 'hooks/useCurrentAccount'
import useHealthComputer from 'hooks/useHealthComputer' import useHealthComputer from 'hooks/useHealthComputer'
import useMarketAssets from 'hooks/useMarketAssets'
import useToggle from 'hooks/useToggle' import useToggle from 'hooks/useToggle'
import { useUpdatedAccount } from 'hooks/useUpdatedAccount' import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
import { getDepositAndLendCoinsToSpend } from 'hooks/useUpdatedAccount/functions' import { getDepositAndLendCoinsToSpend } from 'hooks/useUpdatedAccount/functions'

View File

@ -1,4 +1,4 @@
import AccountFundContent from 'components/Account/AccountFund/AccountFundContent' import AccountFundContent from 'components/account/AccountFund/AccountFundContent'
import useStore from 'store' import useStore from 'store'
interface Props { interface Props {

View File

@ -1,14 +1,15 @@
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import Button from 'components/Button' import Button from 'components/common/Button'
import Divider from 'components/Divider' import Divider from 'components/common/Divider'
import { ArrowRight } from 'components/Icons' import { ArrowRight } from 'components/common/Icons'
import Switch from 'components/Switch' import Switch from 'components/common/Switch'
import Text from 'components/Text' import Text from 'components/common/Text'
import TokenInputWithSlider from 'components/TokenInput/TokenInputWithSlider' import TokenInputWithSlider from 'components/common/TokenInput/TokenInputWithSlider'
import { ASSETS } from 'constants/assets'
import { BN_ZERO } from 'constants/math' import { BN_ZERO } from 'constants/math'
import useAllAssets from 'hooks/assets/useAllAssets'
import useMarketEnabledAssets from 'hooks/assets/useMarketEnabledAssets'
import useHealthComputer from 'hooks/useHealthComputer' import useHealthComputer from 'hooks/useHealthComputer'
import useToggle from 'hooks/useToggle' import useToggle from 'hooks/useToggle'
import { useUpdatedAccount } from 'hooks/useUpdatedAccount' import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
@ -16,7 +17,6 @@ import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { cloneAccount, getMergedBalancesForAsset, removeDepositsAndLends } from 'utils/accounts' import { cloneAccount, getMergedBalancesForAsset, removeDepositsAndLends } from 'utils/accounts'
import { byDenom } from 'utils/array' import { byDenom } from 'utils/array'
import { getEnabledMarketAssets } from 'utils/assets'
interface Props { interface Props {
account: Account account: Account
@ -24,8 +24,9 @@ interface Props {
export default function WithdrawFromAccount(props: Props) { export default function WithdrawFromAccount(props: Props) {
const { account } = props const { account } = props
const assets = useAllAssets()
const defaultAsset = const defaultAsset =
ASSETS.find(byDenom(account.deposits[0]?.denom || account.lends[0]?.denom)) ?? ASSETS[0] assets.find(byDenom(account.deposits[0]?.denom || account.lends[0]?.denom)) ?? assets[0]
const withdraw = useStore((s) => s.withdraw) const withdraw = useStore((s) => s.withdraw)
const [withdrawWithBorrowing, setWithdrawWithBorrowing] = useToggle() const [withdrawWithBorrowing, setWithdrawWithBorrowing] = useToggle()
const [currentAsset, setCurrentAsset] = useState(defaultAsset) const [currentAsset, setCurrentAsset] = useState(defaultAsset)
@ -35,7 +36,8 @@ export default function WithdrawFromAccount(props: Props) {
const accountClone = cloneAccount(account) const accountClone = cloneAccount(account)
const borrowAccount = removeDepositsAndLends(accountClone, currentAsset.denom) const borrowAccount = removeDepositsAndLends(accountClone, currentAsset.denom)
const { computeMaxBorrowAmount } = useHealthComputer(borrowAccount) const { computeMaxBorrowAmount } = useHealthComputer(borrowAccount)
const balances = getMergedBalancesForAsset(account, getEnabledMarketAssets()) const marketEnabledAssets = useMarketEnabledAssets()
const balances = getMergedBalancesForAsset(account, marketEnabledAssets)
const maxWithdrawAmount = computeMaxWithdrawAmount(currentAsset.denom) const maxWithdrawAmount = computeMaxWithdrawAmount(currentAsset.denom)
const maxWithdrawWithBorrowAmount = computeMaxBorrowAmount(currentAsset.denom, 'wallet').plus( const maxWithdrawWithBorrowAmount = computeMaxBorrowAmount(currentAsset.denom, 'wallet').plus(
maxWithdrawAmount, maxWithdrawAmount,
@ -46,10 +48,12 @@ export default function WithdrawFromAccount(props: Props) {
const max = withdrawWithBorrowing ? maxWithdrawWithBorrowAmount : maxWithdrawAmount const max = withdrawWithBorrowing ? maxWithdrawWithBorrowAmount : maxWithdrawAmount
const accountDeposit = account.deposits.find(byDenom(currentAsset.denom))?.amount ?? BN_ZERO const accountDeposit = account.deposits.find(byDenom(currentAsset.denom))?.amount ?? BN_ZERO
const shouldReclaim = const accountLent = account.lends.find(byDenom(currentAsset.denom))?.amount ?? BN_ZERO
amount.isGreaterThan(accountDeposit) && !withdrawWithBorrowing && currentAsset.isAutoLendEnabled const shouldReclaim = amount.isGreaterThan(accountDeposit) && !accountLent.isZero()
const reclaimAmount = shouldReclaim ? amount.minus(accountDeposit) : BN_ZERO const isReclaimingMaxAmount = accountLent.isLessThanOrEqualTo(amount.minus(accountDeposit))
const isReclaimingMaxAmount = maxWithdrawAmount.isEqualTo(amount) const reclaimAmount = isReclaimingMaxAmount
? amount
: accountLent.minus(amount).minus(accountDeposit)
function onChangeAmount(val: BigNumber) { function onChangeAmount(val: BigNumber) {
setAmount(val) setAmount(val)
@ -65,7 +69,8 @@ export default function WithdrawFromAccount(props: Props) {
const borrow = !debtAmount.isZero() const borrow = !debtAmount.isZero()
? [BNCoin.fromDenomAndBigNumber(currentAsset.denom, debtAmount)] ? [BNCoin.fromDenomAndBigNumber(currentAsset.denom, debtAmount)]
: [] : []
const reclaims = !reclaimAmount.isZero() const reclaims =
shouldReclaim && !reclaimAmount.isZero()
? [ ? [
BNCoin.fromDenomAndBigNumber(currentAsset.denom, reclaimAmount).toActionCoin( BNCoin.fromDenomAndBigNumber(currentAsset.denom, reclaimAmount).toActionCoin(
isReclaimingMaxAmount, isReclaimingMaxAmount,

View File

@ -1,7 +1,7 @@
import FundWithdrawModalContent from 'components/Modals/FundWithdraw/FundAndWithdrawModalContent' import FundWithdrawModalContent from 'components/Modals/FundWithdraw/FundAndWithdrawModalContent'
import ModalContentWithSummary from 'components/Modals/ModalContentWithSummary' import ModalContentWithSummary from 'components/Modals/ModalContentWithSummary'
import Text from 'components/Text' import Text from 'components/common/Text'
import useAccount from 'hooks/useAccount' import useAccount from 'hooks/accounts/useAccount'
import useAccountId from 'hooks/useAccountId' import useAccountId from 'hooks/useAccountId'
import useStore from 'store' import useStore from 'store'

View File

@ -1,8 +1,8 @@
import { useCallback } from 'react' import { useCallback } from 'react'
import { ChevronRight, Compass, HandCoins, Luggage } from 'components/Icons' import { ChevronRight, Compass, HandCoins, Luggage } from 'components/common/Icons'
import Modal from 'components/Modal' import Modal from 'components/Modals/Modal'
import Text from 'components/Text' import Text from 'components/common/Text'
import useStore from 'store' import useStore from 'store'
import { DocURL } from 'types/enums/docURL' import { DocURL } from 'types/enums/docURL'

View File

@ -1,10 +1,10 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import { mutate } from 'swr' import { mutate } from 'swr'
import Button from 'components/Button' import Button from 'components/common/Button'
import DocsLink from 'components/DocsLink' import DocsLink from 'components/common/DocsLink'
import { ArrowRight } from 'components/Icons' import { ArrowRight } from 'components/common/Icons'
import Text from 'components/Text' import Text from 'components/common/Text'
import useStore from 'store' import useStore from 'store'
export default function CreateAccount() { export default function CreateAccount() {

View File

@ -1,9 +1,9 @@
import React, { useMemo } from 'react' import React, { useMemo } from 'react'
import Button from 'components/Button' import Button from 'components/common/Button'
import { ArrowRight } from 'components/Icons' import { ArrowRight } from 'components/common/Icons'
import LeverageSummary from 'components/Modals/HLS/Deposit/LeverageSummary' import LeverageSummary from 'components/Modals/HLS/Deposit/LeverageSummary'
import TokenInputWithSlider from 'components/TokenInput/TokenInputWithSlider' import TokenInputWithSlider from 'components/common/TokenInput/TokenInputWithSlider'
import { getLeveragedApy } from 'utils/math' import { getLeveragedApy } from 'utils/math'
interface Props { interface Props {

View File

@ -1,6 +1,6 @@
import React, { useMemo } from 'react' import React, { useMemo } from 'react'
import SummaryItems from 'components/SummaryItems' import SummaryItems from 'components/common/SummaryItems'
import useBorrowAsset from 'hooks/useBorrowAsset' import useBorrowAsset from 'hooks/useBorrowAsset'
interface Props { interface Props {

View File

@ -1,8 +1,8 @@
import React from 'react' import React from 'react'
import Button from 'components/Button' import Button from 'components/common/Button'
import { ArrowRight } from 'components/Icons' import { ArrowRight } from 'components/common/Icons'
import TokenInputWithSlider from 'components/TokenInput/TokenInputWithSlider' import TokenInputWithSlider from 'components/common/TokenInput/TokenInputWithSlider'
interface Props { interface Props {
amount: BigNumber amount: BigNumber

View File

@ -1,9 +1,9 @@
import classNames from 'classnames' import classNames from 'classnames'
import React from 'react' import React from 'react'
import Button from 'components/Button' import Button from 'components/common/Button'
import { ArrowRight } from 'components/Icons' import { ArrowRight } from 'components/common/Icons'
import Radio from 'components/Radio' import Radio from 'components/common/Radio'
interface Props { interface Props {
hlsAccounts: Account[] hlsAccounts: Account[]

View File

@ -1,10 +1,11 @@
import classNames from 'classnames' import classNames from 'classnames'
import React from 'react' import React from 'react'
import DisplayCurrency from 'components/DisplayCurrency' import DisplayCurrency from 'components/common/DisplayCurrency'
import { ExclamationMarkTriangle } from 'components/Icons' import { ExclamationMarkTriangle } from 'components/common/Icons'
import Text from 'components/Text' import Text from 'components/common/Text'
import WarningMessages from 'components/WarningMessages' import WarningMessages from 'components/common/WarningMessages'
import useAllAssets from 'hooks/assets/useAllAssets'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { formatAmountWithSymbol, formatLeverage } from 'utils/formatters' import { formatAmountWithSymbol, formatLeverage } from 'utils/formatters'
@ -33,6 +34,7 @@ interface CollateralSubTitleProps {
} }
export function CollateralSubTitle(props: CollateralSubTitleProps) { export function CollateralSubTitle(props: CollateralSubTitleProps) {
const assets = useAllAssets()
if (props.isOpen) return null if (props.isOpen) return null
if (!props.isOpen && props.amount.isZero()) { if (!props.isOpen && props.amount.isZero()) {
@ -50,10 +52,13 @@ export function CollateralSubTitle(props: CollateralSubTitleProps) {
<div className='flex items-center gap-1'> <div className='flex items-center gap-1'>
<WarningMessages messages={props.warningMessages} /> <WarningMessages messages={props.warningMessages} />
<SubTitle <SubTitle
text={formatAmountWithSymbol({ text={formatAmountWithSymbol(
{
denom: props.denom, denom: props.denom,
amount: props.amount.toString(), amount: props.amount.toString(),
})} },
assets,
)}
color={props.warningMessages.length > 0 ? 'text-warning' : ''} color={props.warningMessages.length > 0 ? 'text-warning' : ''}
/> />
</div> </div>

View File

@ -1,7 +1,7 @@
import React from 'react' import React from 'react'
import { FormattedNumber } from 'components/FormattedNumber' import { FormattedNumber } from 'components/common/FormattedNumber'
import Text from 'components/Text' import Text from 'components/common/Text'
interface Props { interface Props {
items: { title: string; amount: number }[] items: { title: string; amount: number }[]

View File

@ -1,10 +1,10 @@
import React from 'react' import React from 'react'
import AmountAndValue from 'components/AmountAndValue' import AmountAndValue from 'components/common/AmountAndValue'
import AssetImage from 'components/Asset/AssetImage' import AssetImage from 'components/common/assets/AssetImage'
import { FormattedNumber } from 'components/FormattedNumber' import { FormattedNumber } from 'components/common/FormattedNumber'
import Container from 'components/Modals/HLS/Deposit/Summary/Container' import Container from 'components/Modals/HLS/Deposit/Summary/Container'
import Text from 'components/Text' import Text from 'components/common/Text'
interface Props { interface Props {
amount: BigNumber amount: BigNumber

View File

@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import Text from 'components/Text' import Text from 'components/common/Text'
interface Props { interface Props {
children: React.ReactNode children: React.ReactNode

View File

@ -1,12 +1,12 @@
import React, { useMemo } from 'react' import React, { useMemo } from 'react'
import DisplayCurrency from 'components/DisplayCurrency' import DisplayCurrency from 'components/common/DisplayCurrency'
import { FormattedNumber } from 'components/FormattedNumber' import { FormattedNumber } from 'components/common/FormattedNumber'
import { InfoCircle } from 'components/Icons' import { InfoCircle } from 'components/common/Icons'
import AprBreakdown from 'components/Modals/HLS/Deposit/Summary/ApyBreakdown' import AprBreakdown from 'components/Modals/HLS/Deposit/Summary/ApyBreakdown'
import Container from 'components/Modals/HLS/Deposit/Summary/Container' import Container from 'components/Modals/HLS/Deposit/Summary/Container'
import Text from 'components/Text' import Text from 'components/common/Text'
import { Tooltip } from 'components/Tooltip' import { Tooltip } from 'components/common/Tooltip'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
interface Props { interface Props {

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