Merge pull request #1 from mars-protocol/v1.1.0

v1.1.0
This commit is contained in:
Dane 2023-02-04 16:11:55 +08:00 committed by GitHub
commit 79db46721f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
114 changed files with 5189 additions and 8040 deletions

View File

@ -2,11 +2,11 @@
![mars-banner-1200w](https://marsprotocol.io/banner.png) ![mars-banner-1200w](https://marsprotocol.io/banner.png)
## Web App ## 1. Web App
This project is a [NextJS](https://nextjs.org/). React application. This project is a [NextJS](https://nextjs.org/). React application.
The project utilises [React hooks](https://reactjs.org/docs/hooks-intro.html), functional components, Zustand for state management, and useQuery for general data fetching and management. The project utilises [React hooks](https://reactjs.org/docs/hooks-intro.html), functional components, [Zustand](https://github.com/pmndrs/zustand) for state management, and [useQuery](https://github.com/TanStack/query) for general data fetching and management.
Typescript is added and utilised (but optional if you want to create .jsx or .tsx files). Typescript is added and utilised (but optional if you want to create .jsx or .tsx files).
@ -14,7 +14,7 @@ SCSS with [CSS modules](https://create-react-app.dev/docs/adding-a-css-modules-s
Sentry is used for front end error logging/exception & bug reporting. Sentry is used for front end error logging/exception & bug reporting.
## Deployment ## 2. Deployment
Start web server Start web server
@ -22,16 +22,18 @@ Start web server
yarn && yarn dev yarn && yarn dev
``` ```
### Contributing ## 3. Text and translations
We welcome and encourage contributions! Please create a pull request with as much information about the work you did and what your motivation/intention was. This repository makes use of a [translation repository](https://github.com/mars-protocol/translations). This repository containes all of the translation key values that are used within the UI. The rationale is to have no _hardcoded_ display string values in this repository.
## Imports ## 4. Development practices
### 4.1 Imports
Local components are imported via index files, which can be automatically generated with `yarn index`. This command targets index.ts files with a specific pattern in order to automate component exports. This results in clean imports throughout the pages: Local components are imported via index files, which can be automatically generated with `yarn index`. This command targets index.ts files with a specific pattern in order to automate component exports. This results in clean imports throughout the pages:
``` ```
import { Button, Card, Titlte } from 'components/common' import { Button, Card, Title } from 'components/common'
``` ```
or or
@ -42,6 +44,16 @@ import { Breakdown, RepayInput } from 'components/fields'
In order for this to work, components are place in a folder with UpperCamelCase with the respective Component.tsx file. The component cannot be exported at default, so rather export the `const` instead. In order for this to work, components are place in a folder with UpperCamelCase with the respective Component.tsx file. The component cannot be exported at default, so rather export the `const` instead.
## License ### 4.2 Data orchestration
Data is handled with a combination of container components, useQuery and Zustand. Container components are responsible for syncing the application state with the wallet-provider state. This fire of the required queries in useQuery, which are for many cases also stored in Zustand.
We aim to have as much as possible available in Zustand, to have one source of truth.
## 5. Contributing
We welcome and encourage contributions! Please create a pull request with as much information about the work you did and what your motivation/intention was.
## 6. License
Contents of this repository are open source under the [Mars Protocol Web Application License Agreement](./LICENSE). Contents of this repository are open source under the [Mars Protocol Web Application License Agreement](./LICENSE).

View File

@ -1,7 +1,7 @@
{ {
"name": "mars", "name": "mars",
"homepage": "./", "homepage": "./",
"version": "1.0.0", "version": "1.1.0",
"private": false, "private": false,
"license": "SEE LICENSE IN LICENSE FILE", "license": "SEE LICENSE IN LICENSE FILE",
"scripts": { "scripts": {
@ -22,48 +22,48 @@
"@cosmjs/launchpad": "^0.27.1", "@cosmjs/launchpad": "^0.27.1",
"@cosmjs/proto-signing": "^0.29.5", "@cosmjs/proto-signing": "^0.29.5",
"@cosmjs/stargate": "^0.29.5", "@cosmjs/stargate": "^0.29.5",
"@marsprotocol/wallet-connector": "^0.9.12", "@marsprotocol/wallet-connector": "^1.2.3",
"@material-ui/core": "^4.12.4", "@material-ui/core": "^4.12.4",
"@material-ui/icons": "^4.11.3", "@material-ui/icons": "^4.11.3",
"@ramonak/react-progress-bar": "^5.0.2", "@ramonak/react-progress-bar": "^5.0.3",
"@sentry/nextjs": "^7.12.1", "@sentry/nextjs": "^7.34.0",
"@tanstack/react-query": "^4.3.4", "@tanstack/react-query": "^4.24.4",
"@tanstack/react-table": "^8.5.13", "@tanstack/react-table": "^8.7.9",
"@testing-library/dom": "^8.17.1", "@testing-library/dom": "^8.20.0",
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3", "@testing-library/user-event": "^14.4.3",
"@tippyjs/react": "^4.2.6", "@tippyjs/react": "^4.2.6",
"bignumber.js": "^9.1.0", "bignumber.js": "^9.1.1",
"chart.js": "^3.9.1", "chart.js": "^4.2.0",
"classnames": "^2.3.1", "classnames": "^2.3.2",
"create-conical-gradient": "^1.1.0", "create-conical-gradient": "^1.1.0",
"graphql": "^16.6.0", "graphql": "^16.6.0",
"graphql-request": "^5.0.0", "graphql-request": "^5.1.0",
"i18next": "^21.9.1", "i18next": "^22.4.9",
"i18next-browser-languagedetector": "^6.1.5", "i18next-browser-languagedetector": "^7.0.1",
"i18next-http-backend": "^1.4.1", "i18next-http-backend": "^2.1.1",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"lodash.isequal": "^4.5.0", "lodash.isequal": "^4.5.0",
"lodash.throttle": "^4.1.1", "lodash.throttle": "^4.1.1",
"moment": "^2.29.4", "moment": "^2.29.4",
"moment-duration-format": "^2.3.2", "moment-duration-format": "^2.3.2",
"next": "^12.2.5", "next": "^13.1.6",
"numeral": "^2.0.6", "numeral": "^2.0.6",
"ramda": "^0.28.0", "ramda": "^0.28.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-chartjs-2": "^4.3.1", "react-chartjs-2": "^5.2.0",
"react-currency-input-field": "^3.6.4", "react-currency-input-field": "^3.6.9",
"react-device-detect": "^2.2.2", "react-device-detect": "^2.2.2",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-i18next": "^11.18.5", "react-i18next": "^12.1.4",
"react-spring": "^9.5.5", "react-spring": "^9.6.1",
"react-table": "^7.8.0", "react-table": "^7.8.0",
"react-use-clipboard": "^1.0.8", "react-use-clipboard": "^1.0.9",
"sass": "^1.56.1", "sass": "^1.57.1",
"typescript": "^4.8.2", "typescript": "^4.9.5",
"web-vitals": "^3.0.1", "web-vitals": "^3.1.1",
"zustand": "^4.1.1" "zustand": "^4.3.2"
}, },
"browserslist": { "browserslist": {
"production": [ "production": [
@ -80,26 +80,26 @@
"devDependencies": { "devDependencies": {
"@types/chart.js": "^2.9.37", "@types/chart.js": "^2.9.37",
"@types/classnames": "^2.3.1", "@types/classnames": "^2.3.1",
"@types/jest": "^29.2.3", "@types/jest": "^29.4.0",
"@types/lodash.clonedeep": "^4.5.7", "@types/lodash.clonedeep": "^4.5.7",
"@types/lodash.isequal": "^4.5.6", "@types/lodash.isequal": "^4.5.6",
"@types/lodash.throttle": "^4.1.7", "@types/lodash.throttle": "^4.1.7",
"@types/node": "^18.7.15", "@types/node": "^18.11.18",
"@types/numeral": "^2.0.2", "@types/numeral": "^2.0.2",
"@types/prettier": "^2.7.0", "@types/prettier": "^2.7.2",
"@types/ramda": "^0.28.15", "@types/ramda": "^0.28.22",
"@types/react": "^18.0.18", "@types/react": "^18.0.27",
"@types/react-dom": "^18.0.6", "@types/react-dom": "^18.0.10",
"@types/react-table": "^7.7.12", "@types/react-table": "^7.7.14",
"eslint": "^8.23.0", "eslint": "^8.33.0",
"eslint-config-next": "^12.2.5", "eslint-config-next": "^13.1.6",
"eslint-plugin-simple-import-sort": "^8.0.0", "eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-unused-imports": "^2.0.0", "eslint-plugin-unused-imports": "^2.0.0",
"jest": "^29.3.1", "jest": "^29.4.1",
"jest-environment-jsdom": "^29.3.1", "jest-environment-jsdom": "^29.4.1",
"prettier": "^2.7.1", "prettier": "^2.8.3",
"pretty-quick": "^3.1.3", "pretty-quick": "^3.1.3",
"vscode-generate-index-standalone": "^1.6.0" "vscode-generate-index-standalone": "^1.7.1"
}, },
"engines": { "engines": {
"npm": "please-use-yarn", "npm": "please-use-yarn",

View File

@ -1,4 +1,4 @@
import classNames from 'classnames/bind' import classNames from 'classnames'
import styles from './Backdrop.module.scss' import styles from './Backdrop.module.scss'

View File

@ -1,4 +1,4 @@
import classNames from 'classnames/bind' import classNames from 'classnames'
import { AnimatedNumber, DisplayCurrency } from 'components/common' import { AnimatedNumber, DisplayCurrency } from 'components/common'
import { lookup } from 'libs/parse' import { lookup } from 'libs/parse'

View File

@ -1,4 +1,11 @@
import { useWallet } from '@marsprotocol/wallet-connector' import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import {
getChainInfo,
getClient,
useWallet,
useWalletManager,
WalletConnectionStatus,
} from '@marsprotocol/wallet-connector'
import { useQueryClient } from '@tanstack/react-query' import { useQueryClient } from '@tanstack/react-query'
import { MARS_SYMBOL, USDC_SYMBOL } from 'constants/appConstants' import { MARS_SYMBOL, USDC_SYMBOL } from 'constants/appConstants'
import { import {
@ -9,9 +16,10 @@ import {
useUserBalance, useUserBalance,
useUserDebt, useUserDebt,
useUserDeposit, useUserDeposit,
useUserIcns,
} from 'hooks/queries' } from 'hooks/queries'
import { useSpotPrice } from 'hooks/queries/useSpotPrice' import { useSpotPrice } from 'hooks/queries/useSpotPrice'
import { ReactNode, useEffect } from 'react' import { ReactNode, useEffect, useState } from 'react'
import useStore from 'store' import useStore from 'store'
import { State } from 'types/enums' import { State } from 'types/enums'
import { Network } from 'types/enums/network' import { Network } from 'types/enums/network'
@ -24,9 +32,15 @@ export const CommonContainer = ({ children }: CommonContainerProps) => {
// ------------------ // ------------------
// EXTERNAL HOOKS // EXTERNAL HOOKS
// --------------- // ---------------
const { chainInfo, address, signingCosmWasmClient, name } = useWallet() const { recentWallet, simulate, sign, broadcast } = useWallet()
const { status } = useWalletManager()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const chainInfo = recentWallet?.network ? getChainInfo(recentWallet?.network.chainId) : undefined
const address = status !== WalletConnectionStatus.Connected ? '' : recentWallet?.account.address
const [cosmWasmClient, setCosmWasmClient] = useState<CosmWasmClient | undefined>()
// ------------------ // ------------------
// STORE STATE // STORE STATE
// ------------------ // ------------------
@ -54,7 +68,6 @@ export const CommonContainer = ({ children }: CommonContainerProps) => {
const setClient = useStore((s) => s.setClient) const setClient = useStore((s) => s.setClient)
const setUserBalancesState = useStore((s) => s.setUserBalancesState) const setUserBalancesState = useStore((s) => s.setUserBalancesState)
const setUserWalletAddress = useStore((s) => s.setUserWalletAddress) const setUserWalletAddress = useStore((s) => s.setUserWalletAddress)
const setUserWalletName = useStore((s) => s.setUserWalletName)
// ------------------ // ------------------
// SETTERS // SETTERS
@ -75,21 +88,11 @@ export const CommonContainer = ({ children }: CommonContainerProps) => {
setUserWalletAddress(address || '') setUserWalletAddress(address || '')
}, [setUserWalletAddress, address]) }, [setUserWalletAddress, address])
useEffect(() => {
if (!name) return
setUserWalletName(name)
}, [setUserWalletName, name])
useEffect(() => { useEffect(() => {
if (!rpc || !chainID) return if (!rpc || !chainID) return
setLcdClient(rpc, chainID) setLcdClient(rpc, chainID)
}, [rpc, chainID, setLcdClient]) }, [rpc, chainID, setLcdClient])
useEffect(() => {
if (!signingCosmWasmClient) return
setClient(signingCosmWasmClient)
}, [signingCosmWasmClient, setClient])
useEffect(() => { useEffect(() => {
if (userDebts && userDeposits && userBalances) { if (userDebts && userDeposits && userBalances) {
setUserBalancesState(State.READY) setUserBalancesState(State.READY)
@ -98,6 +101,28 @@ export const CommonContainer = ({ children }: CommonContainerProps) => {
} }
}, [userDebts, userDeposits, userBalances, setUserBalancesState]) }, [userDebts, userDeposits, userBalances, setUserBalancesState])
useEffect(() => {
if (!recentWallet) return
if (!cosmWasmClient) {
const getCosmWasmClient = async () => {
const cosmClient = await getClient(recentWallet.network.rpc)
setCosmWasmClient(cosmClient)
}
getCosmWasmClient()
return
}
const client = {
broadcast,
cosmWasmClient,
recentWallet,
sign,
simulate,
}
setClient(client)
}, [simulate, sign, recentWallet, cosmWasmClient, broadcast, setClient])
useEffect(() => { useEffect(() => {
setRedBankAssets() setRedBankAssets()
}, [ }, [
@ -126,6 +151,7 @@ export const CommonContainer = ({ children }: CommonContainerProps) => {
useBlockHeight() useBlockHeight()
useRedBank() useRedBank()
useUserBalance() useUserBalance()
useUserIcns()
useUserDeposit() useUserDeposit()
useUserDebt() useUserDebt()
useMarsOracle() useMarsOracle()

View File

@ -1,5 +1,6 @@
import React, { ReactNode, useEffect } from 'react' import React, { ReactNode, useEffect } from 'react'
import useStore from 'store' import useStore from 'store'
import { AccountNftClient, CreditManagerClient } from 'types/classes'
interface FieldsContainerProps { interface FieldsContainerProps {
children: ReactNode children: ReactNode
@ -9,15 +10,17 @@ export const FieldsContainer = ({ children }: FieldsContainerProps) => {
const client = useStore((s) => s.client) const client = useStore((s) => s.client)
const networkConfig = useStore((s) => s.networkConfig) const networkConfig = useStore((s) => s.networkConfig)
const userWalletAddress = useStore((s) => s.userWalletAddress) const userWalletAddress = useStore((s) => s.userWalletAddress)
const setAccountNftClient = useStore((s) => s.setAccountNftClient)
const setCreditManagerClient = useStore((s) => s.setCreditManagerClient)
const setCreditManagerMsgComposer = useStore((s) => s.setCreditManagerMsgComposer) const setCreditManagerMsgComposer = useStore((s) => s.setCreditManagerMsgComposer)
useEffect(() => { useEffect(() => {
if (!client) return if (!client || !networkConfig) return
setAccountNftClient(client) useStore.setState({
setCreditManagerClient(client) creditManagerClient: new CreditManagerClient(networkConfig?.contracts.creditManager, client),
}, [client, setAccountNftClient, setCreditManagerClient]) })
useStore.setState({
accountNftClient: new AccountNftClient(networkConfig?.contracts.accountNft, client),
})
}, [client, networkConfig])
useEffect(() => { useEffect(() => {
if (!userWalletAddress || !networkConfig?.contracts.creditManager) return if (!userWalletAddress || !networkConfig?.contracts.creditManager) return

View File

@ -1,152 +1,8 @@
@import 'src/styles/master'; @import 'src/styles/master';
.overlay { .loader {
background-color: $alphaBlack60;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100vw;
height: 100vh;
z-index: 50;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
margin: 0;
.loader {
display: flex; display: flex;
flex: 0 0 100%; flex: 0 0 100%;
justify-content: center; justify-content: center;
@include margin(4, 0); @include margin(4, 0);
}
.button {
@include margin(4, 0, 0);
}
.content {
@include layoutTile;
width: rem-calc(540);
max-width: 100vw;
transform: translateX(-50%);
position: absolute;
left: 50%;
@include padding(4);
display: flex;
flex-direction: column;
outline: none;
cursor: auto;
.header {
@include typoXXLcaps;
text-align: center;
color: $fontColorLightPrimary;
font-weight: $fontWeightRegular;
@include margin(0, 0, 4);
}
.enableContent {
display: flex;
flex: 0 0 100%;
flex-wrap: wrap;
justify-content: center;
}
.text {
@include typoM;
text-align: center;
color: $fontColorLightSecondary;
width: 100%;
display: block;
button {
@include typoM;
appearance: none;
font-weight: $fontWeightSemibold;
color: $colorPrimary;
background-color: transparent;
border: none;
text-decoration: none;
&:hover {
text-decoration: underline;
cursor: pointer;
}
}
}
.list {
@include padding(2, 0);
display: flex;
flex-direction: column;
gap: space(4);
.wallet,
button,
a {
background: transparent;
@include padding(2);
box-shadow: none;
display: flex;
align-items: center;
appearance: none;
border: none;
width: 100%;
text-decoration: none;
border-radius: space(2);
cursor: pointer;
&.disabled {
pointer-events: none;
img,
.info .name {
opacity: 0.5;
}
}
&:hover {
background-color: $alphaWhite10;
}
img {
height: space(15);
width: space(15);
}
.info {
font-weight: $fontWeightRegular;
display: flex;
flex-direction: column;
margin-left: space(5);
.name {
@include typoLcaps;
color: $fontColorLightPrimary;
}
.description {
@include margin(1, 0, 0);
color: $fontColorLightTertiary;
text-align: left;
@include typoM;
&.capitalize {
text-transform: capitalize;
}
}
}
}
}
canvas {
max-width: 90vw;
max-height: 90vw;
height: rem-calc(320);
width: rem-calc(320);
border: space(3) solid $colorWhite;
margin: 0 auto;
}
}
} }

View File

@ -1,13 +1,6 @@
import { ChainInfoID, WalletManagerProvider, WalletType } from '@marsprotocol/wallet-connector' import { ChainInfoID, WalletID, WalletManagerProvider } from '@marsprotocol/wallet-connector'
import { CircularProgress, SVG } from 'components/common' import { CircularProgress, SVG } from 'components/common'
import buttonStyles from 'components/common/Button/Button.module.scss' import { useEffect, useState } from 'react'
import { NETWORK_CONFIG } from 'configs/osmo-test-4'
import { SESSION_WALLET_KEY } from 'constants/appConstants'
import KeplrImage from 'images/keplr-wallet-extension.png'
import WalletConnectImage from 'images/walletconnect-keplr.png'
import { FC } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import useStore from 'store'
import styles from './CosmosWalletConnectProvider.module.scss' import styles from './CosmosWalletConnectProvider.module.scss'
@ -15,66 +8,48 @@ type Props = {
children?: React.ReactNode children?: React.ReactNode
} }
export const CosmosWalletConnectProvider: FC<Props> = ({ children }) => { const defaultChain = ChainInfoID.OsmosisTestnet
const { t } = useTranslation()
const chainId = useStore((s) => s.currentNetwork) export const CosmosWalletConnectProvider = ({ children }: Props) => {
const [chainInfoOverrides, setChainInfoOverrides] = useState<{ rpc: string; rest: string }>()
const [enabledWallets, setEnabledWallets] = useState<WalletID[]>([])
useEffect(() => {
if (chainInfoOverrides) return
const fetchConfig = async () => {
const file = await import(
`../../../configs/${
process.env.NEXT_PUBLIC_NETWORK === 'mainnet' ? 'osmosis-1' : 'osmo-test-4'
}.ts`
)
const networkConfig: NetworkConfig = file.NETWORK_CONFIG
setChainInfoOverrides({
rpc: networkConfig.rpcUrl,
rest: networkConfig.restUrl,
})
setEnabledWallets(networkConfig.wallets)
}
fetchConfig()
})
if (!chainInfoOverrides || !enabledWallets?.length) return null
return ( return (
<WalletManagerProvider <WalletManagerProvider
chainInfoOverrides={{ chainInfoOverrides={chainInfoOverrides}
[ChainInfoID.OsmosisTestnet]: {
rpc: NETWORK_CONFIG.rpcUrl,
rest: NETWORK_CONFIG.restUrl,
},
}}
classNames={{
modalContent: styles.content,
modalOverlay: styles.overlay,
modalHeader: styles.header,
modalCloseButton: styles.close,
walletList: styles.list,
wallet: styles.wallet,
walletImage: styles.image,
walletInfo: styles.info,
walletName: styles.name,
walletDescription: styles.description,
textContent: styles.text,
}}
closeIcon={<SVG.Close />} closeIcon={<SVG.Close />}
defaultChainId={chainId} defaultChainId={defaultChain}
enabledWalletTypes={[WalletType.Keplr, WalletType.WalletConnectKeplr]} enabledWallets={enabledWallets}
enablingMeta={{ persistent
text: <Trans i18nKey='global.walletResetText' />,
textClassName: styles.text,
buttonText: <Trans i18nKey='global.walletReset' />,
buttonClassName: ` ${buttonStyles.button} ${buttonStyles.primary} ${buttonStyles.small} ${buttonStyles.solid} ${styles.button}`,
contentClassName: styles.enableContent,
}}
enablingStringOverride={t('global.connectingToWallet')}
localStorageKey={SESSION_WALLET_KEY}
renderLoader={() => ( renderLoader={() => (
<div className={styles.loader}> <div className={styles.loader}>
<CircularProgress size={30} /> <CircularProgress size={30} />
</div> </div>
)} )}
walletConnectClientMeta={{
name: 'Mars Protocol',
description:
'Lend, borrow and earn on the galaxy`s most powerful credit protocol or enter the Fields of Mars for advanced DeFi strategies.',
url: 'https://marsprotocol.io',
icons: ['https://marsprotocol.io/favicon.svg'],
}}
walletMetaOverride={{
[WalletType.Keplr]: {
description: <Trans i18nKey='global.keplrBrowserExtension' />,
imageUrl: KeplrImage.src,
},
[WalletType.WalletConnectKeplr]: {
name: <Trans i18nKey='global.walletConnect' />,
description: <Trans i18nKey='global.walletConnectDescription' />,
imageUrl: WalletConnectImage.src,
},
}}
> >
{children} {children}
</WalletManagerProvider> </WalletManagerProvider>

View File

@ -1,8 +1,8 @@
import { useWallet, WalletConnectionStatus } from '@marsprotocol/wallet-connector' import { useWalletManager, WalletConnectionStatus } from '@marsprotocol/wallet-connector'
import { ConnectButton, ConnectedButton } from 'components/common' import { ConnectButton, ConnectedButton } from 'components/common'
export const Connect = () => { export const Connect = () => {
const { status } = useWallet() const { status } = useWalletManager()
if (status === WalletConnectionStatus.Connected) return <ConnectedButton /> if (status === WalletConnectionStatus.Connected) return <ConnectedButton />

View File

@ -1,4 +1,4 @@
import { ChainInfoID, SimpleChainInfoList, useWalletManager } from '@marsprotocol/wallet-connector' import { ChainInfoID, useWallet, useWalletManager } from '@marsprotocol/wallet-connector'
import { AnimatedNumber, Button, CircularProgress, DisplayCurrency, SVG } from 'components/common' import { AnimatedNumber, Button, CircularProgress, DisplayCurrency, SVG } from 'components/common'
import { findByDenom } from 'functions' import { findByDenom } from 'functions'
import { useUserBalance } from 'hooks/queries' import { useUserBalance } from 'hooks/queries'
@ -16,7 +16,8 @@ export const ConnectedButton = () => {
// --------------- // ---------------
// EXTERNAL HOOKS // EXTERNAL HOOKS
// --------------- // ---------------
const { disconnect } = useWalletManager() const { disconnect } = useWallet()
const { disconnect: terminate } = useWalletManager()
const { t } = useTranslation() const { t } = useTranslation()
// --------------- // ---------------
@ -25,7 +26,7 @@ export const ConnectedButton = () => {
const baseCurrency = useStore((s) => s.baseCurrency) const baseCurrency = useStore((s) => s.baseCurrency)
const chainInfo = useStore((s) => s.chainInfo) const chainInfo = useStore((s) => s.chainInfo)
const userWalletAddress = useStore((s) => s.userWalletAddress) const userWalletAddress = useStore((s) => s.userWalletAddress)
const userWalletName = useStore((s) => s.userWalletName) const userIcns = useStore((s) => s.userIcns)
// --------------- // ---------------
// LOCAL STATE // LOCAL STATE
@ -34,18 +35,16 @@ export const ConnectedButton = () => {
successDuration: 1000 * 5, successDuration: 1000 * 5,
}) })
const { data, isLoading } = useUserBalance() const { data, isLoading } = useUserBalance()
// --------------- // ---------------
// VARIABLES // VARIABLES
// --------------- // ---------------
const baseCurrencyBalance = Number(findByDenom(data || [], baseCurrency.denom || '')?.amount || 0) const baseCurrencyBalance = Number(findByDenom(data || [], baseCurrency.denom || '')?.amount || 0)
const explorerName = const explorerName = chainInfo ? chainInfo.explorerName : ''
chainInfo && SimpleChainInfoList[chainInfo.chainId as ChainInfoID].explorerName
const [showDetails, setShowDetails] = useState(false) const [showDetails, setShowDetails] = useState(false)
const viewOnFinder = useCallback(() => { const viewOnFinder = useCallback(() => {
const explorerUrl = chainInfo && SimpleChainInfoList[chainInfo.chainId as ChainInfoID].explorer const explorerUrl = chainInfo ? chainInfo.explorer : ''
window.open(`${explorerUrl}account/${userWalletAddress}`, '_blank') window.open(`${explorerUrl}account/${userWalletAddress}`, '_blank')
}, [chainInfo, userWalletAddress]) }, [chainInfo, userWalletAddress])
@ -60,6 +59,11 @@ export const ConnectedButton = () => {
baseCurrency.decimals, baseCurrency.decimals,
) )
const handleDisconnect = () => {
disconnect()
terminate()
}
return ( return (
<div className={styles.wrapper}> <div className={styles.wrapper}>
{chainInfo?.chainId !== ChainInfoID.Osmosis1 && ( {chainInfo?.chainId !== ChainInfoID.Osmosis1 && (
@ -75,18 +79,11 @@ export const ConnectedButton = () => {
text={ text={
<> <>
<span className={styles.address}> <span className={styles.address}>
{userWalletName ? userWalletName : truncate(userWalletAddress, [2, 4])} {userIcns ? userIcns.split('.')[0] : truncate(userWalletAddress, [2, 4])}
</span> </span>
<span className={`${styles.balance} number`}> <span className={`${styles.balance} number`}>
{!isLoading ? ( {!isLoading ? (
`${formatValue( `${formatValue(currentBalanceAmount, 2, 2, true, false, ` ${baseCurrency.symbol}`)}`
currentBalanceAmount,
2,
2,
true,
false,
` ${chainInfo?.stakeCurrency?.coinDenom}`,
)}`
) : ( ) : (
<CircularProgress className={styles.circularProgress} size={12} /> <CircularProgress className={styles.circularProgress} size={12} />
)} )}
@ -99,26 +96,28 @@ export const ConnectedButton = () => {
<div className={styles.details}> <div className={styles.details}>
<div className={styles.detailsHeader}> <div className={styles.detailsHeader}>
<div className={styles.detailsBalance}> <div className={styles.detailsBalance}>
<div className={styles.detailsDenom}>{chainInfo?.stakeCurrency?.coinDenom}</div> <div className={styles.detailsDenom}>{baseCurrency.symbol}</div>
<div className={`${styles.detailsBalanceAmount}`}> <div className={`${styles.detailsBalanceAmount}`}>
<AnimatedNumber amount={currentBalanceAmount} abbreviated={false} /> <AnimatedNumber amount={currentBalanceAmount} abbreviated={false} />
<DisplayCurrency <DisplayCurrency
className='s faded' className='s faded'
coin={{ coin={{
amount: baseCurrencyBalance.toString(), amount: baseCurrencyBalance.toString(),
denom: baseCurrency.denom, denom: baseCurrency.symbol,
}} }}
/> />
</div> </div>
</div> </div>
<div className={styles.detailsButton}> <div className={styles.detailsButton}>
<Button color='secondary' onClick={disconnect} text={t('common.disconnect')} /> <Button
color='secondary'
onClick={handleDisconnect}
text={t('common.disconnect')}
/>
</div> </div>
</div> </div>
<div className={styles.detailsBody}> <div className={styles.detailsBody}>
<p className={styles.addressLabel}> <p className={styles.addressLabel}>{userIcns ? userIcns : t('common.yourAddress')}</p>
{userWalletName ? `${userWalletName}` : t('common.yourAddress')}
</p>
<p className={styles.address}>{userWalletAddress}</p> <p className={styles.address}>{userWalletAddress}</p>
<p className={styles.addressMobile}>{truncate(userWalletAddress, [14, 14])}</p> <p className={styles.addressMobile}>{truncate(userWalletAddress, [14, 14])}</p>
<div className={styles.buttons}> <div className={styles.buttons}>

View File

@ -63,7 +63,6 @@
.active { .active {
opacity: 1; opacity: 1;
pointer-events: none;
&:before { &:before {
content: ''; content: '';
@ -74,6 +73,10 @@
background-color: $fontColorLightPrimary; background-color: $fontColorLightPrimary;
} }
} }
.unclickable {
pointer-events: none;
}
} }
} }

View File

@ -20,21 +20,28 @@ export const Header = () => {
<SVG.Logo /> <SVG.Logo />
</div> </div>
<div className={styles.navbar}> <div className={styles.navbar}>
<Link href='/redbank' passHref> <Link
<a className={classNames(styles.nav, !router.pathname.includes('farm') && styles.active)}> passHref
href='/redbank'
className={classNames(
styles.nav,
!router.pathname.includes('farm') && styles.active,
router.pathname === '/redbank' && styles.unclickable,
)}
>
{t('global.redBank')} {t('global.redBank')}
</a>
</Link> </Link>
<Link href='/farm' passHref> <Link
<a passHref
href='/farm'
className={classNames( className={classNames(
!FIELDS_FEATURE && styles.disabled, !FIELDS_FEATURE && styles.disabled,
styles.nav, styles.nav,
router.pathname.includes('farm') && styles.active, router.pathname.includes('farm') && styles.active,
router.pathname === '/farm' && styles.unclickable,
)} )}
> >
{t('global.fields')} {t('global.fields')}
</a>
</Link> </Link>
<a className={styles.nav} href={networkConfig?.councilUrl} target='_blank' rel='noreferrer'> <a className={styles.nav} href={networkConfig?.councilUrl} target='_blank' rel='noreferrer'>
{t('global.council')} {t('global.council')}

View File

@ -1,5 +1,4 @@
import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' import { ChainInfoID, SimpleChainInfoList, TxBroadcastResult } from '@marsprotocol/wallet-connector'
import { ChainInfoID, SimpleChainInfoList } from '@marsprotocol/wallet-connector'
import { useQueryClient } from '@tanstack/react-query' import { useQueryClient } from '@tanstack/react-query'
import classNames from 'classnames' import classNames from 'classnames'
import { AnimatedNumber, Button, DisplayCurrency, SVG, Tooltip, TxLink } from 'components/common' import { AnimatedNumber, Button, DisplayCurrency, SVG, Tooltip, TxLink } from 'components/common'
@ -38,7 +37,7 @@ export const IncentivesButton = () => {
const [showDetails, setShowDetails] = useState(false) const [showDetails, setShowDetails] = useState(false)
const [disabled, setDisabled] = useState(true) const [disabled, setDisabled] = useState(true)
const [submitted, setSubmitted] = useState(false) const [submitted, setSubmitted] = useState(false)
const [response, setResponse] = useState<ExecuteResult>() const [response, setResponse] = useState<TxBroadcastResult>()
const [error, setError] = useState<string>() const [error, setError] = useState<string>()
const [hasUnclaimedRewards, setHasUnclaimedRewards] = useState(false) const [hasUnclaimedRewards, setHasUnclaimedRewards] = useState(false)
@ -77,7 +76,7 @@ export const IncentivesButton = () => {
setSubmitted(false) setSubmitted(false)
return return
} }
if (response?.transactionHash) { if (response?.hash) {
setDisabled(true) setDisabled(true)
setSubmitted(false) setSubmitted(false)
return return
@ -156,8 +155,8 @@ export const IncentivesButton = () => {
<div className={`${styles.container} ${styles.info}`}> <div className={`${styles.container} ${styles.info}`}>
<p className='m'>{t('incentives.successfullyClaimed')}</p> <p className='m'>{t('incentives.successfullyClaimed')}</p>
<TxLink <TxLink
hash={response?.transactionHash || ''} hash={response?.hash || ''}
link={`${explorerUrl}txs/${response?.transactionHash}`} link={`${explorerUrl}txs/${response?.hash}`}
/> />
</div> </div>
) : ( ) : (

View File

@ -1,6 +1,6 @@
import { useWallet, WalletConnectionStatus } from '@marsprotocol/wallet-connector' import { useWalletManager, WalletConnectionStatus } from '@marsprotocol/wallet-connector'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import classNames from 'classnames/bind' import classNames from 'classnames'
import { Button, NumberInput, SVG, Toggle, Tooltip } from 'components/common' import { Button, NumberInput, SVG, Toggle, Tooltip } from 'components/common'
import React, { useState } from 'react' import React, { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -19,11 +19,11 @@ export const Settings = () => {
const [inputRef, setInputRef] = useState<React.RefObject<HTMLInputElement>>() const [inputRef, setInputRef] = useState<React.RefObject<HTMLInputElement>>()
const [isCustom, setIsCustom] = useState(false) const [isCustom, setIsCustom] = useState(false)
const enableAnimations = useStore((s) => s.enableAnimations) const enableAnimations = useStore((s) => s.enableAnimations)
const { status } = useWallet() const { status } = useWalletManager()
const onInputChange = (value: number) => { const onInputChange = (value: number) => {
setCustomSlippage(value.toString()) setCustomSlippage(value.toString())
if (value.toString() === '') { if (!value.toString()) {
return return
} }
} }

View File

@ -1,4 +1,4 @@
import classNames from 'classnames/bind' import classNames from 'classnames'
import { ReactNode } from 'react' import { ReactNode } from 'react'
import styles from './Highlight.module.scss' import styles from './Highlight.module.scss'

View File

@ -1,4 +1,4 @@
import { useWallet, WalletConnectionStatus } from '@marsprotocol/wallet-connector' import { useWalletManager, WalletConnectionStatus } from '@marsprotocol/wallet-connector'
import classNames from 'classnames' import classNames from 'classnames'
import { Footer, Header, MobileNav } from 'components/common' import { Footer, Header, MobileNav } from 'components/common'
import { FieldsNotConnected } from 'components/fields' import { FieldsNotConnected } from 'components/fields'
@ -21,13 +21,11 @@ export const Layout = ({ children }: Props) => {
const enableAnimations = useStore((s) => s.enableAnimations) const enableAnimations = useStore((s) => s.enableAnimations)
const backgroundClasses = classNames('background', !userWalletAddress && 'night') const backgroundClasses = classNames('background', !userWalletAddress && 'night')
const vaultConfigs = useStore((s) => s.vaultConfigs) const vaultConfigs = useStore((s) => s.vaultConfigs)
const wallet = useWallet() const { status } = useWalletManager()
const wasConnectedBefore = !!localStorage.getItem(SESSION_WALLET_KEY) const isConnected = status === WalletConnectionStatus.Connected
const connectionSuccess = !!( const wasConnectedBefore =
(wallet.status === WalletConnectionStatus.Connecting && wasConnectedBefore) || localStorage.getItem(SESSION_WALLET_KEY) !== null &&
wallet.status === WalletConnectionStatus.Connected localStorage.getItem(SESSION_WALLET_KEY) !== '[]'
)
const isConnected = !!userWalletAddress || connectionSuccess
useEffect(() => { useEffect(() => {
if (!userWalletAddress) { if (!userWalletAddress) {
@ -41,7 +39,7 @@ export const Layout = ({ children }: Props) => {
<Header /> <Header />
<div className='appContainer'> <div className='appContainer'>
<div className='widthBox'> <div className='widthBox'>
{isConnected ? ( {isConnected || wasConnectedBefore ? (
<div className={'body'}>{children}</div> <div className={'body'}>{children}</div>
) : router.route.includes('farm') ? ( ) : router.route.includes('farm') ? (
<FieldsNotConnected /> <FieldsNotConnected />

View File

@ -14,21 +14,25 @@ export const MobileNav = () => {
return ( return (
<nav className={styles.mobileNav}> <nav className={styles.mobileNav}>
<Link href='/redbank' passHref> <Link
<a className={classNames(styles.nav, !router.pathname.includes('farm') && styles.active)}> href='/redbank'
passHref
className={classNames(styles.nav, !router.pathname.includes('farm') && styles.active)}
>
<SVG.RedBankIcon /> <SVG.RedBankIcon />
<span>{t('global.redBank')}</span> <span>{t('global.redBank')}</span>
</a>
</Link> </Link>
<a className={styles.nav} target='_blank' href={networkConfig?.councilUrl} rel='noreferrer'> <a className={styles.nav} target='_blank' href={networkConfig?.councilUrl} rel='noreferrer'>
<SVG.CouncilIcon /> <SVG.CouncilIcon />
<span>{t('global.council')}</span> <span>{t('global.council')}</span>
</a> </a>
<Link href='/farm' passHref> <Link
<a className={classNames(styles.nav, router.pathname.includes('farm') && styles.active)}> href='/farm'
passHref
className={classNames(styles.nav, router.pathname.includes('farm') && styles.active)}
>
<SVG.FieldsIcon /> <SVG.FieldsIcon />
<span>{t('global.fields')}</span> <span>{t('global.fields')}</span>
</a>
</Link> </Link>
</nav> </nav>
) )

View File

@ -110,7 +110,7 @@ export const NumberInput = (props: Props) => {
return return
} }
if (value === '') { if (!value) {
updateValues(value, 0) updateValues(value, 0)
return return
} }

View File

@ -0,0 +1,23 @@
import Tippy from '@tippyjs/react'
interface Props {
text: string
tooltip: string
}
export const TextTooltip = (props: Props) => {
return (
<Tippy
appendTo={() => document.body}
animation={false}
render={(attrs) => {
return (
<div className='tippyContainer' {...attrs}>
{props.tooltip}
</div>
)
}}
>
<div>{props.text}</div>
</Tippy>
)
}

View File

@ -1,7 +1,6 @@
@import 'src/styles/master'; @import 'src/styles/master';
.container { .container {
position: fixed;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@include layoutTooltip; @include layoutTooltip;

View File

@ -1,5 +1,5 @@
import Tippy from '@tippyjs/react' import Tippy from '@tippyjs/react'
import classNames from 'classnames/bind' import classNames from 'classnames'
import { SVG } from 'components/common' import { SVG } from 'components/common'
import { ReactNode } from 'react' import { ReactNode } from 'react'

View File

@ -1,15 +1,14 @@
import { ExecuteResult } from '@cosmjs/cosmwasm-stargate'
import { decodeTxRaw } from '@cosmjs/proto-signing'
import { Coin } from '@cosmjs/stargate' import { Coin } from '@cosmjs/stargate'
import { Card } from 'components/common' import { TxBroadcastResult } from '@marsprotocol/wallet-connector'
import { TxFailedContent, TxSuccessContent } from 'components/common' import { Card, TxFailedContent, TxSuccessContent } from 'components/common'
import { getFeeFromResponse } from 'functions'
import { useEffect, useMemo, useState } from 'react' import { useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import useStore from 'store' import useStore from 'store'
import { TxStatus } from 'types/enums/RedBankAction' import { TxStatus } from 'types/enums/RedBankAction'
interface Props { interface Props {
response?: ExecuteResult response?: TxBroadcastResult
error?: string error?: string
title: string title: string
actions?: FieldsAction[] actions?: FieldsAction[]
@ -33,18 +32,18 @@ export const TxResponse = ({
const rpc = useStore((s) => s.chainInfo?.rpc) const rpc = useStore((s) => s.chainInfo?.rpc)
const chainID = useStore((s) => s.chainInfo?.chainId) const chainID = useStore((s) => s.chainInfo?.chainId)
const client = useStore((s) => s.client) const client = useStore((s) => s.client)
const baseCurrency = useStore((s) => s.baseCurrency)
useEffect(() => { useEffect(() => {
const getTxInfo = async (hash: string) => { const getTxInfo = async (hash?: string) => {
if (txStatus !== TxStatus.LOADING) return if (txStatus !== TxStatus.LOADING) return
if (!rpc || !chainID) return if (!rpc || !chainID || !hash || !response) return
try { try {
const txInfoResponse = await client?.getTx(hash) const coin = getFeeFromResponse(response)
if (txInfoResponse) { if (coin) {
const decoded = decodeTxRaw(txInfoResponse?.tx) setTxFee(coin)
setTxFee(decoded.authInfo.fee?.amount[0])
} }
setTxStatus(TxStatus.SUCCESS) setTxStatus(TxStatus.SUCCESS)
@ -56,8 +55,8 @@ export const TxResponse = ({
} }
} }
getTxInfo(response?.transactionHash || '') getTxInfo(response?.hash || undefined)
}, [client, response, rpc, chainID, checkTxStatus, onSuccess, txStatus]) }, [client, response, rpc, chainID, checkTxStatus, onSuccess, txStatus, baseCurrency.denom])
// reset scroll // reset scroll
useEffect(() => { useEffect(() => {
@ -79,7 +78,7 @@ export const TxResponse = ({
{txStatus === TxStatus.FAILURE ? ( {txStatus === TxStatus.FAILURE ? (
<TxFailedContent <TxFailedContent
handleClose={handleClose} handleClose={handleClose}
hash={response?.transactionHash || ''} hash={response?.response.transactionHash || ''}
message={error} message={error}
/> />
) : ( ) : (

View File

@ -1,8 +1,6 @@
import { ExecuteResult } from '@cosmjs/cosmwasm-stargate'
import { Coin } from '@cosmjs/stargate' import { Coin } from '@cosmjs/stargate'
import { ChainInfoID, SimpleChainInfoList } from '@marsprotocol/wallet-connector' import { ChainInfoID, SimpleChainInfoList, TxBroadcastResult } from '@marsprotocol/wallet-connector'
import { Button, TxFee, TxLink } from 'components/common' import { Button, InfoTitle, TxFee, TxLink } from 'components/common'
import { InfoTitle } from 'components/common'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import useStore from 'store' import useStore from 'store'
import { TxStatus } from 'types/enums/RedBankAction' import { TxStatus } from 'types/enums/RedBankAction'
@ -11,7 +9,7 @@ import styles from './TxSuccessContent.module.scss'
interface Props { interface Props {
title: string title: string
response?: ExecuteResult response?: TxBroadcastResult | null
txFee?: Coin txFee?: Coin
txStatus: TxStatus txStatus: TxStatus
actions: { label: string; values: string[] }[] actions: { label: string; values: string[] }[]
@ -68,8 +66,8 @@ export const TxSuccessContent = ({
<div className={styles.item}> <div className={styles.item}>
<div className={styles.label}>{t('common.txHash')}</div> <div className={styles.label}>{t('common.txHash')}</div>
<TxLink <TxLink
hash={response?.transactionHash || ''} hash={response?.response.transactionHash || ''}
link={`${explorerUrl}txs/${response?.transactionHash}`} link={`${explorerUrl}txs/${response?.response.transactionHash}`}
/> />
</div> </div>
</div> </div>

View File

@ -28,6 +28,7 @@ export { MobileNav } from './MobileNav/MobileNav'
export { Notification } from './Notification/Notification' export { Notification } from './Notification/Notification'
export { NumberInput } from './NumberInput/NumberInput' export { NumberInput } from './NumberInput/NumberInput'
export { SVG } from './SVG/SVG' export { SVG } from './SVG/SVG'
export { TextTooltip } from './TextTooltip/TextTooltip'
export { Title } from './Title/Title' export { Title } from './Title/Title'
export { Toggle } from './Toggle/Toggle' export { Toggle } from './Toggle/Toggle'
export { TokenBalance } from './TokenBalance/TokenBalance' export { TokenBalance } from './TokenBalance/TokenBalance'

View File

@ -1,5 +1,17 @@
@import 'src/styles/master'; @import 'src/styles/master';
$color: ':nth-child(1)';
$logo: ':nth-child(2)';
$name: ':nth-child(3)';
$posValue: ':nth-child(4)';
$netValue: ':nth-child(5)';
$borrowValue: ':nth-child(6)';
$vaultCap: ':nth-child(7)';
$apy: ':nth-child(8)';
$leverage: ':nth-child(9)';
$borrowCapacity: ':nth-child(10)';
$actions: ':nth-child(11)';
.table { .table {
display: none; display: none;
@include margin(0, 0, 6, 0); @include margin(0, 0, 6, 0);
@ -32,13 +44,11 @@
} }
} }
// Not color indicator &:not(#{$color}) {
&:not(:first-child) {
@include padding(4, 2); @include padding(4, 2);
} }
// Name &#{$name} {
&:nth-child(3) {
@include padding(4, 2, 4, 0); @include padding(4, 2, 4, 0);
} }
} }
@ -64,24 +74,15 @@
.td { .td {
position: relative; position: relative;
// Logos &#{$logo} {
&:nth-child(2) {
width: rem-calc(64); width: rem-calc(64);
} }
// Not color indicator &:not(#{$color}) {
&:not(:first-child) {
@include padding(0, 2); @include padding(0, 2);
} }
// Name &#{$name} {
&:nth-child(3) {
text-align: left;
@include padding(0, 2, 0, 0);
}
// Leverage
&:nth-child(3) {
text-align: left; text-align: left;
@include padding(0, 2, 0, 0); @include padding(0, 2, 0, 0);
} }
@ -92,11 +93,9 @@
.table { .table {
.td, .td,
.th { .th {
// Leverage &#{$vaultCap},
&:nth-child(5), &#{$borrowValue},
&:nth-child(6), &#{$leverage} {
&:nth-child(8),
&:nth-child(9) {
display: none; display: none;
} }
} }

View File

@ -68,7 +68,7 @@ export const ActiveVaultsTable = () => {
title={t('fields.activeVaults')} title={t('fields.activeVaults')}
hideHeaderBorder hideHeaderBorder
styleOverride={{ marginBottom: 40 }} styleOverride={{ marginBottom: 40 }}
tooltip={<Trans i18nKey='fields.tooltips.activeVaults' />} tooltip={<Trans i18nKey='fields.tooltips.activeVaults.desktop' />}
> >
<table className={styles.table}> <table className={styles.table}>
<thead className={styles.thead}> <thead className={styles.thead}>

View File

@ -4,6 +4,11 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
.link {
color: unset;
text-decoration: none;
}
.grid { .grid {
display: grid; display: grid;
grid-template-columns: rem-calc(64) 1fr 1fr; grid-template-columns: rem-calc(64) 1fr 1fr;

View File

@ -1,8 +1,8 @@
import Tippy from '@tippyjs/react'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { BorrowCapacity, Card, DisplayCurrency } from 'components/common' import { AnimatedNumber, Apy, BorrowCapacity, Card, DisplayCurrency } from 'components/common'
import { VaultLogo, VaultName } from 'components/fields' import { VaultLogo, VaultName } from 'components/fields'
import { FIELDS_TUTORIAL_KEY } from 'constants/appConstants' import { FIELDS_TUTORIAL_KEY } from 'constants/appConstants'
import { formatValue } from 'libs/parse'
import Link from 'next/link' import Link from 'next/link'
import { Trans, useTranslation } from 'react-i18next' import { Trans, useTranslation } from 'react-i18next'
import useStore from 'store' import useStore from 'store'
@ -45,7 +45,7 @@ export const ActiveVaultsTableMobile = () => {
<Card <Card
title={t('fields.activeVaults')} title={t('fields.activeVaults')}
styleOverride={{ marginBottom: 40 }} styleOverride={{ marginBottom: 40 }}
tooltip={<Trans i18nKey='fields.tooltips.activeVaults' />} tooltip={<Trans i18nKey='fields.tooltips.activeVaults.mobile' />}
> >
<div className={styles.container}> <div className={styles.container}>
{activeVaults.map((vault, i) => { {activeVaults.map((vault, i) => {
@ -55,24 +55,39 @@ export const ActiveVaultsTableMobile = () => {
<VaultLogo vault={vault} /> <VaultLogo vault={vault} />
</div> </div>
<div className={styles.name}>{getVaultSubText(vault)}</div> <div className={styles.name}>{getVaultSubText(vault)}</div>
<div className={styles.position}> <div className={styles.position}>
<div className='xl' onClick={(e) => e.preventDefault()}>
<span className='faded'>{t('common.apy')} </span>
<span>
<Tippy
content={
<Apy
apyData={{
borrow: vault.position.apy.borrow,
total: vault.position.apy.total,
}}
leverage={vault.position.currentLeverage}
/>
}
>
<span className='tooltip xl'>
<AnimatedNumber amount={vault.position.apy.net} className='xl' suffix='%' />
</span>
</Tippy>
</span>
</div>
<div className='s'>
<span className='faded'>{t('fields.positionValueShort')} </span>
<DisplayCurrency <DisplayCurrency
coin={{ coin={{
denom: baseCurrency.denom, denom: baseCurrency.denom,
amount: vault.position.values.total.toString(), amount: vault.position.values.total.toString(),
}} }}
className={'xl'} className={`s ${styles.inline}`}
/>
<div className='s'>
<span className='faded'>{t('common.debt')} </span>
<DisplayCurrency
coin={{
denom: baseCurrency.denom,
amount: vault.position.values.borrowed.toString(),
}}
className={styles.inline}
/> />
</div> </div>
<div className='s'> <div className='s'>
<span className='faded'>{t('common.net')} </span> <span className='faded'>{t('common.net')} </span>
<DisplayCurrency <DisplayCurrency
@ -84,20 +99,18 @@ export const ActiveVaultsTableMobile = () => {
/> />
</div> </div>
<div className='s'> <div className='s'>
<span className='faded'>{t('fields.leverage')} </span> <span className='faded'>{t('common.debt')} </span>
{new BigNumber(vault.position.currentLeverage).toPrecision(3)} <DisplayCurrency
coin={{
denom: baseCurrency.denom,
amount: vault.position.values.borrowed.toString(),
}}
className={styles.inline}
/>
</div> </div>
<div className='s'> <div className='s'>
<span className='faded'>{t('fields.vaultCap')} </span> <span className='faded'>{t('fields.leverage')} </span>
{new BigNumber(vault.position.currentLeverage).toPrecision(3)}
{formatValue(
(vault.vaultCap?.max || 0) / 10 ** baseCurrency.decimals,
0,
0,
true,
'',
` ${baseCurrency.symbol}`,
)}
</div> </div>
</div> </div>
<div className={styles.borrowCapacity}> <div className={styles.borrowCapacity}>
@ -124,6 +137,7 @@ export const ActiveVaultsTableMobile = () => {
href={`/farm/vault/${vault.address}/${ href={`/farm/vault/${vault.address}/${
vault.position.status === 'active' ? 'edit' : 'close' vault.position.status === 'active' ? 'edit' : 'close'
}`} }`}
className={styles.link}
> >
<div>{content}</div> <div>{content}</div>
</Link> </Link>

View File

@ -1,20 +1,28 @@
import { ColumnDef, createColumnHelper } from '@tanstack/react-table' import { ColumnDef, createColumnHelper } from '@tanstack/react-table'
import Tippy from '@tippyjs/react' import Tippy from '@tippyjs/react'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import classNames from 'classnames/bind' import classNames from 'classnames'
import { import {
AnimatedNumber, AnimatedNumber,
Apy,
BorrowCapacity, BorrowCapacity,
Button, Button,
DisplayCurrency, DisplayCurrency,
SVG, SVG,
TextTooltip,
TokenBalance, TokenBalance,
} from 'components/common' } from 'components/common'
import { VaultLogo, VaultName } from 'components/fields' import { VaultLogo, VaultName } from 'components/fields'
import { VAULT_DEPOSIT_BUFFER } from 'constants/appConstants' import { VAULT_DEPOSIT_BUFFER } from 'constants/appConstants'
import { convertPercentage } from 'functions' import { convertPercentage } from 'functions'
import { getLiqBorrowValue, getMaxBorrowValue } from 'functions/fields' import { getLiqBorrowValue, getMaxBorrowValue } from 'functions/fields'
import { convertApyToDailyApy, formatUnlockDate, formatValue, ltvToLeverage } from 'libs/parse' import {
convertApyToDailyApy,
formatUnlockDate,
formatValue,
getTimeAndUnit,
ltvToLeverage,
} from 'libs/parse'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { ReactNode, useMemo } from 'react' import { ReactNode, useMemo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -43,11 +51,38 @@ export const useActiveVaultsColumns = () => {
columnHelper.accessor('name', { columnHelper.accessor('name', {
enableSorting: true, enableSorting: true,
header: t('fields.position'), header: t('fields.position'),
cell: ({ row }) => <VaultName vault={row.original} />, cell: ({ row }) => {
return (
<Tippy
appendTo={() => document.body}
animation={false}
render={(attrs) => {
return (
<div className='tippyContainer' {...attrs}>
{t('fields.tooltips.name', {
asset1: row.original.symbols.primary,
asset2: row.original.symbols.secondary,
...getTimeAndUnit(row.original.lockup),
})}
</div>
)
}}
>
<div>
<VaultName vault={row.original} />
</div>
</Tippy>
)
},
}), }),
columnHelper.accessor('position.values.total', { columnHelper.accessor('position.values.total', {
enableSorting: true, enableSorting: true,
header: t('fields.positionValueShort'), header: () => (
<TextTooltip
text={t('fields.positionValueShort')}
tooltip={t('fields.tooltips.positionValue')}
/>
),
cell: ({ row }) => { cell: ({ row }) => {
const primaryCoin = { const primaryCoin = {
denom: row.original.denoms.primary, denom: row.original.denoms.primary,
@ -89,7 +124,9 @@ export const useActiveVaultsColumns = () => {
}), }),
columnHelper.accessor('position.values.primary', { columnHelper.accessor('position.values.primary', {
enableSorting: true, enableSorting: true,
header: t('fields.netValue'), header: () => (
<TextTooltip text={t('fields.netValue')} tooltip={t('fields.tooltips.netValue')} />
),
cell: ({ row }) => { cell: ({ row }) => {
const position = row.original.position const position = row.original.position
const netValue = position.values.primary + position.values.secondary const netValue = position.values.primary + position.values.secondary
@ -129,7 +166,9 @@ export const useActiveVaultsColumns = () => {
}), }),
columnHelper.accessor('position.values.borrowed', { columnHelper.accessor('position.values.borrowed', {
enableSorting: true, enableSorting: true,
header: t('common.borrowed'), header: () => (
<TextTooltip text={t('common.borrowed')} tooltip={t('fields.tooltips.borrowValue')} />
),
cell: (info) => { cell: (info) => {
const borrowAsset = whitelistedAssets.find( const borrowAsset = whitelistedAssets.find(
(asset) => asset.denom === info.row.original.denoms.secondary, (asset) => asset.denom === info.row.original.denoms.secondary,
@ -169,7 +208,9 @@ export const useActiveVaultsColumns = () => {
}), }),
columnHelper.accessor('vaultCap', { columnHelper.accessor('vaultCap', {
enableSorting: true, enableSorting: true,
header: t('fields.vaultCap'), header: () => (
<TextTooltip text={t('fields.vaultCap')} tooltip={t('fields.tooltips.vaultCap')} />
),
cell: ({ row }) => { cell: ({ row }) => {
if (!row.original.vaultCap) { if (!row.original.vaultCap) {
return null return null
@ -217,7 +258,9 @@ export const useActiveVaultsColumns = () => {
columnHelper.accessor('position.apy', { columnHelper.accessor('position.apy', {
id: 'apy', id: 'apy',
enableSorting: true, enableSorting: true,
header: t('common.apy'), header: () => (
<TextTooltip text={t('common.apy')} tooltip={t('fields.tooltips.apy.active')} />
),
cell: ({ row }) => { cell: ({ row }) => {
switch (row.original.position.status) { switch (row.original.position.status) {
case 'unlocked': case 'unlocked':
@ -228,13 +271,28 @@ export const useActiveVaultsColumns = () => {
</> </>
) )
case 'active': case 'active':
const apy = new BigNumber(row.original.position.apy).decimalPlaces(2).toNumber() const apy = new BigNumber(row.original.position.apy.net).decimalPlaces(2).toNumber()
const apyData = {
total: row.original.apy || 0,
borrow: row.original.position.apy.borrow,
}
return ( return (
<> <>
<Tippy
appendTo={() => document.body}
animation={false}
content={
<Apy apyData={apyData} leverage={row.original.position.currentLeverage} />
}
>
<span className='tooltip'>
<AnimatedNumber amount={apy} className='m' suffix='%' /> <AnimatedNumber amount={apy} className='m' suffix='%' />
<p className='s faded'> <p className='s faded'>
{convertApyToDailyApy(row.original.position.apy)}%/{t('common.day')} {convertApyToDailyApy(row.original.position.apy.net)}%/{t('common.day')}
</p> </p>
</span>
</Tippy>
</> </>
) )
case 'unlocking': case 'unlocking':
@ -242,7 +300,6 @@ export const useActiveVaultsColumns = () => {
<> <>
<p className='m'>{t('fields.unlocking')}</p> <p className='m'>{t('fields.unlocking')}</p>
<p className='s faded'> <p className='s faded'>
{/* {new Date(row.original.position?.unlockAtTimestamp || 0).} */}
{formatUnlockDate(row.original.position?.unlockAtTimestamp || 0)} {formatUnlockDate(row.original.position?.unlockAtTimestamp || 0)}
</p> </p>
</> </>
@ -252,7 +309,9 @@ export const useActiveVaultsColumns = () => {
}), }),
columnHelper.accessor('ltv', { columnHelper.accessor('ltv', {
enableSorting: true, enableSorting: true,
header: t('fields.leverage'), header: () => (
<TextTooltip text={t('fields.leverage')} tooltip={t('fields.tooltips.leverage.active')} />
),
cell: ({ row }) => { cell: ({ row }) => {
return ( return (
<> <>
@ -275,7 +334,12 @@ export const useActiveVaultsColumns = () => {
}), }),
columnHelper.accessor('position.amounts.borrowed', { columnHelper.accessor('position.amounts.borrowed', {
enableSorting: false, enableSorting: false,
header: t('common.borrowingCapacity'), header: () => (
<TextTooltip
text={t('common.borrowingCapacity')}
tooltip={t('fields.tooltips.borrowCapacity')}
/>
),
cell: ({ row }) => { cell: ({ row }) => {
const maxBorrowValue = getMaxBorrowValue(row.original, row.original.position) const maxBorrowValue = getMaxBorrowValue(row.original, row.original.position)

View File

@ -47,8 +47,6 @@ $action: ':nth-child(9)';
} }
} }
$number: 1;
&:not(#{$color}) { &:not(#{$color}) {
@include padding(4, 2); @include padding(4, 2);
} }
@ -111,7 +109,7 @@ $action: ':nth-child(9)';
&#{$description} { &#{$description} {
text-align: left; text-align: left;
width: rem-calc(300); width: rem-calc(300);
@include padding(0, 2, 0, 0); @include padding(2, 2, 2, 0);
} }
} }
} }

View File

@ -53,7 +53,7 @@ export const AvailableVaultsTable = () => {
title={t('fields.availableVaults')} title={t('fields.availableVaults')}
hideHeaderBorder hideHeaderBorder
styleOverride={{ marginBottom: 40 }} styleOverride={{ marginBottom: 40 }}
tooltip={<Trans i18nKey='fields.tooltips.activeVaults' />} tooltip={<Trans i18nKey='fields.tooltips.availableVaults.desktop' />}
> >
<table className={styles.table}> <table className={styles.table}>
<thead className={styles.thead}> <thead className={styles.thead}>
@ -68,7 +68,7 @@ export const AvailableVaultsTable = () => {
const wrapperClasses = classes({ const wrapperClasses = classes({
wrapper: true, wrapper: true,
left: header.id === 'name' || header.id === 'description', left: header.id === 'name' || header.id === 'description',
center: header.id === 'provider_name', center: header.id === 'provider',
}) })
return ( return (

View File

@ -4,6 +4,15 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
.inline {
display: inline;
}
.link {
color: unset;
text-decoration: none;
}
.grid { .grid {
display: grid; display: grid;
grid-template-columns: rem-calc(64) 1fr 1fr; grid-template-columns: rem-calc(64) 1fr 1fr;

View File

@ -1,7 +1,9 @@
import Tippy from '@tippyjs/react'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { AnimatedNumber, Card } from 'components/common' import { AnimatedNumber, Apy, Card, DisplayCurrency } from 'components/common'
import { VaultLogo, VaultName } from 'components/fields' import { VaultLogo, VaultName } from 'components/fields'
import { formatValue, ltvToLeverage } from 'libs/parse' import { getTimeAndUnit, ltvToLeverage } from 'libs/parse'
import Link from 'next/link'
import React from 'react' import React from 'react'
import { Trans, useTranslation } from 'react-i18next' import { Trans, useTranslation } from 'react-i18next'
import useStore from 'store' import useStore from 'store'
@ -12,6 +14,7 @@ export const AvailableVaultsTableMobile = () => {
const { t } = useTranslation() const { t } = useTranslation()
const availableVaults = useStore((s) => s.availableVaults) const availableVaults = useStore((s) => s.availableVaults)
const baseCurrency = useStore((s) => s.baseCurrency) const baseCurrency = useStore((s) => s.baseCurrency)
const redBankAssets = useStore((s) => s.redBankAssets)
if (!availableVaults?.length) return null if (!availableVaults?.length) return null
@ -19,14 +22,25 @@ export const AvailableVaultsTableMobile = () => {
<Card <Card
title={t('fields.availableVaults')} title={t('fields.availableVaults')}
styleOverride={{ marginBottom: 40 }} styleOverride={{ marginBottom: 40 }}
tooltip={<Trans i18nKey='fields.tooltips.availableVaults' />} tooltip={<Trans i18nKey='fields.tooltips.availableVaults.mobile' />}
> >
<div className={styles.container}> <div className={styles.container}>
{availableVaults.map((vault, i) => { {availableVaults.map((vault, i) => {
const minAPY = new BigNumber(vault.apy || 0).decimalPlaces(2).toNumber() const borrowAsset = redBankAssets.find((asset) => asset.denom === vault.denoms.secondary)
const maxAPY = new BigNumber(minAPY).times(ltvToLeverage(vault.ltv.max)).toNumber() const maxBorrowRate = Number(borrowAsset?.borrowRate ?? 0) * vault.ltv.max
const minAPY = new BigNumber(vault.apy || 0).toNumber()
const leverage = ltvToLeverage(vault.ltv.max) const leverage = ltvToLeverage(vault.ltv.max)
const maxAPY =
new BigNumber(minAPY).times(leverage).decimalPlaces(2).toNumber() - maxBorrowRate
const apyDataNoLev = { total: vault.apy || 0, borrow: 0 }
const apyDataLev = { total: vault.apy || 0, borrow: maxBorrowRate }
return ( return (
<Link
key={`${vault.address}-${i}`}
href={`/farm/vault/${vault.address}/create`}
className={styles.link}
>
<div className={styles.grid} key={`${vault.address}-${i}`}> <div className={styles.grid} key={`${vault.address}-${i}`}>
<div className={styles.logo}> <div className={styles.logo}>
<VaultLogo vault={vault} /> <VaultLogo vault={vault} />
@ -35,12 +49,24 @@ export const AvailableVaultsTableMobile = () => {
<VaultName vault={vault} /> <VaultName vault={vault} />
</div> </div>
<div className={styles.stats}> <div className={styles.stats}>
<div className='xl'> <div onClick={(e) => e.preventDefault()} className='xl'>
<span className='faded'>{t('common.apy')} </span> <span className='faded'>{t('common.apy')} </span>
<span> <span>
<Tippy content={<Apy apyData={apyDataNoLev} leverage={1} />}>
<span className='tooltip xl'>
<AnimatedNumber amount={minAPY} suffix='-' /> <AnimatedNumber amount={minAPY} suffix='-' />
</span>
</Tippy>
<Tippy
content={
<Apy apyData={apyDataLev} leverage={ltvToLeverage(vault.ltv.max)} />
}
>
<span className='tooltip xl'>
<AnimatedNumber amount={maxAPY} suffix='%' /> <AnimatedNumber amount={maxAPY} suffix='%' />
</span> </span>
</Tippy>
</span>
</div> </div>
<div className='s'> <div className='s'>
<span className='faded'>{t('fields.leverage')} </span> <span className='faded'>{t('fields.leverage')} </span>
@ -49,19 +75,25 @@ export const AvailableVaultsTableMobile = () => {
<div className='s'> <div className='s'>
<span className='faded'>{t('fields.vaultCap')} </span> <span className='faded'>{t('fields.vaultCap')} </span>
<span> <span>
{formatValue( <DisplayCurrency
(vault.vaultCap?.max || 0) / 10 ** baseCurrency.decimals, coin={{
0, denom: baseCurrency.denom,
0, amount: (vault.vaultCap?.max || 0).toString(),
true, }}
'', className={styles.inline}
' ' + baseCurrency.symbol, />
)}
</span> </span>
</div> </div>
</div> </div>
<div className={styles.description}>{vault.description}</div> <div className={styles.description}>
{t('fields.tooltips.name', {
asset1: vault.symbols.primary,
asset2: vault.symbols.secondary,
...getTimeAndUnit(vault.lockup),
})}
</div> </div>
</div>
</Link>
) )
})} })}
</div> </div>

View File

@ -4,6 +4,6 @@
position: absolute; position: absolute;
top: 0; top: 0;
width: rem-calc(7); width: rem-calc(7);
height: space(16); height: 100%;
margin-left: rem-calc(-7); margin-left: rem-calc(-7);
} }

View File

@ -1,12 +1,20 @@
import { ColumnDef, createColumnHelper } from '@tanstack/react-table' import { ColumnDef, createColumnHelper } from '@tanstack/react-table'
import Tippy from '@tippyjs/react' import Tippy from '@tippyjs/react'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import classNames from 'classnames/bind' import classNames from 'classnames'
import { AnimatedNumber, Button, DisplayCurrency, SVG, TokenBalance } from 'components/common' import {
AnimatedNumber,
Apy,
Button,
DisplayCurrency,
SVG,
TextTooltip,
TokenBalance,
} from 'components/common'
import { VaultLogo, VaultName } from 'components/fields' import { VaultLogo, VaultName } from 'components/fields'
import { VAULT_DEPOSIT_BUFFER } from 'constants/appConstants' import { VAULT_DEPOSIT_BUFFER } from 'constants/appConstants'
import { convertPercentage } from 'functions' import { convertPercentage } from 'functions'
import { convertApyToDailyApy, formatValue, ltvToLeverage } from 'libs/parse' import { convertApyToDailyApy, formatValue, getTimeAndUnit, ltvToLeverage } from 'libs/parse'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useMemo } from 'react' import { useMemo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -19,6 +27,8 @@ export const useAvailableVaultsColumns = () => {
const router = useRouter() const router = useRouter()
const baseCurrency = useStore((s) => s.baseCurrency) const baseCurrency = useStore((s) => s.baseCurrency)
const columnHelper = createColumnHelper<Vault>() const columnHelper = createColumnHelper<Vault>()
const redBankAssets = useStore((s) => s.redBankAssets)
const defaultAvailableVaultsColumns: ColumnDef<Vault, any>[] = useMemo( const defaultAvailableVaultsColumns: ColumnDef<Vault, any>[] = useMemo(
() => [ () => [
columnHelper.accessor('color', { columnHelper.accessor('color', {
@ -33,12 +43,39 @@ export const useAvailableVaultsColumns = () => {
}), }),
columnHelper.accessor('name', { columnHelper.accessor('name', {
enableSorting: true, enableSorting: true,
header: t('fields.position'), header: t('fields.name'),
cell: ({ row }) => <VaultName vault={row.original} />, cell: ({ row }) => {
return (
<Tippy
appendTo={() => document.body}
animation={false}
render={(attrs) => {
return (
<div className='tippyContainer' {...attrs}>
{t('fields.tooltips.name', {
asset1: row.original.symbols.primary,
asset2: row.original.symbols.secondary,
...getTimeAndUnit(row.original.lockup),
})}
</div>
)
}}
>
<div>
<VaultName vault={row.original} />
</div>
</Tippy>
)
},
}), }),
columnHelper.accessor('ltv', { columnHelper.accessor('ltv', {
enableSorting: true, enableSorting: true,
header: t('fields.leverage'), header: () => (
<TextTooltip
text={t('fields.leverage')}
tooltip={t('fields.tooltips.leverage.available')}
/>
),
cell: ({ row }) => { cell: ({ row }) => {
return ( return (
<> <>
@ -53,16 +90,22 @@ export const useAvailableVaultsColumns = () => {
columnHelper.accessor('apy', { columnHelper.accessor('apy', {
id: 'apy', id: 'apy',
enableSorting: true, enableSorting: true,
header: t('common.apy'), header: () => (
<TextTooltip text={t('common.apy')} tooltip={t('fields.tooltips.apy.available')} />
),
cell: ({ row }) => { cell: ({ row }) => {
if (!row.original.apy) { if (!row.original.apy) {
return null return null
} }
const maxLeverage = ltvToLeverage(row.original.ltv.max) const maxLeverage = ltvToLeverage(row.original.ltv.max)
const borrowAsset = redBankAssets.find(
(asset) => asset.denom === row.original.denoms.secondary,
)
const maxBorrowRate = Number(borrowAsset?.borrowRate ?? 0) * row.original.ltv.max
const minAPY = new BigNumber(row.original.apy).decimalPlaces(2).toNumber() const minAPY = new BigNumber(row.original.apy).decimalPlaces(2).toNumber()
const maxAPY = new BigNumber(minAPY).times(maxLeverage).decimalPlaces(2).toNumber()
const maxAPY = new BigNumber(minAPY).times(maxLeverage).toNumber() - maxBorrowRate
const minDailyAPY = new BigNumber(convertApyToDailyApy(row.original.apy)) const minDailyAPY = new BigNumber(convertApyToDailyApy(row.original.apy))
.decimalPlaces(2) .decimalPlaces(2)
.toNumber() .toNumber()
@ -70,13 +113,27 @@ export const useAvailableVaultsColumns = () => {
.times(maxLeverage) .times(maxLeverage)
.decimalPlaces(2) .decimalPlaces(2)
.toNumber() .toNumber()
const apyDataNoLev = { total: row.original.apy || 0, borrow: 0 }
const apyDataLev = { total: row.original.apy || 0, borrow: maxBorrowRate }
return ( return (
<> <>
<p className='m'> <Tippy content={<Apy apyData={apyDataNoLev} leverage={1} />}>
<span className='tooltip m'>
<AnimatedNumber amount={minAPY} /> <AnimatedNumber amount={minAPY} />
</span>
</Tippy>
<span>-</span> <span>-</span>
<Tippy
content={
<Apy apyData={apyDataLev} leverage={ltvToLeverage(row.original.ltv.max)} />
}
>
<span className='tooltip ,'>
<AnimatedNumber amount={maxAPY} suffix='%' /> <AnimatedNumber amount={maxAPY} suffix='%' />
</p> </span>
</Tippy>
<p className='s faded'> <p className='s faded'>
{minDailyAPY}-{maxDailyAPY}%/ {minDailyAPY}-{maxDailyAPY}%/
{t('common.day')} {t('common.day')}
@ -87,7 +144,9 @@ export const useAvailableVaultsColumns = () => {
}), }),
columnHelper.accessor('vaultCap', { columnHelper.accessor('vaultCap', {
enableSorting: true, enableSorting: true,
header: t('fields.vaultCap'), header: () => (
<TextTooltip text={t('fields.vaultCap')} tooltip={t('fields.tooltips.vaultCap')} />
),
cell: ({ row }) => { cell: ({ row }) => {
if (!row.original.vaultCap) { if (!row.original.vaultCap) {
return null return null
@ -140,7 +199,25 @@ export const useAvailableVaultsColumns = () => {
columnHelper.accessor('description', { columnHelper.accessor('description', {
enableSorting: true, enableSorting: true,
header: t('common.description'), header: t('common.description'),
cell: ({ row }) => <p>{row.original.description}</p>, cell: ({ row }) => (
<Tippy
appendTo={() => document.body}
animation={false}
render={(attrs) => {
return (
<div className='tippyContainer' {...attrs}>
{t('fields.tooltips.name', {
asset1: row.original.symbols.primary,
asset2: row.original.symbols.secondary,
...getTimeAndUnit(row.original.lockup),
})}
</div>
)
}}
>
<p>{row.original.description}</p>
</Tippy>
),
}), }),
columnHelper.display({ columnHelper.display({
id: 'actions', id: 'actions',

View File

@ -1,4 +1,4 @@
import classNames from 'classnames/bind' import classNames from 'classnames'
import { BreakdownGraph, BreakdownTable } from 'components/fields' import { BreakdownGraph, BreakdownTable } from 'components/fields'
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'

View File

@ -1,5 +1,5 @@
import Tippy from '@tippyjs/react' import Tippy from '@tippyjs/react'
import classNames from 'classnames/bind' import classNames from 'classnames'
import styles from './AssetBar.module.scss' import styles from './AssetBar.module.scss'
@ -12,11 +12,11 @@ interface Props {
} }
export const AssetBar = (props: Props) => { export const AssetBar = (props: Props) => {
const labelClasses = classNames([ const labelClasses = classNames(
styles.label, styles.label,
props.alignRight && styles.alignRight, props.alignRight && styles.alignRight,
props.showLabel ? styles.show : styles.hide, props.showLabel ? styles.show : styles.hide,
]) )
return ( return (
<Tippy <Tippy
content={ content={

View File

@ -1,8 +1,7 @@
import classNames from 'classnames/bind' import classNames from 'classnames'
import { DisplayCurrency } from 'components/common' import { DisplayCurrency } from 'components/common'
import { AssetBar } from 'components/fields' import { AssetBar } from 'components/fields'
import { lookup } from 'libs/parse' import { lookup } from 'libs/parse'
import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import useStore from 'store' import useStore from 'store'

View File

@ -1,5 +1,5 @@
import Tippy from '@tippyjs/react' import Tippy from '@tippyjs/react'
import classNames from 'classnames/bind' import classNames from 'classnames'
import { import {
AnimatedNumber, AnimatedNumber,
Apy, Apy,

View File

@ -1,16 +1,13 @@
import { ExecuteResult } from '@cosmjs/cosmwasm-stargate'
import { TxResponse } from 'components/common' import { TxResponse } from 'components/common'
import { useFieldsActionMessages } from 'hooks/data' import { useFieldsActionMessages } from 'hooks/data'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import useStore from 'store' import useStore from 'store'
import styles from './ClosePositionResponse.module.scss' import styles from './ClosePositionResponse.module.scss'
interface Props { interface Props {
data?: ExecuteResult data?: ResultData
error: Error | null
isLoading: boolean isLoading: boolean
accountId: string accountId: string
} }
@ -20,7 +17,7 @@ export const ClosePositionResponse = (props: Props) => {
const router = useRouter() const router = useRouter()
const getVaults = useStore((s) => s.getVaults) const getVaults = useStore((s) => s.getVaults)
const { repayMessage, withdrawMessage } = useFieldsActionMessages(props.data) const { repayMessage, withdrawMessage } = useFieldsActionMessages(props.data?.result)
const actions = [ const actions = [
...(repayMessage ? [repayMessage] : []), ...(repayMessage ? [repayMessage] : []),
@ -30,12 +27,12 @@ export const ClosePositionResponse = (props: Props) => {
return ( return (
<div className={styles.container}> <div className={styles.container}>
<TxResponse <TxResponse
error={props.error?.message} error={props.data?.error}
handleClose={() => router.replace('/farm')} handleClose={() => router.replace('/farm')}
onSuccess={() => { onSuccess={() => {
getVaults({ refetch: true }) getVaults({ refetch: true })
}} }}
response={props.data} response={props.data?.result}
title={t('fields.txMessages.close')} title={t('fields.txMessages.close')}
actions={actions} actions={actions}
/> />

View File

@ -1,40 +1,38 @@
import { ExecuteResult } from '@cosmjs/cosmwasm-stargate'
import { TxResponse } from 'components/common' import { TxResponse } from 'components/common'
import { useFieldsActionMessages } from 'hooks/data' import { useFieldsActionMessages } from 'hooks/data'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import useStore from 'store' import useStore from 'store'
import styles from './EditResponse.module.scss' import styles from './EditResponse.module.scss'
interface Props { interface Props {
data?: ExecuteResult data?: ResultData
error: Error | null
isLoading: boolean isLoading: boolean
activeVault: ActiveVault activeVault: ActiveVault
} }
export const EditResponse = (props: Props) => { export const EditResponse = (props: Props) => {
const { t } = useTranslation() const { t } = useTranslation()
const getVaults = useStore((s) => s.getVaults)
const router = useRouter() const router = useRouter()
const { depositMessage, borrowMessage, swapMessage } = useFieldsActionMessages(props.data) const getVaults = useStore((s) => s.getVaults)
const { depositMessage, borrowMessage, swapMessage } = useFieldsActionMessages(props.data?.result)
const actions = [ const actions = [
...(depositMessage ? [depositMessage] : []), ...(depositMessage ? [depositMessage] : []),
...(borrowMessage ? [borrowMessage] : []), ...(borrowMessage ? [borrowMessage] : []),
...(swapMessage ? [swapMessage] : []), ...(swapMessage ? [swapMessage] : []),
] ]
return ( return (
<div className={styles.container}> <div className={styles.container}>
<TxResponse <TxResponse
error={props.error?.message} error={props.data?.error}
handleClose={() => router.replace('/farm')} handleClose={() => router.replace('/farm')}
onSuccess={() => { onSuccess={() => {
getVaults({ refetch: true }) getVaults({ refetch: true })
}} }}
response={props.data} response={props.data?.result}
title={t('fields.txMessages.edit')} title={t('fields.txMessages.edit')}
actions={actions} actions={actions}
/> />

View File

@ -1,4 +1,4 @@
import classNames from 'classnames/bind' import classNames from 'classnames'
import { Tutorial } from 'components/common' import { Tutorial } from 'components/common'
import { TokenInput } from 'components/fields' import { TokenInput } from 'components/fields'
import { FIELDS_TUTORIAL_KEY } from 'constants/appConstants' import { FIELDS_TUTORIAL_KEY } from 'constants/appConstants'

View File

@ -1,5 +1,5 @@
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import classNames from 'classnames/bind' import classNames from 'classnames'
import { InputSlider, Tutorial } from 'components/common' import { InputSlider, Tutorial } from 'components/common'
import { FIELDS_TUTORIAL_KEY } from 'constants/appConstants' import { FIELDS_TUTORIAL_KEY } from 'constants/appConstants'
import { formatValue, ltvToLeverage } from 'libs/parse' import { formatValue, ltvToLeverage } from 'libs/parse'

View File

@ -1,4 +1,4 @@
import classNames from 'classnames/bind' import classNames from 'classnames'
import { Button, SVG, Tutorial } from 'components/common' import { Button, SVG, Tutorial } from 'components/common'
import { TokenInput } from 'components/fields' import { TokenInput } from 'components/fields'
import { FIELDS_TUTORIAL_KEY } from 'constants/appConstants' import { FIELDS_TUTORIAL_KEY } from 'constants/appConstants'

View File

@ -1,5 +1,5 @@
import { Coin } from '@cosmjs/proto-signing' import { Coin } from '@cosmjs/proto-signing'
import classNames from 'classnames/bind' import classNames from 'classnames'
import { Button, DisplayCurrency, NumberInput } from 'components/common' import { Button, DisplayCurrency, NumberInput } from 'components/common'
import { findByDenom } from 'functions' import { findByDenom } from 'functions'
import { useAsset } from 'hooks/data' import { useAsset } from 'hooks/data'

View File

@ -1,16 +1,13 @@
import { ExecuteResult } from '@cosmjs/cosmwasm-stargate'
import { TxResponse } from 'components/common' import { TxResponse } from 'components/common'
import { useFieldsActionMessages } from 'hooks/data' import { useFieldsActionMessages } from 'hooks/data'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import useStore from 'store' import useStore from 'store'
import styles from './RepayResponse.module.scss' import styles from './RepayResponse.module.scss'
interface Props { interface Props {
data?: ExecuteResult data?: ResultData
error: Error | null
isLoading: boolean isLoading: boolean
vault: ActiveVault vault: ActiveVault
} }
@ -19,19 +16,19 @@ export const RepayResponse = (props: Props) => {
const { t } = useTranslation() const { t } = useTranslation()
const getVaults = useStore((s) => s.getVaults) const getVaults = useStore((s) => s.getVaults)
const router = useRouter() const router = useRouter()
const { repayMessage } = useFieldsActionMessages(props.data) const { repayMessage } = useFieldsActionMessages(props.data?.result)
const actions = [...(repayMessage ? [repayMessage] : [])] const actions = [...(repayMessage ? [repayMessage] : [])]
return ( return (
<div className={styles.container}> <div className={styles.container}>
<TxResponse <TxResponse
error={props.error?.message} error={props.data?.error}
handleClose={() => router.replace('/farm')} handleClose={() => router.replace('/farm')}
onSuccess={() => { onSuccess={() => {
getVaults({ refetch: true }) getVaults({ refetch: true })
}} }}
response={props.data} response={props.data?.result}
title={t('fields.txMessages.repay')} title={t('fields.txMessages.repay')}
actions={actions} actions={actions}
/> />

View File

@ -1,16 +1,13 @@
import { ExecuteResult } from '@cosmjs/cosmwasm-stargate'
import { TxResponse } from 'components/common' import { TxResponse } from 'components/common'
import { useFieldsActionMessages } from 'hooks/data' import { useFieldsActionMessages } from 'hooks/data'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import useStore from 'store' import useStore from 'store'
import styles from './SetUpResponse.module.scss' import styles from './SetUpResponse.module.scss'
interface Props { interface Props {
data?: ExecuteResult data?: ResultData
error: Error | null
isLoading: boolean isLoading: boolean
accountId: string accountId: string
} }
@ -18,8 +15,8 @@ interface Props {
export const SetUpResponse = (props: Props) => { export const SetUpResponse = (props: Props) => {
const { t } = useTranslation() const { t } = useTranslation()
const router = useRouter() const router = useRouter()
const { depositMessage, borrowMessage, swapMessage } = useFieldsActionMessages(props.data)
const getVaults = useStore((s) => s.getVaults) const getVaults = useStore((s) => s.getVaults)
const { depositMessage, borrowMessage, swapMessage } = useFieldsActionMessages(props.data?.result)
const actions = [ const actions = [
...(depositMessage ? [depositMessage] : []), ...(depositMessage ? [depositMessage] : []),
@ -30,12 +27,12 @@ export const SetUpResponse = (props: Props) => {
return ( return (
<div className={styles.container}> <div className={styles.container}>
<TxResponse <TxResponse
error={props.error?.message} error={props.data?.error}
handleClose={() => router.replace('/farm')} handleClose={() => router.replace('/farm')}
onSuccess={() => { onSuccess={() => {
getVaults({ refetch: true }) getVaults({ refetch: true })
}} }}
response={props.data} response={props.data?.result}
title={t('fields.txMessages.setup')} title={t('fields.txMessages.setup')}
actions={actions} actions={actions}
/> />

View File

@ -1,16 +1,13 @@
import { ExecuteResult } from '@cosmjs/cosmwasm-stargate'
import { TxResponse } from 'components/common' import { TxResponse } from 'components/common'
import { useFieldsActionMessages } from 'hooks/data' import { useFieldsActionMessages } from 'hooks/data'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import useStore from 'store' import useStore from 'store'
import styles from './UnlockResponse.module.scss' import styles from './UnlockResponse.module.scss'
interface Props { interface Props {
data?: ExecuteResult data?: ResultData
error: Error | null
isLoading: boolean isLoading: boolean
activeVault: ActiveVault activeVault: ActiveVault
} }
@ -19,15 +16,15 @@ export const UnlockResponse = (props: Props) => {
const { t } = useTranslation() const { t } = useTranslation()
const getVaults = useStore((s) => s.getVaults) const getVaults = useStore((s) => s.getVaults)
const router = useRouter() const router = useRouter()
const { unlockMessages } = useFieldsActionMessages(props.data, props.activeVault) const { unlockMessages } = useFieldsActionMessages(props.data?.result, props.activeVault)
return ( return (
<div className={styles.container}> <div className={styles.container}>
<TxResponse <TxResponse
error={props.error?.message} error={props.data?.error}
handleClose={() => router.replace('/farm')} handleClose={() => router.replace('/farm')}
onSuccess={() => getVaults({ refetch: true })} onSuccess={() => getVaults({ refetch: true })}
response={props.data} response={props.data?.result}
title={t('fields.txMessages.unlock')} title={t('fields.txMessages.unlock')}
actions={unlockMessages} actions={unlockMessages}
/> />

View File

@ -58,7 +58,7 @@ export const ActionsRow = ({ row, type }: Props) => {
<div className='tippyContainer' {...attrs}> <div className='tippyContainer' {...attrs}>
{t('redbank.toDepositAssetOnChain', { {t('redbank.toDepositAssetOnChain', {
asset: assetSymbol, asset: assetSymbol,
chain: chainInfo?.chainName, chain: chainInfo?.name,
})} })}
</div> </div>
) )

View File

@ -27,7 +27,7 @@ export const useBorrowColumns = () => {
header: '', header: '',
cell: (info) => ( cell: (info) => (
<div className={styles.logo}> <div className={styles.logo}>
<Image alt='logo' height='100%' src={info.getValue().src} width='100%' /> <Image alt='logo' src={info.getValue().src} width={32} height={32} />
</div> </div>
), ),
}), }),

View File

@ -1,6 +1,6 @@
import { ColumnDef, createColumnHelper } from '@tanstack/react-table' import { ColumnDef, createColumnHelper } from '@tanstack/react-table'
import Tippy from '@tippyjs/react' import Tippy from '@tippyjs/react'
import classNames from 'classnames/bind' import classNames from 'classnames'
import { AnimatedNumber, Apr, Button, CellAmount, SVG } from 'components/common' import { AnimatedNumber, Apr, Button, CellAmount, SVG } from 'components/common'
import { convertPercentage } from 'functions' import { convertPercentage } from 'functions'
import { formatValue } from 'libs/parse' import { formatValue } from 'libs/parse'
@ -32,7 +32,7 @@ export const useDepositColumns = () => {
header: '', header: '',
cell: (info) => ( cell: (info) => (
<div className={styles.logo}> <div className={styles.logo}>
<Image alt='logo' height='100%' src={info.getValue().src} width='100%' /> <Image alt='logo' src={info.getValue().src} width={32} height={32} />
</div> </div>
), ),
}), }),

View File

@ -1,4 +1,4 @@
import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' import { TxBroadcastResult } from '@marsprotocol/wallet-connector'
import { useQueryClient } from '@tanstack/react-query' import { useQueryClient } from '@tanstack/react-query'
import { Action, Notification, TxResponse } from 'components/common' import { Action, Notification, TxResponse } from 'components/common'
import { findByDenom } from 'functions' import { findByDenom } from 'functions'
@ -13,8 +13,7 @@ import { ltvWeightedDepositValue, maintainanceMarginWeightedDepositValue } from
import { lookup, lookupDecimals } from 'libs/parse' import { lookup, lookupDecimals } from 'libs/parse'
import isEqual from 'lodash.isequal' import isEqual from 'lodash.isequal'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useMemo, useState } from 'react' import React, { useMemo, useState } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import useStore from 'store' import useStore from 'store'
import { NotificationType, ViewType } from 'types/enums' import { NotificationType, ViewType } from 'types/enums'
@ -54,7 +53,7 @@ export const RedbankAction = React.memo(
// ------------------ // ------------------
const [amount, setAmount] = useState(0) const [amount, setAmount] = useState(0)
const [submitted, setSubmitted] = useState(false) const [submitted, setSubmitted] = useState(false)
const [response, setResponse] = useState<ExecuteResult>() const [response, setResponse] = useState<TxBroadcastResult>()
const [error, setError] = useState<string>() const [error, setError] = useState<string>()
const [isMax, setIsMax] = useState<boolean>(false) const [isMax, setIsMax] = useState<boolean>(false)
const [capHit, setCapHit] = useState<boolean>(false) const [capHit, setCapHit] = useState<boolean>(false)
@ -126,7 +125,7 @@ export const RedbankAction = React.memo(
funds: funds:
activeView === ViewType.Deposit || activeView === ViewType.Repay activeView === ViewType.Deposit || activeView === ViewType.Repay
? [{ denom, amount: amount > 0 ? amount.toFixed(0) : '1' }] ? [{ denom, amount: amount > 0 ? amount.toFixed(0) : '1' }]
: [], : undefined,
contract: redBankContractAddress, contract: redBankContractAddress,
}) })

View File

@ -1,4 +1,4 @@
import { ChainInfoID } from '@marsprotocol/wallet-connector' import { ChainInfoID, WalletID } from '@marsprotocol/wallet-connector'
import atom from 'images/atom.svg' import atom from 'images/atom.svg'
import axlusdc from 'images/axlusdc.svg' import axlusdc from 'images/axlusdc.svg'
import juno from 'images/juno.svg' import juno from 'images/juno.svg'
@ -63,9 +63,9 @@ const OTHER_ASSETS: { [denom: string]: OtherAsset } = {
export const NETWORK_CONFIG: NetworkConfig = { export const NETWORK_CONFIG: NetworkConfig = {
name: ChainInfoID.OsmosisTestnet, name: ChainInfoID.OsmosisTestnet,
hiveUrl: 'https://osmosis-delphi-testnet-1.simply-vc.com.mt/XF32UOOU55CX/osmosis-hive/graphql', hiveUrl: 'https://osmosis-delphi-testnet-1.simply-vc.com.mt/XF32UOOU55CX/osmosis-hive/graphql/',
rpcUrl: 'https://osmosis-delphi-testnet-1.simply-vc.com.mt/XF32UOOU55CX/osmosis-rpc', rpcUrl: 'https://osmosis-delphi-testnet-1.simply-vc.com.mt/XF32UOOU55CX/osmosis-rpc/',
restUrl: 'https://osmosis-delphi-testnet-1.simply-vc.com.mt/XF32UOOU55CX/osmosis-lcd', restUrl: 'https://osmosis-delphi-testnet-1.simply-vc.com.mt/XF32UOOU55CX/osmosis-lcd/',
apolloAprUrl: 'https://stats.apollo.farm/api/apr/v1/all', apolloAprUrl: 'https://stats.apollo.farm/api/apr/v1/all',
contracts: { contracts: {
addressProvider: 'osmo17dyy6hyzzy6u5khy5lau7afa2y9kwknu0aprwqn8twndw2qhv8ls6msnjr', addressProvider: 'osmo17dyy6hyzzy6u5khy5lau7afa2y9kwknu0aprwqn8twndw2qhv8ls6msnjr',
@ -76,8 +76,8 @@ export const NETWORK_CONFIG: NetworkConfig = {
treasury: 'osmo1qv74pu0gjc9vuvkhayuj5j3q8fzmf4pnl643djqpv7enxr925g5q0wf7p3', treasury: 'osmo1qv74pu0gjc9vuvkhayuj5j3q8fzmf4pnl643djqpv7enxr925g5q0wf7p3',
safetyFund: 'osmo1j2mnzs7eqld4umtwky4hyf6f7kqcsg7ragh2l76ev7ucxcjvdjrs3tdezf', safetyFund: 'osmo1j2mnzs7eqld4umtwky4hyf6f7kqcsg7ragh2l76ev7ucxcjvdjrs3tdezf',
protocolRewardsCollector: 'osmo1xl7jguvkg807ya00s0l722nwcappfzyzrac3ug5tnjassnrmnfrs47wguz', protocolRewardsCollector: 'osmo1xl7jguvkg807ya00s0l722nwcappfzyzrac3ug5tnjassnrmnfrs47wguz',
creditManager: 'osmo1prwnxn3vlvh0kqmwxn8whqnavk8ze9hrccwpsapysgpa3pj8r2csy84grp', creditManager: 'osmo169xhpftsee275j3cjudj6qfzdpfp8sdllgeeprud4ynwr4sj6m4qel2ezp',
accountNft: 'osmo1ua5rw84jxg6e7ma4hx7v7yhqcks74cjnx38gpnsvtfzrtxhwcvjqgsxulx', accountNft: 'osmo1xpgx06z2c6zjk49feq75swgv78m6dvht6wramu2gltzjz5j959nq4hggxz',
}, },
assets: { assets: {
base: ASSETS.osmo, base: ASSETS.osmo,
@ -85,7 +85,8 @@ export const NETWORK_CONFIG: NetworkConfig = {
other: [OTHER_ASSETS.mars, OTHER_ASSETS.axlusdc], other: [OTHER_ASSETS.mars, OTHER_ASSETS.axlusdc],
}, },
appUrl: 'https://testnet.osmosis.zone', appUrl: 'https://testnet.osmosis.zone',
councilUrl: 'https://testnet.keplr.app/chains/mars-protocol-testnet', councilUrl: 'https://testnet.keplr.app/chains/mars-hub-testnet',
wallets: [WalletID.Keplr, WalletID.Leap, WalletID.Cosmostation],
} }
export const VAULT_CONFIGS: Vault[] = [ export const VAULT_CONFIGS: Vault[] = [
@ -104,7 +105,8 @@ export const VAULT_CONFIGS: Vault[] = [
color: '#DD5B65', color: '#DD5B65',
lockup: 86400, lockup: 86400,
provider: 'Apollo vault', provider: 'Apollo vault',
description: 'Up to 2× Leveraged Yield Farming with auto compounding of the LP tokens.', description:
'Up to 2.67× leveraged yield farming with auto compounding of the OSMO-ATOM LP tokens.',
ltv: { ltv: {
max: 0.625, max: 0.625,
contract: 0.63, contract: 0.63,
@ -126,7 +128,8 @@ export const VAULT_CONFIGS: Vault[] = [
color: '#DD5B65', color: '#DD5B65',
lockup: 86400 * 14, lockup: 86400 * 14,
provider: 'Apollo vault', provider: 'Apollo vault',
description: 'Up to 2× Leveraged Yield Farming with auto compounding of the LP tokens.', description:
'Up to 2.67× leveraged yield farming with auto compounding of the OSMO-ATOM LP tokens.',
ltv: { ltv: {
max: 0.625, max: 0.625,
contract: 0.63, contract: 0.63,
@ -148,7 +151,8 @@ export const VAULT_CONFIGS: Vault[] = [
color: '#DD5B65', color: '#DD5B65',
lockup: 86400 * 1, lockup: 86400 * 1,
provider: 'Apollo vault', provider: 'Apollo vault',
description: 'Up to 2× Leveraged Yield Farming with auto compounding of the LP tokens.', description:
'Up to 1.67× leveraged yield farming with auto compounding of the OSMO-JUNO LP tokens.',
ltv: { ltv: {
max: 0.4, max: 0.4,
contract: 0.4115, contract: 0.4115,
@ -170,7 +174,8 @@ export const VAULT_CONFIGS: Vault[] = [
color: '#DD5B65', color: '#DD5B65',
lockup: 86400 * 14, lockup: 86400 * 14,
provider: 'Apollo vault', provider: 'Apollo vault',
description: 'Up to 2× Leveraged Yield Farming with auto compounding of the LP tokens.', description:
'Up to 1.67× leveraged yield farming with auto compounding of the OSMO-JUNO LP tokens.',
ltv: { ltv: {
max: 0.4, max: 0.4,
contract: 4.115, contract: 4.115,

View File

@ -1,4 +1,4 @@
import { ChainInfoID } from '@marsprotocol/wallet-connector' import { ChainInfoID, WalletID } from '@marsprotocol/wallet-connector'
import atom from 'images/atom.svg' import atom from 'images/atom.svg'
import axlusdc from 'images/axlusdc.svg' import axlusdc from 'images/axlusdc.svg'
import mars from 'images/mars.svg' import mars from 'images/mars.svg'
@ -50,9 +50,9 @@ const OTHER_ASSETS: { [denom: string]: OtherAsset } = {
export const NETWORK_CONFIG: NetworkConfig = { export const NETWORK_CONFIG: NetworkConfig = {
name: ChainInfoID.OsmosisTestnet, name: ChainInfoID.OsmosisTestnet,
hiveUrl: 'https://osmosis-delphi-testnet-1.simply-vc.com.mt/XF32UOOU55CX/osmosis-hive/graphql', hiveUrl: 'https://osmosis-delphi-testnet-1.simply-vc.com.mt/XF32UOOU55CX/osmosis-hive/graphql/',
rpcUrl: 'https://osmosis-delphi-testnet-1.simply-vc.com.mt/XF32UOOU55CX/osmosis-rpc', rpcUrl: 'https://osmosis-delphi-testnet-1.simply-vc.com.mt/XF32UOOU55CX/osmosis-rpc/',
restUrl: 'https://osmosis-delphi-testnet-1.simply-vc.com.mt/XF32UOOU55CX/osmosis-lcd', restUrl: 'https://osmosis-delphi-testnet-1.simply-vc.com.mt/XF32UOOU55CX/osmosis-lcd/',
apolloAprUrl: 'https://stats.apollo.farm/api/apr/v1/all', apolloAprUrl: 'https://stats.apollo.farm/api/apr/v1/all',
contracts: { contracts: {
addressProvider: 'osmo17dyy6hyzzy6u5khy5lau7afa2y9kwknu0aprwqn8twndw2qhv8ls6msnjr', addressProvider: 'osmo17dyy6hyzzy6u5khy5lau7afa2y9kwknu0aprwqn8twndw2qhv8ls6msnjr',
@ -72,7 +72,8 @@ export const NETWORK_CONFIG: NetworkConfig = {
other: [OTHER_ASSETS.mars], other: [OTHER_ASSETS.mars],
}, },
appUrl: 'https://testnet.osmosis.zone', appUrl: 'https://testnet.osmosis.zone',
councilUrl: 'https://testnet.keplr.app/chains/mars-protocol-testnet', councilUrl: 'https://council.marsprotocol.io',
wallets: [WalletID.Keplr, WalletID.Leap, WalletID.Cosmostation, WalletID.Falcon],
} }
export const VAULT_CONFIGS: Vault[] = [ export const VAULT_CONFIGS: Vault[] = [

View File

@ -11,7 +11,7 @@ export const DEFAULT_SLIPPAGE = 0.01
/* other */ /* other */
export const FEE_EST_AMOUNT = '1' export const FEE_EST_AMOUNT = '1'
export const SESSION_WALLET_KEY = 'walletConnection' export const SESSION_WALLET_KEY = 'shuttle'
export const SWAP_THRESHOLD = 10 export const SWAP_THRESHOLD = 10
export const VAULT_DEPOSIT_BUFFER = 0.99 export const VAULT_DEPOSIT_BUFFER = 0.99
export const GAS_ADJUSTMENT = 1.3 export const GAS_ADJUSTMENT = 1.3

View File

@ -19,7 +19,11 @@ export const DEFAULT_POSITION: Position = {
total: 0, total: 0,
net: 0, net: 0,
}, },
apy: 0, apy: {
borrow: 5.2,
net: 7.7,
total: 19,
},
ltv: 0.5, ltv: 0.5,
currentLeverage: 1, currentLeverage: 1,
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +0,0 @@
import { ExecuteResult } from '@cosmjs/cosmwasm-stargate'
export const getAmountFromUnlockRes = (unlockData: ExecuteResult) => {
const stringValue = unlockData?.logs[0].events
.find((item) => item.type === 'coin_received')
?.attributes.find((item) => item.key === 'amount')?.value
if (!stringValue) return ''
if (stringValue?.indexOf('ibc/') > 0) {
return stringValue.split('ibc/')[0]
}
return stringValue.split('factory/')[0]
}

View File

@ -1,4 +1,4 @@
import { findAssetByDenom, formatValue } from 'libs/parse' import { findAssetByDenom, formatValue, lookup } from 'libs/parse'
import { Coin } from 'types/generated/mars-credit-manager/MarsCreditManager.types' import { Coin } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
export const getTokenValueFromCoins = (assets: Asset[], coins: Coin[] = []) => { export const getTokenValueFromCoins = (assets: Asset[], coins: Coin[] = []) => {
@ -7,6 +7,15 @@ export const getTokenValueFromCoins = (assets: Asset[], coins: Coin[] = []) => {
if (!asset) return '' if (!asset) return ''
return `${formatValue(Number(token.amount) / 10 ** asset.decimals, 2, 2, true)} ${asset.symbol}` const convertedValue = lookup(Number(token.amount), asset.symbol, asset.decimals)
return formatValue(
convertedValue,
2,
2,
true,
convertedValue >= 0.01 ? false : '>',
` ${asset.symbol}`,
)
}) })
} }

View File

@ -1,6 +1,5 @@
// @index(['./*.ts'], f => `export { ${f.name} } from '${f.path}'`) // @index(['./*.ts'], f => `export { ${f.name} } from '${f.path}'`)
export { coinsToActionCoins } from './coinsToActionCoins' export { coinsToActionCoins } from './coinsToActionCoins'
export { getAmountFromUnlockRes } from './getAmountFromUnlockRes'
export { getAmountsFromActiveVault } from './getAmountsFromActiveVault' export { getAmountsFromActiveVault } from './getAmountsFromActiveVault'
export { getClosePositionActions } from './getClosePositionActions' export { getClosePositionActions } from './getClosePositionActions'
export { getCoinFromPosition } from './getCoinFromPosition' export { getCoinFromPosition } from './getCoinFromPosition'

View File

@ -0,0 +1,15 @@
import { Coin } from '@cosmjs/launchpad'
import { TxBroadcastResult } from '@marsprotocol/wallet-connector'
import { extractCoinFromLog } from 'libs/parse'
export const getFeeFromResponse = (response: TxBroadcastResult): Coin | null => {
const stringValue = response?.response.events
.filter((msg: Record<string, string>) => msg.type === 'tx')
.map((msg: Record<string, string>) => msg.attributes)
.flat()
.find((msg: Record<string, string>) => msg.key === 'fee')?.value
if (!stringValue) return null
return extractCoinFromLog(stringValue)
}

View File

@ -2,6 +2,7 @@
export { convertPercentage } from './convertPercentage' export { convertPercentage } from './convertPercentage'
export { findByDenom } from './findByDenom' export { findByDenom } from './findByDenom'
export { formatToValueSymbol } from './formatToValueSymbol' export { formatToValueSymbol } from './formatToValueSymbol'
export { getFeeFromResponse } from './getFeeFromResponse'
export { getSwapUrl } from './getSwapUrl' export { getSwapUrl } from './getSwapUrl'
export { updateExchangeRate } from './updateExchangeRate' export { updateExchangeRate } from './updateExchangeRate'
// @endindex // @endindex

View File

@ -1,5 +1,5 @@
import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' import { TxBroadcastResult } from '@marsprotocol/wallet-connector'
import { getAmountFromUnlockRes, getTokenValueFromCoins } from 'functions/fields' import { getTokenValueFromCoins } from 'functions/fields'
import { useUnlockMessages } from 'hooks/queries' import { useUnlockMessages } from 'hooks/queries'
import { extractCoinFromLog, parseActionMessages } from 'libs/parse' import { extractCoinFromLog, parseActionMessages } from 'libs/parse'
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
@ -8,7 +8,7 @@ import useStore from 'store'
import { Coin } from 'types/generated/mars-credit-manager/MarsCreditManager.types' import { Coin } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
export const useFieldsActionMessages = ( export const useFieldsActionMessages = (
data?: ExecuteResult, data?: TxBroadcastResult,
vault?: ActiveVault, vault?: ActiveVault,
): { ): {
depositMessage?: FieldsAction depositMessage?: FieldsAction
@ -31,11 +31,9 @@ export const useFieldsActionMessages = (
const getDepositedCoins = useCallback((messages: unknown[]): Coin[] => { const getDepositedCoins = useCallback((messages: unknown[]): Coin[] => {
try { try {
return ( return (messages.filter((message: any) => message?.action === 'callback/deposit') as any).map(
messages.filter( (msg: any) => extractCoinFromLog(msg.coin_deposited),
(message: any) => message?.action === 'rover/execute/update_credit_account', )
) as any
).map((msg: any) => extractCoinFromLog(msg.coin_deposited))
} catch { } catch {
return [] return []
} }
@ -44,7 +42,7 @@ export const useFieldsActionMessages = (
const getBorrowedCoins = useCallback((messages: any[]): Coin[] => { const getBorrowedCoins = useCallback((messages: any[]): Coin[] => {
try { try {
return ( return (
messages.filter((message: any) => message?.action === 'outposts/red-bank/borrow') as any messages.filter((message: any) => message?.action === 'borrow' && message?.denom) as any
).map((msg: any) => ({ denom: msg.denom, amount: msg.amount })) ).map((msg: any) => ({ denom: msg.denom, amount: msg.amount }))
} catch { } catch {
return [] return []
@ -53,9 +51,9 @@ export const useFieldsActionMessages = (
const getSwapCoinIn = useCallback((messages: unknown[]): Coin | null => { const getSwapCoinIn = useCallback((messages: unknown[]): Coin | null => {
try { try {
return ( return (messages.filter((message: any) => message?.action === 'swap_fn') as any).map(
messages.filter((message: any) => message?.action === 'rover/swapper/swap_fn') as any (msg: any) => ({ denom: msg.denom_in, amount: msg.amount_in }),
).map((msg: any) => ({ denom: msg.denom_in, amount: msg.amount_in }))[0] )[0]
} catch { } catch {
return null return null
} }
@ -64,7 +62,7 @@ export const useFieldsActionMessages = (
const getSwapCoinOut = useCallback((messages: unknown[]): Coin | null => { const getSwapCoinOut = useCallback((messages: unknown[]): Coin | null => {
try { try {
const coinsSwapOutIndex = messages.findIndex( const coinsSwapOutIndex = messages.findIndex(
(message: any) => message?.action === 'rover/swapper/transfer_result', (message: any) => message?.action === 'transfer_result',
) as any ) as any
const msg: any = messages[coinsSwapOutIndex + 1] const msg: any = messages[coinsSwapOutIndex + 1]
@ -80,8 +78,10 @@ export const useFieldsActionMessages = (
const getRepayCoin = useCallback((messages: any[]): Coin | null => { const getRepayCoin = useCallback((messages: any[]): Coin | null => {
try { try {
return messages return messages
.filter((message: any) => message?.action === ('outposts/red-bank/repay' as any)) .filter((message: any) => message?.action === ('repay' as any) && message?.denom)
.map((msg: any) => ({ denom: msg.denom, amount: msg.amount }))[0] .map((msg: any) => {
return { denom: msg.denom, amount: msg.amount }
})[0]
} catch { } catch {
return null return null
} }
@ -90,9 +90,7 @@ export const useFieldsActionMessages = (
const getWithdrawnCoins = useCallback((messages: any[]): Coin[] => { const getWithdrawnCoins = useCallback((messages: any[]): Coin[] => {
try { try {
return ( return (
messages.filter( messages.filter((message: any) => message?.action === 'callback/withdraw') as any
(message: any) => message?.action === 'rover/credit-manager/callback/withdraw',
) as any
).map((msg: any) => { ).map((msg: any) => {
return extractCoinFromLog(msg.coin_withdrawn) return extractCoinFromLog(msg.coin_withdrawn)
}) })
@ -101,6 +99,18 @@ export const useFieldsActionMessages = (
} }
}, []) }, [])
const getVaultUnlockAmount = useCallback((messages: any[]): string | null => {
try {
return (
messages.filter((message: any) => message?.action === 'vault/request_unlock') as any
).map((msg: any) => {
return msg.unlock_amount
})[0]
} catch {
return null
}
}, [])
useEffect(() => { useEffect(() => {
if (!data) { if (!data) {
return return
@ -117,9 +127,10 @@ export const useFieldsActionMessages = (
const swapCoinOut = getSwapCoinOut(messages) const swapCoinOut = getSwapCoinOut(messages)
const repayCoin = getRepayCoin(messages) const repayCoin = getRepayCoin(messages)
const withdrawnCoins = getWithdrawnCoins(messages) const withdrawnCoins = getWithdrawnCoins(messages)
const vaultUnlockAmount = getVaultUnlockAmount(messages)
if (!vaultTokenAmount) { if (!vaultTokenAmount && vaultUnlockAmount) {
setVaultTokenAmount(getAmountFromUnlockRes(data)) setVaultTokenAmount(vaultUnlockAmount)
} }
if (depositedCoins.length) { if (depositedCoins.length) {
@ -170,6 +181,7 @@ export const useFieldsActionMessages = (
getRepayCoin, getRepayCoin,
getWithdrawnCoins, getWithdrawnCoins,
vaultTokenAmount, vaultTokenAmount,
getVaultUnlockAmount,
whitelistedAssets, whitelistedAssets,
]) ])

View File

@ -1,12 +1,32 @@
import { StdFee } from '@cosmjs/stargate' import { StdFee } from '@cosmjs/stargate'
import { useMutation } from '@tanstack/react-query' import { useMutation } from '@tanstack/react-query'
import { parseActionMessages } from 'libs/parse'
import useStore from 'store' import useStore from 'store'
export const useCreateCreditAccount = () => { export const useCreateCreditAccount = () => {
const creditManagerClient = useStore((s) => s.creditManagerClient) const executeMsg = useStore((s) => s.executeMsg)
const networkConfig = useStore((s) => s.networkConfig)
return useMutation(async (fee: StdFee) => { return useMutation(async (fee: StdFee) => {
const executeResult = await creditManagerClient?.createCreditAccount(fee) const message = { create_credit_account: {} }
return executeResult?.logs[0].events[2].attributes[6].value
if (!networkConfig) return null
return executeMsg({
msg: message,
fee,
contract: networkConfig.contracts.creditManager,
}).then((broadcastResult) => {
if (broadcastResult) {
try {
const messages = parseActionMessages(broadcastResult)
return (
messages?.find(
(message: Record<string, string>) => message?.action === 'mint',
) as Record<string, string>
)['token_id']
} catch {}
}
})
}) })
} }

View File

@ -13,29 +13,33 @@ interface Props {
export const useUpdateAccount = () => { export const useUpdateAccount = () => {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const creditManagerClient = useStore((s) => s.creditManagerClient) const executeMsg = useStore((s) => s.executeMsg)
const networkConfig = useStore((s) => s.networkConfig)
const getVaults = useStore((s) => s.getVaults) const getVaults = useStore((s) => s.getVaults)
return useMutation( return useMutation(async (props: Props) => {
async (props: Props) => {
queryClient.removeQueries([QUERY_KEYS.ESTIMATE_FARM_FEE]) queryClient.removeQueries([QUERY_KEYS.ESTIMATE_FARM_FEE])
return creditManagerClient?.updateCreditAccount( const message = {
{ update_credit_account: {
accountId: props.accountId, account_id: props.accountId,
actions: props.actions, actions: props.actions,
}, },
props.fee, }
undefined,
props.funds, if (!networkConfig) return
)
}, return executeMsg({
{ msg: message,
onSuccess: () => { fee: props.fee,
contract: networkConfig.contracts.creditManager,
funds: props.funds,
}).then((broadcastResult) => {
if (broadcastResult?.response.code === 0) {
getVaults({ refetch: true }) getVaults({ refetch: true })
}, return { result: broadcastResult }
onError: (error: Error) => { } else {
return `${error.message}` return { error: broadcastResult?.rawLogs }
}, }
}, })
) })
} }

View File

@ -16,4 +16,5 @@ export { useUnlockMessages } from './useUnlockMessages'
export { useUserBalance } from './useUserBalance' export { useUserBalance } from './useUserBalance'
export { useUserDebt } from './useUserDebt' export { useUserDebt } from './useUserDebt'
export { useUserDeposit } from './useUserDeposit' export { useUserDeposit } from './useUserDeposit'
export { useUserIcns } from './useUserIcns'
// @endindex // @endindex

View File

@ -9,7 +9,7 @@ import { Action, Coin } from 'types/generated/mars-credit-manager/MarsCreditMana
import { useEstimateFarmFee } from './useEstimateFarmFee' import { useEstimateFarmFee } from './useEstimateFarmFee'
interface Props { interface Props {
accountId?: string accountId?: null | string
prevPosition?: Position prevPosition?: Position
position: Position position: Position
vault: Vault vault: Vault

View File

@ -1,14 +1,13 @@
import { MsgExecuteContractEncodeObject } from '@cosmjs/cosmwasm-stargate' import { MsgExecuteContract } from '@marsprotocol/wallet-connector'
import { GasPrice } from '@cosmjs/stargate'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { GAS_ADJUSTMENT, GAS_PRICE } from 'constants/appConstants' import { GAS_ADJUSTMENT } from 'constants/appConstants'
import useStore from 'store' import useStore from 'store'
import { QUERY_KEYS } from 'types/enums/queryKeys' import { QUERY_KEYS } from 'types/enums/queryKeys'
import { Action, Coin } from 'types/generated/mars-credit-manager/MarsCreditManager.types' import { Action, Coin } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
interface Props { interface Props {
accountId?: string accountId?: null | string
actions?: Action[] actions?: Action[]
funds?: Coin[] funds?: Coin[]
isCreate?: boolean isCreate?: boolean
@ -17,44 +16,48 @@ interface Props {
export const useEstimateFarmFee = (props: Props) => { export const useEstimateFarmFee = (props: Props) => {
const userWalletAddress = useStore((s) => s.userWalletAddress) const userWalletAddress = useStore((s) => s.userWalletAddress)
const creditManagerMsgComposer = useStore((s) => s.creditManagerMsgComposer)
const client = useStore((s) => s.client) const client = useStore((s) => s.client)
const networkConfig = useStore((s) => s.networkConfig)
return useQuery( return useQuery(
[QUERY_KEYS.ESTIMATE_FARM_FEE, props.actions], [QUERY_KEYS.ESTIMATE_FARM_FEE, props.actions],
async () => { async () => {
const gasPrice = GasPrice.fromString(GAS_PRICE)
const gasAdjustment = GAS_ADJUSTMENT const gasAdjustment = GAS_ADJUSTMENT
if (!creditManagerMsgComposer || !client) return null if (!client) return null
let msg: MsgExecuteContractEncodeObject | null = null if (!networkConfig) return null
if (props.isCreate) {
msg = creditManagerMsgComposer.createCreditAccount()
} else if (props.accountId && props.actions?.length) {
msg = creditManagerMsgComposer.updateCreditAccount(
{
accountId: props.accountId,
actions: props.actions,
},
props.funds,
)
}
if (!msg) return null
try { try {
const gasUsed = await client.simulate(userWalletAddress, [msg], undefined) const simulateOptions = {
messages: [
const fee = new BigNumber(Number(gasPrice.amount)) new MsgExecuteContract({
.multipliedBy(gasUsed) sender: userWalletAddress,
.multipliedBy(gasAdjustment) contract: networkConfig.contracts.creditManager,
msg: props.isCreate
return { ? { create_credit_account: {} }
amount: [{ denom: 'uosmo', amount: fee.toFixed(0) }], : {
gas: new BigNumber(gasUsed).multipliedBy(gasAdjustment).toFixed(0), update_credit_account: {
account_id: props.accountId,
actions: props.actions,
},
},
funds: props.funds,
}),
],
wallet: client.recentWallet,
} }
const result = await client.simulate(simulateOptions)
return result.success
? {
amount: result.fee ? result.fee.amount : [],
gas: new BigNumber(result.fee ? result.fee.gas : 0)
.multipliedBy(gasAdjustment)
.toFixed(0),
}
: null
} catch { } catch {
return null return null
} }
@ -63,7 +66,6 @@ export const useEstimateFarmFee = (props: Props) => {
enabled: enabled:
!props.isLoading && !props.isLoading &&
!!client && !!client &&
!!creditManagerMsgComposer &&
!!userWalletAddress && !!userWalletAddress &&
(props.isCreate || (!!props.accountId && !!props.actions?.length)), (props.isCreate || (!!props.accountId && !!props.actions?.length)),
}, },

View File

@ -1,10 +1,9 @@
import { MsgExecuteContractEncodeObject } from '@cosmjs/cosmwasm-stargate' import { MsgExecuteContractEncodeObject } from '@cosmjs/cosmwasm-stargate'
import { toUtf8 } from '@cosmjs/encoding'
import { Coin } from '@cosmjs/proto-signing' import { Coin } from '@cosmjs/proto-signing'
import { GasPrice } from '@cosmjs/stargate' import { MsgExecuteContract } from '@marsprotocol/wallet-connector'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { GAS_ADJUSTMENT, GAS_PRICE } from 'constants/appConstants' import { GAS_ADJUSTMENT } from 'constants/appConstants'
import useStore from 'store' import useStore from 'store'
import { QUERY_KEYS } from 'types/enums/queryKeys' import { QUERY_KEYS } from 'types/enums/queryKeys'
import { ContractMsg } from 'types/types' import { ContractMsg } from 'types/types'
@ -22,37 +21,35 @@ export const useEstimateFee = (props: Props) => {
const client = useStore((s) => s.client) const client = useStore((s) => s.client)
return useQuery( return useQuery(
[QUERY_KEYS.ESTIMATE_FEE], [QUERY_KEYS.ESTIMATE_FEE, props.msg],
async () => { async () => {
const sender = props.sender ? props.sender : userWalletAddress const sender = props.sender ? props.sender : userWalletAddress
const gasPrice = GasPrice.fromString(GAS_PRICE)
const gasAdjustment = GAS_ADJUSTMENT const gasAdjustment = GAS_ADJUSTMENT
if (!client) return if (!client || !props.contract || !props.msg) return
const msg = props.executeMsg
? props.executeMsg
: {
typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract',
value: {
sender: sender,
contract: props.contract,
msg: toUtf8(JSON.stringify(props.msg)),
funds: props.funds,
},
}
try { try {
const gasUsed = await client.simulate(sender, [msg], undefined) const simulateOptions = {
messages: [
const fee = new BigNumber(Number(gasPrice.amount)) new MsgExecuteContract({
.multipliedBy(gasUsed) sender: sender,
.multipliedBy(gasAdjustment) contract: props.contract,
msg: props.msg,
return { funds: props.funds,
amount: [{ denom: 'uosmo', amount: fee.toFixed(0) }], }),
gas: new BigNumber(gasUsed).multipliedBy(gasAdjustment).toFixed(0), ],
wallet: client.recentWallet,
} }
const result = await client.simulate(simulateOptions)
return result.success
? {
amount: result.fee ? result.fee.amount : [],
gas: new BigNumber(result.fee ? result.fee.gas : 0)
.multipliedBy(gasAdjustment)
.toFixed(0),
}
: null
} catch { } catch {
return null return null
} }

View File

@ -11,13 +11,15 @@ interface Props {
export const useProvideLiquidity = (props: Props) => { export const useProvideLiquidity = (props: Props) => {
const creditManagerClient = useStore((s) => s.creditManagerClient) const creditManagerClient = useStore((s) => s.creditManagerClient)
return useQuery( return useQuery<number>(
[QUERY_KEYS.PROVIDE_LIQUIDITY, props.coins], [QUERY_KEYS.PROVIDE_LIQUIDITY, props.coins],
async () => { async () => {
if (!creditManagerClient || !props.coins.length) return null if (!creditManagerClient || !props.coins.length) return null
return creditManagerClient.estimateProvideLiquidity({ return creditManagerClient.query({
coinsIn: props.coins, estimate_provide_liquidity: {
lpTokenOut: props.vault?.denoms.lpToken || '', coins_in: props.coins,
lp_token_out: props.vault?.denoms.lpToken || '',
},
}) })
}, },
{ {

View File

@ -5,7 +5,7 @@ import { useAsset } from 'hooks/data'
import useStore from 'store' import useStore from 'store'
import { QUERY_KEYS } from 'types/enums/queryKeys' import { QUERY_KEYS } from 'types/enums/queryKeys'
const poolsEndpoint = '/osmosis/gamm/v1beta1/pools/' const poolsEndpoint = 'osmosis/gamm/v1beta1/pools/'
export const useSpotPrice = (symbol: string) => { export const useSpotPrice = (symbol: string) => {
const displayCurrency = useStore((s) => s.displayCurrency) const displayCurrency = useStore((s) => s.displayCurrency)

View File

@ -3,8 +3,8 @@ import { getTokenValueFromCoins } from 'functions/fields'
import { formatUnlockDate } from 'libs/parse' import { formatUnlockDate } from 'libs/parse'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import useStore from 'store' import useStore from 'store'
import { VaultClient } from 'types/classes'
import { QUERY_KEYS } from 'types/enums/queryKeys' import { QUERY_KEYS } from 'types/enums/queryKeys'
import { MarsMockVaultClient } from 'types/generated/mars-mock-vault/MarsMockVault.client'
interface Props { interface Props {
vault?: ActiveVault vault?: ActiveVault
@ -23,15 +23,19 @@ export const useUnlockMessages = (props: Props) => {
async () => { async () => {
if (!client || !userWalletAddress || !props.vault || !creditManagerClient) return null if (!client || !userWalletAddress || !props.vault || !creditManagerClient) return null
const vaultClient = new MarsMockVaultClient(client, userWalletAddress, props.vault.address) const vaultClient = new VaultClient(props.vault.address, client)
const lpTokenAmount = await vaultClient.previewRedeem({ const lpTokenAmount = await vaultClient.query({
preview_redeem: {
amount: props.vaultTokenAmount, amount: props.vaultTokenAmount,
},
}) })
const lpToken = { denom: props.vault.denoms.lpToken, amount: lpTokenAmount } const lpToken = { denom: props.vault.denoms.lpToken, amount: lpTokenAmount }
const coins = await creditManagerClient.estimateWithdrawLiquidity({ const coins = await creditManagerClient.query({
lpToken, estimate_withdraw_liquidity: {
lp_token: lpToken,
},
}) })
return [ return [

View File

@ -3,6 +3,7 @@ import { useQuery } from '@tanstack/react-query'
import { gql, request } from 'graphql-request' import { gql, request } from 'graphql-request'
import { useMemo } from 'react' import { useMemo } from 'react'
import useStore from 'store' import useStore from 'store'
import { QUERY_KEYS } from 'types/enums/queryKeys'
export interface UserBalanceData { export interface UserBalanceData {
balance: { balance: {
@ -16,7 +17,7 @@ export const useUserBalance = () => {
const processUserBalanceQuery = useStore((s) => s.processUserBalanceQuery) const processUserBalanceQuery = useStore((s) => s.processUserBalanceQuery)
const result = useQuery<UserBalanceData>( const result = useQuery<UserBalanceData>(
[], [QUERY_KEYS.USER_BALANCE],
async () => { async () => {
return await request( return await request(
hiveUrl!, hiveUrl!,

View File

@ -0,0 +1,54 @@
import { useQuery } from '@tanstack/react-query'
import { gql, request } from 'graphql-request'
import { useMemo } from 'react'
import useStore from 'store'
import { QUERY_KEYS } from 'types/enums/queryKeys'
export interface UserIcnsData {
wasm: {
contractQuery: {
names: string[]
primary_name: string
}
}
}
export const useUserIcns = () => {
/* only possible to query on mainnet */
const hiveUrl = 'https://osmosis-mars-frontend.simply-vc.com.mt/GGSFGSFGFG34/osmosis-hive/graphql'
const resolverContract = 'osmo1xk0s8xgktn9x5vwcgtjdxqzadg88fgn33p8u9cnpdxwemvxscvast52cdd'
const userWalletAddress = useStore((s) => s.userWalletAddress)
const setUserIcns = useStore((s) => s.setUserIcns)
const result = useQuery<UserIcnsData>(
[QUERY_KEYS.USER_ICNS],
async () => {
return await request(
hiveUrl!,
gql`
query UserIcnsQuery {
wasm {
contractQuery(contractAddress:"${resolverContract}", query: {
icns_names:{
address: "${userWalletAddress}"
}
})
}
}`,
)
},
{
enabled: !!userWalletAddress || false,
onSuccess: (data) => {
const icns = data.wasm.contractQuery.primary_name
if (icns !== '') setUserIcns(icns)
},
},
)
return {
...result,
data: useMemo(() => result.data, [result.data]),
}
}

View File

@ -3,6 +3,12 @@ import LanguageDetector from 'i18next-browser-languagedetector'
import HttpApi from 'i18next-http-backend' import HttpApi from 'i18next-http-backend'
import { initReactI18next } from 'react-i18next' import { initReactI18next } from 'react-i18next'
declare module 'i18next' {
interface CustomTypeOptions {
returnNull: false
}
}
i18next i18next
.use(HttpApi) .use(HttpApi)
.use(LanguageDetector) .use(LanguageDetector)
@ -11,7 +17,7 @@ i18next
backend: { backend: {
crossDomain: true, crossDomain: true,
loadPath() { loadPath() {
return 'https://raw.githubusercontent.com/mars-protocol/translations/develop/{{lng}}.json' return 'https://raw.githubusercontent.com/mars-protocol/translations/main/{{lng}}.json'
}, },
}, },
react: { react: {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,4 +1,4 @@
import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' import { TxBroadcastResult } from '@marsprotocol/wallet-connector'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { DAY_IN_SECONDS, HOUR_IN_SECONDS, MINUTE_IN_SECONDS } from 'constants/timeConstants' import { DAY_IN_SECONDS, HOUR_IN_SECONDS, MINUTE_IN_SECONDS } from 'constants/timeConstants'
import moment from 'moment' import moment from 'moment'
@ -282,21 +282,31 @@ export const extractCoinFromLog = (text: string) => {
return { amount: arr[0], denom: arr[1] } return { amount: arr[0], denom: arr[1] }
} }
export const parseActionMessages = (data: ExecuteResult) => { export const parseActionMessages = (data: TxBroadcastResult) => {
const wasmEvents = data.logs[0].events.find((object) => object.type === 'wasm') const wasmEvents: [] = data.response.events
if (wasmEvents) { .filter((object: Record<string, string>) => object.type === 'wasm')
return wasmEvents.attributes.reduce((prev: {}[], curr) => { .map((event: Record<string, string>) => event?.attributes)
if (curr.key === '_contract_address') { .flat()
if (wasmEvents.length) {
return wasmEvents.reduce((prev: {}[], curr: any) => {
if (curr.key === 'action') {
prev.push({ [curr.key]: curr.value }) prev.push({ [curr.key]: curr.value })
return prev return prev
} else { } else {
if (prev.length) {
Object.assign(prev[prev.length - 1], { [curr.key]: curr.value }) Object.assign(prev[prev.length - 1], { [curr.key]: curr.value })
}
return prev return prev
} }
}, []) }, [])
} }
} }
export const toBase64 = (obj: object) => {
return Buffer.from(JSON.stringify(obj)).toString('base64')
}
export const ltvToLeverage = (ltv: number) => { export const ltvToLeverage = (ltv: number) => {
const leverage = 1 / (1 - ltv) const leverage = 1 / (1 - ltv)
return new BigNumber(leverage).decimalPlaces(2).toNumber() return new BigNumber(leverage).decimalPlaces(2).toNumber()

View File

@ -19,7 +19,11 @@ export const position: Position = {
total: 0, total: 0,
net: 0, net: 0,
}, },
apy: 0, apy: {
borrow: 5.2,
net: 7.7,
total: 19,
},
ltv: 0.5, ltv: 0.5,
currentLeverage: 1, currentLeverage: 1,
} }

View File

@ -7,7 +7,7 @@ import {
} from 'components/fields' } from 'components/fields'
import { FIELDS_FEATURE } from 'constants/appConstants' import { FIELDS_FEATURE } from 'constants/appConstants'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import React, { useEffect } from 'react' import { useEffect } from 'react'
import useStore from 'store' import useStore from 'store'
import styles from './Fields.module.scss' import styles from './Fields.module.scss'
@ -23,7 +23,8 @@ const Fields = () => {
const userWalletAddress = useStore((s) => s.userWalletAddress) const userWalletAddress = useStore((s) => s.userWalletAddress)
useEffect(() => { useEffect(() => {
if (!getVaults || !accountNftClient || !client || !creditManagerClient) return if (!getVaults || !accountNftClient || !client || !creditManagerClient || !userWalletAddress)
return
if (userWalletAddress && prefUserWalletAddress !== userWalletAddress) { if (userWalletAddress && prefUserWalletAddress !== userWalletAddress) {
prefUserWalletAddress = userWalletAddress prefUserWalletAddress = userWalletAddress
getVaults({ refetch: true }) getVaults({ refetch: true })

View File

@ -25,7 +25,9 @@ const CloseVaultPosition = () => {
} }
useEffect(() => { useEffect(() => {
if (!closeFee || !closeActions || !activeVault || isLoading || data || error) return if (!closeFee || !closeActions || !activeVault || isLoading || data?.error || data?.result)
return
mutate({ mutate({
accountId: activeVault.position.accountId, accountId: activeVault.position.accountId,
actions: closeActions, actions: closeActions,
@ -46,7 +48,6 @@ const CloseVaultPosition = () => {
return ( return (
<ClosePositionResponse <ClosePositionResponse
data={data} data={data}
error={error}
isLoading={isLoading} isLoading={isLoading}
accountId={ref.current.position.accountId} accountId={ref.current.position.accountId}
/> />

View File

@ -4,7 +4,6 @@ import { useCreateCreditAccount, useUpdateAccount } from 'hooks/mutations'
import { useEditPosition, useEstimateFarmFee } from 'hooks/queries' import { useEditPosition, useEstimateFarmFee } from 'hooks/queries'
import { getTimeAndUnit } from 'libs/parse' import { getTimeAndUnit } from 'libs/parse'
import router from 'next/router' import router from 'next/router'
import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styles from './SetupPosition.module.scss' import styles from './SetupPosition.module.scss'
@ -22,7 +21,6 @@ const SetupPosition = (props: Props) => {
data: accountId, data: accountId,
isLoading: isLoadingCreate, isLoading: isLoadingCreate,
} = useCreateCreditAccount() } = useCreateCreditAccount()
const { const {
mutate: enterVault, mutate: enterVault,
data: enterVaultData, data: enterVaultData,
@ -57,11 +55,10 @@ const SetupPosition = (props: Props) => {
enterVault({ accountId, actions: editActions, fee: editFee, funds: editFunds }) enterVault({ accountId, actions: editActions, fee: editFee, funds: editFunds })
} }
if (isLoadingEnterVault || enterVaultError || enterVaultData) { if (isLoadingEnterVault || enterVaultData) {
return ( return (
<SetUpResponse <SetUpResponse
data={enterVaultData} data={enterVaultData}
error={enterVaultError}
isLoading={isLoadingEnterVault} isLoading={isLoadingEnterVault}
accountId={accountId || ''} accountId={accountId || ''}
/> />

View File

@ -162,31 +162,18 @@ const EditVault = (props: Props) => {
if (isLoadingEdit || editData || editError) { if (isLoadingEdit || editData || editError) {
return ( return (
<EditResponse <EditResponse data={editData} isLoading={isLoadingEdit} activeVault={props.activeVault} />
data={editData}
error={editError}
isLoading={isLoadingEdit}
activeVault={props.activeVault}
/>
) )
} }
if (isLoadingRepay || repayData || repayError) { if (isLoadingRepay || repayData || repayError) {
return ( return <RepayResponse data={repayData} isLoading={isLoadingRepay} vault={props.activeVault} />
<RepayResponse
data={repayData}
error={repayError}
isLoading={isLoadingRepay}
vault={props.activeVault}
/>
)
} }
if (isLoadingUnlock || unlockData || unlockError) { if (isLoadingUnlock || unlockData || unlockError) {
return ( return (
<UnlockResponse <UnlockResponse
data={unlockData} data={unlockData}
error={unlockError}
isLoading={isLoadingUnlock} isLoading={isLoadingUnlock}
activeVault={props.activeVault} activeVault={props.activeVault}
/> />

View File

@ -49,14 +49,7 @@ const RepayVault = (props: Props) => {
} }
if (isLoadingRepay || repayData || repayError) { if (isLoadingRepay || repayData || repayError) {
return ( return <RepayResponse data={repayData} isLoading={isLoadingRepay} vault={props.activeVault} />
<RepayResponse
data={repayData}
error={repayError}
isLoading={isLoadingRepay}
vault={props.activeVault}
/>
)
} }
const isSameAmounts = isEqual(prevPosition.amounts, repayPosition.amounts) const isSameAmounts = isEqual(prevPosition.amounts, repayPosition.amounts)

View File

@ -5,7 +5,6 @@ import { useActiveVault } from 'hooks/data'
import { useUpdateAccount } from 'hooks/mutations' import { useUpdateAccount } from 'hooks/mutations'
import { useRequestUnlockPosition } from 'hooks/queries/useRequestUnlockPosition' import { useRequestUnlockPosition } from 'hooks/queries/useRequestUnlockPosition'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styles from './UnlockDisclaimer.module.scss' import styles from './UnlockDisclaimer.module.scss'
@ -49,12 +48,7 @@ const Unlock = () => {
if (unlockData || unlockError || isLoadingUnlock) { if (unlockData || unlockError || isLoadingUnlock) {
return ( return (
<UnlockResponse <UnlockResponse data={unlockData} isLoading={isLoadingUnlock} activeVault={activeVault} />
data={unlockData}
error={unlockError}
isLoading={isLoadingUnlock}
activeVault={activeVault}
/>
) )
} }

View File

@ -1,11 +1,15 @@
import { ExecuteResult } from '@cosmjs/cosmwasm-stargate'
import { LcdClient } from '@cosmjs/launchpad' import { LcdClient } from '@cosmjs/launchpad'
import { Coin, StdFee } from '@cosmjs/stargate' import { Coin, StdFee } from '@cosmjs/stargate'
import { WalletChainInfo, WalletSigningCosmWasmClient } from '@marsprotocol/wallet-connector' import {
SimplifiedChainInfo,
TxBroadcastResult,
WalletClient,
} from '@marsprotocol/wallet-connector'
import { BlockHeightData } from 'hooks/queries/useBlockHeight' import { BlockHeightData } from 'hooks/queries/useBlockHeight'
import { MarketDepositsData } from 'hooks/queries/useMarketDeposits' import { MarketDepositsData } from 'hooks/queries/useMarketDeposits'
import { SafetyFundBalanceData } from 'hooks/queries/useSafetyFundBalance' import { SafetyFundBalanceData } from 'hooks/queries/useSafetyFundBalance'
import { UserBalanceData } from 'hooks/queries/useUserBalance' import { UserBalanceData } from 'hooks/queries/useUserBalance'
import { UserIcnsData } from 'hooks/queries/useUserIcns'
import { Network } from 'types/enums/network' import { Network } from 'types/enums/network'
import { ContractMsg } from 'types/types' import { ContractMsg } from 'types/types'
@ -22,8 +26,8 @@ export interface CommonSlice {
decimals: number decimals: number
} }
baseToDisplayCurrencyRatio?: number baseToDisplayCurrencyRatio?: number
chainInfo?: WalletChainInfo chainInfo?: SimplifiedChainInfo
client?: WalletSigningCosmWasmClient client?: WalletClient
currentNetwork: Network currentNetwork: Network
displayCurrency: { displayCurrency: {
denom: string denom: string
@ -51,7 +55,7 @@ export interface CommonSlice {
userUnclaimedRewards: string userUnclaimedRewards: string
userMarsTokenBalances: Coin[] userMarsTokenBalances: Coin[]
userWalletAddress: string userWalletAddress: string
userWalletName: string userIcns?: string
vaultConfigs: Vault[] vaultConfigs: Vault[]
whitelistedAssets: Asset[] whitelistedAssets: Asset[]
// ------------------ // ------------------
@ -65,7 +69,7 @@ export interface CommonSlice {
contract: string contract: string
fee: StdFee fee: StdFee
sender?: string sender?: string
}) => Promise<ExecuteResult | undefined> }) => Promise<TxBroadcastResult | undefined>
loadNetworkConfig: () => void loadNetworkConfig: () => void
queryContract: <T>( queryContract: <T>(
contractAddress: string, contractAddress: string,
@ -76,22 +80,23 @@ export interface CommonSlice {
// ------------------ // ------------------
// SETTERS // SETTERS
// ------------------ // ------------------
setChainInfo: (chainInfo: WalletChainInfo) => void setChainInfo: (chainInfo: SimplifiedChainInfo) => void
setCurrentNetwork: (network: Network) => void setCurrentNetwork: (network: Network) => void
setTutorialStep: (type: 'fields' | 'redbank', step?: number) => void setTutorialStep: (type: 'fields' | 'redbank', step?: number) => void
setLcdClient: (rpc: string, chainID: string) => void setLcdClient: (rpc: string, chainID: string) => void
setNetworkError: (isError: boolean) => void setNetworkError: (isError: boolean) => void
setClient: (client: WalletSigningCosmWasmClient) => void setClient: (client: WalletClient) => void
setQueryError: (name: string, isError: boolean) => void setQueryError: (name: string, isError: boolean) => void
setServerError: (isError: boolean) => void setServerError: (isError: boolean) => void
setUserIcns: (icns: string) => void
setUserWalletAddress: (address: string) => void setUserWalletAddress: (address: string) => void
setUserWalletName: (name: string) => void
// ------------------ // ------------------
// QUERY RELATED // QUERY RELATED
// ------------------ // ------------------
previousBlockHeightQueryData?: BlockHeightData previousBlockHeightQueryData?: BlockHeightData
previousSafetyFundBalanceQueryData?: SafetyFundBalanceData previousSafetyFundBalanceQueryData?: SafetyFundBalanceData
previousUserBalanceQueryData?: UserBalanceData previousUserBalanceQueryData?: UserBalanceData
previousUserIcnsQueryData?: UserIcnsData
previousUserUnclaimedBalanceQueryData?: number previousUserUnclaimedBalanceQueryData?: number
processMarketDepositsQuery: (data: MarketDepositsData) => void processMarketDepositsQuery: (data: MarketDepositsData) => void
processUserBalanceQuery: (data: UserBalanceData) => void processUserBalanceQuery: (data: UserBalanceData) => void

View File

@ -1,16 +1,12 @@
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate' import { AccountNftClient, CreditManagerClient } from 'types/classes'
import { MarsAccountNftInterface } from 'types/generated/mars-account-nft/MarsAccountNft.client'
import { MarsCreditManagerClient } from 'types/generated/mars-credit-manager/MarsCreditManager.client'
import { MarsCreditManagerMessageComposer } from 'types/generated/mars-credit-manager/MarsCreditManager.message-composer' import { MarsCreditManagerMessageComposer } from 'types/generated/mars-credit-manager/MarsCreditManager.message-composer'
export interface FieldsSlice { export interface FieldsSlice {
accountNftClient?: MarsAccountNftInterface accountNftClient?: AccountNftClient
creditManagerClient?: MarsCreditManagerClient creditManagerClient?: CreditManagerClient
creditManagerMsgComposer?: MarsCreditManagerMessageComposer creditManagerMsgComposer?: MarsCreditManagerMessageComposer
isRepay: boolean isRepay: boolean
position?: Position position?: Position
setAccountNftClient: (client: SigningCosmWasmClient) => void
setCreditManagerClient: (client: SigningCosmWasmClient) => void
setCreditManagerMsgComposer: (address: string, contract: string) => void setCreditManagerMsgComposer: (address: string, contract: string) => void
setIsRepay: (isRepay: boolean) => void setIsRepay: (isRepay: boolean) => void
setPosition: (position?: Position) => void setPosition: (position?: Position) => void

View File

@ -1,7 +1,11 @@
import { ExecuteResult } from '@cosmjs/cosmwasm-stargate'
import { LcdClient } from '@cosmjs/launchpad' import { LcdClient } from '@cosmjs/launchpad'
import { Coin } from '@cosmjs/stargate' import { Coin } from '@cosmjs/stargate'
import { WalletChainInfo, WalletSigningCosmWasmClient } from '@marsprotocol/wallet-connector' import {
MsgExecuteContract,
SimplifiedChainInfo,
TxBroadcastResult,
WalletClient,
} from '@marsprotocol/wallet-connector'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { BlockHeightData } from 'hooks/queries/useBlockHeight' import { BlockHeightData } from 'hooks/queries/useBlockHeight'
import { MarketDepositsData } from 'hooks/queries/useMarketDeposits' import { MarketDepositsData } from 'hooks/queries/useMarketDeposits'
@ -46,7 +50,6 @@ const commonSlice = (
queryErrors: [], queryErrors: [],
slippage: 0.02, slippage: 0.02,
tutorialSteps: { redbank: 1, fields: 1 }, tutorialSteps: { redbank: 1, fields: 1 },
userWalletName: '',
userBalances: [], userBalances: [],
userMarsTokenBalances: [], userMarsTokenBalances: [],
userUnclaimedRewards: '0', userUnclaimedRewards: '0',
@ -80,20 +83,30 @@ const commonSlice = (
return new BigNumber(coin.amount).div(exchangeRate).toNumber() return new BigNumber(coin.amount).div(exchangeRate).toNumber()
}, },
executeMsg: async (options: StrategyExecuteMsgOptions): Promise<ExecuteResult | undefined> => { executeMsg: async (
options: StrategyExecuteMsgOptions,
): Promise<TxBroadcastResult | undefined> => {
const client = get().client! const client = get().client!
if (!options.sender) options.sender = get().userWalletAddress if (!options.sender) options.sender = get().userWalletAddress
const broadcastOptions = {
messages: [
new MsgExecuteContract({
sender: options.sender,
contract: options.contract,
msg: options.msg,
funds: options.funds,
}),
],
feeAmount: options.fee.amount[0].amount,
gasLimit: options.fee.gas,
memo: undefined,
wallet: client.recentWallet,
}
try { try {
return client.execute( return client.broadcast(broadcastOptions)
options.sender,
options.contract,
options.msg as Record<string, unknown>,
options.fee,
undefined,
options.funds,
)
} catch (e) {} } catch (e) {}
}, },
loadNetworkConfig: async () => { loadNetworkConfig: async () => {
@ -133,7 +146,7 @@ const commonSlice = (
lcdClient: new LcdClient(rpc), lcdClient: new LcdClient(rpc),
}) })
}, },
setChainInfo: (chainInfo: WalletChainInfo) => set({ chainInfo }), setChainInfo: (chainInfo: SimplifiedChainInfo) => set({ chainInfo }),
setCurrentNetwork: (network: Network) => set({ currentNetwork: network }), setCurrentNetwork: (network: Network) => set({ currentNetwork: network }),
setNetworkError: (isError: boolean) => { setNetworkError: (isError: boolean) => {
const errors = get().errors const errors = get().errors
@ -142,7 +155,7 @@ const commonSlice = (
set({ errors }) set({ errors })
} }
}, },
setClient: (client: WalletSigningCosmWasmClient) => set({ client }), setClient: (client: WalletClient) => set({ client }),
setQueryError: (name: string, isError: boolean) => { setQueryError: (name: string, isError: boolean) => {
const errors = get().errors const errors = get().errors
const queryErrors = get().queryErrors const queryErrors = get().queryErrors
@ -171,7 +184,7 @@ const commonSlice = (
set({ tutorialSteps }) set({ tutorialSteps })
}, },
setUserWalletAddress: (address: string) => set({ userWalletAddress: address }), setUserWalletAddress: (address: string) => set({ userWalletAddress: address }),
setUserWalletName: (name: string) => set({ userWalletName: name }), setUserIcns: (icns: string) => set({ userIcns: icns }),
// ------------------- // -------------------
// QUERY RELATED // QUERY RELATED
// ------------------- // -------------------

View File

@ -1,38 +1,11 @@
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import { FieldsSlice } from 'store/interfaces/fields.interface' import { FieldsSlice } from 'store/interfaces/fields.interface'
import { Store } from 'store/interfaces/store.interface' import { Store } from 'store/interfaces/store.interface'
import { MarsAccountNftClient } from 'types/generated/mars-account-nft/MarsAccountNft.client'
import { MarsCreditManagerClient } from 'types/generated/mars-credit-manager/MarsCreditManager.client'
import { MarsCreditManagerMessageComposer } from 'types/generated/mars-credit-manager/MarsCreditManager.message-composer' import { MarsCreditManagerMessageComposer } from 'types/generated/mars-credit-manager/MarsCreditManager.message-composer'
import { GetState } from 'zustand' import { GetState } from 'zustand'
import { NamedSet } from 'zustand/middleware' import { NamedSet } from 'zustand/middleware'
const fieldsSlice = (set: NamedSet<Store>, get: GetState<Store>): FieldsSlice => ({ const fieldsSlice = (set: NamedSet<Store>, get: GetState<Store>): FieldsSlice => ({
isRepay: false, isRepay: false,
setAccountNftClient: (client: SigningCosmWasmClient) => {
const contracts = get().networkConfig?.contracts
const userWalletAddress = get().userWalletAddress
if (contracts?.accountNft) {
set({
accountNftClient: new MarsAccountNftClient(client, userWalletAddress, contracts.accountNft),
})
}
},
setCreditManagerClient: (client: SigningCosmWasmClient) => {
const contracts = get().networkConfig?.contracts
const userWalletAddress = get().userWalletAddress
if (contracts?.accountNft && userWalletAddress) {
set({
creditManagerClient: new MarsCreditManagerClient(
client,
userWalletAddress,
contracts.creditManager,
),
})
}
},
setCreditManagerMsgComposer: (sender: string, contract: string) => { setCreditManagerMsgComposer: (sender: string, contract: string) => {
set({ creditManagerMsgComposer: new MarsCreditManagerMessageComposer(sender, contract) }) set({ creditManagerMsgComposer: new MarsCreditManagerMessageComposer(sender, contract) })
}, },

View File

@ -90,7 +90,7 @@ const oraclesSlice = (set: NamedSet<Store>, get: GetState<Store>): OraclesSlice
const denom = asset.denom const denom = asset.denom
if (denom === get().baseCurrency.denom) { if (denom === get().baseCurrency.denom) {
updateExchangeRate({ denom, amount: '1' }, exchangeRates) exchangeRates.push({ denom, amount: '1' })
return return
} }

View File

@ -5,12 +5,13 @@ import { convertAprToApy, leverageToLtv } from 'libs/parse'
import moment from 'moment' import moment from 'moment'
import { Store } from 'store/interfaces/store.interface' import { Store } from 'store/interfaces/store.interface'
import { Options, VaultsSlice } from 'store/interfaces/vaults.interface.' import { Options, VaultsSlice } from 'store/interfaces/vaults.interface.'
import { VaultClient } from 'types/classes'
import { import {
ArrayOfVaultInfoResponse,
LockingVaultAmount, LockingVaultAmount,
Positions, Positions,
} from 'types/generated/mars-credit-manager/MarsCreditManager.types' } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
import { VaultBaseForString } from 'types/generated/mars-mock-credit-manager/MarsMockCreditManager.types' import { VaultBaseForString } from 'types/generated/mars-mock-credit-manager/MarsMockCreditManager.types'
import { MarsMockVaultClient } from 'types/generated/mars-mock-vault/MarsMockVault.client'
import { GetState } from 'zustand' import { GetState } from 'zustand'
import { NamedSet } from 'zustand/middleware' import { NamedSet } from 'zustand/middleware'
@ -25,12 +26,14 @@ export const vaultsSlice = (set: NamedSet<Store>, get: GetState<Store>): VaultsS
const nftClient = get().accountNftClient! const nftClient = get().accountNftClient!
const address = get().userWalletAddress const address = get().userWalletAddress
const accountIds = await nftClient const accountIds: string[] = await nftClient
.tokens({ owner: address, limit: 100 }) .query({ tokens: { owner: address, limit: 100 } })
.then((result) => result.tokens) .then((result: { tokens: string[] }) => result.tokens)
const creditManagerClient = get().creditManagerClient const creditManagerClient = get().creditManagerClient
const promises = accountIds?.map((id) => creditManagerClient?.positions({ accountId: id })) const promises = accountIds?.map((id) =>
creditManagerClient?.query({ positions: { account_id: id } }),
)
const newCreditAccounts = await Promise.all(promises).then((result) => const newCreditAccounts = await Promise.all(promises).then((result) =>
result.map((value) => value as Positions).filter((positions) => positions.vaults.length), result.map((value) => value as Positions).filter((positions) => positions.vaults.length),
@ -58,11 +61,13 @@ export const vaultsSlice = (set: NamedSet<Store>, get: GetState<Store>): VaultsS
.toString() .toString()
return { return {
coins: await creditManagerClient.estimateWithdrawLiquidity({ coins: await creditManagerClient.query({
lpToken: { estimate_withdraw_liquidity: {
lp_token: {
amount: amount, amount: amount,
denom: lpToken.denom, denom: lpToken.denom,
}, },
},
}), }),
vaultAddress: lpToken.vaultAddress, vaultAddress: lpToken.vaultAddress,
} }
@ -91,11 +96,14 @@ export const vaultsSlice = (set: NamedSet<Store>, get: GetState<Store>): VaultsS
?.unlocking[0]?.id ?.unlocking[0]?.id
if (!client || !vault || isNaN(lockupId)) return null if (!client || !vault || isNaN(lockupId)) return null
const vaultClient = new VaultClient(vault.address, client)
return { return {
unlockAtTimestamp: Math.round( unlockAtTimestamp: Math.round(
Number( Number(
( (
await client.queryContractSmart(vault.address, { await vaultClient.query({
vault_extension: { lockup: { unlocking_position: { lockup_id: lockupId } } }, vault_extension: { lockup: { unlocking_position: { lockup_id: lockupId } } },
}) })
).release_at?.at_time, ).release_at?.at_time,
@ -152,7 +160,9 @@ export const vaultsSlice = (set: NamedSet<Store>, get: GetState<Store>): VaultsS
let data: VaultCapData[] = [] let data: VaultCapData[] = []
const getBatch = async (startAfter?: VaultBaseForString): Promise<void> => { const getBatch = async (startAfter?: VaultBaseForString): Promise<void> => {
const batch = await creditManagerClient?.vaultsInfo({ limit: 5, startAfter }) const batch: ArrayOfVaultInfoResponse = await creditManagerClient.query({
vaults_info: { limit: 5, start_after: startAfter },
})
const batchProcessed = batch?.map( const batchProcessed = batch?.map(
(vaultInfo) => (vaultInfo) =>
@ -184,19 +194,20 @@ export const vaultsSlice = (set: NamedSet<Store>, get: GetState<Store>): VaultsS
const creditAccounts = await get().getCreditAccounts(options) const creditAccounts = await get().getCreditAccounts(options)
const client = get().client! const client = get().client!
const userWalletAddress = get().userWalletAddress
const promises = creditAccounts.map(async (creditAccount) => { const promises = creditAccounts.map(async (creditAccount) => {
const vaultAddress = creditAccount.vaults[0].vault.address const vaultAddress = creditAccount.vaults[0].vault.address
const vault = get().vaultConfigs.find((vault) => vault.address === vaultAddress) const vault = get().vaultConfigs.find((vault) => vault.address === vaultAddress)
const vaultClient = new MarsMockVaultClient(client, userWalletAddress, vaultAddress) const vaultClient = new VaultClient(vaultAddress, client)
const amounts = getAmountsFromActiveVault(creditAccount.vaults[0].amount) const amounts = getAmountsFromActiveVault(creditAccount.vaults[0].amount)
return { return {
locked: Number( locked: Number(
await vaultClient.previewRedeem({ await vaultClient.query({
preview_redeem: {
amount: amounts.locked, amount: amounts.locked,
},
}), }),
), ),
unlocking: amounts.unlocking, unlocking: amounts.unlocking,
@ -206,7 +217,7 @@ export const vaultsSlice = (set: NamedSet<Store>, get: GetState<Store>): VaultsS
} }
}) })
const newLpTokens = await Promise.all(promises) const newLpTokens = (await Promise.all(promises)).filter((lpToken) => !!lpToken.denom)
set({ lpTokens: newLpTokens }) set({ lpTokens: newLpTokens })
return newLpTokens return newLpTokens
@ -378,7 +389,11 @@ export const vaultsSlice = (set: NamedSet<Store>, get: GetState<Store>): VaultsS
vault: vaultTokenAmounts.locked, vault: vaultTokenAmounts.locked,
}, },
values, values,
apy: apy, apy: {
total: curr.apy,
borrow: trueBorrowRate,
net: apy,
},
currentLeverage: leverage, currentLeverage: leverage,
ltv: leverageToLtv(leverage), ltv: leverageToLtv(leverage),
...(unlockTime ? { unlockAtTimestamp: unlockTime } : {}), ...(unlockTime ? { unlockAtTimestamp: unlockTime } : {}),

View File

@ -49,6 +49,14 @@ a {
color: $colorPrimaryHighlight; color: $colorPrimaryHighlight;
text-decoration: underline; text-decoration: underline;
} }
&:has(button) {
&:hover,
&:focus,
&:active {
text-decoration: none;
}
}
} }
b, b,

View File

@ -1,35 +1,35 @@
@use 'sass:math'; @use 'sass:math';
$rem-base: 16px; $rem-base: 15px;
/* Colors */ /* Colors */
$colorWhite: #f5f5f5; $colorWhite: #ffffff;
$colorGrey: #bdbdbd; $colorGrey: #3a3c49;
$colorGreyLight: #e0e0e0; $colorGreyLight: #bfbfbf;
$colorGreyHighlight: #efefef; $colorGreyHighlight: #4c4c4c;
$colorGreyMedium: #9e9e9e; $colorGreyMedium: #5f697a;
$colorGreyDark: #616161; $colorGreyDark: #1a1c25;
/* CI Colors */ /* CI Colors */
$colorPrimary: #0000ff; $colorPrimary: #14a693;
$colorPrimaryHighlight: #6962cc; $colorPrimaryHighlight: #15bfa9;
$colorPrimaryAlpha: rgba(0, 0, 255, 0.05); $colorPrimaryAlpha: rgba(20, 166, 147, 0.15);
$colorSecondary: #212121; $colorSecondary: #524bb1;
$colorSecondaryHighlight: #424242; $colorSecondaryHighlight: #6962cc;
$colorSecondaryDark: #111111; $colorSecondaryDark: #440b37;
$colorSecondaryAlpha: rgba(17, 17, 17, 0.15); $colorSecondaryAlpha: rgba(68, 11, 55, 0.7);
$colorAccent: $colorGreyMedium; $colorAccent: #2c1b2f;
$colorAccentHighlight: $colorGreyMedium; $colorAccentHighlight: #421f32;
$colorAccentDark: $colorGreyDark; $colorAccentDark: #341a2a;
$colorAccentInverted: $colorGreyLight; $colorAccentInverted: #345dff;
/* Info Colors */ /* Info Colors */
$colorInfoProfit: #c4e7e9; $colorInfoProfit: #41a4a9;
$colorInfoLoss: #c8aaaa; $colorInfoLoss: #f96363;
$colorInfoWarning: #ffb5b5; $colorInfoWarning: #c83333;
$colorInfoVoteAgainst: #6c5a46; $colorInfoVoteAgainst: #eb9e49;
/* Token Colors */ /* Token Colors */
$colorTokenMARS: #dd5b65; $colorTokenMARS: #a03b45;
$colorTokenOSMO: #9f1ab9; $colorTokenOSMO: #9f1ab9;
$colorTokenATOM: #6f7390; $colorTokenATOM: #6f7390;
$colorTokenAxlUSDC: #478edc; $colorTokenAxlUSDC: #478edc;
@ -60,29 +60,32 @@ $alphaBlack80: rgba(0, 0, 0, 0.8);
$alphaBlack90: rgba(0, 0, 0, 0.9); $alphaBlack90: rgba(0, 0, 0, 0.9);
/* Background Colors */ /* Background Colors */
$backgroundBody: $colorGrey; $backgroundBody: #562a3b;
$backgroundBodyDark: $backgroundBody; $backgroundBodyDark: #141621;
$backgroundInTile: transparent; $backgroundInTile: $alphaBlack30;
$backgroundFooter: transparent; $backgroundFooter: $alphaBlack20;
/* Slider Colors */ /* Slider Colors */
$sliderThumb: $colorGreyDark; $sliderThumb: $colorWhite;
$sliderMark: $colorGreyDark; $sliderMark: $colorGreyLight;
/* Tooltip Colors */ /* Tooltip Colors */
$tooltipIconColor: $alphaBlack60; $tooltipIconColor: $alphaWhite20;
$tableSort: $alphaWhite20;
$tableSortActive: $colorWhite;
$tableHeader: $alphaWhite50;
/* Table Colors */ /* Table Colors */
$tableBorder: $alphaBlack30; $tableBorder: $alphaWhite10;
$tableBorderEnd: $alphaBlack80; $tableBorderEnd: $alphaWhite80;
$tableSort: $alphaBlack20; $tableSort: $alphaWhite20;
$tableSortActive: $alphaBlack90; $tableSortActive: $colorWhite;
$tableHeader: $alphaBlack40; $tableHeader: $alphaWhite40;
$tableLabel: $colorSecondaryDark; $tableLabel: $alphaWhite60;
/* Graph Colors */ /* Graph Colors */
$graphLiquidationsLine: $alphaBlack70; $graphLiquidationsLine: $alphaWhite70;
$graphAxis: $alphaBlack40; $graphAxis: $alphaWhite40;
/* Shadows */ /* Shadows */
$shadowInset: inset 0px 2px 2px rgba(0, 0, 0, 0.25); $shadowInset: inset 0px 2px 2px rgba(0, 0, 0, 0.25);
@ -93,40 +96,43 @@ $shadowInset: inset 0px 2px 2px rgba(0, 0, 0, 0.25);
/* Devider */ /* Devider */
@mixin devider10 { @mixin devider10 {
border-bottom: 1px solid $alphaBlack10; border-bottom: 1px solid $alphaWhite10;
} }
@mixin devider20 { @mixin devider20 {
border-bottom: 1px solid $alphaBlack20; border-bottom: 1px solid $alphaWhite20;
} }
@mixin devider40 { @mixin devider40 {
border-bottom: 1px solid $alphaBlack40; border-bottom: 1px solid $alphaWhite40;
} }
@mixin devider60 { @mixin devider60 {
border-bottom: 1px solid $alphaBlack60; border-bottom: 1px solid $alphaWhite60;
} }
/* Backgrounds */ /* Backgrounds */
@mixin bgBody { @mixin bgBody {
background-color: $backgroundBody; background-color: $backgroundBody;
} background-size: 100% auto;
background-image: url('../images/bg.svg');
@mixin bgTableHover { background-position: center top;
background-color: transparent;
} }
@mixin bgBodyDark { @mixin bgBodyDark {
background-color: $backgroundBodyDark; background-color: $backgroundBodyDark;
} }
@mixin bgTableHover {
background-color: rgba($colorPrimary, 0.2);
}
@mixin bgProposalActive { @mixin bgProposalActive {
background: linear-gradient(90deg, #10aa93 2.6%, #248aa9 97.92%); background: linear-gradient(90deg, #10aa93 2.6%, #248aa9 97.92%);
} }
@mixin bgProposalHover { @mixin bgProposalHover {
background-color: $colorGreyMedium; background-color: #05252f;
} }
@mixin bgTile($deg: 99.79) { @mixin bgTile($deg: 99.79) {
@ -138,7 +144,7 @@ $shadowInset: inset 0px 2px 2px rgba(0, 0, 0, 0.25);
} }
@mixin bgInTile { @mixin bgInTile {
background: $backgroundInTile; background: $alphaBlack30;
} }
@mixin bgOverlay { @mixin bgOverlay {
@ -155,11 +161,11 @@ $shadowInset: inset 0px 2px 2px rgba(0, 0, 0, 0.25);
} }
@mixin bgTileDevider { @mixin bgTileDevider {
background-color: $alphaBlack60; background-color: $alphaWhite60;
} }
@mixin bgDevider { @mixin bgDevider {
background-color: $alphaBlack20; background-color: $alphaWhite20;
} }
@mixin bgInput { @mixin bgInput {
@ -168,60 +174,68 @@ $shadowInset: inset 0px 2px 2px rgba(0, 0, 0, 0.25);
@mixin bgPrimary { @mixin bgPrimary {
background-color: $colorPrimary; background-color: $colorPrimary;
color: $colorWhite;
} }
@mixin bgSecondary { @mixin bgSecondary {
background-color: $colorSecondary; background-color: $colorSecondary;
color: $colorWhite;
} }
@mixin bgTertiary { @mixin bgTertiary {
background-color: $alphaBlack60; background-color: rgba(82, 75, 177, 0.5);
color: $colorWhite;
} }
@mixin bgLimit { @mixin bgLimit {
background: $colorPrimary; background: linear-gradient(
to right,
#15bfa9 20.9%,
#5e4bb1 49.68%,
#382685 82.55%,
#c83333 100%
);
} }
@mixin bgLimitOpacity { @mixin bgLimitOpacity {
background: $colorPrimary; background: linear-gradient(
to right,
#15bfa830 20.9%,
#5e4bb130 49.68%,
#38268530 82.55%,
#c8333330 100%
);
} }
@mixin bgHatched { @mixin bgHatched {
background-image: linear-gradient( background-image: linear-gradient(
135deg, 135deg,
#1a1c25 33.33%, transparent 33.33%,
rgba(255, 255, 255, 0.2) 33.33%, #826d6b 33.33%,
rgba(255, 255, 255, 0.2) 50%, #826d6b 50%,
#1a1c25 50%, transparent 50%,
#1a1c25 83.33%, transparent 83.33%,
rgba(255, 255, 255, 0.2) 83.33%, #826d6b 83.33%,
rgba(255, 255, 255, 0.2) 100% #826d6b 100%
); );
background-size: 5px 5px; background-size: 5px 5px;
} }
/* GLOWS */
/* GLOWS */ /* GLOWS */
@mixin glowXS { @mixin glowXS {
display: none; filter: blur(1px);
} }
@mixin glowS { @mixin glowS {
display: none; filter: blur(3px);
} }
@mixin glowM { @mixin glowM {
display: none; filter: blur(4px);
} }
@mixin glowL { @mixin glowL {
display: none; filter: blur(5px);
} }
@mixin glowXL { @mixin glowXL {
display: none; filter: blur(8px);
} }
@mixin glowXXL { @mixin glowXXL {
display: none; filter: blur(24px);
} }
/* Typography */ /* Typography */
@ -229,11 +243,11 @@ $fontWeightLight: 300;
$fontWeightRegular: 400; $fontWeightRegular: 400;
$fontWeightSemibold: 600; $fontWeightSemibold: 600;
$fontColorDarkPrimary: $colorWhite; $fontColorDarkPrimary: $colorSecondaryDark;
$fontColorDarkSecondary: $colorSecondaryDark; $fontColorDarkSecondary: rgba(68, 8, 55, 0.7);
$fontColorLightPrimary: $colorSecondaryDark; $fontColorLightPrimary: $colorWhite;
$fontColorLightSecondary: $alphaBlack30; $fontColorLightSecondary: $alphaWhite60;
$fontColorLightTertiary: $colorSecondaryDark; $fontColorLightTertiary: rgba(255, 255, 255, 0.4);
$fontColorLtv: $colorWhite; $fontColorLtv: $colorWhite;
@mixin typoH1 { @mixin typoH1 {
@ -249,6 +263,8 @@ $fontColorLtv: $colorWhite;
@mixin typoH2caps { @mixin typoH2caps {
@include typoH2; @include typoH2;
text-transform: uppercase;
letter-spacing: rem-calc(9);
} }
@mixin typoH3 { @mixin typoH3 {
@ -258,6 +274,8 @@ $fontColorLtv: $colorWhite;
@mixin typoH3caps { @mixin typoH3caps {
font-size: rem-calc(30.42); font-size: rem-calc(30.42);
line-height: space(10);
text-transform: uppercase;
} }
@mixin typoH4 { @mixin typoH4 {
@ -268,6 +286,8 @@ $fontColorLtv: $colorWhite;
@mixin typoH4caps { @mixin typoH4caps {
@include typoH4; @include typoH4;
text-transform: uppercase;
letter-spacing: rem-calc(3);
} }
@mixin typoXXL { @mixin typoXXL {
@ -277,7 +297,9 @@ $fontColorLtv: $colorWhite;
@mixin typoXXLcaps { @mixin typoXXLcaps {
@include typoXXL; @include typoXXL;
font-weight: $fontWeightLight; font-weight: $fontWeightRegular;
text-transform: uppercase;
letter-spacing: rem-calc(3);
} }
@mixin typoXL { @mixin typoXL {
@ -287,6 +309,8 @@ $fontColorLtv: $colorWhite;
@mixin typoXLcaps { @mixin typoXLcaps {
@include typoXL; @include typoXL;
letter-spacing: rem-calc(5);
text-transform: uppercase;
font-weight: $fontWeightLight; font-weight: $fontWeightLight;
} }
@ -298,6 +322,8 @@ $fontColorLtv: $colorWhite;
@mixin typoLcaps { @mixin typoLcaps {
@include typoL; @include typoL;
font-weight: $fontWeightSemibold; font-weight: $fontWeightSemibold;
text-transform: uppercase;
letter-spacing: rem-calc(3);
} }
@mixin typoM { @mixin typoM {
@ -307,6 +333,7 @@ $fontColorLtv: $colorWhite;
@mixin typoMcaps { @mixin typoMcaps {
@include typoM; @include typoM;
text-transform: uppercase;
} }
@mixin typoS { @mixin typoS {
@ -317,6 +344,8 @@ $fontColorLtv: $colorWhite;
@mixin typoScaps { @mixin typoScaps {
@include typoS; @include typoS;
font-weight: $fontWeightSemibold; font-weight: $fontWeightSemibold;
text-transform: uppercase;
letter-spacing: rem-calc(3);
} }
@mixin typoXS { @mixin typoXS {
@ -327,6 +356,8 @@ $fontColorLtv: $colorWhite;
@mixin typoXScaps { @mixin typoXScaps {
@include typoXS; @include typoXS;
font-weight: $fontWeightSemibold; font-weight: $fontWeightSemibold;
text-transform: uppercase;
letter-spacing: rem-calc(3);
} }
@mixin typoXXS { @mixin typoXXS {
@ -337,6 +368,8 @@ $fontColorLtv: $colorWhite;
@mixin typoXXScaps { @mixin typoXXScaps {
@include typoXXS; @include typoXXS;
font-weight: $fontWeightSemibold; font-weight: $fontWeightSemibold;
text-transform: uppercase;
letter-spacing: rem-calc(2);
} }
@mixin typoXXXS { @mixin typoXXXS {
@ -347,15 +380,20 @@ $fontColorLtv: $colorWhite;
@mixin typoXXXScaps { @mixin typoXXXScaps {
@include typoXXXS; @include typoXXXS;
font-weight: $fontWeightSemibold; font-weight: $fontWeightSemibold;
text-transform: uppercase;
letter-spacing: rem-calc(2);
} }
@mixin typoButton { @mixin typoButton {
font-family: Inter, sans-serif;
@include typoS; @include typoS;
font-weight: $fontWeightSemibold; font-weight: $fontWeightSemibold;
} }
@mixin typoNav { @mixin typoNav {
@include typoL; @include typoL;
text-transform: uppercase;
letter-spacing: rem-calc(5);
} }
@mixin typoNetwork { @mixin typoNetwork {
@ -429,70 +467,100 @@ $spacingBase: 4;
/* LAYOUTS */ /* LAYOUTS */
@mixin layoutTile { @mixin layoutTile {
padding: space(1); @include bgTile;
background: $colorGreyHighlight; border: rem-calc(7) solid $colorAccentHighlight;
border: 2px solid $colorWhite; border-radius: $borderRadiusXL;
box-shadow: 0 0 0 3px $colorGreyHighlight, 12px 12px 0 0 rgb(0 0 0 / 50%) !important;
height: fit-content; height: fit-content;
} }
@mixin layoutTooltip { @mixin layoutTooltip {
padding: space(3); @include padding(2, 4);
background: $colorGreyLight; @include bgTooltip;
border: 1px solid $colorSecondaryDark; @include typoS;
box-shadow: 0 rem-calc(3) rem-calc(4) rgba(0, 0, 0, 0.14),
0 rem-calc(3) rem-calc(3) rgba(0, 0, 0, 0.12), 0 rem-calc(1) rem-calc(8) rgba(0, 0, 0, 0.2);
border-radius: $borderRadiusL;
max-width: rem-calc(350);
} }
@mixin layoutPopover { @mixin layoutPopover {
padding: space(3); @include bgPopover;
background: $colorGreyLight; box-shadow: 0 rem-calc(2) rem-calc(2) rgba(0, 0, 0, 0.14),
border: 1px solid $colorSecondaryDark; 0 rem-calc(1) rem-calc(5) rgba(0, 0, 0, 0.2);
border-radius: $borderRadiusL;
} }
@mixin layoutIncentiveButton { @mixin layoutIncentiveButton {
--border-width: 3px;
background-color: #946582;
position: relative;
border: none;
margin: rem-calc(3) rem-calc(11) 0 0;
height: rem-calc(28);
&:hover {
border: none;
background-color: darken(#946582, 10%);
}
&::after {
border-radius: $borderRadiusXXL;
position: absolute;
content: '';
top: calc(-1 * var(--border-width));
left: calc(-1 * var(--border-width));
z-index: -1;
width: calc(100% + var(--border-width) * 2);
height: calc(100% + var(--border-width) * 2);
background: linear-gradient(
90deg,
rgba(105, 98, 204, 0.8) 0%,
rgba(105, 98, 204, 1) 40%,
rgba(255, 255, 255, 1) 50%,
rgba(105, 98, 204, 1) 60%,
rgba(105, 98, 204, 0.8) 100%
);
background-size: 300% 300%;
background-position: 0 50%;
animation: moveGradient 6s alternate infinite;
}
} }
@mixin layoutLogo { @mixin layoutLogo {
> svg { > svg {
width: rem-calc(57); width: rem-calc(50);
height: rem-calc(57); height: rem-calc(50);
path {
stroke: $fontColorLightPrimary;
}
} }
} }
@mixin layoutGlobal { @mixin layoutGlobal {
opacity: 1 !important;
box-shadow: none !important;
} }
/* Buttons */ /* Buttons */
$buttonBorder: $alphaBlack40; $buttonBorder: $alphaWhite40;
$buttonBorderHover: $colorSecondaryDark; $buttonBorderHover: $colorWhite;
@mixin buttonS { @mixin buttonS {
@include typoS; @include typoS;
@include padding(1.5, 5); @include padding(1.5, 5);
height: rem-calc(32); min-height: rem-calc(32);
} }
@mixin buttonM { @mixin buttonM {
@include typoM; @include typoM;
@include padding(2.5, 6); @include padding(2.5, 6);
height: rem-calc(40); min-height: rem-calc(40);
} }
@mixin buttonL { @mixin buttonL {
@include typoL; @include typoL;
@include padding(2.5, 6); @include padding(2.5, 6);
height: rem-calc(56); min-height: rem-calc(56);
} }
@mixin buttonSolidPrimary { @mixin buttonSolidPrimary {
&.primary { &.primary {
background-color: $colorPrimary; background-color: $colorPrimary;
color: $colorWhite;
&:hover, &:hover,
&:focus { &:focus {
@ -508,7 +576,6 @@ $buttonBorderHover: $colorSecondaryDark;
@mixin buttonSolidSecondary { @mixin buttonSolidSecondary {
&.secondary { &.secondary {
background-color: $colorSecondary; background-color: $colorSecondary;
color: $colorWhite;
&:hover, &:hover,
&:focus { &:focus {
@ -523,33 +590,34 @@ $buttonBorderHover: $colorSecondaryDark;
@mixin buttonSolidTertiary { @mixin buttonSolidTertiary {
&.tertiary { &.tertiary {
background-color: $colorSecondaryDark; background-color: $colorSecondaryAlpha;
color: $colorWhite; border: 1px solid $alphaWhite60;
border: 1px solid $alphaBlack30;
&:hover, &:hover,
&:focus { &:focus {
border: 1px solid $alphaBlack20; border: 1px solid $fontColorLightPrimary;
background-color: $colorSecondaryDark;
} }
&:active { &:active {
border: 1px solid $fontColorLightPrimary;
background-color: lighten($colorSecondaryDark, 10%); background-color: lighten($colorSecondaryDark, 10%);
} }
} }
} }
/* Border Radius */ /* Border Radius */
$borderRadiusXXXS: 0; $borderRadiusXXXS: rem-calc(3);
$borderRadiusXXS: 0; $borderRadiusXXS: rem-calc(4);
$borderRadiusXS: 0; $borderRadiusXS: rem-calc(5);
$borderRadiusS: 0; $borderRadiusS: rem-calc(8);
$borderRadiusM: 0; $borderRadiusM: rem-calc(9);
$borderRadiusL: 0; $borderRadiusL: rem-calc(12);
$borderRadiusXL: 0; $borderRadiusXL: rem-calc(16);
$borderRadiusXXL: 0; $borderRadiusXXL: rem-calc(20);
$borderRadiusXXXL: 0; $borderRadiusXXXL: rem-calc(30);
$borderRadiusXXXXL: 0; $borderRadiusXXXXL: rem-calc(100);
$borderRadiusRound: 0; $borderRadiusRound: 50%;
/* Dimensions */ /* Dimensions */
$headerHeight: rem-calc(86); $headerHeight: rem-calc(86);

View File

@ -1,6 +1,62 @@
import { Coin } from '@cosmjs/stargate'
import { WalletClient } from '@marsprotocol/wallet-connector'
import {
ArrayOfCoin,
QueryMsg as CreditQueryMsg,
} from 'types/generated/mars-credit-manager/MarsCreditManager.types'
import { QueryMsg as AccountQueryMsg } from './generated/mars-account-nft/MarsAccountNft.types'
import { QueryMsg as VaultQueryMsg } from './generated/mars-mock-vault/MarsMockVault.types'
export class SetupError extends Error { export class SetupError extends Error {
constructor(name: string, message: string) { constructor(name: string, message: string) {
super(message) super(message)
this.name = name this.name = name
} }
} }
export class CreditManagerClient {
address: string
client: WalletClient
constructor(address: string, client: WalletClient) {
this.address = address
this.client = client
}
query(message: CreditQueryMsg) {
return this.client.cosmWasmClient.queryContractSmart(this.address, message)
}
estimateWithdrawLiquidity({ lpToken }: { lpToken: Coin }) {
return Promise<ArrayOfCoin>
}
}
export class AccountNftClient {
address: string
client: WalletClient
constructor(address: string, client: WalletClient) {
this.address = address
this.client = client
}
query(message: AccountQueryMsg) {
return this.client.cosmWasmClient.queryContractSmart(this.address, message)
}
}
export class VaultClient {
address: string
client: WalletClient
constructor(address: string, client: WalletClient) {
this.address = address
this.client = client
}
query(message: VaultQueryMsg) {
return this.client.cosmWasmClient.queryContractSmart(this.address, message)
}
}

View File

@ -7,6 +7,7 @@ export enum QUERY_KEYS {
USER_BALANCE = 'userBalance', USER_BALANCE = 'userBalance',
USER_DEBT = 'userDebt', USER_DEBT = 'userDebt',
USER_DEPOSIT = 'userDeposits', USER_DEPOSIT = 'userDeposits',
USER_ICNS = 'userIcns',
ATOM_PRICE = 'atomPrice', ATOM_PRICE = 'atomPrice',
SAFETY_FUND_BALANCE = 'safetyFundBalance', SAFETY_FUND_BALANCE = 'safetyFundBalance',
MARKET_DEPOSITS = 'marketDeposits', MARKET_DEPOSITS = 'marketDeposits',

View File

@ -13,11 +13,11 @@ import {
ApprovalResponse, ApprovalResponse,
ApprovalsResponse, ApprovalsResponse,
Binary, Binary,
ConfigBaseForString,
ConfigUpdates,
ContractInfoResponse, ContractInfoResponse,
Expiration, Expiration,
MinterResponse, MinterResponse,
NftConfigBaseForString,
NftConfigUpdates,
NftInfoResponseForEmpty, NftInfoResponseForEmpty,
NumTokensResponse, NumTokensResponse,
OperatorsResponse, OperatorsResponse,
@ -27,7 +27,7 @@ import {
} from './MarsAccountNft.types' } from './MarsAccountNft.types'
export interface MarsAccountNftReadOnlyInterface { export interface MarsAccountNftReadOnlyInterface {
contractAddress: string contractAddress: string
config: () => Promise<ConfigBaseForString> config: () => Promise<NftConfigBaseForString>
nextId: () => Promise<Uint64> nextId: () => Promise<Uint64>
ownerOf: ({ ownerOf: ({
includeExpired, includeExpired,
@ -113,7 +113,7 @@ export class MarsAccountNftQueryClient implements MarsAccountNftReadOnlyInterfac
this.minter = this.minter.bind(this) this.minter = this.minter.bind(this)
} }
config = async (): Promise<ConfigBaseForString> => { config = async (): Promise<NftConfigBaseForString> => {
return this.client.queryContractSmart(this.contractAddress, { return this.client.queryContractSmart(this.contractAddress, {
config: {}, config: {},
}) })
@ -263,7 +263,7 @@ export interface MarsAccountNftInterface extends MarsAccountNftReadOnlyInterface
{ {
updates, updates,
}: { }: {
updates: ConfigUpdates updates: NftConfigUpdates
}, },
fee?: number | StdFee | 'auto', fee?: number | StdFee | 'auto',
memo?: string, memo?: string,
@ -398,7 +398,7 @@ export class MarsAccountNftClient
{ {
updates, updates,
}: { }: {
updates: ConfigUpdates updates: NftConfigUpdates
}, },
fee: number | StdFee | 'auto' = 'auto', fee: number | StdFee | 'auto' = 'auto',
memo?: string, memo?: string,

View File

@ -10,7 +10,7 @@ import { toUtf8 } from '@cosmjs/encoding'
import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx'
import { MsgExecuteContractEncodeObject } from 'cosmwasm' import { MsgExecuteContractEncodeObject } from 'cosmwasm'
import { Binary, ConfigUpdates, Expiration } from './MarsAccountNft.types' import { Binary, Expiration, NftConfigUpdates } from './MarsAccountNft.types'
export interface MarsAccountNftMessage { export interface MarsAccountNftMessage {
contractAddress: string contractAddress: string
sender: string sender: string
@ -18,7 +18,7 @@ export interface MarsAccountNftMessage {
{ {
updates, updates,
}: { }: {
updates: ConfigUpdates updates: NftConfigUpdates
}, },
funds?: Coin[], funds?: Coin[],
) => MsgExecuteContractEncodeObject ) => MsgExecuteContractEncodeObject
@ -125,7 +125,7 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage {
{ {
updates, updates,
}: { }: {
updates: ConfigUpdates updates: NftConfigUpdates
}, },
funds?: Coin[], funds?: Coin[],
): MsgExecuteContractEncodeObject => { ): MsgExecuteContractEncodeObject => {

View File

@ -15,11 +15,11 @@ import {
ApprovalResponse, ApprovalResponse,
ApprovalsResponse, ApprovalsResponse,
Binary, Binary,
ConfigBaseForString,
ConfigUpdates,
ContractInfoResponse, ContractInfoResponse,
Expiration, Expiration,
MinterResponse, MinterResponse,
NftConfigBaseForString,
NftConfigUpdates,
NftInfoResponseForEmpty, NftInfoResponseForEmpty,
NumTokensResponse, NumTokensResponse,
OperatorsResponse, OperatorsResponse,
@ -329,12 +329,12 @@ export function useMarsAccountNftNextIdQuery<TData = Uint64>({
) )
} }
export interface MarsAccountNftConfigQuery<TData> export interface MarsAccountNftConfigQuery<TData>
extends MarsAccountNftReactQuery<ConfigBaseForString, TData> {} extends MarsAccountNftReactQuery<NftConfigBaseForString, TData> {}
export function useMarsAccountNftConfigQuery<TData = ConfigBaseForString>({ export function useMarsAccountNftConfigQuery<TData = NftConfigBaseForString>({
client, client,
options, options,
}: MarsAccountNftConfigQuery<TData>) { }: MarsAccountNftConfigQuery<TData>) {
return useQuery<ConfigBaseForString, Error, TData>( return useQuery<NftConfigBaseForString, Error, TData>(
marsAccountNftQueryKeys.config(client?.contractAddress), marsAccountNftQueryKeys.config(client?.contractAddress),
() => (client ? client.config() : Promise.reject(new Error('Invalid client'))), () => (client ? client.config() : Promise.reject(new Error('Invalid client'))),
{ ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) },
@ -545,7 +545,7 @@ export function useMarsAccountNftAcceptMinterRoleMutation(
export interface MarsAccountNftUpdateConfigMutation { export interface MarsAccountNftUpdateConfigMutation {
client: MarsAccountNftClient client: MarsAccountNftClient
msg: { msg: {
updates: ConfigUpdates updates: NftConfigUpdates
} }
args?: { args?: {
fee?: number | StdFee | 'auto' fee?: number | StdFee | 'auto'

View File

@ -15,7 +15,7 @@ export interface InstantiateMsg {
export type ExecuteMsg = export type ExecuteMsg =
| { | {
update_config: { update_config: {
updates: ConfigUpdates updates: NftConfigUpdates
} }
} }
| { | {
@ -81,7 +81,7 @@ export type Expiration =
} }
export type Timestamp = Uint64 export type Timestamp = Uint64
export type Uint64 = string export type Uint64 = string
export interface ConfigUpdates { export interface NftConfigUpdates {
max_value_for_burn?: Uint128 | null max_value_for_burn?: Uint128 | null
proposed_new_minter?: string | null proposed_new_minter?: string | null
} }
@ -183,7 +183,7 @@ export interface ApprovalResponse {
export interface ApprovalsResponse { export interface ApprovalsResponse {
approvals: Approval[] approvals: Approval[]
} }
export interface ConfigBaseForString { export interface NftConfigBaseForString {
max_value_for_burn: Uint128 max_value_for_burn: Uint128
proposed_new_minter?: string | null proposed_new_minter?: string | null
} }

View File

@ -13,6 +13,7 @@ import {
ArrayOfCoin, ArrayOfCoin,
ArrayOfCoinBalanceResponseItem, ArrayOfCoinBalanceResponseItem,
ArrayOfDebtShares, ArrayOfDebtShares,
ArrayOfLentShares,
ArrayOfSharesResponseItem, ArrayOfSharesResponseItem,
ArrayOfString, ArrayOfString,
ArrayOfVaultInfoResponse, ArrayOfVaultInfoResponse,
@ -23,6 +24,8 @@ import {
ConfigUpdates, ConfigUpdates,
DebtShares, DebtShares,
HealthResponse, HealthResponse,
LentShares,
NftConfigUpdates,
Positions, Positions,
Uint128, Uint128,
VaultBaseForString, VaultBaseForString,
@ -68,6 +71,21 @@ export interface MarsCreditManagerReadOnlyInterface {
limit?: number limit?: number
startAfter?: string startAfter?: string
}) => Promise<ArrayOfDebtShares> }) => Promise<ArrayOfDebtShares>
allLentShares: ({
limit,
startAfter,
}: {
limit?: number
startAfter?: string[][]
}) => Promise<ArrayOfSharesResponseItem>
totalLentShares: () => Promise<LentShares>
allTotalLentShares: ({
limit,
startAfter,
}: {
limit?: number
startAfter?: string
}) => Promise<ArrayOfLentShares>
allVaultPositions: ({ allVaultPositions: ({
limit, limit,
startAfter, startAfter,
@ -108,6 +126,9 @@ export class MarsCreditManagerQueryClient implements MarsCreditManagerReadOnlyIn
this.allDebtShares = this.allDebtShares.bind(this) this.allDebtShares = this.allDebtShares.bind(this)
this.totalDebtShares = this.totalDebtShares.bind(this) this.totalDebtShares = this.totalDebtShares.bind(this)
this.allTotalDebtShares = this.allTotalDebtShares.bind(this) this.allTotalDebtShares = this.allTotalDebtShares.bind(this)
this.allLentShares = this.allLentShares.bind(this)
this.totalLentShares = this.totalLentShares.bind(this)
this.allTotalLentShares = this.allTotalLentShares.bind(this)
this.allVaultPositions = this.allVaultPositions.bind(this) this.allVaultPositions = this.allVaultPositions.bind(this)
this.totalVaultCoinBalance = this.totalVaultCoinBalance.bind(this) this.totalVaultCoinBalance = this.totalVaultCoinBalance.bind(this)
this.allTotalVaultCoinBalances = this.allTotalVaultCoinBalances.bind(this) this.allTotalVaultCoinBalances = this.allTotalVaultCoinBalances.bind(this)
@ -209,6 +230,39 @@ export class MarsCreditManagerQueryClient implements MarsCreditManagerReadOnlyIn
}, },
}) })
} }
allLentShares = async ({
limit,
startAfter,
}: {
limit?: number
startAfter?: string[][]
}): Promise<ArrayOfSharesResponseItem> => {
return this.client.queryContractSmart(this.contractAddress, {
all_lent_shares: {
limit,
start_after: startAfter,
},
})
}
totalLentShares = async (): Promise<LentShares> => {
return this.client.queryContractSmart(this.contractAddress, {
total_lent_shares: {},
})
}
allTotalLentShares = async ({
limit,
startAfter,
}: {
limit?: number
startAfter?: string
}): Promise<ArrayOfLentShares> => {
return this.client.queryContractSmart(this.contractAddress, {
all_total_lent_shares: {
limit,
start_after: startAfter,
},
})
}
allVaultPositions = async ({ allVaultPositions = async ({
limit, limit,
startAfter, startAfter,
@ -288,9 +342,9 @@ export interface MarsCreditManagerInterface extends MarsCreditManagerReadOnlyInt
) => Promise<ExecuteResult> ) => Promise<ExecuteResult>
updateConfig: ( updateConfig: (
{ {
newConfig, updates,
}: { }: {
newConfig: ConfigUpdates updates: ConfigUpdates
}, },
fee?: number | StdFee | 'auto', fee?: number | StdFee | 'auto',
memo?: string, memo?: string,
@ -301,6 +355,16 @@ export interface MarsCreditManagerInterface extends MarsCreditManagerReadOnlyInt
memo?: string, memo?: string,
funds?: Coin[], funds?: Coin[],
) => Promise<ExecuteResult> ) => Promise<ExecuteResult>
updateNftConfig: (
{
updates,
}: {
updates: NftConfigUpdates
},
fee?: number | StdFee | 'auto',
memo?: string,
funds?: Coin[],
) => Promise<ExecuteResult>
callback: ( callback: (
fee?: number | StdFee | 'auto', fee?: number | StdFee | 'auto',
memo?: string, memo?: string,
@ -324,6 +388,7 @@ export class MarsCreditManagerClient
this.updateCreditAccount = this.updateCreditAccount.bind(this) this.updateCreditAccount = this.updateCreditAccount.bind(this)
this.updateConfig = this.updateConfig.bind(this) this.updateConfig = this.updateConfig.bind(this)
this.updateOwner = this.updateOwner.bind(this) this.updateOwner = this.updateOwner.bind(this)
this.updateNftConfig = this.updateNftConfig.bind(this)
this.callback = this.callback.bind(this) this.callback = this.callback.bind(this)
} }
@ -371,9 +436,9 @@ export class MarsCreditManagerClient
} }
updateConfig = async ( updateConfig = async (
{ {
newConfig, updates,
}: { }: {
newConfig: ConfigUpdates updates: ConfigUpdates
}, },
fee: number | StdFee | 'auto' = 'auto', fee: number | StdFee | 'auto' = 'auto',
memo?: string, memo?: string,
@ -384,7 +449,7 @@ export class MarsCreditManagerClient
this.contractAddress, this.contractAddress,
{ {
update_config: { update_config: {
new_config: newConfig, updates,
}, },
}, },
fee, fee,
@ -408,6 +473,29 @@ export class MarsCreditManagerClient
funds, funds,
) )
} }
updateNftConfig = async (
{
updates,
}: {
updates: NftConfigUpdates
},
fee: number | StdFee | 'auto' = 'auto',
memo?: string,
funds?: Coin[],
): Promise<ExecuteResult> => {
return await this.client.execute(
this.sender,
this.contractAddress,
{
update_nft_config: {
updates,
},
},
fee,
memo,
funds,
)
}
callback = async ( callback = async (
fee: number | StdFee | 'auto' = 'auto', fee: number | StdFee | 'auto' = 'auto',
memo?: string, memo?: string,

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