MP-2017: Deposit Funds and Account stats (#21)

* style: fund account font size adjustments

* client instance. contract addresses updates. prices hook added

* persist lend assets value for every credit account

* feat: account stats and semi circular progress

* minor code cleanup

* display borrowed assets interest rate

* fallback screen when no wallet is connected

* fix: hydration mismatch

* update osmosis testnet endpoints

* style: body text color

* coin interface imported from cosmos package

* risk calculation from ltv assets comment added

* svgr setup. inline svg extracted to Icons folder

* address removed from local storage. wallet store improvements

* rename setAddress action to connect

* yield page renamed to earn

* refactor: accountStats using BigNumber

* update contract addresses

* update hardcoded fee

* update market mocked values

* current leverage added to useAccountStats hook return

* leverage naming disambiguation

* debt positions labels color update. negative sign before values

* remove prefers-color-scheme media query

* update redbank mock data
This commit is contained in:
Gustavo Mauricio 2022-10-12 16:41:03 +01:00 committed by GitHub
parent f709c12da2
commit 3022ae9a6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1733 additions and 263 deletions

View File

@ -7,7 +7,7 @@ import { Coin } from '@cosmjs/stargate'
import { getInjectiveAddress } from 'utils/address' import { getInjectiveAddress } from 'utils/address'
import { getExperimentalChainConfigBasedOnChainId } from 'utils/experimental-chains' import { getExperimentalChainConfigBasedOnChainId } from 'utils/experimental-chains'
import { ChainId } from 'types' import { ChainId, Wallet } from 'types'
import useWalletStore from 'stores/useWalletStore' import useWalletStore from 'stores/useWalletStore'
import { chain } from 'utils/chains' import { chain } from 'utils/chains'
@ -48,7 +48,7 @@ const ConnectModal = ({ isOpen, onClose }: Props) => {
} }
const key = await window.keplr.getKey(chain.chainId) const key = await window.keplr.getKey(chain.chainId)
actions.setAddress(key.bech32Address) actions.connect(key.bech32Address, Wallet.Keplr)
handleConnectSuccess() handleConnectSuccess()
} catch (e) { } catch (e) {
@ -72,7 +72,7 @@ const ConnectModal = ({ isOpen, onClose }: Props) => {
method: 'eth_requestAccounts', method: 'eth_requestAccounts',
}) })
const [address] = addresses const [address] = addresses
actions.setAddress(getInjectiveAddress(address)) actions.connect(getInjectiveAddress(address), Wallet.Metamask)
handleConnectSuccess() handleConnectSuccess()
} catch (e) { } catch (e) {
// TODO: handle exception // TODO: handle exception

View File

@ -2,6 +2,7 @@ import React, { useEffect, useMemo, useState } from 'react'
import * as Slider from '@radix-ui/react-slider' import * as Slider from '@radix-ui/react-slider'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { Switch } from '@headlessui/react' import { Switch } from '@headlessui/react'
import useLocalStorageState from 'use-local-storage-state'
import Button from '../Button' import Button from '../Button'
import useAllowedCoins from 'hooks/useAllowedCoins' import useAllowedCoins from 'hooks/useAllowedCoins'
@ -14,10 +15,13 @@ import CreditManagerContainer from './CreditManagerContainer'
const FundAccount = () => { const FundAccount = () => {
const [amount, setAmount] = useState(0) const [amount, setAmount] = useState(0)
const [selectedToken, setSelectedToken] = useState('') const [selectedToken, setSelectedToken] = useState('')
const [enabled, setEnabled] = useState(false)
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount) const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
const [lendAssets, setLendAssets] = useLocalStorageState(`lendAssets_${selectedAccount}`, {
defaultValue: false,
})
const { data: balancesData } = useAllBalances() const { data: balancesData } = useAllBalances()
const { data: allowedCoinsData, isLoading: isLoadingAllowedCoins } = useAllowedCoins() const { data: allowedCoinsData, isLoading: isLoadingAllowedCoins } = useAllowedCoins()
const { mutate } = useDepositCreditAccount( const { mutate } = useDepositCreditAccount(
@ -58,7 +62,7 @@ const FundAccount = () => {
return ( return (
<> <>
<CreditManagerContainer className="mb-2 p-3"> <CreditManagerContainer className="mb-2 p-3">
<p className="mb-6"> <p className="mb-6 text-sm">
Transfer assets from your injective wallet to your Mars credit account. If you dont have Transfer assets from your injective wallet to your Mars credit account. If you dont have
any assets in your injective wallet use the injective bridge to transfer funds to your any assets in your injective wallet use the injective bridge to transfer funds to your
injective wallet. injective wallet.
@ -67,7 +71,7 @@ const FundAccount = () => {
<p>Loading...</p> <p>Loading...</p>
) : ( ) : (
<> <>
<div className="mb-4"> <div className="mb-4 text-sm">
<div className="mb-1 flex justify-between"> <div className="mb-1 flex justify-between">
<div>Asset:</div> <div>Asset:</div>
<select <select
@ -95,7 +99,7 @@ const FundAccount = () => {
/> />
</div> </div>
</div> </div>
<p>In wallet: {walletAmount.toLocaleString()}</p> <p className="text-sm">In wallet: {walletAmount.toLocaleString()}</p>
{/* SLIDER - initial implementation to test functionality */} {/* SLIDER - initial implementation to test functionality */}
{/* TODO: will need to be revamped later on */} {/* TODO: will need to be revamped later on */}
<div className="relative mb-6 flex flex-1 items-center"> <div className="relative mb-6 flex flex-1 items-center">
@ -134,19 +138,19 @@ const FundAccount = () => {
<CreditManagerContainer className="mb-2 flex items-center justify-between"> <CreditManagerContainer className="mb-2 flex items-center justify-between">
<div> <div>
<h3 className="font-bold">Lending Assets</h3> <h3 className="font-bold">Lending Assets</h3>
<div className="opacity-50">Lend assets from account to earn yield.</div> <div className="text-sm opacity-50">Lend assets from account to earn yield.</div>
</div> </div>
<Switch <Switch
checked={enabled} checked={lendAssets}
onChange={setEnabled} onChange={setLendAssets}
className={`${ className={`${
enabled ? 'bg-blue-600' : 'bg-gray-400' lendAssets ? 'bg-blue-600' : 'bg-gray-400'
} relative inline-flex h-6 w-11 items-center rounded-full`} } relative inline-flex h-6 w-11 items-center rounded-full`}
> >
<span <span
className={`${ className={`${
enabled ? 'translate-x-6' : 'translate-x-1' lendAssets ? 'translate-x-6' : 'translate-x-1'
} inline-block h-4 w-4 transform rounded-full bg-white transition`} } inline-block h-4 w-4 transform rounded-full bg-white transition`}
/> />
</Switch> </Switch>

View File

@ -5,10 +5,13 @@ import Button from '../Button'
import { formatCurrency } from 'utils/formatters' import { formatCurrency } from 'utils/formatters'
import useCreditManagerStore from 'stores/useCreditManagerStore' import useCreditManagerStore from 'stores/useCreditManagerStore'
import useWalletStore from 'stores/useWalletStore' import useWalletStore from 'stores/useWalletStore'
import useCreditAccountBalances from 'hooks/useCreditAccountPositions' import useCreditAccountPositions from 'hooks/useCreditAccountPositions'
import { getTokenDecimals } from 'utils/tokens' import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
import FundAccount from './FundAccount' import FundAccount from './FundAccount'
import CreditManagerContainer from './CreditManagerContainer' import CreditManagerContainer from './CreditManagerContainer'
import useTokenPrices from 'hooks/useTokenPrices'
import useAccountStats from 'hooks/useAccountStats'
import useMarkets from 'hooks/useMarkets'
const CreditManager = () => { const CreditManager = () => {
const [isFund, setIsFund] = useState(false) const [isFund, setIsFund] = useState(false)
@ -16,19 +19,24 @@ const CreditManager = () => {
const address = useWalletStore((s) => s.address) const address = useWalletStore((s) => s.address)
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount) const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
const { data: positionsData, isLoading: isLoadingPositions } = useCreditAccountBalances( const { data: positionsData, isLoading: isLoadingPositions } = useCreditAccountPositions(
selectedAccount ?? '' selectedAccount ?? ''
) )
const totalPosition = const { data: tokenPrices } = useTokenPrices()
positionsData?.coins.reduce((acc, coin) => { const { data: marketsData } = useMarkets()
return Number(coin.value) + acc const accountStats = useAccountStats()
}, 0) ?? 0
const totalDebt = const getTokenTotalUSDValue = (amount: string, denom: string) => {
positionsData?.debt.reduce((acc, coin) => { // early return if prices are not fetched yet
return Number(coin.value) + acc if (!tokenPrices) return 0
}, 0) ?? 0
return (
BigNumber(amount)
.div(10 ** getTokenDecimals(denom))
.toNumber() * tokenPrices[denom]
)
}
if (!address) { if (!address) {
return ( return (
@ -66,11 +74,13 @@ const CreditManager = () => {
<CreditManagerContainer className="mb-2 text-sm"> <CreditManagerContainer className="mb-2 text-sm">
<div className="mb-1 flex justify-between"> <div className="mb-1 flex justify-between">
<div>Total Position:</div> <div>Total Position:</div>
<div className="font-semibold">{formatCurrency(totalPosition)}</div> <div className="font-semibold">
{formatCurrency(accountStats?.totalPosition ?? 0)}
</div>
</div> </div>
<div className="flex justify-between"> <div className="flex justify-between">
<div>Total Liabilities:</div> <div>Total Liabilities:</div>
<div className="font-semibold">{formatCurrency(totalDebt)}</div> <div className="font-semibold">{formatCurrency(accountStats?.totalDebt ?? 0)}</div>
</div> </div>
</CreditManagerContainer> </CreditManagerContainer>
<CreditManagerContainer> <CreditManagerContainer>
@ -87,8 +97,10 @@ const CreditManager = () => {
</div> </div>
{positionsData?.coins.map((coin) => ( {positionsData?.coins.map((coin) => (
<div key={coin.denom} className="flex text-xs text-black/40"> <div key={coin.denom} className="flex text-xs text-black/40">
<div className="flex-1">{coin.denom}</div> <div className="flex-1">{getTokenSymbol(coin.denom)}</div>
<div className="flex-1">{formatCurrency(coin.value)}</div> <div className="flex-1">
{formatCurrency(getTokenTotalUSDValue(coin.amount, coin.denom))}
</div>
<div className="flex-1"> <div className="flex-1">
{BigNumber(coin.amount) {BigNumber(coin.amount)
.div(10 ** getTokenDecimals(coin.denom)) .div(10 ** getTokenDecimals(coin.denom))
@ -100,11 +112,14 @@ const CreditManager = () => {
<div className="flex-1">-</div> <div className="flex-1">-</div>
</div> </div>
))} ))}
{positionsData?.debt.map((coin) => ( {positionsData?.debts.map((coin) => (
<div key={coin.denom} className="flex text-xs text-red-500"> <div key={coin.denom} className="flex text-xs text-red-500">
<div className="flex-1">{coin.denom}</div> <div className="flex-1 text-black/40">{getTokenSymbol(coin.denom)}</div>
<div className="flex-1">{formatCurrency(coin.value)}</div>
<div className="flex-1"> <div className="flex-1">
-{formatCurrency(getTokenTotalUSDValue(coin.amount, coin.denom))}
</div>
<div className="flex-1">
-
{BigNumber(coin.amount) {BigNumber(coin.amount)
.div(10 ** getTokenDecimals(coin.denom)) .div(10 ** getTokenDecimals(coin.denom))
.toNumber() .toNumber()
@ -112,7 +127,9 @@ const CreditManager = () => {
maximumFractionDigits: 6, maximumFractionDigits: 6,
})} })}
</div> </div>
<div className="flex-1">-</div> <div className="flex-1">
{(Number(marketsData?.[coin.denom].borrow_rate) * 100).toFixed(1)}%
</div>
</div> </div>
))} ))}
</> </>

View File

@ -0,0 +1,3 @@
<svg width="14" height="13" viewBox="0 0 14 13" fill="currentColor">
<path d="M0.234863 6.57567C0.234863 7.07288 0.581403 7.41188 1.08615 7.41188H8.04708L9.62912 7.33655L7.45194 9.31785L5.93771 10.8547C5.77951 11.0129 5.68157 11.2163 5.68157 11.4574C5.68157 11.9244 6.02811 12.2634 6.50272 12.2634C6.72872 12.2634 6.93213 12.173 7.12047 11.9922L11.859 7.20094C11.9871 7.07288 12.0775 6.92221 12.1152 6.74894V11.5478C12.1152 12.0148 12.4692 12.3538 12.9363 12.3538C13.4109 12.3538 13.765 12.0148 13.765 11.5478V1.6111C13.765 1.14403 13.4109 0.797485 12.9363 0.797485C12.4692 0.797485 12.1152 1.14403 12.1152 1.6111V6.39486C12.0775 6.22913 11.9871 6.07846 11.859 5.95039L7.12047 1.15156C6.93213 0.970755 6.72872 0.880354 6.50272 0.880354C6.02811 0.880354 5.68157 1.22689 5.68157 1.68644C5.68157 1.92751 5.77951 2.13845 5.93771 2.28911L7.45194 3.83348L9.62912 5.80725L8.04708 5.73192H1.08615C0.581403 5.73192 0.234863 6.07846 0.234863 6.57567Z" />
</svg>

After

Width:  |  Height:  |  Size: 956 B

View File

@ -13,13 +13,17 @@ import useCreditAccounts from 'hooks/useCreditAccounts'
import useCreateCreditAccount from 'hooks/useCreateCreditAccount' import useCreateCreditAccount from 'hooks/useCreateCreditAccount'
import useDeleteCreditAccount from 'hooks/useDeleteCreditAccount' import useDeleteCreditAccount from 'hooks/useDeleteCreditAccount'
import useCreditManagerStore from 'stores/useCreditManagerStore' import useCreditManagerStore from 'stores/useCreditManagerStore'
import useAccountStats from 'hooks/useAccountStats'
import SemiCircleProgress from './SemiCircleProgress'
import useWalletStore from 'stores/useWalletStore'
import ArrowRightLine from 'components/Icons/arrow-right-line.svg'
// TODO: will require some tweaks depending on how lower viewport mocks pans out // TODO: will require some tweaks depending on how lower viewport mocks pans out
const MAX_VISIBLE_CREDIT_ACCOUNTS = 5 const MAX_VISIBLE_CREDIT_ACCOUNTS = 5
const navItems = [ const navItems = [
{ href: '/trade', label: 'Trade' }, { href: '/trade', label: 'Trade' },
{ href: '/yield', label: 'Yield' }, { href: '/earn', label: 'Earn' },
{ href: '/borrow', label: 'Borrow' }, { href: '/borrow', label: 'Borrow' },
{ href: '/portfolio', label: 'Portfolio' }, { href: '/portfolio', label: 'Portfolio' },
{ href: '/council', label: 'Council' }, { href: '/council', label: 'Council' },
@ -38,6 +42,7 @@ const NavLink = ({ href, children }: { href: string; children: string }) => {
} }
const Navigation = () => { const Navigation = () => {
const address = useWalletStore((s) => s.address)
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount) const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
const setSelectedAccount = useCreditManagerStore((s) => s.actions.setSelectedAccount) const setSelectedAccount = useCreditManagerStore((s) => s.actions.setSelectedAccount)
const toggleCreditManager = useCreditManagerStore((s) => s.actions.toggleCreditManager) const toggleCreditManager = useCreditManagerStore((s) => s.actions.toggleCreditManager)
@ -48,6 +53,8 @@ const Navigation = () => {
selectedAccount || '' selectedAccount || ''
) )
const accountStats = useAccountStats()
const { firstCreditAccounts, restCreditAccounts } = useMemo(() => { const { firstCreditAccounts, restCreditAccounts } = useMemo(() => {
return { return {
firstCreditAccounts: creditAccountsList?.slice(0, MAX_VISIBLE_CREDIT_ACCOUNTS) ?? [], firstCreditAccounts: creditAccountsList?.slice(0, MAX_VISIBLE_CREDIT_ACCOUNTS) ?? [],
@ -74,105 +81,115 @@ const Navigation = () => {
<Wallet /> <Wallet />
</div> </div>
{/* Sub navigation bar */} {/* Sub navigation bar */}
<div className="flex justify-between border-b border-white/20 px-6 py-3 text-sm text-white/40"> {address && (
<div className="flex items-center"> <div className="flex justify-between border-b border-white/20 px-6 py-3 text-sm text-white/40">
<SearchInput /> <div className="flex items-center">
{firstCreditAccounts.map((account) => ( <SearchInput />
<div {firstCreditAccounts.map((account) => (
key={account} <div
className={`cursor-pointer px-4 hover:text-white ${ key={account}
selectedAccount === account ? 'text-white' : '' className={`cursor-pointer px-4 hover:text-white ${
}`} selectedAccount === account ? 'text-white' : ''
onClick={() => setSelectedAccount(account)} }`}
> onClick={() => setSelectedAccount(account)}
Account {account} >
</div> Account {account}
))} </div>
{restCreditAccounts.length > 0 && ( ))}
{restCreditAccounts.length > 0 && (
<Popover className="relative">
<Popover.Button>
<div className="flex cursor-pointer items-center px-3 hover:text-white">
More
<ChevronDownIcon className="ml-1 h-4 w-4" />
</div>
</Popover.Button>
<Popover.Panel className="absolute z-10 w-[200px] pt-2">
<div className="rounded-2xl bg-white p-4 text-gray-900">
{restCreditAccounts.map((account) => (
<div
key={account}
className={`cursor-pointer hover:text-orange-500 ${
selectedAccount === account ? 'text-orange-500' : ''
}`}
onClick={() => setSelectedAccount(account)}
>
Account {account}
</div>
))}
</div>
</Popover.Panel>
</Popover>
)}
<Popover className="relative"> <Popover className="relative">
<Popover.Button> <Popover.Button>
<div className="flex cursor-pointer items-center px-3 hover:text-white"> <div className="flex cursor-pointer items-center px-3 hover:text-white">
More Manage
<ChevronDownIcon className="ml-1 h-4 w-4" /> <ChevronDownIcon className="ml-1 h-4 w-4" />
</div> </div>
</Popover.Button> </Popover.Button>
<Popover.Panel className="absolute z-10 w-[200px] pt-2"> <Popover.Panel className="absolute z-10 w-[200px] pt-2">
<div className="rounded-2xl bg-white p-4 text-gray-900"> {({ close }) => (
{restCreditAccounts.map((account) => ( <div className="rounded-2xl bg-white p-4 text-gray-900">
<div <div
key={account} className="mb-2 cursor-pointer hover:text-orange-500"
className={`cursor-pointer hover:text-orange-500 ${ onClick={() => {
selectedAccount === account ? 'text-orange-500' : '' close()
}`} createCreditAccount()
onClick={() => setSelectedAccount(account)} }}
> >
Account {account} Create Account
</div> </div>
))} <div
</div> className="mb-2 cursor-pointer hover:text-orange-500"
onClick={() => {
close()
deleteCreditAccount()
}}
>
Close Account
</div>
<div
className="mb-2 cursor-pointer hover:text-orange-500"
onClick={() => alert('TODO')}
>
Transfer Balance
</div>
<div
className="cursor-pointer hover:text-orange-500"
onClick={() => alert('TODO')}
>
Rearrange
</div>
</div>
)}
</Popover.Panel> </Popover.Panel>
</Popover> </Popover>
)} </div>
<Popover className="relative"> <div className="flex items-center gap-4">
<Popover.Button> {accountStats && (
<div className="flex cursor-pointer items-center px-3 hover:text-white"> <>
Manage <p>{formatCurrency(accountStats.netWorth)}</p>
<ChevronDownIcon className="ml-1 h-4 w-4" /> {/* TOOLTIP */}
</div> <div title={`${String(accountStats.currentLeverage.toFixed(1))}x`}>
</Popover.Button> <SemiCircleProgress
<Popover.Panel className="absolute z-10 w-[200px] pt-2"> value={accountStats.currentLeverage / accountStats.maxLeverage}
{({ close }) => ( label="Lvg"
<div className="rounded-2xl bg-white p-4 text-gray-900"> />
<div
className="mb-2 cursor-pointer hover:text-orange-500"
onClick={() => {
close()
createCreditAccount()
}}
>
Create Account
</div>
<div
className="mb-2 cursor-pointer hover:text-orange-500"
onClick={() => {
close()
deleteCreditAccount()
}}
>
Close Account
</div>
<div
className="mb-2 cursor-pointer hover:text-orange-500"
onClick={() => alert('TODO')}
>
Transfer Balance
</div>
<div
className="cursor-pointer hover:text-orange-500"
onClick={() => alert('TODO')}
>
Rearrange
</div>
</div> </div>
)} <SemiCircleProgress value={accountStats.risk} label="Risk" />
</Popover.Panel> <ProgressBar value={accountStats.health} />
</Popover> </>
</div> )}
<div className="flex items-center gap-4"> <div
<p>{formatCurrency(2500)}</p> className="flex w-16 cursor-pointer justify-center hover:text-white"
<div>Lvg</div> onClick={toggleCreditManager}
<div>Risk</div> >
<ProgressBar value={0.43} /> <ArrowRightLine />
<div </div>
className="flex w-16 cursor-pointer justify-center hover:text-white"
onClick={toggleCreditManager}
>
<svg width="14" height="13" viewBox="0 0 14 13" fill="currentColor">
<path d="M0.234863 6.57567C0.234863 7.07288 0.581403 7.41188 1.08615 7.41188H8.04708L9.62912 7.33655L7.45194 9.31785L5.93771 10.8547C5.77951 11.0129 5.68157 11.2163 5.68157 11.4574C5.68157 11.9244 6.02811 12.2634 6.50272 12.2634C6.72872 12.2634 6.93213 12.173 7.12047 11.9922L11.859 7.20094C11.9871 7.07288 12.0775 6.92221 12.1152 6.74894V11.5478C12.1152 12.0148 12.4692 12.3538 12.9363 12.3538C13.4109 12.3538 13.765 12.0148 13.765 11.5478V1.6111C13.765 1.14403 13.4109 0.797485 12.9363 0.797485C12.4692 0.797485 12.1152 1.14403 12.1152 1.6111V6.39486C12.0775 6.22913 11.9871 6.07846 11.859 5.95039L7.12047 1.15156C6.93213 0.970755 6.72872 0.880354 6.50272 0.880354C6.02811 0.880354 5.68157 1.22689 5.68157 1.68644C5.68157 1.92751 5.77951 2.13845 5.93771 2.28911L7.45194 3.83348L9.62912 5.80725L8.04708 5.73192H1.08615C0.581403 5.73192 0.234863 6.07846 0.234863 6.57567Z" />
</svg>
</div> </div>
</div> </div>
</div> )}
{(isLoadingCreate || isLoadingDelete) && ( {(isLoadingCreate || isLoadingDelete) && (
<div className="fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2"> <div className="fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">
<Spinner /> <Spinner />

View File

@ -1,5 +1,4 @@
import React, { useEffect, useState } from 'react' import React from 'react'
import { ArrowRightIcon } from '@heroicons/react/24/solid'
type Props = { type Props = {
value: number value: number
@ -7,16 +6,6 @@ type Props = {
const ProgressBar = ({ value }: Props) => { const ProgressBar = ({ value }: Props) => {
const percentageValue = `${(value * 100).toFixed(0)}%` const percentageValue = `${(value * 100).toFixed(0)}%`
const [newValue, setNewValue] = useState(0.77)
useEffect(() => {
setInterval(() => {
// randomizing value between value and 1
setNewValue(Math.random() * (1 - value) + value)
}, 3000)
}, [value])
const percentageNewValue = `${(newValue * 100).toFixed(0)}%`
return ( return (
<div className="relative z-0 h-4 w-[130px] rounded-full bg-black"> <div className="relative z-0 h-4 w-[130px] rounded-full bg-black">
@ -24,14 +13,8 @@ const ProgressBar = ({ value }: Props) => {
className="absolute z-10 h-4 rounded-full bg-green-500" className="absolute z-10 h-4 rounded-full bg-green-500"
style={{ width: percentageValue }} style={{ width: percentageValue }}
/> />
<div
className="absolute h-4 rounded-full bg-red-500 transition-[width] duration-500"
style={{ width: percentageNewValue }}
/>
<div className="absolute z-20 flex w-full items-center justify-center gap-x-2 text-xs font-medium text-white"> <div className="absolute z-20 flex w-full items-center justify-center gap-x-2 text-xs font-medium text-white">
{percentageValue} {percentageValue}
<ArrowRightIcon className="h-3 w-3" />
{percentageNewValue}
</div> </div>
</div> </div>
) )

View File

@ -0,0 +1,104 @@
import React from 'react'
type Props = {
stroke?: string
strokeWidth?: number
background?: string
diameter?: 60
orientation?: any
direction?: any
value: number
label?: string
}
const SemiCircleProgress = ({
stroke = '#02B732',
strokeWidth = 6,
background = '#D0D0CE',
diameter = 60,
orientation = 'up',
direction = 'right',
value = 0,
label,
}: Props) => {
const coordinateForCircle = diameter / 2
const radius = (diameter - 2 * strokeWidth) / 2
const circumference = Math.PI * radius
const percentage = value * 100
let percentageValue
if (percentage > 100) {
percentageValue = 100
} else if (percentage < 0) {
percentageValue = 0
} else {
percentageValue = percentage
}
const semiCirclePercentage = percentageValue * (circumference / 100)
let rotation
if (orientation === 'down') {
if (direction === 'left') {
rotation = 'rotate(180deg) rotateY(180deg)'
} else {
rotation = 'rotate(180deg)'
}
} else {
if (direction === 'right') {
rotation = 'rotateY(180deg)'
}
}
return (
<div className="semicircle-container" style={{ position: 'relative' }}>
<svg
width={diameter}
height={diameter / 2}
style={{ transform: rotation, overflow: 'hidden' }}
>
<circle
cx={coordinateForCircle}
cy={coordinateForCircle}
r={radius}
fill="none"
stroke={background}
strokeWidth={strokeWidth}
strokeDasharray={circumference}
style={{
strokeDashoffset: circumference,
}}
/>
<circle
cx={coordinateForCircle}
cy={coordinateForCircle}
r={radius}
fill="none"
stroke={stroke}
strokeWidth={strokeWidth}
strokeDasharray={circumference}
style={{
strokeDashoffset: semiCirclePercentage,
transition: 'stroke-dashoffset .3s ease 0s, stroke-dasharray .3s ease 0s, stroke .3s',
}}
/>
</svg>
{label && (
<span
className="text-xs"
style={{
width: '100%',
left: '0',
textAlign: 'center',
bottom: orientation === 'down' ? 'auto' : '-4px',
position: 'absolute',
}}
>
{label}
</span>
)}
</div>
)
}
export default SemiCircleProgress

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react' import React, { useState } from 'react'
import { Popover } from '@headlessui/react' import { Popover } from '@headlessui/react'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import Image from 'next/image' import Image from 'next/image'
@ -34,7 +34,7 @@ const WalletPopover = ({ children }: { children: React.ReactNode }) => {
</div> </div>
<Button <Button
className=" bg-[#524bb1] hover:bg-[#6962cc]" className=" bg-[#524bb1] hover:bg-[#6962cc]"
onClick={() => actions.setAddress('')} onClick={() => actions.disconnect()}
> >
Disconnect Disconnect
</Button> </Button>
@ -71,18 +71,12 @@ const WalletPopover = ({ children }: { children: React.ReactNode }) => {
const Wallet = () => { const Wallet = () => {
const [showConnectModal, setShowConnectModal] = useState(false) const [showConnectModal, setShowConnectModal] = useState(false)
const [hasHydrated, setHasHydrated] = useState<boolean>(false)
const address = useWalletStore((s) => s.address) const address = useWalletStore((s) => s.address)
// avoid server-client hydration mismatch
useEffect(() => {
setHasHydrated(true)
}, [])
return ( return (
<> <>
{hasHydrated && address ? ( {address ? (
<WalletPopover>{formatWalletAddress(address)}</WalletPopover> <WalletPopover>{formatWalletAddress(address)}</WalletPopover>
) : ( ) : (
<Button className="w-[200px]" onClick={() => setShowConnectModal(true)}> <Button className="w-[200px]" onClick={() => setShowConnectModal(true)}>

View File

@ -1,9 +1,8 @@
// https://github.com/mars-protocol/rover/blob/master/scripts/deploy/addresses/osmo-test-4.json // https://github.com/mars-protocol/rover/blob/master/scripts/deploy/addresses/osmo-test-4.json
export const contractAddresses = { export const contractAddresses = {
accountNft: 'osmo16v3mvsdnkh4c6ykc885n3x5ay9e36akdzxcl2g93698rqw007xxqesld8w', accountNft: 'osmo1dravtyd0425fkdmkysc3ns7zud05clf5uhj6qqsnkdtrpkewu73q9f3f02',
mockRedBank: 'osmo1xrnx0q3x7kwzss53fry0dwwsc7pff6aq628l6n0rmvegkalp4y7qzl7j7z', mockVault: 'osmo1emcckulm2mkx36xeanhsn3z3zjeql6pgd8yf8a5cf03ccvy7a4dqjw9tl7',
mockOracle: 'osmo1r9u2tfq8n5xpn2g0fq8ha0rj0cyp2fzr5w9jvcqwt3r8lxdfm6yszmtza5', marsOracleAdapter: 'osmo1cw6pv97g7fmhqykrn0gc9ngrx5tnky75rmlwkzxuqhsk58u0n8asz036g0',
mockVault: 'osmo1gg4rpug7vwrnq0ask0k7nmw23z6wl8c8fr7jmup9pdpaal9uc5nqq7lyrm', swapper: 'osmo1w2552km2u9w4k2gjw4n8drmuz5yxw8x4qzy6dl3da824km5cjlys00x3qp',
swapper: 'osmo1ak4x8k2h7s6pq5dnlncmgsmx2nqcaplpfxlmklx2ln7qn6dtny8q70apjv', creditManager: 'osmo18dt5y0ecyd5qg8nqwzrgxuljfejglyh2fjd984s8cy7fcx8mxh9qfl3hwq',
creditManager: 'osmo1963xgmt8agyc6q4k2vhf980kffq6ukkj9mgtwdxxnpj3dak2akdq20z9dw',
} }

86
hooks/useAccountStats.tsx Normal file
View File

@ -0,0 +1,86 @@
import { useMemo } from 'react'
import BigNumber from 'bignumber.js'
import useCreditManagerStore from 'stores/useCreditManagerStore'
import { getTokenDecimals } from 'utils/tokens'
import useCreditAccountPositions from './useCreditAccountPositions'
import useMarkets from './useMarkets'
import useTokenPrices from './useTokenPrices'
// displaying 3 levels of risk based on the weighted average of liquidation LTVs
// 0.85 -> 25% risk
// 0.65 - 0.85 -> 50% risk
// < 0.65 -> 100% risk
const getRiskFromAverageLiquidationLTVs = (value: number) => {
if (value >= 0.85) return 0.25
if (value > 0.65) return 0.5
return 1
}
const useAccountStats = () => {
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
const { data: positionsData } = useCreditAccountPositions(selectedAccount ?? '')
const { data: marketsData } = useMarkets()
const { data: tokenPrices } = useTokenPrices()
return useMemo(() => {
if (!marketsData || !tokenPrices || !positionsData) return null
const getTokenTotalUSDValue = (amount: string, denom: string) => {
// early return if prices are not fetched yet
if (!tokenPrices) return 0
return BigNumber(amount)
.div(10 ** getTokenDecimals(denom))
.times(tokenPrices[denom])
.toNumber()
}
const totalPosition = positionsData.coins.reduce((acc, coin) => {
const tokenTotalValue = getTokenTotalUSDValue(coin.amount, coin.denom)
return BigNumber(tokenTotalValue).plus(acc).toNumber()
}, 0)
const totalDebt = positionsData.debts.reduce((acc, coin) => {
const tokenTotalValue = getTokenTotalUSDValue(coin.amount, coin.denom)
return BigNumber(tokenTotalValue).plus(acc).toNumber()
}, 0)
const totalWeightedPositions = positionsData.coins.reduce((acc, coin) => {
const tokenWeightedValue = BigNumber(getTokenTotalUSDValue(coin.amount, coin.denom)).times(
Number(marketsData[coin.denom].max_loan_to_value)
)
return tokenWeightedValue.plus(acc).toNumber()
}, 0)
const netWorth = BigNumber(totalPosition).minus(totalDebt).toNumber()
const liquidationLTVsWeightedAverage = BigNumber(totalWeightedPositions)
.div(totalPosition)
.toNumber()
const maxLeverage = BigNumber(1)
.div(BigNumber(1).minus(liquidationLTVsWeightedAverage))
.toNumber()
const currentLeverage = BigNumber(totalPosition).div(netWorth).toNumber()
const health = BigNumber(1).minus(BigNumber(currentLeverage).div(maxLeverage)).toNumber() || 1
const risk = liquidationLTVsWeightedAverage
? getRiskFromAverageLiquidationLTVs(liquidationLTVsWeightedAverage)
: 0
return {
health,
maxLeverage,
currentLeverage,
netWorth,
risk,
totalPosition,
totalDebt,
}
}, [marketsData, positionsData, tokenPrices])
}
export default useAccountStats

View File

@ -1,9 +1,6 @@
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { useEffect, useState } from 'react'
import useWalletStore from 'stores/useWalletStore' import useWalletStore from 'stores/useWalletStore'
import { chain } from 'utils/chains'
import { contractAddresses } from 'config/contracts' import { contractAddresses } from 'config/contracts'
import { queryKeys } from 'types/query-keys-factory' import { queryKeys } from 'types/query-keys-factory'
@ -14,25 +11,14 @@ const queryMsg = {
} }
const useAllowedCoins = () => { const useAllowedCoins = () => {
const [signingClient, setSigningClient] = useState<SigningCosmWasmClient>()
const address = useWalletStore((s) => s.address) const address = useWalletStore((s) => s.address)
const client = useWalletStore((s) => s.client)
useEffect(() => {
;(async () => {
if (!window.keplr) return
const offlineSigner = window.keplr.getOfflineSigner(chain.chainId)
const clientInstance = await SigningCosmWasmClient.connectWithSigner(chain.rpc, offlineSigner)
setSigningClient(clientInstance)
})()
}, [address])
const result = useQuery<Result>( const result = useQuery<Result>(
queryKeys.allowedCoins(), queryKeys.allowedCoins(),
async () => signingClient?.queryContractSmart(contractAddresses.creditManager, queryMsg), async () => client?.queryContractSmart(contractAddresses.creditManager, queryMsg),
{ {
enabled: !!address && !!signingClient, enabled: !!address && !!client,
staleTime: Infinity, staleTime: Infinity,
} }
) )

View File

@ -1,69 +1,43 @@
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { useEffect, useMemo, useState } from 'react' import { useMemo } from 'react'
import { Coin } from '@cosmjs/stargate'
import useWalletStore from 'stores/useWalletStore' import useWalletStore from 'stores/useWalletStore'
import { chain } from 'utils/chains'
import { contractAddresses } from 'config/contracts' import { contractAddresses } from 'config/contracts'
import { queryKeys } from 'types/query-keys-factory' import { queryKeys } from 'types/query-keys-factory'
interface CoinValue { interface DebtAmount {
amount: string amount: string
denom: string denom: string
price: string
value: string
}
interface DebtSharesValue {
amount: string
denom: string
price: string
shares: string shares: string
value: string
} }
export interface VaultPosition { interface VaultPosition {
locked: string locked: string
unlocked: string unlocked: string
} }
interface VaultPositionWithAddr {
addr: string
position: VaultPosition
}
interface Result { interface Result {
account_id: string account_id: string
coins: CoinValue[] coins: Coin[]
debt: DebtSharesValue[] debts: DebtAmount[]
vault_positions: VaultPositionWithAddr[] vaults: VaultPosition[]
} }
const useCreditAccountPositions = (accountId: string) => { const useCreditAccountPositions = (accountId: string) => {
const [signingClient, setSigningClient] = useState<SigningCosmWasmClient>()
const address = useWalletStore((s) => s.address) const address = useWalletStore((s) => s.address)
const client = useWalletStore((s) => s.client)
useEffect(() => {
;(async () => {
if (!window.keplr) return
const offlineSigner = window.keplr.getOfflineSigner(chain.chainId)
const clientInstance = await SigningCosmWasmClient.connectWithSigner(chain.rpc, offlineSigner)
setSigningClient(clientInstance)
})()
}, [address])
const result = useQuery<Result>( const result = useQuery<Result>(
queryKeys.creditAccountsPositions(accountId), queryKeys.creditAccountsPositions(accountId),
async () => async () =>
signingClient?.queryContractSmart(contractAddresses.creditManager, { client?.queryContractSmart(contractAddresses.creditManager, {
positions: { positions: {
account_id: accountId, account_id: accountId,
}, },
}), }),
{ {
enabled: !!address && !!signingClient, enabled: !!address && !!client,
staleTime: Infinity, staleTime: Infinity,
} }
) )
@ -71,7 +45,7 @@ const useCreditAccountPositions = (accountId: string) => {
return { return {
...result, ...result,
data: useMemo(() => { data: useMemo(() => {
return result?.data return result?.data && { ...result.data }
}, [result.data]), }, [result.data]),
} }
} }

View File

@ -1,9 +1,7 @@
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { useEffect, useMemo, useState } from 'react' import { useMemo } from 'react'
import useWalletStore from 'stores/useWalletStore' import useWalletStore from 'stores/useWalletStore'
import { chain } from 'utils/chains'
import { contractAddresses } from 'config/contracts' import { contractAddresses } from 'config/contracts'
import useCreditManagerStore from 'stores/useCreditManagerStore' import useCreditManagerStore from 'stores/useCreditManagerStore'
import { queryKeys } from 'types/query-keys-factory' import { queryKeys } from 'types/query-keys-factory'
@ -13,8 +11,8 @@ type Result = {
} }
const useCreditAccounts = () => { const useCreditAccounts = () => {
const [signingClient, setSigningClient] = useState<SigningCosmWasmClient>()
const address = useWalletStore((s) => s.address) const address = useWalletStore((s) => s.address)
const client = useWalletStore((s) => s.client)
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount) const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
const creditManagerActions = useCreditManagerStore((s) => s.actions) const creditManagerActions = useCreditManagerStore((s) => s.actions)
@ -26,22 +24,12 @@ const useCreditAccounts = () => {
} }
}, [address]) }, [address])
useEffect(() => {
;(async () => {
if (!window.keplr) return
const offlineSigner = window.keplr.getOfflineSigner(chain.chainId)
const clientInstance = await SigningCosmWasmClient.connectWithSigner(chain.rpc, offlineSigner)
setSigningClient(clientInstance)
})()
}, [address])
const result = useQuery<Result>( const result = useQuery<Result>(
queryKeys.creditAccounts(address), queryKeys.creditAccounts(address),
async () => signingClient?.queryContractSmart(contractAddresses.accountNft, queryMsg), async () => client?.queryContractSmart(contractAddresses.accountNft, queryMsg),
{ {
enabled: !!address && !!signingClient, staleTime: Infinity,
enabled: !!address && !!client,
onSuccess: (data) => { onSuccess: (data) => {
if (!data.tokens.includes(selectedAccount || '') && data.tokens.length > 0) { if (!data.tokens.includes(selectedAccount || '') && data.tokens.length > 0) {
creditManagerActions.setSelectedAccount(data.tokens[0]) creditManagerActions.setSelectedAccount(data.tokens[0])

100
hooks/useMarkets.tsx Normal file
View File

@ -0,0 +1,100 @@
import { useQuery } from '@tanstack/react-query'
import { useMemo } from 'react'
interface Market {
denom: string
max_loan_to_value: string
liquidation_threshold: string
liquidation_bonus: string
reserve_factor: string
interest_rate_model: {
optimal_utilization_rate: string
base: string
slope_1: string
slope_2: string
}
borrow_index: string
liquidity_index: string
borrow_rate: string
liquidity_rate: string
indexes_last_updated: number
collateral_total_scaled: string
debt_total_scaled: string
deposit_enabled: boolean
borrow_enabled: boolean
deposit_cap: string
}
interface Result {
wasm: {
[key: string]: Market
}
}
const useMarkets = () => {
const result = useQuery<Result>(
['marketInfo'],
() => ({
wasm: {
uosmo: {
denom: 'uosmo',
max_loan_to_value: '0.7',
liquidation_threshold: '0.65',
liquidation_bonus: '0.1',
reserve_factor: '0.2',
interest_rate_model: {
optimal_utilization_rate: '0.7',
base: '0.3',
slope_1: '0.25',
slope_2: '0.3',
},
borrow_index: '1.002171957411401332',
liquidity_index: '1.00055035491698614',
borrow_rate: '0.1',
liquidity_rate: '0',
indexes_last_updated: 1664544343,
collateral_total_scaled: '89947659146708',
debt_total_scaled: '0',
deposit_enabled: true,
borrow_enabled: true,
deposit_cap: '1000000000000',
},
'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2': {
denom: 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2',
max_loan_to_value: '0.8',
liquidation_threshold: '0.7',
liquidation_bonus: '0.1',
reserve_factor: '0.2',
interest_rate_model: {
optimal_utilization_rate: '0.1',
base: '0.3',
slope_1: '0.25',
slope_2: '0.3',
},
borrow_index: '1.000000224611044228',
liquidity_index: '1.000000023465246067',
borrow_rate: '0.25',
liquidity_rate: '0',
indexes_last_updated: 1664367327,
collateral_total_scaled: '0',
debt_total_scaled: '0',
deposit_enabled: true,
borrow_enabled: true,
deposit_cap: '1000000000',
},
},
}),
{
staleTime: Infinity,
}
)
return {
...result,
data: useMemo(() => {
return result?.data && result.data.wasm
}, [result.data]),
}
}
export default useMarkets

16
hooks/useTokenPrices.tsx Normal file
View File

@ -0,0 +1,16 @@
import { useQuery } from '@tanstack/react-query'
const useTokenPrices = () => {
return useQuery<{ [key in string]: number }>(
['tokenPrices'],
() => ({
uosmo: 1.1,
'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2': 11,
}),
{
staleTime: Infinity,
}
)
}
export default useTokenPrices

View File

@ -13,6 +13,15 @@ const nextConfig = {
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#use-hidden-source-map // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#use-hidden-source-map
hideSourceMaps: true, hideSourceMaps: true,
}, },
webpack(config) {
config.module.rules.push({
test: /\.svg$/i,
issuer: /\.[jt]sx?$/,
use: ['@svgr/webpack'],
})
return config
},
} }
const sentryWebpackPluginOptions = { const sentryWebpackPluginOptions = {

View File

@ -25,10 +25,12 @@
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-toastify": "^9.0.8", "react-toastify": "^9.0.8",
"use-local-storage-state": "^18.1.1",
"zustand": "^4.1.1" "zustand": "^4.1.1"
}, },
"devDependencies": { "devDependencies": {
"@keplr-wallet/types": "^0.10.24", "@keplr-wallet/types": "^0.10.24",
"@svgr/webpack": "^6.4.0",
"@types/node": "18.7.14", "@types/node": "18.7.14",
"@types/react": "18.0.18", "@types/react": "18.0.18",
"@types/react-dom": "18.0.6", "@types/react-dom": "18.0.6",

View File

@ -1,3 +1,4 @@
import { useEffect } from 'react'
import type { AppProps } from 'next/app' import type { AppProps } from 'next/app'
import Head from 'next/head' import Head from 'next/head'
import { ToastContainer, Zoom } from 'react-toastify' import { ToastContainer, Zoom } from 'react-toastify'
@ -7,7 +8,6 @@ import detectEthereumProvider from '@metamask/detect-provider'
import '../styles/globals.css' import '../styles/globals.css'
import Layout from 'components/Layout' import Layout from 'components/Layout'
import { useEffect } from 'react'
import useWalletStore from 'stores/useWalletStore' import useWalletStore from 'stores/useWalletStore'
async function isMetamaskInstalled(): Promise<boolean> { async function isMetamaskInstalled(): Promise<boolean> {
@ -19,6 +19,7 @@ async function isMetamaskInstalled(): Promise<boolean> {
const queryClient = new QueryClient() const queryClient = new QueryClient()
function MyApp({ Component, pageProps }: AppProps) { function MyApp({ Component, pageProps }: AppProps) {
const address = useWalletStore((s) => s.address)
const actions = useWalletStore((s) => s.actions) const actions = useWalletStore((s) => s.actions)
// init store // init store
@ -27,6 +28,8 @@ function MyApp({ Component, pageProps }: AppProps) {
actions.setMetamaskInstalledStatus(await isMetamaskInstalled()) actions.setMetamaskInstalledStatus(await isMetamaskInstalled())
} }
actions.initialize()
verifyMetamask() verifyMetamask()
}, [actions]) }, [actions])
@ -38,9 +41,7 @@ function MyApp({ Component, pageProps }: AppProps) {
<link rel="icon" href="/favicon.svg" /> <link rel="icon" href="/favicon.svg" />
</Head> </Head>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<Layout> <Layout>{address ? <Component {...pageProps} /> : <div>No wallet connected</div>}</Layout>
<Component {...pageProps} />
</Layout>
<ToastContainer <ToastContainer
autoClose={1500} autoClose={1500}
closeButton={false} closeButton={false}

View File

@ -1,7 +1,7 @@
import React from 'react' import React from 'react'
import Container from 'components/Container' import Container from 'components/Container'
const Yield = () => { const Earn = () => {
return ( return (
<div className="flex gap-4"> <div className="flex gap-4">
<Container className="flex-1">Yield Module</Container> <Container className="flex-1">Yield Module</Container>
@ -10,4 +10,4 @@ const Yield = () => {
) )
} }
export default Yield export default Earn

View File

@ -2,15 +2,18 @@ import create from 'zustand'
import { persist } from 'zustand/middleware' import { persist } from 'zustand/middleware'
import { Wallet } from 'types' import { Wallet } from 'types'
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import { chain } from 'utils/chains'
interface WalletStore { interface WalletStore {
address: string address: string
injectiveAddress: string
addresses: string[]
metamaskInstalled: boolean metamaskInstalled: boolean
wallet: Wallet wallet: Wallet | null
client?: CosmWasmClient
actions: { actions: {
setAddress: (address: string) => void disconnect: () => void
initialize: () => void
connect: (address: string, wallet: Wallet) => void
setMetamaskInstalledStatus: (value: boolean) => void setMetamaskInstalledStatus: (value: boolean) => void
} }
} }
@ -19,12 +22,24 @@ const useWalletStore = create<WalletStore>()(
persist( persist(
(set, get) => ({ (set, get) => ({
address: '', address: '',
injectiveAddress: '',
addresses: [],
metamaskInstalled: false, metamaskInstalled: false,
wallet: Wallet.Metamask, wallet: null,
actions: { actions: {
setAddress: (address: string) => set(() => ({ address })), disconnect: () => {
set(() => ({ address: '', wallet: null }))
},
initialize: async () => {
const clientInstance = await CosmWasmClient.connect(chain.rpc)
let address = ''
if (get().wallet === Wallet.Keplr && window.keplr) {
const key = await window.keplr.getKey(chain.chainId)
address = key.bech32Address
}
set(() => ({ client: clientInstance, address }))
},
connect: (address: string, wallet: Wallet) => set(() => ({ address, wallet })),
setMetamaskInstalledStatus: (value: boolean) => set(() => ({ metamaskInstalled: value })), setMetamaskInstalledStatus: (value: boolean) => set(() => ({ metamaskInstalled: value })),
}, },
}), }),
@ -32,7 +47,9 @@ const useWalletStore = create<WalletStore>()(
name: 'wallet', name: 'wallet',
partialize: (state) => partialize: (state) =>
Object.fromEntries( Object.fromEntries(
Object.entries(state).filter(([key]) => !['metamaskInstalled', 'actions'].includes(key)) Object.entries(state).filter(
([key]) => !['client', 'metamaskInstalled', 'actions', 'address'].includes(key)
)
), ),
} }
) )

View File

@ -6,8 +6,9 @@ html,
body { body {
padding: 0; padding: 0;
margin: 0; margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
color: white;
} }
a { a {
@ -19,16 +20,6 @@ a {
box-sizing: border-box; box-sizing: border-box;
} }
@media (prefers-color-scheme: dark) {
html {
color-scheme: dark;
}
body {
color: white;
background: black;
}
}
/* react-toastify */ /* react-toastify */
/* https://fkhadra.github.io/react-toastify/how-to-style#override-css-variables */ /* https://fkhadra.github.io/react-toastify/how-to-style#override-css-variables */
.Toastify__toast { .Toastify__toast {

View File

@ -40,8 +40,8 @@ export const chainsInfo = {
}, },
OsmosisTestnet: { OsmosisTestnet: {
chainId: 'osmo-test-4', chainId: 'osmo-test-4',
rpc: 'https://rpc-test.osmosis.zone', rpc: 'https://osmosis-delphi-testnet-1.simply-vc.com.mt/XF32UOOU55CX/osmosis-rpc',
rest: 'https://lcd-test.osmosis.zone', rest: 'https://osmosis-delphi-testnet-1.simply-vc.com.mt/XF32UOOU55CX/osmosis-lcd',
stakeCurrency: { stakeCurrency: {
coinDenom: 'OSMO', coinDenom: 'OSMO',
coinMinimalDenom: 'uosmo', coinMinimalDenom: 'uosmo',

View File

@ -9,5 +9,5 @@ export const hardcodedFee = {
amount: '100000', amount: '100000',
}, },
], ],
gas: '750000', gas: '1500000',
} }

1205
yarn.lock

File diff suppressed because it is too large Load Diff