v2.2.0 (#746)
This commit is contained in:
parent
68c60c29df
commit
f6f46d3372
45
.env.example
45
.env.example
@ -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_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
|
||||
CHARTING_LIBRARY_REPOSITORY=github.com/tradingview/charting_library
|
||||
CHARTING_LIBRARY_ACCESS_TOKEN=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
|
||||
CHARTING_LIBRARY_USERNAME=mars-git-demo
|
||||
NEXT_PUBLIC_STRIDE_APRS=https://edge.stride.zone/api/stake-stats
|
||||
CHARTING_LIBRARY_USERNAME=git_username
|
||||
CHARTING_LIBRARY_ACCESS_TOKEN=access_token
|
||||
CHARTING_LIBRARY_REPOSITORY=github.com/tradingview/charting_library/
|
||||
|
||||
NEXT_PUBLIC_PYTH_API=https://mars.rpc.p2p.world/api
|
||||
|
||||
|
@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
src: '/img.jpg',
|
||||
height: 24,
|
||||
width: 24,
|
||||
blurDataURL: 'data:image/png;base64,imagedata',
|
||||
}
|
@ -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),
|
||||
}
|
||||
})
|
@ -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,
|
||||
}
|
||||
})
|
@ -1 +0,0 @@
|
||||
module.exports = {}
|
@ -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
|
@ -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()
|
||||
})
|
||||
})
|
@ -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()
|
||||
})
|
||||
})
|
@ -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'])
|
||||
})
|
||||
})
|
@ -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()
|
||||
})
|
||||
})
|
@ -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()
|
||||
})
|
||||
})
|
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
@ -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)
|
||||
})
|
||||
})
|
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
@ -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()
|
||||
})
|
||||
})
|
||||
})
|
@ -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)$'],
|
||||
}
|
@ -1 +0,0 @@
|
||||
import '@testing-library/jest-dom/extend-expect'
|
31
package.json
31
package.json
@ -1,14 +1,12 @@
|
||||
{
|
||||
"name": "mars-v2-frontend",
|
||||
"version": "2.1.2",
|
||||
"version": "2.2.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "yarn validate-env && next build",
|
||||
"dev": "next dev",
|
||||
"test": "jest",
|
||||
"test:cov": "jest --coverage",
|
||||
"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/",
|
||||
"start": "next start",
|
||||
"validate-env": "node ./validate-env",
|
||||
@ -17,15 +15,15 @@
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.ts*": [
|
||||
"eslint ./src/ ./__tests__/ --fix",
|
||||
"prettier --write ./src/ ./__tests__/"
|
||||
"eslint ./src/ --fix",
|
||||
"prettier --write ./src/"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@cosmjs/cosmwasm-stargate": "^0.31.1",
|
||||
"@cosmjs/cosmwasm-stargate": "^0.32.2",
|
||||
"@delphi-labs/shuttle-react": "^3.10.0",
|
||||
"@keplr-wallet/cosmos": "^0.12.42",
|
||||
"@sentry/nextjs": "^7.84.0",
|
||||
"@sentry/nextjs": "^7.94.1",
|
||||
"@splinetool/react-spline": "^2.2.6",
|
||||
"@splinetool/runtime": "^0.9.521",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
@ -45,40 +43,35 @@
|
||||
"react-draggable": "^4.4.6",
|
||||
"react-helmet-async": "^2.0.3",
|
||||
"react-qr-code": "^2.0.12",
|
||||
"react-router-dom": "^6.17.0",
|
||||
"react-router-dom": "^6.21.3",
|
||||
"react-spring": "^9.7.3",
|
||||
"react-toastify": "^9.1.3",
|
||||
"react-use-clipboard": "^1.0.9",
|
||||
"recharts": "^2.10.1",
|
||||
"sharp": "^0.33.0",
|
||||
"sharp": "^0.33.2",
|
||||
"swr": "^2.2.4",
|
||||
"tailwind-scrollbar-hide": "^1.1.7",
|
||||
"zustand": "^4.4.6"
|
||||
"zustand": "^4.4.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@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/lodash.debounce": "^4.0.9",
|
||||
"@types/lodash.throttle": "^4.1.8",
|
||||
"@types/node": "^20.8.6",
|
||||
"@types/node": "^20.10.6",
|
||||
"@types/react": "18.2.41",
|
||||
"@types/react-dom": "18.2.15",
|
||||
"@types/react-helmet": "^6.1.11",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"babel-jest": "^29.7.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"dotenv-cli": "^7.3.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-next": "^14.0.4",
|
||||
"eslint-plugin-import": "^2.29.0",
|
||||
"husky": "^8.0.3",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"lint-staged": "^15.2.0",
|
||||
"prettier": "^3.0.3",
|
||||
"prettier": "^3.2.4",
|
||||
"prettier-plugin-tailwindcss": "^0.5.6",
|
||||
"shelljs": "^0.8.5",
|
||||
"tailwindcss": "^3.3.3",
|
||||
|
16
public/images/tokens/ntrn.svg
Normal file
16
public/images/tokens/ntrn.svg
Normal 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 |
BIN
public/images/wallets/vectis.png
Normal file
BIN
public/images/wallets/vectis.png
Normal file
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 |
@ -40,6 +40,16 @@
|
||||
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-toolbar-react-widgets__button:hover,
|
||||
[class^='button-']:hover:before {
|
||||
|
@ -3,21 +3,25 @@ import { getCreditManagerQueryClient } from 'api/cosmwasm-client'
|
||||
import getDepositedVaults from 'api/vaults/getDepositedVaults'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
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'))
|
||||
|
||||
const creditManagerQueryClient = await getCreditManagerQueryClient()
|
||||
const creditManagerQueryClient = await getCreditManagerQueryClient(chainConfig)
|
||||
|
||||
const accountPosition: Positions = await cacheFn(
|
||||
() => creditManagerQueryClient.positions({ accountId: accountId }),
|
||||
positionsCache,
|
||||
`account/${accountId}`,
|
||||
`${chainConfig.id}/account/${accountId}`,
|
||||
)
|
||||
|
||||
const accountKind = await creditManagerQueryClient.accountKind({ accountId: accountId })
|
||||
|
||||
const depositedVaults = await getDepositedVaults(accountId, accountPosition)
|
||||
const depositedVaults = await getDepositedVaults(accountId, chainConfig, accountPosition)
|
||||
|
||||
if (accountPosition) {
|
||||
return {
|
||||
@ -26,6 +30,7 @@ export default async function getAccount(accountId?: string): Promise<Account> {
|
||||
lends: accountPosition.lends.map((lend) => new BNCoin(lend)),
|
||||
deposits: accountPosition.deposits.map((deposit) => new BNCoin(deposit)),
|
||||
vaults: depositedVaults,
|
||||
perps: resolvePerpsPositions(accountPosition.perps),
|
||||
kind: accountKind,
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
||||
|
||||
import { ENV } from 'constants/env'
|
||||
import { ICNSQueryClient } from 'types/classes/ICNSClient.client'
|
||||
import { MarsAccountNftQueryClient } from 'types/generated/mars-account-nft/MarsAccountNft.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 { MarsOracleOsmosisQueryClient } from 'types/generated/mars-oracle-osmosis/MarsOracleOsmosis.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 { MarsSwapperOsmosisQueryClient } from 'types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.client'
|
||||
|
||||
let _cosmWasmClient: CosmWasmClient
|
||||
let _accountNftQueryClient: MarsAccountNftQueryClient
|
||||
let _creditManagerQueryClient: MarsCreditManagerQueryClient
|
||||
let _oracleQueryClient: MarsOracleOsmosisQueryClient
|
||||
let _redBankQueryClient: MarsRedBankQueryClient
|
||||
let _paramsQueryClient: MarsParamsQueryClient
|
||||
let _incentivesQueryClient: MarsIncentivesQueryClient
|
||||
let _swapperOsmosisClient: MarsSwapperOsmosisQueryClient
|
||||
let _ICNSQueryClient: ICNSQueryClient
|
||||
let _cosmWasmClient: Map<string, CosmWasmClient> = new Map()
|
||||
let _accountNftQueryClient: Map<string, MarsAccountNftQueryClient> = new Map()
|
||||
let _creditManagerQueryClient: Map<string, MarsCreditManagerQueryClient> = new Map()
|
||||
let _oracleQueryClient: Map<string, MarsOracleOsmosisQueryClient> = new Map()
|
||||
let _redBankQueryClient: Map<string, MarsRedBankQueryClient> = new Map()
|
||||
let _paramsQueryClient: Map<string, MarsParamsQueryClient> = new Map()
|
||||
let _incentivesQueryClient: Map<string, MarsIncentivesQueryClient> = new Map()
|
||||
let _swapperOsmosisClient: Map<string, MarsSwapperOsmosisQueryClient> = new Map()
|
||||
let _perpsClient: Map<string, MarsPerpsQueryClient> = new Map()
|
||||
let _ICNSQueryClient: Map<string, ICNSQueryClient> = new Map()
|
||||
|
||||
const getClient = async () => {
|
||||
const getClient = async (rpc: string) => {
|
||||
try {
|
||||
if (!_cosmWasmClient) {
|
||||
_cosmWasmClient = await CosmWasmClient.connect(ENV.URL_RPC)
|
||||
if (!_cosmWasmClient.get(rpc)) {
|
||||
const client = await CosmWasmClient.connect(rpc)
|
||||
_cosmWasmClient.set(rpc, client)
|
||||
}
|
||||
|
||||
return _cosmWasmClient
|
||||
return _cosmWasmClient.get(rpc)!
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const getAccountNftQueryClient = async () => {
|
||||
const getAccountNftQueryClient = async (chainConfig: ChainConfig) => {
|
||||
try {
|
||||
if (!_accountNftQueryClient) {
|
||||
const client = await getClient()
|
||||
_accountNftQueryClient = new MarsAccountNftQueryClient(client, ENV.ADDRESS_ACCOUNT_NFT)
|
||||
const contract = chainConfig.contracts.accountNft
|
||||
const rpc = chainConfig.endpoints.rpc
|
||||
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) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const getCreditManagerQueryClient = async () => {
|
||||
const getCreditManagerQueryClient = async (chainConfig: ChainConfig) => {
|
||||
try {
|
||||
if (!_creditManagerQueryClient) {
|
||||
const client = await getClient()
|
||||
_creditManagerQueryClient = new MarsCreditManagerQueryClient(
|
||||
client,
|
||||
ENV.ADDRESS_CREDIT_MANAGER,
|
||||
)
|
||||
const contract = chainConfig.contracts.creditManager
|
||||
const rpc = chainConfig.endpoints.rpc
|
||||
const key = rpc + contract
|
||||
|
||||
if (!_creditManagerQueryClient.get(key)) {
|
||||
const client = await getClient(rpc)
|
||||
_creditManagerQueryClient.set(key, new MarsCreditManagerQueryClient(client, contract))
|
||||
}
|
||||
|
||||
return _creditManagerQueryClient
|
||||
return _creditManagerQueryClient.get(key)!
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const getParamsQueryClient = async () => {
|
||||
const getParamsQueryClient = async (chainConfig: ChainConfig) => {
|
||||
try {
|
||||
if (!_paramsQueryClient) {
|
||||
const client = await getClient()
|
||||
_paramsQueryClient = new MarsParamsQueryClient(client, ENV.ADDRESS_PARAMS)
|
||||
const contract = chainConfig.contracts.params
|
||||
const rpc = chainConfig.endpoints.rpc
|
||||
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) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const getOracleQueryClient = async () => {
|
||||
const getOracleQueryClient = async (chainConfig: ChainConfig) => {
|
||||
try {
|
||||
if (!_oracleQueryClient) {
|
||||
const client = await getClient()
|
||||
_oracleQueryClient = new MarsOracleOsmosisQueryClient(client, ENV.ADDRESS_ORACLE)
|
||||
const contract = chainConfig.contracts.oracle
|
||||
const rpc = chainConfig.endpoints.rpc
|
||||
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) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const getRedBankQueryClient = async () => {
|
||||
const getRedBankQueryClient = async (chainConfig: ChainConfig) => {
|
||||
try {
|
||||
if (!_redBankQueryClient) {
|
||||
const client = await getClient()
|
||||
_redBankQueryClient = new MarsRedBankQueryClient(client, ENV.ADDRESS_RED_BANK)
|
||||
const contract = chainConfig.contracts.redBank
|
||||
const rpc = chainConfig.endpoints.rpc
|
||||
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) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const getVaultQueryClient = async (address: string) => {
|
||||
const getVaultQueryClient = async (chainConfig: ChainConfig, address: string) => {
|
||||
try {
|
||||
const client = await getClient()
|
||||
const client = await getClient(chainConfig.endpoints.rpc)
|
||||
return new MarsMockVaultQueryClient(client, address)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const getIncentivesQueryClient = async () => {
|
||||
const getIncentivesQueryClient = async (chainConfig: ChainConfig) => {
|
||||
try {
|
||||
if (!_incentivesQueryClient) {
|
||||
const client = await getClient()
|
||||
_incentivesQueryClient = new MarsIncentivesQueryClient(client, ENV.ADDRESS_INCENTIVES)
|
||||
const contract = chainConfig.contracts.incentives
|
||||
const rpc = chainConfig.endpoints.rpc
|
||||
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) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const getSwapperQueryClient = async () => {
|
||||
const getSwapperQueryClient = async (chainConfig: ChainConfig) => {
|
||||
try {
|
||||
if (!_swapperOsmosisClient) {
|
||||
const client = await getClient()
|
||||
_swapperOsmosisClient = new MarsSwapperOsmosisQueryClient(client, ENV.ADDRESS_SWAPPER)
|
||||
const contract = chainConfig.contracts.swapper
|
||||
const rpc = chainConfig.endpoints.rpc
|
||||
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) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const getICNSQueryClient = async () => {
|
||||
const getPerpsQueryClient = async (chainConfig: ChainConfig) => {
|
||||
try {
|
||||
if (!_ICNSQueryClient) {
|
||||
const client = await getClient()
|
||||
_ICNSQueryClient = new ICNSQueryClient(client)
|
||||
const contract = chainConfig.contracts.perps
|
||||
const rpc = chainConfig.endpoints.rpc
|
||||
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) {
|
||||
throw error
|
||||
}
|
||||
@ -160,4 +204,5 @@ export {
|
||||
getRedBankQueryClient,
|
||||
getSwapperQueryClient,
|
||||
getVaultQueryClient,
|
||||
getPerpsQueryClient,
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { cacheFn, stakingAprCache } from 'api/cache'
|
||||
import { ENV } from 'constants/env'
|
||||
|
||||
export default async function getStakingAprs() {
|
||||
export default async function getStakingAprs(url: string) {
|
||||
try {
|
||||
return cacheFn(() => fetchAprs(), stakingAprCache, `stakingAprs`)
|
||||
return cacheFn(() => fetchAprs(url), stakingAprCache, `stakingAprs`)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
@ -13,9 +12,7 @@ interface StakingAprResponse {
|
||||
stats: StakingApr[]
|
||||
}
|
||||
|
||||
async function fetchAprs(): Promise<StakingApr[]> {
|
||||
const url = ENV.STRIDE_APRS
|
||||
|
||||
async function fetchAprs(url: string): Promise<StakingApr[]> {
|
||||
const response = await fetch(url)
|
||||
const body = (await response.json()) as StakingAprResponse
|
||||
return body.stats
|
||||
|
@ -4,24 +4,29 @@ import getAccounts from 'api/wallets/getAccounts'
|
||||
import { calculateAccountLeverage, getAccountPositionValues, isAccountEmpty } from 'utils/accounts'
|
||||
|
||||
export default async function getHLSStakingAccounts(
|
||||
chainConfig: ChainConfig,
|
||||
address?: string,
|
||||
): 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 hlsStrategies = await getHLSStakingAssets()
|
||||
const prices = await getPrices()
|
||||
const hlsStrategies = await getHLSStakingAssets(chainConfig)
|
||||
const prices = await getPrices(chainConfig)
|
||||
const hlsAccountsWithStrategy: HLSAccountWithStrategy[] = []
|
||||
|
||||
activeAccounts.forEach((account) => {
|
||||
if (account.deposits.length === 0) return
|
||||
|
||||
const strategy = hlsStrategies.find(
|
||||
(strategy) => strategy.denoms.deposit === account.deposits.at(0).denom,
|
||||
(strategy) => strategy.denoms.deposit === account.deposits[0].denom,
|
||||
)
|
||||
|
||||
if (!strategy) return
|
||||
|
||||
const [deposits, lends, debts, vaults] = getAccountPositionValues(account, prices)
|
||||
const [deposits, lends, debts, vaults] = getAccountPositionValues(
|
||||
account,
|
||||
prices,
|
||||
chainConfig.assets,
|
||||
)
|
||||
|
||||
hlsAccountsWithStrategy.push({
|
||||
...account,
|
||||
@ -31,7 +36,7 @@ export default async function getHLSStakingAccounts(
|
||||
debt: debts,
|
||||
total: deposits,
|
||||
},
|
||||
leverage: calculateAccountLeverage(account, prices).toNumber(),
|
||||
leverage: calculateAccountLeverage(account, prices, chainConfig.assets).toNumber(),
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -1,28 +1,29 @@
|
||||
import { getParamsQueryClient } from 'api/cosmwasm-client'
|
||||
import getStakingAprs from 'api/hls/getAprs'
|
||||
import getAssetParams from 'api/params/getAssetParams'
|
||||
import { getAssetByDenom, getStakingAssets } from 'utils/assets'
|
||||
import { byDenom } from 'utils/array'
|
||||
import { BN } from 'utils/helpers'
|
||||
import { resolveHLSStrategies } from 'utils/resolvers'
|
||||
|
||||
export default async function getHLSStakingAssets() {
|
||||
const stakingAssetDenoms = getStakingAssets().map((asset) => asset.denom)
|
||||
const assetParams = await getAssetParams()
|
||||
export default async function getHLSStakingAssets(chainConfig: ChainConfig) {
|
||||
const stakingAssetDenoms = chainConfig.assets
|
||||
.filter((asset) => asset.isStaking)
|
||||
.map((asset) => asset.denom)
|
||||
const assetParams = await getAssetParams(chainConfig)
|
||||
const HLSAssets = assetParams
|
||||
.filter((asset) => stakingAssetDenoms.includes(asset.denom))
|
||||
.filter((asset) => asset.credit_manager.hls)
|
||||
const strategies = resolveHLSStrategies('coin', HLSAssets)
|
||||
|
||||
const client = await getParamsQueryClient()
|
||||
const client = await getParamsQueryClient(chainConfig)
|
||||
const depositCaps$ = strategies.map((strategy) =>
|
||||
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 depositCaps.map((depositCap, index) => {
|
||||
const borrowSymbol = getAssetByDenom(strategies[index].denoms.borrow)?.symbol
|
||||
const borrowSymbol = chainConfig.assets.find(byDenom(strategies[index].denoms.borrow))?.symbol
|
||||
return {
|
||||
...strategies[index],
|
||||
depositCap: {
|
||||
|
@ -4,10 +4,10 @@ import { getVaultConfigs } from 'api/vaults/getVaultConfigs'
|
||||
import { BN } from 'utils/helpers'
|
||||
import { resolveHLSStrategies } from 'utils/resolvers'
|
||||
|
||||
export default async function getHLSVaults() {
|
||||
const assetParams = await getAssetParams()
|
||||
const client = await getCreditManagerQueryClient()
|
||||
const vaultConfigs = await getVaultConfigs()
|
||||
export default async function getHLSVaults(chainConfig: ChainConfig) {
|
||||
const assetParams = await getAssetParams(chainConfig)
|
||||
const client = await getCreditManagerQueryClient(chainConfig)
|
||||
const vaultConfigs = await getVaultConfigs(chainConfig)
|
||||
const HLSAssets = assetParams.filter((asset) => asset.credit_manager.hls)
|
||||
const strategies = resolveHLSStrategies('vault', HLSAssets)
|
||||
|
||||
|
@ -2,28 +2,28 @@ import getTotalActiveEmissionValue from 'api/incentives/getTotalActiveEmissionVa
|
||||
import getMarket from 'api/markets/getMarket'
|
||||
import getUnderlyingLiquidityAmount from 'api/markets/getMarketUnderlyingLiquidityAmount'
|
||||
import getPrice from 'api/prices/getPrice'
|
||||
import { ASSETS } from 'constants/assets'
|
||||
import { byDenom } from 'utils/array'
|
||||
import { SECONDS_IN_A_YEAR } from 'utils/constants'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
export default async function calculateAssetIncentivesApy(
|
||||
chainConfig: ChainConfig,
|
||||
denom: string,
|
||||
): Promise<BigNumber | null> {
|
||||
try {
|
||||
const [totalActiveEmissionValue, market] = await Promise.all([
|
||||
getTotalActiveEmissionValue(denom),
|
||||
getMarket(denom),
|
||||
getTotalActiveEmissionValue(chainConfig, denom),
|
||||
getMarket(chainConfig, denom),
|
||||
])
|
||||
|
||||
if (!totalActiveEmissionValue) return null
|
||||
|
||||
const [marketLiquidityAmount, assetPrice] = await Promise.all([
|
||||
getUnderlyingLiquidityAmount(market),
|
||||
getPrice(denom),
|
||||
getUnderlyingLiquidityAmount(chainConfig, market),
|
||||
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)
|
||||
.shiftedBy(-assetDecimals)
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { cacheFn, emissionsCache } from 'api/cache'
|
||||
import { getIncentivesQueryClient } from 'api/cosmwasm-client'
|
||||
import getPrice from 'api/prices/getPrice'
|
||||
import { ASSETS } from 'constants/assets'
|
||||
import { BN_ZERO } from 'constants/math'
|
||||
import { byDenom } from 'utils/array'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
export default async function getTotalActiveEmissionValue(
|
||||
chainConfig: ChainConfig,
|
||||
denom: string,
|
||||
): Promise<BigNumber | null> {
|
||||
try {
|
||||
const client = await getIncentivesQueryClient()
|
||||
const client = await getIncentivesQueryClient(chainConfig)
|
||||
const activeEmissions = await cacheFn(
|
||||
() =>
|
||||
client.activeEmissions({
|
||||
@ -26,12 +26,12 @@ export default async function getTotalActiveEmissionValue(
|
||||
}
|
||||
|
||||
const prices = await Promise.all(
|
||||
activeEmissions.map((activeEmission) => getPrice(activeEmission.denom)),
|
||||
activeEmissions.map((activeEmission) => getPrice(chainConfig, activeEmission.denom)),
|
||||
)
|
||||
|
||||
return activeEmissions.reduce((accumulation, current, 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)
|
||||
|
||||
return accumulation.plus(emissionValue)
|
||||
|
@ -1,17 +1,19 @@
|
||||
import { cacheFn, unclaimedRewardsCache } from 'api/cache'
|
||||
import { getIncentivesQueryClient } from 'api/cosmwasm-client'
|
||||
import { ENV } from 'constants/env'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
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 {
|
||||
const client = await getIncentivesQueryClient()
|
||||
const client = await getIncentivesQueryClient(chainConfig)
|
||||
const unclaimedRewards = await cacheFn(
|
||||
() =>
|
||||
iterateContractQuery(() =>
|
||||
client.userUnclaimedRewards({
|
||||
user: ENV.ADDRESS_CREDIT_MANAGER,
|
||||
user: chainConfig.contracts.creditManager,
|
||||
accountId,
|
||||
}),
|
||||
),
|
||||
|
@ -2,14 +2,14 @@ import { cacheFn, marketCache } from 'api/cache'
|
||||
import { getParamsQueryClient, getRedBankQueryClient } from 'api/cosmwasm-client'
|
||||
import { resolveMarketResponse } from 'utils/resolvers'
|
||||
|
||||
export default async function getMarket(denom: string): Promise<Market> {
|
||||
return cacheFn(() => fetchMarket(denom), marketCache, denom, 60)
|
||||
export default async function getMarket(chainConfig: ChainConfig, denom: string): Promise<Market> {
|
||||
return cacheFn(() => fetchMarket(chainConfig, denom), marketCache, denom, 60)
|
||||
}
|
||||
|
||||
async function fetchMarket(denom: string) {
|
||||
async function fetchMarket(chainConfig: ChainConfig, denom: string) {
|
||||
try {
|
||||
const redBankClient = await getRedBankQueryClient()
|
||||
const paramsClient = await getParamsQueryClient()
|
||||
const redBankClient = await getRedBankQueryClient(chainConfig)
|
||||
const paramsClient = await getParamsQueryClient(chainConfig)
|
||||
|
||||
const [market, assetParams, assetCap] = await Promise.all([
|
||||
redBankClient.market({ denom }),
|
||||
|
@ -1,19 +1,21 @@
|
||||
import getMarketLiquidities from 'api/markets/getMarketLiquidities'
|
||||
import getMarkets from 'api/markets/getMarkets'
|
||||
import getPrices from 'api/prices/getPrices'
|
||||
import { getEnabledMarketAssets } from 'utils/assets'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
export default async function getMarketBorrowings(): Promise<BorrowAsset[]> {
|
||||
const liquidities = await getMarketLiquidities()
|
||||
const enabledAssets = getEnabledMarketAssets()
|
||||
const borrowEnabledMarkets = (await getMarkets()).filter((market: Market) => market.borrowEnabled)
|
||||
const prices = await getPrices()
|
||||
export default async function getMarketBorrowings(
|
||||
chainConfig: ChainConfig,
|
||||
): Promise<BorrowAsset[]> {
|
||||
const liquidities = await getMarketLiquidities(chainConfig)
|
||||
const borrowEnabledMarkets = (await getMarkets(chainConfig)).filter(
|
||||
(market: Market) => market.borrowEnabled,
|
||||
)
|
||||
const prices = await getPrices(chainConfig)
|
||||
|
||||
const borrow: BorrowAsset[] = borrowEnabledMarkets.map((market) => {
|
||||
const price = prices.find((coin) => coin.denom === market.denom)?.amount ?? '1'
|
||||
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 {
|
||||
...asset,
|
||||
|
@ -3,10 +3,10 @@ import { getRedBankQueryClient } from 'api/cosmwasm-client'
|
||||
import getMarkets from 'api/markets/getMarkets'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
|
||||
export default async function getMarketDebts(): Promise<BNCoin[]> {
|
||||
export default async function getMarketDebts(chainConfig: ChainConfig): Promise<BNCoin[]> {
|
||||
try {
|
||||
const markets: Market[] = await getMarkets()
|
||||
const redBankQueryClient = await getRedBankQueryClient()
|
||||
const markets: Market[] = await getMarkets(chainConfig)
|
||||
const redBankQueryClient = await getRedBankQueryClient(chainConfig)
|
||||
|
||||
const debtQueries = markets.map((asset) =>
|
||||
cacheFn(
|
||||
|
@ -2,10 +2,12 @@ import getMarkets from 'api/markets/getMarkets'
|
||||
import getUnderlyingLiquidityAmount from 'api/markets/getMarketUnderlyingLiquidityAmount'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
|
||||
export default async function getMarketDeposits(): Promise<BNCoin[]> {
|
||||
export default async function getMarketDeposits(chainConfig: ChainConfig): Promise<BNCoin[]> {
|
||||
try {
|
||||
const markets: Market[] = await getMarkets()
|
||||
const depositQueries = markets.map(getUnderlyingLiquidityAmount)
|
||||
const markets: Market[] = await getMarkets(chainConfig)
|
||||
const depositQueries = markets.map((market) =>
|
||||
getUnderlyingLiquidityAmount(chainConfig, market),
|
||||
)
|
||||
const depositsResults = await Promise.all(depositQueries)
|
||||
|
||||
return depositsResults.map<BNCoin>(
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { BN } from 'utils/helpers'
|
||||
import getMarketDeposits from 'api/markets/getMarketDeposits'
|
||||
import getMarketDebts from 'api/markets/getMarketDebts'
|
||||
import getMarketDeposits from 'api/markets/getMarketDeposits'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
|
||||
export default async function getMarketLiquidities(): Promise<BNCoin[]> {
|
||||
const deposits = await getMarketDeposits()
|
||||
const debts = await getMarketDebts()
|
||||
export default async function getMarketLiquidities(chainConfig: ChainConfig): Promise<BNCoin[]> {
|
||||
const deposits = await getMarketDeposits(chainConfig)
|
||||
const debts = await getMarketDebts(chainConfig)
|
||||
|
||||
const liquidity: BNCoin[] = deposits.map((deposit) => {
|
||||
const debt = debts.find((debt) => debt.denom === deposit.denom)
|
||||
|
@ -1,18 +1,21 @@
|
||||
import { cacheFn, underlyingLiquidityAmountCache } from 'api/cache'
|
||||
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(
|
||||
() => fetchUnderlyingLiquidityAmount(market),
|
||||
() => fetchUnderlyingLiquidityAmount(chainConfig, market),
|
||||
underlyingLiquidityAmountCache,
|
||||
`underlyingLiquidity/${market.denom}/amount/${market.collateralTotalScaled}`,
|
||||
60,
|
||||
)
|
||||
}
|
||||
|
||||
async function fetchUnderlyingLiquidityAmount(market: Market) {
|
||||
async function fetchUnderlyingLiquidityAmount(chainConfig: ChainConfig, market: Market) {
|
||||
try {
|
||||
const client = await getRedBankQueryClient()
|
||||
const client = await getRedBankQueryClient(chainConfig)
|
||||
return await client.underlyingLiquidityAmount({
|
||||
denom: market.denom,
|
||||
amountScaled: market.collateralTotalScaled,
|
||||
|
@ -6,36 +6,46 @@ import {
|
||||
} from 'types/generated/mars-params/MarsParams.types'
|
||||
import { Market as RedBankMarket } from 'types/generated/mars-red-bank/MarsRedBank.types'
|
||||
import { byDenom } from 'utils/array'
|
||||
import { getEnabledMarketAssets } from 'utils/assets'
|
||||
import iterateContractQuery from 'utils/iterateContractQuery'
|
||||
import { resolveMarketResponse } from 'utils/resolvers'
|
||||
|
||||
export default async function getMarkets(): Promise<Market[]> {
|
||||
export default async function getMarkets(chainConfig: ChainConfig): Promise<Market[]> {
|
||||
try {
|
||||
const redBankClient = await getRedBankQueryClient()
|
||||
const paramsClient = await getParamsQueryClient()
|
||||
const redBankClient = await getRedBankQueryClient(chainConfig)
|
||||
const paramsClient = await getParamsQueryClient(chainConfig)
|
||||
|
||||
const enabledAssets = getEnabledMarketAssets()
|
||||
const capQueries = enabledAssets.map((asset) =>
|
||||
const marketAssets = chainConfig.assets.filter((asset) => asset.isMarket)
|
||||
|
||||
const capQueries = marketAssets
|
||||
.filter((asset) => asset.isMarket)
|
||||
.map((asset) =>
|
||||
cacheFn(
|
||||
() => paramsClient.totalDeposit({ denom: asset.denom }),
|
||||
totalDepositCache,
|
||||
`enabledMarkets/${asset.denom}`,
|
||||
`chains/${chainConfig.id}/enabledMarkets/${asset.denom}`,
|
||||
60,
|
||||
),
|
||||
)
|
||||
|
||||
const caps = await Promise.all(capQueries)
|
||||
|
||||
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(
|
||||
async () => await iterateContractQuery(paramsClient.allAssetParams),
|
||||
allParamsCache,
|
||||
'params',
|
||||
`chains/${chainConfig.id}/params`,
|
||||
60,
|
||||
),
|
||||
Promise.all(capQueries),
|
||||
])
|
||||
|
||||
return enabledAssets.map((asset) =>
|
||||
return marketAssets.map((asset) =>
|
||||
resolveMarketResponse(
|
||||
markets.find(byDenom(asset.denom)) as RedBankMarket,
|
||||
assetParams.find(byDenom(asset.denom)) as AssetParams,
|
||||
@ -43,6 +53,7 @@ export default async function getMarkets(): Promise<Market[]> {
|
||||
),
|
||||
)
|
||||
} catch (ex) {
|
||||
console.log(ex)
|
||||
throw ex
|
||||
}
|
||||
}
|
||||
|
@ -3,15 +3,17 @@ import { getParamsQueryClient } from 'api/cosmwasm-client'
|
||||
import { AssetParamsBaseForAddr } from 'types/generated/mars-params/MarsParams.types'
|
||||
import iterateContractQuery from 'utils/iterateContractQuery'
|
||||
|
||||
export default async function getAssetParams(): Promise<AssetParamsBaseForAddr[]> {
|
||||
export default async function getAssetParams(
|
||||
chainConfig: ChainConfig,
|
||||
): Promise<AssetParamsBaseForAddr[]> {
|
||||
try {
|
||||
return await cacheFn(
|
||||
async () => {
|
||||
const paramsQueryClient = await getParamsQueryClient()
|
||||
const paramsQueryClient = await getParamsQueryClient(chainConfig)
|
||||
return iterateContractQuery(paramsQueryClient.allAssetParams)
|
||||
},
|
||||
assetParamsCache,
|
||||
'assetParams',
|
||||
`${chainConfig.id}/assetParams`,
|
||||
600,
|
||||
)
|
||||
} catch (ex) {
|
||||
|
14
src/api/perps/getOpeningFee.ts
Normal file
14
src/api/perps/getOpeningFee.ts
Normal 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))
|
||||
}
|
@ -1,11 +1,10 @@
|
||||
import getPoolPrice from 'api/prices/getPoolPrice'
|
||||
import { ASSETS } from 'constants/assets'
|
||||
import { bySymbol } from 'utils/array'
|
||||
|
||||
async function getMarsPrice() {
|
||||
const marsAsset = ASSETS.find(bySymbol('MARS'))
|
||||
async function getMarsPrice(chainConfig: ChainConfig) {
|
||||
const marsAsset = chainConfig.assets.find(bySymbol('MARS'))
|
||||
if (!marsAsset) return 0
|
||||
return await getPoolPrice(marsAsset)
|
||||
return await getPoolPrice(chainConfig, marsAsset)
|
||||
}
|
||||
|
||||
export default getMarsPrice
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { cacheFn, oraclePriceCache } from 'api/cache'
|
||||
import { getOracleQueryClient } from 'api/cosmwasm-client'
|
||||
import { BN_ZERO } from 'constants/math'
|
||||
import { PRICE_ORACLE_DECIMALS } from 'constants/query'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
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 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 {
|
||||
if (!assets.length) return []
|
||||
|
||||
const oracleQueryClient = await getOracleQueryClient()
|
||||
const oracleQueryClient = await getOracleQueryClient(chainConfig)
|
||||
const priceResults = await cacheFn(
|
||||
() => iterateContractQuery(oracleQueryClient.prices),
|
||||
oraclePriceCache,
|
||||
'oraclePrices',
|
||||
`${chainConfig.id}/oraclePrices`,
|
||||
60,
|
||||
)
|
||||
|
||||
@ -24,7 +28,7 @@ export default async function getOraclePrices(...assets: Asset[]): Promise<BNCoi
|
||||
const decimalDiff = asset.decimals - PRICE_ORACLE_DECIMALS
|
||||
return BNCoin.fromDenomAndBigNumber(
|
||||
asset.denom,
|
||||
BN(priceResponse.price).shiftedBy(decimalDiff),
|
||||
BN(priceResponse?.price ?? BN_ZERO).shiftedBy(decimalDiff),
|
||||
)
|
||||
})
|
||||
} catch (ex) {
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { cacheFn, poolPriceCache } from 'api/cache'
|
||||
import getPrice from 'api/prices/getPrice'
|
||||
import { ENV } from 'constants/env'
|
||||
import { BN_ONE } from 'constants/math'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { byDenom, byTokenDenom, partition } from 'utils/array'
|
||||
@ -17,28 +16,29 @@ interface PoolAsset {
|
||||
}
|
||||
|
||||
export default async function getPoolPrice(
|
||||
chainConfig: ChainConfig,
|
||||
asset: Asset,
|
||||
lookupPricesForBaseAsset?: BNCoin[],
|
||||
): Promise<BigNumber> {
|
||||
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 =
|
||||
(lookupPricesForBaseAsset &&
|
||||
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'
|
||||
|
||||
return assetRate.multipliedBy(baseAssetPrice)
|
||||
}
|
||||
|
||||
const getAssetRate = async (asset: Asset) => {
|
||||
const url = `${ENV.URL_REST}osmosis/gamm/v1beta1/pools/${asset.poolId}`
|
||||
const getAssetRate = async (chainConfig: ChainConfig, asset: Asset) => {
|
||||
const url = chainConfig.endpoints.pools.replace('POOL_ID', asset.poolId!.toString())
|
||||
const response = await cacheFn(
|
||||
() => fetch(url).then((res) => res.json()),
|
||||
poolPriceCache,
|
||||
`poolPrices/${(asset.poolId || 0).toString()}`,
|
||||
`${chainConfig.id}/poolPrices/${(asset.poolId || 0).toString()}`,
|
||||
60,
|
||||
)
|
||||
const pool = response.pool
|
||||
|
@ -1,37 +1,20 @@
|
||||
import { cacheFn, priceCache } from 'api/cache'
|
||||
import { getOracleQueryClient } from 'api/cosmwasm-client'
|
||||
import getPoolPrice from 'api/prices/getPoolPrice'
|
||||
import getPythPrice from 'api/prices/getPythPrices'
|
||||
import { ASSETS } from 'constants/assets'
|
||||
import { PRICE_ORACLE_DECIMALS } from 'constants/query'
|
||||
import getPrices from 'api/prices/getPrices'
|
||||
import { BN_ZERO } from 'constants/math'
|
||||
import { byDenom } from 'utils/array'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
export default async function getPrice(denom: string): Promise<BigNumber> {
|
||||
return cacheFn(() => fetchPrice(denom), priceCache, `price/${denom}`, 60)
|
||||
export default async function getPrice(
|
||||
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 {
|
||||
const asset = ASSETS.find(byDenom(denom)) as Asset
|
||||
const prices = await getPrices(chainConfig)
|
||||
|
||||
if (asset.pythPriceFeedId) {
|
||||
return (await getPythPrice(asset.pythPriceFeedId))[0]
|
||||
}
|
||||
|
||||
if (asset.hasOraclePrice) {
|
||||
const oracleQueryClient = await getOracleQueryClient()
|
||||
const priceResponse = await oracleQueryClient.price({ denom: asset.denom })
|
||||
const decimalDiff = asset.decimals - PRICE_ORACLE_DECIMALS
|
||||
|
||||
return BN(priceResponse.price).shiftedBy(decimalDiff)
|
||||
}
|
||||
|
||||
if (asset.poolId) {
|
||||
return await getPoolPrice(asset)
|
||||
}
|
||||
|
||||
throw `could not fetch the price info for the given denom: ${denom}`
|
||||
return prices.find(byDenom(denom))?.amount ?? BN_ZERO
|
||||
} catch (ex) {
|
||||
throw ex
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
@ -1,29 +1,35 @@
|
||||
import getOraclePrices from 'api/prices/getOraclePrices'
|
||||
import getPoolPrice from 'api/prices/getPoolPrice'
|
||||
import fetchPythPrices from 'api/prices/getPythPrices'
|
||||
import chains from 'configs/chains'
|
||||
import useStore from 'store'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
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 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 {
|
||||
const assetsToFetchPrices = getAssetsMustHavePriceInfo()
|
||||
const [assetsWithPythPriceFeedId, assetsWithOraclePrices, assetsWithPoolIds] =
|
||||
const [assetsWithOraclePrices, assetsWithPoolIds] =
|
||||
separateAssetsByPriceSources(assetsToFetchPrices)
|
||||
const oraclePrices = await getOraclePrices(chainConfig, assetsWithOraclePrices)
|
||||
const poolPrices = await requestPoolPrices(chainConfig, assetsWithPoolIds, pythAndOraclePrices)
|
||||
|
||||
const pythAndOraclePrices = (
|
||||
await Promise.all([
|
||||
requestPythPrices(assetsWithPythPriceFeedId),
|
||||
getOraclePrices(...assetsWithOraclePrices),
|
||||
])
|
||||
).flat()
|
||||
const poolPrices = await requestPoolPrices(assetsWithPoolIds, pythAndOraclePrices)
|
||||
if (oraclePrices) useStore.setState({ isOracleStale: false })
|
||||
|
||||
useStore.setState({ isOracleStale: false })
|
||||
|
||||
return [...pythAndOraclePrices, ...poolPrices, usdPrice]
|
||||
return [...pythAndOraclePrices, ...oraclePrices, ...poolPrices, usdPrice]
|
||||
} catch (ex) {
|
||||
console.error(ex)
|
||||
let message = 'Unknown Error'
|
||||
@ -31,41 +37,43 @@ export default async function getPrices(): Promise<BNCoin[]> {
|
||||
if (message.includes('price publish time is too old'))
|
||||
useStore.setState({ isOracleStale: true })
|
||||
|
||||
throw ex
|
||||
return [...pythAndOraclePrices, usdPrice]
|
||||
}
|
||||
}
|
||||
|
||||
async function requestPythPrices(assets: Asset[]): Promise<BNCoin[]> {
|
||||
if (!assets.length) return []
|
||||
|
||||
const priceFeedIds = assets.map((a) => a.pythPriceFeedId) as string[]
|
||||
return await fetchPythPrices(...priceFeedIds).then(mapResponseToBnCoin(assets))
|
||||
const priceFeedIds = 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[]> {
|
||||
const requests = assets.map((asset) => getPoolPrice(asset, lookupPrices))
|
||||
async function requestPoolPrices(
|
||||
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))
|
||||
}
|
||||
|
||||
const mapResponseToBnCoin = (assets: Asset[]) => (prices: BigNumber[]) =>
|
||||
prices.map((price: BigNumber, index: number) =>
|
||||
const mapResponseToBnCoin = (assets: Asset[]) => (prices: BigNumber[]) => {
|
||||
return prices.map((price: BigNumber, index: number) =>
|
||||
BNCoin.fromDenomAndBigNumber(assets[index].denom, price),
|
||||
)
|
||||
}
|
||||
|
||||
function separateAssetsByPriceSources(assets: Asset[]) {
|
||||
// Only fetch Pyth prices for mainnet
|
||||
const [assetsWithPythPriceFeedId, assetsWithoutPythPriceFeedId] = partition(
|
||||
assets,
|
||||
(asset) => !!asset.pythPriceFeedId,
|
||||
)
|
||||
const assetsWithoutPythPriceFeedId = assets.filter((asset) => !asset.pythPriceFeedId)
|
||||
|
||||
// Don't get oracle price if it's not mainnet and there is a poolId
|
||||
const [assetsWithOraclePrice, assetsWithoutOraclePrice] = partition(
|
||||
assetsWithoutPythPriceFeedId,
|
||||
(asset) => asset.hasOraclePrice || !asset.poolId,
|
||||
)
|
||||
const assetsWithPoolId = assetsWithoutOraclePrice.filter((asset) => !!asset.poolId)
|
||||
|
||||
return [assetsWithPythPriceFeedId, assetsWithOraclePrice, assetsWithPoolId]
|
||||
return [assetsWithOraclePrice, assetsWithPoolId]
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
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 {
|
||||
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))
|
||||
|
||||
const pythDataResponse: string[] = await cacheFn(
|
||||
@ -14,6 +14,7 @@ export default async function fetchPythPriceData(...priceFeedIds: string[]) {
|
||||
)
|
||||
return pythDataResponse
|
||||
} catch (ex) {
|
||||
throw ex
|
||||
console.log(ex)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
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'
|
||||
|
||||
export default async function fetchPythPrices(...priceFeedIds: string[]) {
|
||||
export default async function fetchPythPrices(priceFeedIds: string[], assets: Asset[]) {
|
||||
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))
|
||||
|
||||
const pythResponse: PythPriceData[] = await cacheFn(
|
||||
@ -14,7 +15,18 @@ export default async function fetchPythPrices(...priceFeedIds: string[]) {
|
||||
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) {
|
||||
throw ex
|
||||
}
|
||||
|
@ -2,9 +2,13 @@ import { getSwapperQueryClient } from 'api/cosmwasm-client'
|
||||
import { BN_ZERO } from 'constants/math'
|
||||
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 {
|
||||
const swapperClient = await getSwapperQueryClient()
|
||||
const swapperClient = await getSwapperQueryClient(chainConfig)
|
||||
const estimatedAmount = (await swapperClient.estimateExactInSwap({ coinIn, denomOut })).amount
|
||||
|
||||
return BN(estimatedAmount)
|
||||
|
53
src/api/swap/getOsmosisSwapFee.ts
Normal file
53
src/api/swap/getOsmosisSwapFee.ts
Normal 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
|
||||
}
|
@ -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
|
||||
}
|
@ -1,8 +1,12 @@
|
||||
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 {
|
||||
const swapperClient = await getSwapperQueryClient()
|
||||
const swapperClient = await getSwapperQueryClient(chainConfig)
|
||||
const routes = await swapperClient.route({
|
||||
denomIn,
|
||||
denomOut,
|
||||
|
@ -21,9 +21,13 @@ import {
|
||||
import { getCoinValue } from 'utils/formatters'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
async function getUnlocksAtTimestamp(unlockingId: number, vaultAddress: string) {
|
||||
async function getUnlocksAtTimestamp(
|
||||
chainConfig: ChainConfig,
|
||||
unlockingId: number,
|
||||
vaultAddress: string,
|
||||
) {
|
||||
try {
|
||||
const client = await getClient()
|
||||
const client = await getClient(chainConfig.endpoints.rpc)
|
||||
|
||||
const vaultExtension = (await cacheFn(
|
||||
() =>
|
||||
@ -42,6 +46,7 @@ async function getUnlocksAtTimestamp(unlockingId: number, vaultAddress: string)
|
||||
}
|
||||
|
||||
async function getVaultPositionStatusAndUnlockIdAndUnlockTime(
|
||||
chainConfig: ChainConfig,
|
||||
vaultPosition: VaultPosition,
|
||||
): Promise<[VaultStatus, number | undefined, number | undefined]> {
|
||||
const amount = vaultPosition.amount
|
||||
@ -50,7 +55,11 @@ async function getVaultPositionStatusAndUnlockIdAndUnlockTime(
|
||||
|
||||
if (amount.locking.unlocking.length) {
|
||||
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())) {
|
||||
return [VaultStatus.UNLOCKED, unlockId, unlocksAtTimestamp]
|
||||
@ -83,12 +92,13 @@ export function flatVaultPositionAmount(
|
||||
}
|
||||
|
||||
export async function getLpTokensForVaultPosition(
|
||||
chainConfig: ChainConfig,
|
||||
vault: Vault,
|
||||
vaultPosition: VaultPosition,
|
||||
): Promise<Coin[]> {
|
||||
try {
|
||||
const vaultQueryClient = await getVaultQueryClient(vault.address)
|
||||
const creditManagerQueryClient = await getCreditManagerQueryClient()
|
||||
const vaultQueryClient = await getVaultQueryClient(chainConfig, vault.address)
|
||||
const creditManagerQueryClient = await getCreditManagerQueryClient(chainConfig)
|
||||
const amounts = flatVaultPositionAmount(vaultPosition.amount)
|
||||
const totalAmount = amounts.locked.plus(amounts.unlocked).plus(amounts.unlocking).toString()
|
||||
|
||||
@ -133,15 +143,16 @@ export async function getLpTokensForVaultPosition(
|
||||
async function getVaultValuesAndAmounts(
|
||||
vault: Vault,
|
||||
vaultPosition: VaultPosition,
|
||||
chainConfig: ChainConfig,
|
||||
): Promise<VaultValuesAndAmounts> {
|
||||
try {
|
||||
const pricesQueries = Promise.all([
|
||||
getPrice(vault.denoms.primary),
|
||||
getPrice(vault.denoms.secondary),
|
||||
getPrice(vault.denoms.lp),
|
||||
getPrice(chainConfig, vault.denoms.primary),
|
||||
getPrice(chainConfig, vault.denoms.secondary),
|
||||
getPrice(chainConfig, vault.denoms.lp),
|
||||
])
|
||||
|
||||
const lpTokensQuery = getLpTokensForVaultPosition(vault, vaultPosition)
|
||||
const lpTokensQuery = getLpTokensForVaultPosition(chainConfig, vault, vaultPosition)
|
||||
const amounts = flatVaultPositionAmount(vaultPosition.amount)
|
||||
|
||||
const [[primaryLpToken, secondaryLpToken], [primaryPrice, secondaryPrice, lpPrice]] =
|
||||
@ -154,18 +165,26 @@ async function getVaultValuesAndAmounts(
|
||||
secondary: BN(secondaryLpToken.amount),
|
||||
},
|
||||
values: {
|
||||
primary: getCoinValue(new BNCoin(primaryLpToken), [
|
||||
BNCoin.fromDenomAndBigNumber(primaryLpToken.denom, primaryPrice),
|
||||
]),
|
||||
secondary: getCoinValue(new BNCoin(secondaryLpToken), [
|
||||
BNCoin.fromDenomAndBigNumber(secondaryLpToken.denom, secondaryPrice),
|
||||
]),
|
||||
unlocking: getCoinValue(BNCoin.fromDenomAndBigNumber(vault.denoms.lp, amounts.unlocking), [
|
||||
BNCoin.fromDenomAndBigNumber(vault.denoms.lp, lpPrice),
|
||||
]),
|
||||
unlocked: getCoinValue(BNCoin.fromDenomAndBigNumber(vault.denoms.lp, amounts.unlocked), [
|
||||
BNCoin.fromDenomAndBigNumber(vault.denoms.lp, lpPrice),
|
||||
]),
|
||||
primary: getCoinValue(
|
||||
new BNCoin(primaryLpToken),
|
||||
[BNCoin.fromDenomAndBigNumber(primaryLpToken.denom, primaryPrice)],
|
||||
chainConfig.assets,
|
||||
),
|
||||
secondary: getCoinValue(
|
||||
new BNCoin(secondaryLpToken),
|
||||
[BNCoin.fromDenomAndBigNumber(secondaryLpToken.denom, secondaryPrice)],
|
||||
chainConfig.assets,
|
||||
),
|
||||
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) {
|
||||
@ -175,10 +194,11 @@ async function getVaultValuesAndAmounts(
|
||||
|
||||
async function getDepositedVaults(
|
||||
accountId: string,
|
||||
chainConfig: ChainConfig,
|
||||
positions?: Positions,
|
||||
): Promise<DepositedVault[]> {
|
||||
try {
|
||||
const creditManagerQueryClient = await getCreditManagerQueryClient()
|
||||
const creditManagerQueryClient = await getCreditManagerQueryClient(chainConfig)
|
||||
|
||||
if (!positions)
|
||||
positions = await cacheFn(
|
||||
@ -189,7 +209,7 @@ async function getDepositedVaults(
|
||||
|
||||
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 vault = allVaults.find((v) => v.address === vaultPosition.vault.address)
|
||||
@ -199,8 +219,8 @@ async function getDepositedVaults(
|
||||
}
|
||||
|
||||
const [[status, unlockId, unlocksAt], valuesAndAmounts] = await Promise.all([
|
||||
getVaultPositionStatusAndUnlockIdAndUnlockTime(vaultPosition),
|
||||
getVaultValuesAndAmounts(vault, vaultPosition),
|
||||
getVaultPositionStatusAndUnlockIdAndUnlockTime(chainConfig, vaultPosition),
|
||||
getVaultValuesAndAmounts(vault, vaultPosition, chainConfig),
|
||||
])
|
||||
|
||||
return {
|
||||
|
@ -1,17 +1,22 @@
|
||||
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 {
|
||||
const response = await cacheFn(
|
||||
() => fetch(ENV.URL_VAULT_APR),
|
||||
() => fetch(chainConfig.endpoints.aprs.vaults),
|
||||
aprsCacheResponse,
|
||||
'aprsResponse',
|
||||
`${chainConfig.id}/aprsResponse`,
|
||||
60,
|
||||
)
|
||||
|
||||
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) => {
|
||||
const finalApr = aprData.apr.projected_apr * 100
|
||||
|
@ -3,13 +3,15 @@ import { getParamsQueryClient } from 'api/cosmwasm-client'
|
||||
import { VaultConfigBaseForAddr } from 'types/generated/mars-params/MarsParams.types'
|
||||
import iterateContractQuery from 'utils/iterateContractQuery'
|
||||
|
||||
export const getVaultConfigs = async (): Promise<VaultConfigBaseForAddr[]> => {
|
||||
export const getVaultConfigs = async (
|
||||
chainConfig: ChainConfig,
|
||||
): Promise<VaultConfigBaseForAddr[]> => {
|
||||
try {
|
||||
const paramsQueryClient = await getParamsQueryClient()
|
||||
const paramsQueryClient = await getParamsQueryClient(chainConfig)
|
||||
return await cacheFn(
|
||||
() => iterateContractQuery(paramsQueryClient.allVaultConfigs, 'addr'),
|
||||
vaultConfigsCache,
|
||||
'vaultConfigs',
|
||||
`${chainConfig.id}/vaultConfigs`,
|
||||
600,
|
||||
)
|
||||
} catch (ex) {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -1,14 +1,13 @@
|
||||
import { cacheFn, vaultUtilizationCache } from 'api/cache'
|
||||
import { getCreditManagerQueryClient } from 'api/cosmwasm-client'
|
||||
import { ENV } from 'constants/env'
|
||||
import { VaultUtilizationResponse } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
|
||||
import { VaultConfigBaseForString } from 'types/generated/mars-params/MarsParams.types'
|
||||
|
||||
export const getVaultUtilizations = async (
|
||||
chainConfig: ChainConfig,
|
||||
vaultConfigs: VaultConfigBaseForString[],
|
||||
): Promise<VaultUtilizationResponse[]> => {
|
||||
if (!ENV.ADDRESS_PARAMS) return []
|
||||
const creditManagerQueryClient = await getCreditManagerQueryClient()
|
||||
const creditManagerQueryClient = await getCreditManagerQueryClient(chainConfig)
|
||||
try {
|
||||
const vaultUtilizations$ = vaultConfigs.map((vaultConfig) => {
|
||||
return cacheFn(
|
||||
|
@ -2,20 +2,16 @@ import getAssetParams from 'api/params/getAssetParams'
|
||||
import getAprs from 'api/vaults/getVaultAprs'
|
||||
import { getVaultConfigs } from 'api/vaults/getVaultConfigs'
|
||||
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 { convertAprToApy } from 'utils/parsers'
|
||||
import { resolveHLSStrategies } from 'utils/resolvers'
|
||||
|
||||
export default async function getVaults(): Promise<Vault[]> {
|
||||
const assetParams = await getAssetParams()
|
||||
const vaultConfigs = await getVaultConfigs()
|
||||
const $vaultUtilizations = getVaultUtilizations(vaultConfigs)
|
||||
const $aprs = getAprs()
|
||||
const vaultMetaDatas =
|
||||
ENV.NETWORK === NETWORK.TESTNET ? TESTNET_VAULTS_META_DATA : VAULTS_META_DATA
|
||||
export default async function getVaults(chainConfig: ChainConfig): Promise<Vault[]> {
|
||||
const assetParams = await getAssetParams(chainConfig)
|
||||
const vaultConfigs = await getVaultConfigs(chainConfig)
|
||||
const $vaultUtilizations = getVaultUtilizations(chainConfig, vaultConfigs)
|
||||
const $aprs = getAprs(chainConfig)
|
||||
const vaultMetaDatas = chainConfig.vaults
|
||||
const HLSAssets = assetParams.filter((asset) => asset.credit_manager.hls)
|
||||
const hlsStrategies = resolveHLSStrategies('vault', HLSAssets)
|
||||
|
||||
|
@ -2,12 +2,13 @@ import { getCreditManagerQueryClient } from 'api/cosmwasm-client'
|
||||
import { ITEM_LIMIT_PER_QUERY } from 'constants/query'
|
||||
|
||||
export default async function getAccountIds(
|
||||
chainConfig: ChainConfig,
|
||||
address?: string,
|
||||
previousResults?: AccountIdAndKind[],
|
||||
): Promise<AccountIdAndKind[]> {
|
||||
if (!address) return []
|
||||
try {
|
||||
const client = await getCreditManagerQueryClient()
|
||||
const client = await getCreditManagerQueryClient(chainConfig)
|
||||
|
||||
const lastItem = previousResults && previousResults.at(-1)
|
||||
const accounts = (
|
||||
@ -24,7 +25,7 @@ export default async function getAccountIds(
|
||||
return accumulated.sort((a, b) => parseInt(a.id) - parseInt(b.id))
|
||||
}
|
||||
|
||||
return await getAccountIds(address, accumulated)
|
||||
return await getAccountIds(chainConfig, address, accumulated)
|
||||
} catch {
|
||||
return new Promise((_, reject) => reject('No data'))
|
||||
}
|
||||
|
@ -2,16 +2,19 @@ import getAccount from 'api/accounts/getAccount'
|
||||
import getWalletAccountIds from 'api/wallets/getAccountIds'
|
||||
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'))
|
||||
const accountIdsAndKinds = await getWalletAccountIds(address)
|
||||
const accountIdsAndKinds = await getWalletAccountIds(chainConfig, address)
|
||||
|
||||
const $accounts = accountIdsAndKinds
|
||||
.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)
|
||||
|
||||
if (accounts) {
|
||||
return accounts.sort((a, b) => Number(a.id) - Number(b.id))
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { getICNSQueryClient } from 'api/cosmwasm-client'
|
||||
import { ENV } from 'constants/env'
|
||||
import { ChainInfoID } from 'types/enums/wallet'
|
||||
|
||||
export default async function getICNS(address?: string): Promise<ICNSResult | undefined> {
|
||||
if (!address || ENV.CHAIN_ID !== ChainInfoID.Osmosis1) return
|
||||
export default async function getICNS(
|
||||
chainConfig: ChainConfig,
|
||||
address?: string,
|
||||
): Promise<ICNSResult | undefined> {
|
||||
if (!address || chainConfig.id !== ChainInfoID.Osmosis1) return
|
||||
try {
|
||||
const icnsQueryClient = await getICNSQueryClient()
|
||||
const icnsQueryClient = await getICNSQueryClient(chainConfig)
|
||||
return icnsQueryClient.primaryName({ address })
|
||||
} catch (ex) {
|
||||
throw ex
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { ENV } from 'constants/env'
|
||||
|
||||
export default async function getWalletBalances(address: string): Promise<Coin[]> {
|
||||
export default async function getWalletBalances(
|
||||
chainConfig: ChainConfig,
|
||||
address: string,
|
||||
): Promise<Coin[]> {
|
||||
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) {
|
||||
const data = await response.json()
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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
|
||||
/>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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 |
@ -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
|
@ -1,6 +1,6 @@
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import { Enter, InfoCircle } from 'components/Icons'
|
||||
import { Enter, InfoCircle } from 'components/common/Icons'
|
||||
import useAlertDialog from 'hooks/useAlertDialog'
|
||||
|
||||
interface Props {
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useLocation, useNavigate, useParams, useSearchParams } from 'react-router-dom'
|
||||
|
||||
import AssetBalanceRow from 'components/Asset/AssetBalanceRow'
|
||||
import { ArrowRight, ExclamationMarkCircled } from 'components/Icons'
|
||||
import AssetBalanceRow from 'components/common/assets/AssetBalanceRow'
|
||||
import { ArrowRight, ExclamationMarkCircled } from 'components/common/Icons'
|
||||
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 { BNCoin } from 'types/classes/BNCoin'
|
||||
import { getAssetByDenom } from 'utils/assets'
|
||||
import { byDenom } from 'utils/array'
|
||||
import { combineBNCoins } from 'utils/parsers'
|
||||
import { getPage, getRoute } from 'utils/route'
|
||||
|
||||
@ -31,7 +32,7 @@ function AccountDeleteModal(props: Props) {
|
||||
const { address } = useParams()
|
||||
const { debts, vaults, id: accountId } = modal || {}
|
||||
const [searchParams] = useSearchParams()
|
||||
|
||||
const assets = useAllAssets()
|
||||
const closeDeleteAccountModal = useCallback(() => {
|
||||
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'>
|
||||
{depositsAndLends.map((position, index) => {
|
||||
const coin = BNCoin.fromDenomAndBigNumber(position.denom, position.amount)
|
||||
const asset = getAssetByDenom(position.denom)
|
||||
const asset = assets.find(byDenom(position.denom))
|
||||
if (!asset) return null
|
||||
return <AssetBalanceRow key={index} asset={asset} coin={coin} />
|
||||
})}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
|
||||
import AssetSelectTable from 'components/Modals/AssetsSelect/AssetSelectTable'
|
||||
import SearchBar from 'components/SearchBar'
|
||||
import Text from 'components/Text'
|
||||
import useMarketBorrowings from 'hooks/useMarketBorrowings'
|
||||
import AssetsSelect from 'components/Modals/AssetsSelect'
|
||||
import SearchBar from 'components/common/SearchBar'
|
||||
import Text from 'components/common/Text'
|
||||
import useMarketBorrowings from 'hooks/markets/useMarketBorrowings'
|
||||
import useStore from 'store'
|
||||
|
||||
interface Props {
|
||||
@ -89,11 +89,11 @@ export default function AddVaultAssetsModalContent(props: Props) {
|
||||
Leverage will be set at 50% for both assets by default
|
||||
</Text>
|
||||
</div>
|
||||
<AssetSelectTable
|
||||
isBorrow={true}
|
||||
<AssetsSelect
|
||||
assets={poolAssets}
|
||||
onChangeSelected={onChangePoolDenoms}
|
||||
selectedDenoms={selectedPoolDenoms}
|
||||
isBorrow
|
||||
/>
|
||||
<div className='p-4'>
|
||||
<Text>Assets not in the liquidity pool</Text>
|
||||
@ -102,11 +102,11 @@ export default function AddVaultAssetsModalContent(props: Props) {
|
||||
these assets below.
|
||||
</Text>
|
||||
</div>
|
||||
<AssetSelectTable
|
||||
isBorrow={true}
|
||||
<AssetsSelect
|
||||
assets={stableAssets}
|
||||
onChangeSelected={onChangeOtherDenoms}
|
||||
selectedDenoms={selectedOtherDenoms}
|
||||
isBorrow
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
|
||||
import Button from 'components/Button'
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import Modal from 'components/Modal'
|
||||
import Button from 'components/common/Button'
|
||||
import { CircularProgress } from 'components/common/CircularProgress'
|
||||
import Modal from 'components/Modals/Modal'
|
||||
import AddVaultAssetsModalContent from 'components/Modals/AddVaultAssets/AddVaultBorrowAssetsModalContent'
|
||||
import Text from 'components/Text'
|
||||
import Text from 'components/common/Text'
|
||||
import useStore from 'store'
|
||||
|
||||
export default function AddVaultBorrowAssetsModal() {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Enter } from 'components/Icons'
|
||||
import { Enter } from 'components/common/Icons'
|
||||
|
||||
export function NoIcon() {
|
||||
return (
|
||||
|
@ -1,10 +1,10 @@
|
||||
import classNames from 'classnames'
|
||||
|
||||
import Button from 'components/Button'
|
||||
import Checkbox from 'components/Checkbox'
|
||||
import Modal from 'components/Modal'
|
||||
import Button from 'components/common/Button'
|
||||
import Checkbox from 'components/common/Checkbox'
|
||||
import Modal from 'components/Modals/Modal'
|
||||
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 useToggle from 'hooks/useToggle'
|
||||
|
||||
|
@ -1,22 +1,23 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
|
||||
import CurrentAccountSummary from 'components/Account/CurrentAccountSummary'
|
||||
import AssetImage from 'components/Asset/AssetImage'
|
||||
import Button from 'components/Button'
|
||||
import Card from 'components/Card'
|
||||
import Divider from 'components/Divider'
|
||||
import { ArrowRight } from 'components/Icons'
|
||||
import Modal from 'components/Modal'
|
||||
import Text from 'components/Text'
|
||||
import TokenInputWithSlider from 'components/TokenInput/TokenInputWithSlider'
|
||||
import Modal from 'components/Modals/Modal'
|
||||
import CurrentAccountSummary from 'components/account/CurrentAccountSummary'
|
||||
import Button from 'components/common/Button'
|
||||
import Card from 'components/common/Card'
|
||||
import Divider from 'components/common/Divider'
|
||||
import { ArrowRight } from 'components/common/Icons'
|
||||
import Text from 'components/common/Text'
|
||||
import TokenInputWithSlider from 'components/common/TokenInput/TokenInputWithSlider'
|
||||
import AssetImage from 'components/common/assets/AssetImage'
|
||||
import { BN_ZERO } from 'constants/math'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { byDenom } from 'utils/array'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
interface Props {
|
||||
asset: Asset
|
||||
title: string
|
||||
coinBalances: Coin[]
|
||||
coinBalances: BNCoin[]
|
||||
actionButtonText: string
|
||||
contentHeader?: JSX.Element
|
||||
onClose: () => void
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
55
src/components/Modals/AssetsSelect/Columns/Asset.tsx
Normal file
55
src/components/Modals/AssetsSelect/Columns/Asset.tsx
Normal 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>
|
||||
)
|
||||
}
|
38
src/components/Modals/AssetsSelect/Columns/Balance.tsx
Normal file
38
src/components/Modals/AssetsSelect/Columns/Balance.tsx
Normal 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>
|
||||
)
|
||||
}
|
25
src/components/Modals/AssetsSelect/Columns/BorrowRate.tsx
Normal file
25
src/components/Modals/AssetsSelect/Columns/BorrowRate.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -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])
|
||||
}
|
81
src/components/Modals/AssetsSelect/index.tsx
Normal file
81
src/components/Modals/AssetsSelect/index.tsx
Normal 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}
|
||||
/>
|
||||
)
|
||||
}
|
@ -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],
|
||||
)
|
||||
}
|
@ -1,24 +1,24 @@
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import AccountSummary from 'components/Account/AccountSummary'
|
||||
import AssetImage from 'components/Asset/AssetImage'
|
||||
import Button from 'components/Button'
|
||||
import Card from 'components/Card'
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import Divider from 'components/Divider'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import { ArrowRight, InfoCircle } from 'components/Icons'
|
||||
import Modal from 'components/Modal'
|
||||
import Switch from 'components/Switch'
|
||||
import Text from 'components/Text'
|
||||
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||
import TokenInputWithSlider from 'components/TokenInput/TokenInputWithSlider'
|
||||
import AccountSummary from 'components/account/AccountSummary'
|
||||
import AssetImage from 'components/common/assets/AssetImage'
|
||||
import Button from 'components/common/Button'
|
||||
import Card from 'components/common/Card'
|
||||
import DisplayCurrency from 'components/common/DisplayCurrency'
|
||||
import Divider from 'components/common/Divider'
|
||||
import { FormattedNumber } from 'components/common/FormattedNumber'
|
||||
import { ArrowRight, InfoCircle } from 'components/common/Icons'
|
||||
import Switch from 'components/common/Switch'
|
||||
import Text from 'components/common/Text'
|
||||
import TitleAndSubCell from 'components/common/TitleAndSubCell'
|
||||
import TokenInputWithSlider from 'components/common/TokenInput/TokenInputWithSlider'
|
||||
import Modal from 'components/Modals/Modal'
|
||||
import { BN_ZERO } from 'constants/math'
|
||||
import useCurrentAccount from 'hooks/accounts/useCurrentAccount'
|
||||
import useMarketAssets from 'hooks/markets/useMarketAssets'
|
||||
import useAutoLend from 'hooks/useAutoLend'
|
||||
import useCurrentAccount from 'hooks/useCurrentAccount'
|
||||
import useHealthComputer from 'hooks/useHealthComputer'
|
||||
import useMarketAssets from 'hooks/useMarketAssets'
|
||||
import useToggle from 'hooks/useToggle'
|
||||
import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
|
||||
import { getDepositAndLendCoinsToSpend } from 'hooks/useUpdatedAccount/functions'
|
||||
|
@ -1,4 +1,4 @@
|
||||
import AccountFundContent from 'components/Account/AccountFund/AccountFundContent'
|
||||
import AccountFundContent from 'components/account/AccountFund/AccountFundContent'
|
||||
import useStore from 'store'
|
||||
|
||||
interface Props {
|
||||
|
@ -1,14 +1,15 @@
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import Button from 'components/Button'
|
||||
import Divider from 'components/Divider'
|
||||
import { ArrowRight } from 'components/Icons'
|
||||
import Switch from 'components/Switch'
|
||||
import Text from 'components/Text'
|
||||
import TokenInputWithSlider from 'components/TokenInput/TokenInputWithSlider'
|
||||
import { ASSETS } from 'constants/assets'
|
||||
import Button from 'components/common/Button'
|
||||
import Divider from 'components/common/Divider'
|
||||
import { ArrowRight } from 'components/common/Icons'
|
||||
import Switch from 'components/common/Switch'
|
||||
import Text from 'components/common/Text'
|
||||
import TokenInputWithSlider from 'components/common/TokenInput/TokenInputWithSlider'
|
||||
import { BN_ZERO } from 'constants/math'
|
||||
import useAllAssets from 'hooks/assets/useAllAssets'
|
||||
import useMarketEnabledAssets from 'hooks/assets/useMarketEnabledAssets'
|
||||
import useHealthComputer from 'hooks/useHealthComputer'
|
||||
import useToggle from 'hooks/useToggle'
|
||||
import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
|
||||
@ -16,7 +17,6 @@ import useStore from 'store'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { cloneAccount, getMergedBalancesForAsset, removeDepositsAndLends } from 'utils/accounts'
|
||||
import { byDenom } from 'utils/array'
|
||||
import { getEnabledMarketAssets } from 'utils/assets'
|
||||
|
||||
interface Props {
|
||||
account: Account
|
||||
@ -24,8 +24,9 @@ interface Props {
|
||||
|
||||
export default function WithdrawFromAccount(props: Props) {
|
||||
const { account } = props
|
||||
const assets = useAllAssets()
|
||||
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 [withdrawWithBorrowing, setWithdrawWithBorrowing] = useToggle()
|
||||
const [currentAsset, setCurrentAsset] = useState(defaultAsset)
|
||||
@ -35,7 +36,8 @@ export default function WithdrawFromAccount(props: Props) {
|
||||
const accountClone = cloneAccount(account)
|
||||
const borrowAccount = removeDepositsAndLends(accountClone, currentAsset.denom)
|
||||
const { computeMaxBorrowAmount } = useHealthComputer(borrowAccount)
|
||||
const balances = getMergedBalancesForAsset(account, getEnabledMarketAssets())
|
||||
const marketEnabledAssets = useMarketEnabledAssets()
|
||||
const balances = getMergedBalancesForAsset(account, marketEnabledAssets)
|
||||
const maxWithdrawAmount = computeMaxWithdrawAmount(currentAsset.denom)
|
||||
const maxWithdrawWithBorrowAmount = computeMaxBorrowAmount(currentAsset.denom, 'wallet').plus(
|
||||
maxWithdrawAmount,
|
||||
@ -46,10 +48,12 @@ export default function WithdrawFromAccount(props: Props) {
|
||||
const max = withdrawWithBorrowing ? maxWithdrawWithBorrowAmount : maxWithdrawAmount
|
||||
|
||||
const accountDeposit = account.deposits.find(byDenom(currentAsset.denom))?.amount ?? BN_ZERO
|
||||
const shouldReclaim =
|
||||
amount.isGreaterThan(accountDeposit) && !withdrawWithBorrowing && currentAsset.isAutoLendEnabled
|
||||
const reclaimAmount = shouldReclaim ? amount.minus(accountDeposit) : BN_ZERO
|
||||
const isReclaimingMaxAmount = maxWithdrawAmount.isEqualTo(amount)
|
||||
const accountLent = account.lends.find(byDenom(currentAsset.denom))?.amount ?? BN_ZERO
|
||||
const shouldReclaim = amount.isGreaterThan(accountDeposit) && !accountLent.isZero()
|
||||
const isReclaimingMaxAmount = accountLent.isLessThanOrEqualTo(amount.minus(accountDeposit))
|
||||
const reclaimAmount = isReclaimingMaxAmount
|
||||
? amount
|
||||
: accountLent.minus(amount).minus(accountDeposit)
|
||||
|
||||
function onChangeAmount(val: BigNumber) {
|
||||
setAmount(val)
|
||||
@ -65,7 +69,8 @@ export default function WithdrawFromAccount(props: Props) {
|
||||
const borrow = !debtAmount.isZero()
|
||||
? [BNCoin.fromDenomAndBigNumber(currentAsset.denom, debtAmount)]
|
||||
: []
|
||||
const reclaims = !reclaimAmount.isZero()
|
||||
const reclaims =
|
||||
shouldReclaim && !reclaimAmount.isZero()
|
||||
? [
|
||||
BNCoin.fromDenomAndBigNumber(currentAsset.denom, reclaimAmount).toActionCoin(
|
||||
isReclaimingMaxAmount,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import FundWithdrawModalContent from 'components/Modals/FundWithdraw/FundAndWithdrawModalContent'
|
||||
import ModalContentWithSummary from 'components/Modals/ModalContentWithSummary'
|
||||
import Text from 'components/Text'
|
||||
import useAccount from 'hooks/useAccount'
|
||||
import Text from 'components/common/Text'
|
||||
import useAccount from 'hooks/accounts/useAccount'
|
||||
import useAccountId from 'hooks/useAccountId'
|
||||
import useStore from 'store'
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { useCallback } from 'react'
|
||||
|
||||
import { ChevronRight, Compass, HandCoins, Luggage } from 'components/Icons'
|
||||
import Modal from 'components/Modal'
|
||||
import Text from 'components/Text'
|
||||
import { ChevronRight, Compass, HandCoins, Luggage } from 'components/common/Icons'
|
||||
import Modal from 'components/Modals/Modal'
|
||||
import Text from 'components/common/Text'
|
||||
import useStore from 'store'
|
||||
import { DocURL } from 'types/enums/docURL'
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
import React, { useState } from 'react'
|
||||
import { mutate } from 'swr'
|
||||
|
||||
import Button from 'components/Button'
|
||||
import DocsLink from 'components/DocsLink'
|
||||
import { ArrowRight } from 'components/Icons'
|
||||
import Text from 'components/Text'
|
||||
import Button from 'components/common/Button'
|
||||
import DocsLink from 'components/common/DocsLink'
|
||||
import { ArrowRight } from 'components/common/Icons'
|
||||
import Text from 'components/common/Text'
|
||||
import useStore from 'store'
|
||||
|
||||
export default function CreateAccount() {
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React, { useMemo } from 'react'
|
||||
|
||||
import Button from 'components/Button'
|
||||
import { ArrowRight } from 'components/Icons'
|
||||
import Button from 'components/common/Button'
|
||||
import { ArrowRight } from 'components/common/Icons'
|
||||
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'
|
||||
|
||||
interface Props {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useMemo } from 'react'
|
||||
|
||||
import SummaryItems from 'components/SummaryItems'
|
||||
import SummaryItems from 'components/common/SummaryItems'
|
||||
import useBorrowAsset from 'hooks/useBorrowAsset'
|
||||
|
||||
interface Props {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React from 'react'
|
||||
|
||||
import Button from 'components/Button'
|
||||
import { ArrowRight } from 'components/Icons'
|
||||
import TokenInputWithSlider from 'components/TokenInput/TokenInputWithSlider'
|
||||
import Button from 'components/common/Button'
|
||||
import { ArrowRight } from 'components/common/Icons'
|
||||
import TokenInputWithSlider from 'components/common/TokenInput/TokenInputWithSlider'
|
||||
|
||||
interface Props {
|
||||
amount: BigNumber
|
||||
|
@ -1,9 +1,9 @@
|
||||
import classNames from 'classnames'
|
||||
import React from 'react'
|
||||
|
||||
import Button from 'components/Button'
|
||||
import { ArrowRight } from 'components/Icons'
|
||||
import Radio from 'components/Radio'
|
||||
import Button from 'components/common/Button'
|
||||
import { ArrowRight } from 'components/common/Icons'
|
||||
import Radio from 'components/common/Radio'
|
||||
|
||||
interface Props {
|
||||
hlsAccounts: Account[]
|
||||
|
@ -1,10 +1,11 @@
|
||||
import classNames from 'classnames'
|
||||
import React from 'react'
|
||||
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import { ExclamationMarkTriangle } from 'components/Icons'
|
||||
import Text from 'components/Text'
|
||||
import WarningMessages from 'components/WarningMessages'
|
||||
import DisplayCurrency from 'components/common/DisplayCurrency'
|
||||
import { ExclamationMarkTriangle } from 'components/common/Icons'
|
||||
import Text from 'components/common/Text'
|
||||
import WarningMessages from 'components/common/WarningMessages'
|
||||
import useAllAssets from 'hooks/assets/useAllAssets'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { formatAmountWithSymbol, formatLeverage } from 'utils/formatters'
|
||||
|
||||
@ -33,6 +34,7 @@ interface CollateralSubTitleProps {
|
||||
}
|
||||
|
||||
export function CollateralSubTitle(props: CollateralSubTitleProps) {
|
||||
const assets = useAllAssets()
|
||||
if (props.isOpen) return null
|
||||
|
||||
if (!props.isOpen && props.amount.isZero()) {
|
||||
@ -50,10 +52,13 @@ export function CollateralSubTitle(props: CollateralSubTitleProps) {
|
||||
<div className='flex items-center gap-1'>
|
||||
<WarningMessages messages={props.warningMessages} />
|
||||
<SubTitle
|
||||
text={formatAmountWithSymbol({
|
||||
text={formatAmountWithSymbol(
|
||||
{
|
||||
denom: props.denom,
|
||||
amount: props.amount.toString(),
|
||||
})}
|
||||
},
|
||||
assets,
|
||||
)}
|
||||
color={props.warningMessages.length > 0 ? 'text-warning' : ''}
|
||||
/>
|
||||
</div>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import Text from 'components/Text'
|
||||
import { FormattedNumber } from 'components/common/FormattedNumber'
|
||||
import Text from 'components/common/Text'
|
||||
|
||||
interface Props {
|
||||
items: { title: string; amount: number }[]
|
||||
|
@ -1,10 +1,10 @@
|
||||
import React from 'react'
|
||||
|
||||
import AmountAndValue from 'components/AmountAndValue'
|
||||
import AssetImage from 'components/Asset/AssetImage'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import AmountAndValue from 'components/common/AmountAndValue'
|
||||
import AssetImage from 'components/common/assets/AssetImage'
|
||||
import { FormattedNumber } from 'components/common/FormattedNumber'
|
||||
import Container from 'components/Modals/HLS/Deposit/Summary/Container'
|
||||
import Text from 'components/Text'
|
||||
import Text from 'components/common/Text'
|
||||
|
||||
interface Props {
|
||||
amount: BigNumber
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
|
||||
import Text from 'components/Text'
|
||||
import Text from 'components/common/Text'
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode
|
||||
|
@ -1,12 +1,12 @@
|
||||
import React, { useMemo } from 'react'
|
||||
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import { InfoCircle } from 'components/Icons'
|
||||
import DisplayCurrency from 'components/common/DisplayCurrency'
|
||||
import { FormattedNumber } from 'components/common/FormattedNumber'
|
||||
import { InfoCircle } from 'components/common/Icons'
|
||||
import AprBreakdown from 'components/Modals/HLS/Deposit/Summary/ApyBreakdown'
|
||||
import Container from 'components/Modals/HLS/Deposit/Summary/Container'
|
||||
import Text from 'components/Text'
|
||||
import { Tooltip } from 'components/Tooltip'
|
||||
import Text from 'components/common/Text'
|
||||
import { Tooltip } from 'components/common/Tooltip'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
|
||||
interface Props {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user