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:
parent
f709c12da2
commit
3022ae9a6a
@ -7,7 +7,7 @@ import { Coin } from '@cosmjs/stargate'
|
||||
|
||||
import { getInjectiveAddress } from 'utils/address'
|
||||
import { getExperimentalChainConfigBasedOnChainId } from 'utils/experimental-chains'
|
||||
import { ChainId } from 'types'
|
||||
import { ChainId, Wallet } from 'types'
|
||||
import useWalletStore from 'stores/useWalletStore'
|
||||
import { chain } from 'utils/chains'
|
||||
|
||||
@ -48,7 +48,7 @@ const ConnectModal = ({ isOpen, onClose }: Props) => {
|
||||
}
|
||||
|
||||
const key = await window.keplr.getKey(chain.chainId)
|
||||
actions.setAddress(key.bech32Address)
|
||||
actions.connect(key.bech32Address, Wallet.Keplr)
|
||||
|
||||
handleConnectSuccess()
|
||||
} catch (e) {
|
||||
@ -72,7 +72,7 @@ const ConnectModal = ({ isOpen, onClose }: Props) => {
|
||||
method: 'eth_requestAccounts',
|
||||
})
|
||||
const [address] = addresses
|
||||
actions.setAddress(getInjectiveAddress(address))
|
||||
actions.connect(getInjectiveAddress(address), Wallet.Metamask)
|
||||
handleConnectSuccess()
|
||||
} catch (e) {
|
||||
// TODO: handle exception
|
||||
|
@ -2,6 +2,7 @@ import React, { useEffect, useMemo, useState } from 'react'
|
||||
import * as Slider from '@radix-ui/react-slider'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { Switch } from '@headlessui/react'
|
||||
import useLocalStorageState from 'use-local-storage-state'
|
||||
|
||||
import Button from '../Button'
|
||||
import useAllowedCoins from 'hooks/useAllowedCoins'
|
||||
@ -14,10 +15,13 @@ import CreditManagerContainer from './CreditManagerContainer'
|
||||
const FundAccount = () => {
|
||||
const [amount, setAmount] = useState(0)
|
||||
const [selectedToken, setSelectedToken] = useState('')
|
||||
const [enabled, setEnabled] = useState(false)
|
||||
|
||||
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
|
||||
|
||||
const [lendAssets, setLendAssets] = useLocalStorageState(`lendAssets_${selectedAccount}`, {
|
||||
defaultValue: false,
|
||||
})
|
||||
|
||||
const { data: balancesData } = useAllBalances()
|
||||
const { data: allowedCoinsData, isLoading: isLoadingAllowedCoins } = useAllowedCoins()
|
||||
const { mutate } = useDepositCreditAccount(
|
||||
@ -58,7 +62,7 @@ const FundAccount = () => {
|
||||
return (
|
||||
<>
|
||||
<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 don’t have
|
||||
any assets in your injective wallet use the injective bridge to transfer funds to your
|
||||
injective wallet.
|
||||
@ -67,7 +71,7 @@ const FundAccount = () => {
|
||||
<p>Loading...</p>
|
||||
) : (
|
||||
<>
|
||||
<div className="mb-4">
|
||||
<div className="mb-4 text-sm">
|
||||
<div className="mb-1 flex justify-between">
|
||||
<div>Asset:</div>
|
||||
<select
|
||||
@ -95,7 +99,7 @@ const FundAccount = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p>In wallet: {walletAmount.toLocaleString()}</p>
|
||||
<p className="text-sm">In wallet: {walletAmount.toLocaleString()}</p>
|
||||
{/* SLIDER - initial implementation to test functionality */}
|
||||
{/* TODO: will need to be revamped later on */}
|
||||
<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">
|
||||
<div>
|
||||
<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>
|
||||
|
||||
<Switch
|
||||
checked={enabled}
|
||||
onChange={setEnabled}
|
||||
checked={lendAssets}
|
||||
onChange={setLendAssets}
|
||||
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`}
|
||||
>
|
||||
<span
|
||||
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`}
|
||||
/>
|
||||
</Switch>
|
||||
|
@ -5,10 +5,13 @@ import Button from '../Button'
|
||||
import { formatCurrency } from 'utils/formatters'
|
||||
import useCreditManagerStore from 'stores/useCreditManagerStore'
|
||||
import useWalletStore from 'stores/useWalletStore'
|
||||
import useCreditAccountBalances from 'hooks/useCreditAccountPositions'
|
||||
import { getTokenDecimals } from 'utils/tokens'
|
||||
import useCreditAccountPositions from 'hooks/useCreditAccountPositions'
|
||||
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
|
||||
import FundAccount from './FundAccount'
|
||||
import CreditManagerContainer from './CreditManagerContainer'
|
||||
import useTokenPrices from 'hooks/useTokenPrices'
|
||||
import useAccountStats from 'hooks/useAccountStats'
|
||||
import useMarkets from 'hooks/useMarkets'
|
||||
|
||||
const CreditManager = () => {
|
||||
const [isFund, setIsFund] = useState(false)
|
||||
@ -16,19 +19,24 @@ const CreditManager = () => {
|
||||
const address = useWalletStore((s) => s.address)
|
||||
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
|
||||
|
||||
const { data: positionsData, isLoading: isLoadingPositions } = useCreditAccountBalances(
|
||||
const { data: positionsData, isLoading: isLoadingPositions } = useCreditAccountPositions(
|
||||
selectedAccount ?? ''
|
||||
)
|
||||
|
||||
const totalPosition =
|
||||
positionsData?.coins.reduce((acc, coin) => {
|
||||
return Number(coin.value) + acc
|
||||
}, 0) ?? 0
|
||||
const { data: tokenPrices } = useTokenPrices()
|
||||
const { data: marketsData } = useMarkets()
|
||||
const accountStats = useAccountStats()
|
||||
|
||||
const totalDebt =
|
||||
positionsData?.debt.reduce((acc, coin) => {
|
||||
return Number(coin.value) + acc
|
||||
}, 0) ?? 0
|
||||
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))
|
||||
.toNumber() * tokenPrices[denom]
|
||||
)
|
||||
}
|
||||
|
||||
if (!address) {
|
||||
return (
|
||||
@ -66,11 +74,13 @@ const CreditManager = () => {
|
||||
<CreditManagerContainer className="mb-2 text-sm">
|
||||
<div className="mb-1 flex justify-between">
|
||||
<div>Total Position:</div>
|
||||
<div className="font-semibold">{formatCurrency(totalPosition)}</div>
|
||||
<div className="font-semibold">
|
||||
{formatCurrency(accountStats?.totalPosition ?? 0)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<div>Total Liabilities:</div>
|
||||
<div className="font-semibold">{formatCurrency(totalDebt)}</div>
|
||||
<div className="font-semibold">{formatCurrency(accountStats?.totalDebt ?? 0)}</div>
|
||||
</div>
|
||||
</CreditManagerContainer>
|
||||
<CreditManagerContainer>
|
||||
@ -87,8 +97,10 @@ const CreditManager = () => {
|
||||
</div>
|
||||
{positionsData?.coins.map((coin) => (
|
||||
<div key={coin.denom} className="flex text-xs text-black/40">
|
||||
<div className="flex-1">{coin.denom}</div>
|
||||
<div className="flex-1">{formatCurrency(coin.value)}</div>
|
||||
<div className="flex-1">{getTokenSymbol(coin.denom)}</div>
|
||||
<div className="flex-1">
|
||||
{formatCurrency(getTokenTotalUSDValue(coin.amount, coin.denom))}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
{BigNumber(coin.amount)
|
||||
.div(10 ** getTokenDecimals(coin.denom))
|
||||
@ -100,11 +112,14 @@ const CreditManager = () => {
|
||||
<div className="flex-1">-</div>
|
||||
</div>
|
||||
))}
|
||||
{positionsData?.debt.map((coin) => (
|
||||
{positionsData?.debts.map((coin) => (
|
||||
<div key={coin.denom} className="flex text-xs text-red-500">
|
||||
<div className="flex-1">{coin.denom}</div>
|
||||
<div className="flex-1">{formatCurrency(coin.value)}</div>
|
||||
<div className="flex-1 text-black/40">{getTokenSymbol(coin.denom)}</div>
|
||||
<div className="flex-1">
|
||||
-{formatCurrency(getTokenTotalUSDValue(coin.amount, coin.denom))}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
-
|
||||
{BigNumber(coin.amount)
|
||||
.div(10 ** getTokenDecimals(coin.denom))
|
||||
.toNumber()
|
||||
@ -112,7 +127,9 @@ const CreditManager = () => {
|
||||
maximumFractionDigits: 6,
|
||||
})}
|
||||
</div>
|
||||
<div className="flex-1">-</div>
|
||||
<div className="flex-1">
|
||||
{(Number(marketsData?.[coin.denom].borrow_rate) * 100).toFixed(1)}%
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
|
3
components/Icons/arrow-right-line.svg
Normal file
3
components/Icons/arrow-right-line.svg
Normal 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 |
@ -13,13 +13,17 @@ import useCreditAccounts from 'hooks/useCreditAccounts'
|
||||
import useCreateCreditAccount from 'hooks/useCreateCreditAccount'
|
||||
import useDeleteCreditAccount from 'hooks/useDeleteCreditAccount'
|
||||
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
|
||||
const MAX_VISIBLE_CREDIT_ACCOUNTS = 5
|
||||
|
||||
const navItems = [
|
||||
{ href: '/trade', label: 'Trade' },
|
||||
{ href: '/yield', label: 'Yield' },
|
||||
{ href: '/earn', label: 'Earn' },
|
||||
{ href: '/borrow', label: 'Borrow' },
|
||||
{ href: '/portfolio', label: 'Portfolio' },
|
||||
{ href: '/council', label: 'Council' },
|
||||
@ -38,6 +42,7 @@ const NavLink = ({ href, children }: { href: string; children: string }) => {
|
||||
}
|
||||
|
||||
const Navigation = () => {
|
||||
const address = useWalletStore((s) => s.address)
|
||||
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
|
||||
const setSelectedAccount = useCreditManagerStore((s) => s.actions.setSelectedAccount)
|
||||
const toggleCreditManager = useCreditManagerStore((s) => s.actions.toggleCreditManager)
|
||||
@ -48,6 +53,8 @@ const Navigation = () => {
|
||||
selectedAccount || ''
|
||||
)
|
||||
|
||||
const accountStats = useAccountStats()
|
||||
|
||||
const { firstCreditAccounts, restCreditAccounts } = useMemo(() => {
|
||||
return {
|
||||
firstCreditAccounts: creditAccountsList?.slice(0, MAX_VISIBLE_CREDIT_ACCOUNTS) ?? [],
|
||||
@ -74,105 +81,115 @@ const Navigation = () => {
|
||||
<Wallet />
|
||||
</div>
|
||||
{/* Sub navigation bar */}
|
||||
<div className="flex justify-between border-b border-white/20 px-6 py-3 text-sm text-white/40">
|
||||
<div className="flex items-center">
|
||||
<SearchInput />
|
||||
{firstCreditAccounts.map((account) => (
|
||||
<div
|
||||
key={account}
|
||||
className={`cursor-pointer px-4 hover:text-white ${
|
||||
selectedAccount === account ? 'text-white' : ''
|
||||
}`}
|
||||
onClick={() => setSelectedAccount(account)}
|
||||
>
|
||||
Account {account}
|
||||
</div>
|
||||
))}
|
||||
{restCreditAccounts.length > 0 && (
|
||||
{address && (
|
||||
<div className="flex justify-between border-b border-white/20 px-6 py-3 text-sm text-white/40">
|
||||
<div className="flex items-center">
|
||||
<SearchInput />
|
||||
{firstCreditAccounts.map((account) => (
|
||||
<div
|
||||
key={account}
|
||||
className={`cursor-pointer px-4 hover:text-white ${
|
||||
selectedAccount === account ? 'text-white' : ''
|
||||
}`}
|
||||
onClick={() => setSelectedAccount(account)}
|
||||
>
|
||||
Account {account}
|
||||
</div>
|
||||
))}
|
||||
{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.Button>
|
||||
<div className="flex cursor-pointer items-center px-3 hover:text-white">
|
||||
More
|
||||
Manage
|
||||
<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) => (
|
||||
{({ close }) => (
|
||||
<div className="rounded-2xl bg-white p-4 text-gray-900">
|
||||
<div
|
||||
key={account}
|
||||
className={`cursor-pointer hover:text-orange-500 ${
|
||||
selectedAccount === account ? 'text-orange-500' : ''
|
||||
}`}
|
||||
onClick={() => setSelectedAccount(account)}
|
||||
className="mb-2 cursor-pointer hover:text-orange-500"
|
||||
onClick={() => {
|
||||
close()
|
||||
createCreditAccount()
|
||||
}}
|
||||
>
|
||||
Account {account}
|
||||
Create Account
|
||||
</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>
|
||||
)}
|
||||
<Popover className="relative">
|
||||
<Popover.Button>
|
||||
<div className="flex cursor-pointer items-center px-3 hover:text-white">
|
||||
Manage
|
||||
<ChevronDownIcon className="ml-1 h-4 w-4" />
|
||||
</div>
|
||||
</Popover.Button>
|
||||
<Popover.Panel className="absolute z-10 w-[200px] pt-2">
|
||||
{({ close }) => (
|
||||
<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 className="flex items-center gap-4">
|
||||
{accountStats && (
|
||||
<>
|
||||
<p>{formatCurrency(accountStats.netWorth)}</p>
|
||||
{/* TOOLTIP */}
|
||||
<div title={`${String(accountStats.currentLeverage.toFixed(1))}x`}>
|
||||
<SemiCircleProgress
|
||||
value={accountStats.currentLeverage / accountStats.maxLeverage}
|
||||
label="Lvg"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Popover.Panel>
|
||||
</Popover>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<p>{formatCurrency(2500)}</p>
|
||||
<div>Lvg</div>
|
||||
<div>Risk</div>
|
||||
<ProgressBar value={0.43} />
|
||||
<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>
|
||||
<SemiCircleProgress value={accountStats.risk} label="Risk" />
|
||||
<ProgressBar value={accountStats.health} />
|
||||
</>
|
||||
)}
|
||||
<div
|
||||
className="flex w-16 cursor-pointer justify-center hover:text-white"
|
||||
onClick={toggleCreditManager}
|
||||
>
|
||||
<ArrowRightLine />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{(isLoadingCreate || isLoadingDelete) && (
|
||||
<div className="fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">
|
||||
<Spinner />
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { ArrowRightIcon } from '@heroicons/react/24/solid'
|
||||
import React from 'react'
|
||||
|
||||
type Props = {
|
||||
value: number
|
||||
@ -7,16 +6,6 @@ type Props = {
|
||||
|
||||
const ProgressBar = ({ value }: Props) => {
|
||||
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 (
|
||||
<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"
|
||||
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">
|
||||
{percentageValue}
|
||||
<ArrowRightIcon className="h-3 w-3" />
|
||||
{percentageNewValue}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
104
components/SemiCircleProgress.tsx
Normal file
104
components/SemiCircleProgress.tsx
Normal 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
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { Popover } from '@headlessui/react'
|
||||
import { toast } from 'react-toastify'
|
||||
import Image from 'next/image'
|
||||
@ -34,7 +34,7 @@ const WalletPopover = ({ children }: { children: React.ReactNode }) => {
|
||||
</div>
|
||||
<Button
|
||||
className=" bg-[#524bb1] hover:bg-[#6962cc]"
|
||||
onClick={() => actions.setAddress('')}
|
||||
onClick={() => actions.disconnect()}
|
||||
>
|
||||
Disconnect
|
||||
</Button>
|
||||
@ -71,18 +71,12 @@ const WalletPopover = ({ children }: { children: React.ReactNode }) => {
|
||||
|
||||
const Wallet = () => {
|
||||
const [showConnectModal, setShowConnectModal] = useState(false)
|
||||
const [hasHydrated, setHasHydrated] = useState<boolean>(false)
|
||||
|
||||
const address = useWalletStore((s) => s.address)
|
||||
|
||||
// avoid server-client hydration mismatch
|
||||
useEffect(() => {
|
||||
setHasHydrated(true)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
{hasHydrated && address ? (
|
||||
{address ? (
|
||||
<WalletPopover>{formatWalletAddress(address)}</WalletPopover>
|
||||
) : (
|
||||
<Button className="w-[200px]" onClick={() => setShowConnectModal(true)}>
|
||||
|
@ -1,9 +1,8 @@
|
||||
// https://github.com/mars-protocol/rover/blob/master/scripts/deploy/addresses/osmo-test-4.json
|
||||
export const contractAddresses = {
|
||||
accountNft: 'osmo16v3mvsdnkh4c6ykc885n3x5ay9e36akdzxcl2g93698rqw007xxqesld8w',
|
||||
mockRedBank: 'osmo1xrnx0q3x7kwzss53fry0dwwsc7pff6aq628l6n0rmvegkalp4y7qzl7j7z',
|
||||
mockOracle: 'osmo1r9u2tfq8n5xpn2g0fq8ha0rj0cyp2fzr5w9jvcqwt3r8lxdfm6yszmtza5',
|
||||
mockVault: 'osmo1gg4rpug7vwrnq0ask0k7nmw23z6wl8c8fr7jmup9pdpaal9uc5nqq7lyrm',
|
||||
swapper: 'osmo1ak4x8k2h7s6pq5dnlncmgsmx2nqcaplpfxlmklx2ln7qn6dtny8q70apjv',
|
||||
creditManager: 'osmo1963xgmt8agyc6q4k2vhf980kffq6ukkj9mgtwdxxnpj3dak2akdq20z9dw',
|
||||
accountNft: 'osmo1dravtyd0425fkdmkysc3ns7zud05clf5uhj6qqsnkdtrpkewu73q9f3f02',
|
||||
mockVault: 'osmo1emcckulm2mkx36xeanhsn3z3zjeql6pgd8yf8a5cf03ccvy7a4dqjw9tl7',
|
||||
marsOracleAdapter: 'osmo1cw6pv97g7fmhqykrn0gc9ngrx5tnky75rmlwkzxuqhsk58u0n8asz036g0',
|
||||
swapper: 'osmo1w2552km2u9w4k2gjw4n8drmuz5yxw8x4qzy6dl3da824km5cjlys00x3qp',
|
||||
creditManager: 'osmo18dt5y0ecyd5qg8nqwzrgxuljfejglyh2fjd984s8cy7fcx8mxh9qfl3hwq',
|
||||
}
|
||||
|
86
hooks/useAccountStats.tsx
Normal file
86
hooks/useAccountStats.tsx
Normal 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
|
@ -1,9 +1,6 @@
|
||||
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import useWalletStore from 'stores/useWalletStore'
|
||||
import { chain } from 'utils/chains'
|
||||
import { contractAddresses } from 'config/contracts'
|
||||
import { queryKeys } from 'types/query-keys-factory'
|
||||
|
||||
@ -14,25 +11,14 @@ const queryMsg = {
|
||||
}
|
||||
|
||||
const useAllowedCoins = () => {
|
||||
const [signingClient, setSigningClient] = useState<SigningCosmWasmClient>()
|
||||
const address = useWalletStore((s) => s.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 client = useWalletStore((s) => s.client)
|
||||
|
||||
const result = useQuery<Result>(
|
||||
queryKeys.allowedCoins(),
|
||||
async () => signingClient?.queryContractSmart(contractAddresses.creditManager, queryMsg),
|
||||
async () => client?.queryContractSmart(contractAddresses.creditManager, queryMsg),
|
||||
{
|
||||
enabled: !!address && !!signingClient,
|
||||
enabled: !!address && !!client,
|
||||
staleTime: Infinity,
|
||||
}
|
||||
)
|
||||
|
@ -1,69 +1,43 @@
|
||||
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
||||
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 { chain } from 'utils/chains'
|
||||
import { contractAddresses } from 'config/contracts'
|
||||
import { queryKeys } from 'types/query-keys-factory'
|
||||
|
||||
interface CoinValue {
|
||||
interface DebtAmount {
|
||||
amount: string
|
||||
denom: string
|
||||
price: string
|
||||
value: string
|
||||
}
|
||||
|
||||
interface DebtSharesValue {
|
||||
amount: string
|
||||
denom: string
|
||||
price: string
|
||||
shares: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface VaultPosition {
|
||||
interface VaultPosition {
|
||||
locked: string
|
||||
unlocked: string
|
||||
}
|
||||
|
||||
interface VaultPositionWithAddr {
|
||||
addr: string
|
||||
position: VaultPosition
|
||||
}
|
||||
|
||||
interface Result {
|
||||
account_id: string
|
||||
coins: CoinValue[]
|
||||
debt: DebtSharesValue[]
|
||||
vault_positions: VaultPositionWithAddr[]
|
||||
coins: Coin[]
|
||||
debts: DebtAmount[]
|
||||
vaults: VaultPosition[]
|
||||
}
|
||||
|
||||
const useCreditAccountPositions = (accountId: string) => {
|
||||
const [signingClient, setSigningClient] = useState<SigningCosmWasmClient>()
|
||||
const address = useWalletStore((s) => s.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 client = useWalletStore((s) => s.client)
|
||||
|
||||
const result = useQuery<Result>(
|
||||
queryKeys.creditAccountsPositions(accountId),
|
||||
async () =>
|
||||
signingClient?.queryContractSmart(contractAddresses.creditManager, {
|
||||
client?.queryContractSmart(contractAddresses.creditManager, {
|
||||
positions: {
|
||||
account_id: accountId,
|
||||
},
|
||||
}),
|
||||
{
|
||||
enabled: !!address && !!signingClient,
|
||||
enabled: !!address && !!client,
|
||||
staleTime: Infinity,
|
||||
}
|
||||
)
|
||||
@ -71,7 +45,7 @@ const useCreditAccountPositions = (accountId: string) => {
|
||||
return {
|
||||
...result,
|
||||
data: useMemo(() => {
|
||||
return result?.data
|
||||
return result?.data && { ...result.data }
|
||||
}, [result.data]),
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import useWalletStore from 'stores/useWalletStore'
|
||||
import { chain } from 'utils/chains'
|
||||
import { contractAddresses } from 'config/contracts'
|
||||
import useCreditManagerStore from 'stores/useCreditManagerStore'
|
||||
import { queryKeys } from 'types/query-keys-factory'
|
||||
@ -13,8 +11,8 @@ type Result = {
|
||||
}
|
||||
|
||||
const useCreditAccounts = () => {
|
||||
const [signingClient, setSigningClient] = useState<SigningCosmWasmClient>()
|
||||
const address = useWalletStore((s) => s.address)
|
||||
const client = useWalletStore((s) => s.client)
|
||||
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
|
||||
const creditManagerActions = useCreditManagerStore((s) => s.actions)
|
||||
|
||||
@ -26,22 +24,12 @@ const useCreditAccounts = () => {
|
||||
}
|
||||
}, [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>(
|
||||
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) => {
|
||||
if (!data.tokens.includes(selectedAccount || '') && data.tokens.length > 0) {
|
||||
creditManagerActions.setSelectedAccount(data.tokens[0])
|
||||
|
100
hooks/useMarkets.tsx
Normal file
100
hooks/useMarkets.tsx
Normal 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
16
hooks/useTokenPrices.tsx
Normal 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
|
@ -13,6 +13,15 @@ const nextConfig = {
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#use-hidden-source-map
|
||||
hideSourceMaps: true,
|
||||
},
|
||||
webpack(config) {
|
||||
config.module.rules.push({
|
||||
test: /\.svg$/i,
|
||||
issuer: /\.[jt]sx?$/,
|
||||
use: ['@svgr/webpack'],
|
||||
})
|
||||
|
||||
return config
|
||||
},
|
||||
}
|
||||
|
||||
const sentryWebpackPluginOptions = {
|
||||
|
@ -25,10 +25,12 @@
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-toastify": "^9.0.8",
|
||||
"use-local-storage-state": "^18.1.1",
|
||||
"zustand": "^4.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@keplr-wallet/types": "^0.10.24",
|
||||
"@svgr/webpack": "^6.4.0",
|
||||
"@types/node": "18.7.14",
|
||||
"@types/react": "18.0.18",
|
||||
"@types/react-dom": "18.0.6",
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { useEffect } from 'react'
|
||||
import type { AppProps } from 'next/app'
|
||||
import Head from 'next/head'
|
||||
import { ToastContainer, Zoom } from 'react-toastify'
|
||||
@ -7,7 +8,6 @@ import detectEthereumProvider from '@metamask/detect-provider'
|
||||
|
||||
import '../styles/globals.css'
|
||||
import Layout from 'components/Layout'
|
||||
import { useEffect } from 'react'
|
||||
import useWalletStore from 'stores/useWalletStore'
|
||||
|
||||
async function isMetamaskInstalled(): Promise<boolean> {
|
||||
@ -19,6 +19,7 @@ async function isMetamaskInstalled(): Promise<boolean> {
|
||||
const queryClient = new QueryClient()
|
||||
|
||||
function MyApp({ Component, pageProps }: AppProps) {
|
||||
const address = useWalletStore((s) => s.address)
|
||||
const actions = useWalletStore((s) => s.actions)
|
||||
|
||||
// init store
|
||||
@ -27,6 +28,8 @@ function MyApp({ Component, pageProps }: AppProps) {
|
||||
actions.setMetamaskInstalledStatus(await isMetamaskInstalled())
|
||||
}
|
||||
|
||||
actions.initialize()
|
||||
|
||||
verifyMetamask()
|
||||
}, [actions])
|
||||
|
||||
@ -38,9 +41,7 @@ function MyApp({ Component, pageProps }: AppProps) {
|
||||
<link rel="icon" href="/favicon.svg" />
|
||||
</Head>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Layout>
|
||||
<Component {...pageProps} />
|
||||
</Layout>
|
||||
<Layout>{address ? <Component {...pageProps} /> : <div>No wallet connected</div>}</Layout>
|
||||
<ToastContainer
|
||||
autoClose={1500}
|
||||
closeButton={false}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
import Container from 'components/Container'
|
||||
|
||||
const Yield = () => {
|
||||
const Earn = () => {
|
||||
return (
|
||||
<div className="flex gap-4">
|
||||
<Container className="flex-1">Yield Module</Container>
|
||||
@ -10,4 +10,4 @@ const Yield = () => {
|
||||
)
|
||||
}
|
||||
|
||||
export default Yield
|
||||
export default Earn
|
@ -2,15 +2,18 @@ import create from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
|
||||
import { Wallet } from 'types'
|
||||
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
||||
import { chain } from 'utils/chains'
|
||||
|
||||
interface WalletStore {
|
||||
address: string
|
||||
injectiveAddress: string
|
||||
addresses: string[]
|
||||
metamaskInstalled: boolean
|
||||
wallet: Wallet
|
||||
wallet: Wallet | null
|
||||
client?: CosmWasmClient
|
||||
actions: {
|
||||
setAddress: (address: string) => void
|
||||
disconnect: () => void
|
||||
initialize: () => void
|
||||
connect: (address: string, wallet: Wallet) => void
|
||||
setMetamaskInstalledStatus: (value: boolean) => void
|
||||
}
|
||||
}
|
||||
@ -19,12 +22,24 @@ const useWalletStore = create<WalletStore>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
address: '',
|
||||
injectiveAddress: '',
|
||||
addresses: [],
|
||||
metamaskInstalled: false,
|
||||
wallet: Wallet.Metamask,
|
||||
wallet: null,
|
||||
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 })),
|
||||
},
|
||||
}),
|
||||
@ -32,7 +47,9 @@ const useWalletStore = create<WalletStore>()(
|
||||
name: 'wallet',
|
||||
partialize: (state) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(state).filter(([key]) => !['metamaskInstalled', 'actions'].includes(key))
|
||||
Object.entries(state).filter(
|
||||
([key]) => !['client', 'metamaskInstalled', 'actions', 'address'].includes(key)
|
||||
)
|
||||
),
|
||||
}
|
||||
)
|
||||
|
@ -6,8 +6,9 @@ html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell,
|
||||
Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
color: white;
|
||||
}
|
||||
|
||||
a {
|
||||
@ -19,16 +20,6 @@ a {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
color-scheme: dark;
|
||||
}
|
||||
body {
|
||||
color: white;
|
||||
background: black;
|
||||
}
|
||||
}
|
||||
|
||||
/* react-toastify */
|
||||
/* https://fkhadra.github.io/react-toastify/how-to-style#override-css-variables */
|
||||
.Toastify__toast {
|
||||
|
@ -40,8 +40,8 @@ export const chainsInfo = {
|
||||
},
|
||||
OsmosisTestnet: {
|
||||
chainId: 'osmo-test-4',
|
||||
rpc: 'https://rpc-test.osmosis.zone',
|
||||
rest: 'https://lcd-test.osmosis.zone',
|
||||
rpc: 'https://osmosis-delphi-testnet-1.simply-vc.com.mt/XF32UOOU55CX/osmosis-rpc',
|
||||
rest: 'https://osmosis-delphi-testnet-1.simply-vc.com.mt/XF32UOOU55CX/osmosis-lcd',
|
||||
stakeCurrency: {
|
||||
coinDenom: 'OSMO',
|
||||
coinMinimalDenom: 'uosmo',
|
||||
|
@ -9,5 +9,5 @@ export const hardcodedFee = {
|
||||
amount: '100000',
|
||||
},
|
||||
],
|
||||
gas: '750000',
|
||||
gas: '1500000',
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user