Borrow improvements (#29)

* update token prices and market data to match smart contract

* feat: redbank balances query and respective rendering on ui

* query key for rb balances and respective invalidations

* update contracts config

* fix: avoid returning negative max borrow amounts

* fix: added deposit action to repay execute message

* add minus sign before apy on debt positions

* consider market liquidity on max borrow calculation

* hive url added to chain config

* update hardcoded token decimals
This commit is contained in:
Gustavo Mauricio 2022-10-24 16:15:26 +01:00 committed by GitHub
parent 5cb1da132b
commit d22de166da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 130 additions and 22 deletions

View File

@ -128,7 +128,7 @@ const CreditManager = () => {
})}
</div>
<div className="flex-1">
{(Number(marketsData?.[coin.denom].borrow_rate) * 100).toFixed(1)}%
-{(Number(marketsData?.[coin.denom].borrow_rate) * 100).toFixed(1)}%
</div>
</div>
))}

View File

@ -1,8 +1,13 @@
// https://github.com/mars-protocol/rover/blob/master/scripts/deploy/addresses/osmo-test-4.json
export const contractAddresses = {
export const roverContracts = {
accountNft: 'osmo1dravtyd0425fkdmkysc3ns7zud05clf5uhj6qqsnkdtrpkewu73q9f3f02',
mockVault: 'osmo1emcckulm2mkx36xeanhsn3z3zjeql6pgd8yf8a5cf03ccvy7a4dqjw9tl7',
marsOracleAdapter: 'osmo1cw6pv97g7fmhqykrn0gc9ngrx5tnky75rmlwkzxuqhsk58u0n8asz036g0',
swapper: 'osmo1w2552km2u9w4k2gjw4n8drmuz5yxw8x4qzy6dl3da824km5cjlys00x3qp',
creditManager: 'osmo18dt5y0ecyd5qg8nqwzrgxuljfejglyh2fjd984s8cy7fcx8mxh9qfl3hwq',
}
export const contractAddresses = {
...roverContracts,
redBank: 'osmo1w5rqrdhut890jplmsqnr8gj3uf0wq6lj5rfdnhrtl63lpf6e7v6qalrhhn',
}

View File

@ -10,6 +10,7 @@ import { contractAddresses } from 'config/contracts'
import { hardcodedFee } from 'utils/contants'
import useCreditManagerStore from 'stores/useCreditManagerStore'
import { queryKeys } from 'types/query-keys-factory'
import { getTokenDecimals } from 'utils/tokens'
const useBorrowFunds = (
amount: string | number,
@ -36,6 +37,8 @@ const useBorrowFunds = (
}, [address])
const executeMsg = useMemo(() => {
const tokenDecimals = getTokenDecimals(denom)
if (!withdraw) {
return {
update_credit_account: {
@ -45,7 +48,7 @@ const useBorrowFunds = (
borrow: {
denom: denom,
amount: BigNumber(amount)
.times(10 ** 6)
.times(10 ** tokenDecimals)
.toString(),
},
},
@ -62,7 +65,7 @@ const useBorrowFunds = (
borrow: {
denom: denom,
amount: BigNumber(amount)
.times(10 ** 6)
.times(10 ** tokenDecimals)
.toString(),
},
},
@ -70,14 +73,14 @@ const useBorrowFunds = (
withdraw: {
denom: denom,
amount: BigNumber(amount)
.times(10 ** 6)
.times(10 ** tokenDecimals)
.toString(),
},
},
],
},
}
}, [amount, denom, withdraw, selectedAccount])
}, [withdraw, selectedAccount, denom, amount])
return useMutation(
async () =>
@ -90,6 +93,7 @@ const useBorrowFunds = (
{
onSettled: () => {
queryClient.invalidateQueries(queryKeys.creditAccountsPositions(selectedAccount ?? ''))
queryClient.invalidateQueries(queryKeys.redbankBalances())
// if withdrawing to wallet, need to explicility invalidate balances queries
if (withdraw) {

View File

@ -6,6 +6,7 @@ import { getTokenDecimals } from 'utils/tokens'
import useCreditAccountPositions from './useCreditAccountPositions'
import useMarkets from './useMarkets'
import useTokenPrices from './useTokenPrices'
import useRedbankBalances from './useRedbankBalances'
const useCalculateMaxBorrowAmount = (denom: string, isUnderCollateralized: boolean) => {
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
@ -13,9 +14,10 @@ const useCalculateMaxBorrowAmount = (denom: string, isUnderCollateralized: boole
const { data: positionsData } = useCreditAccountPositions(selectedAccount ?? '')
const { data: marketsData } = useMarkets()
const { data: tokenPrices } = useTokenPrices()
const { data: redbankBalances } = useRedbankBalances()
return useMemo(() => {
if (!marketsData || !tokenPrices || !positionsData) return 0
if (!marketsData || !tokenPrices || !positionsData || !redbankBalances) return 0
const getTokenTotalUSDValue = (amount: string, denom: string) => {
// early return if prices are not fetched yet
@ -27,6 +29,7 @@ const useCalculateMaxBorrowAmount = (denom: string, isUnderCollateralized: boole
.toNumber()
}
// max ltv adjusted collateral
const totalWeightedPositions = positionsData?.coins.reduce((acc, coin) => {
const tokenWeightedValue = BigNumber(getTokenTotalUSDValue(coin.amount, coin.denom)).times(
Number(marketsData[coin.denom].max_loan_to_value)
@ -35,6 +38,7 @@ const useCalculateMaxBorrowAmount = (denom: string, isUnderCollateralized: boole
return tokenWeightedValue.plus(acc).toNumber()
}, 0)
// total debt value
const totalLiabilitiesValue = positionsData?.debts.reduce((acc, coin) => {
const tokenUSDValue = BigNumber(getTokenTotalUSDValue(coin.amount, coin.denom))
@ -44,20 +48,31 @@ const useCalculateMaxBorrowAmount = (denom: string, isUnderCollateralized: boole
const borrowTokenPrice = tokenPrices[denom]
const tokenDecimals = getTokenDecimals(denom)
let maxValue
if (isUnderCollateralized) {
return BigNumber(totalLiabilitiesValue)
// MAX TO CREDIT ACCOUNT
maxValue = BigNumber(totalLiabilitiesValue)
.minus(totalWeightedPositions)
.div(borrowTokenPrice * Number(marketsData[denom].max_loan_to_value) - borrowTokenPrice)
.decimalPlaces(tokenDecimals)
.toNumber()
} else {
return BigNumber(totalWeightedPositions)
// MAX TO WALLET
maxValue = BigNumber(totalWeightedPositions)
.minus(totalLiabilitiesValue)
.div(borrowTokenPrice)
.decimalPlaces(tokenDecimals)
.toNumber()
}
}, [denom, isUnderCollateralized, marketsData, positionsData, tokenPrices])
const marketLiquidity = BigNumber(redbankBalances?.[denom] ?? '')
.div(10 ** getTokenDecimals(denom))
.toNumber()
if (marketLiquidity < maxValue) return marketLiquidity
return maxValue > 0 ? maxValue : 0
}, [denom, isUnderCollateralized, marketsData, positionsData, redbankBalances, tokenPrices])
}
export default useCalculateMaxBorrowAmount

View File

@ -38,8 +38,8 @@ const useMarkets = () => {
wasm: {
uosmo: {
denom: 'uosmo',
max_loan_to_value: '0.65',
liquidation_threshold: '0.7',
max_loan_to_value: '0.55',
liquidation_threshold: '0.65',
liquidation_bonus: '0.1',
reserve_factor: '0.2',
interest_rate_model: {
@ -61,8 +61,8 @@ const useMarkets = () => {
},
'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2': {
denom: 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2',
max_loan_to_value: '0.77',
liquidation_threshold: '0.8',
max_loan_to_value: '0.65',
liquidation_threshold: '0.7',
liquidation_bonus: '0.1',
reserve_factor: '0.2',
interest_rate_model: {

View File

@ -0,0 +1,59 @@
import { useMemo } from 'react'
import { useQuery } from '@tanstack/react-query'
import { Coin } from '@cosmjs/stargate'
import { contractAddresses } from 'config/contracts'
import { queryKeys } from 'types/query-keys-factory'
import { chain } from 'utils/chains'
interface Result {
data: {
bank: {
balance: Coin[]
}
}
}
const fetchBalances = () => {
return fetch(chain.hive, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: `
query RedbankBalances {
bank {
balance(
address: "${contractAddresses.redBank}"
) {
amount
denom
}
}
}
`,
}),
}).then((res) => res.json())
}
const useRedbankBalances = () => {
const result = useQuery<Result>(queryKeys.redbankBalances(), fetchBalances)
return {
...result,
data: useMemo(() => {
if (!result.data) return
return result.data?.data.bank.balance.reduce(
(acc, coin) => ({
...acc,
[coin.denom]: coin.amount,
}),
{}
) as { [key in string]: string }
}, [result.data]),
}
}
export default useRedbankBalances

View File

@ -10,6 +10,7 @@ import { contractAddresses } from 'config/contracts'
import { hardcodedFee } from 'utils/contants'
import useCreditManagerStore from 'stores/useCreditManagerStore'
import { queryKeys } from 'types/query-keys-factory'
import { getTokenDecimals } from 'utils/tokens'
const useRepayFunds = (
amount: string | number,
@ -34,23 +35,33 @@ const useRepayFunds = (
})()
}, [address])
const tokenDecimals = getTokenDecimals(denom)
const executeMsg = useMemo(() => {
return {
update_credit_account: {
account_id: selectedAccount,
actions: [
{
deposit: {
denom: denom,
amount: BigNumber(amount)
.times(10 ** tokenDecimals)
.toString(),
},
},
{
repay: {
denom: denom,
amount: BigNumber(amount)
.times(10 ** 6)
.times(10 ** tokenDecimals)
.toString(),
},
},
],
},
}
}, [amount, denom, selectedAccount])
}, [amount, denom, selectedAccount, tokenDecimals])
return useMutation(
async () =>
@ -58,13 +69,23 @@ const useRepayFunds = (
address,
contractAddresses.creditManager,
executeMsg,
hardcodedFee
hardcodedFee,
undefined,
[
{
denom,
amount: BigNumber(amount)
.times(10 ** tokenDecimals)
.toString(),
},
]
),
{
onSettled: () => {
queryClient.invalidateQueries(queryKeys.creditAccountsPositions(selectedAccount ?? ''))
queryClient.invalidateQueries(queryKeys.tokenBalance(address, denom))
queryClient.invalidateQueries(queryKeys.allBalances(address))
queryClient.invalidateQueries(queryKeys.redbankBalances())
},
onError: (err: Error) => {
toast.error(err.message)

View File

@ -4,8 +4,8 @@ const useTokenPrices = () => {
return useQuery<{ [key in string]: number }>(
['tokenPrices'],
() => ({
uosmo: 1.1,
'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2': 11,
uosmo: 1,
'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2': 1.5,
}),
{
staleTime: Infinity,

View File

@ -10,6 +10,7 @@ import useMarkets from 'hooks/useMarkets'
import useTokenPrices from 'hooks/useTokenPrices'
import { BorrowFunds, RepayFunds } from 'components/Borrow'
import BorrowTable from 'components/Borrow/BorrowTable'
import useRedbankBalances from 'hooks/useRedbankBalances'
type ModuleState =
| {
@ -35,6 +36,7 @@ const Borrow = () => {
const { data: positionsData } = useCreditAccountPositions(selectedAccount ?? '')
const { data: marketsData } = useMarkets()
const { data: tokenPrices } = useTokenPrices()
const { data: redbankBalances } = useRedbankBalances()
const borrowedAssetsMap = useMemo(() => {
let borrowedAssetsMap: Map<string, string> = new Map()
@ -54,7 +56,7 @@ const Borrow = () => {
.map((denom) => {
const { symbol, chain, icon } = getTokenInfo(denom)
const borrowRate = Number(marketsData?.[denom].borrow_rate) || 0
const marketLiquidity = BigNumber(marketsData?.[denom].deposit_cap ?? '')
const marketLiquidity = BigNumber(redbankBalances?.[denom] ?? '')
.div(10 ** getTokenDecimals(denom))
.toNumber()
@ -84,7 +86,7 @@ const Borrow = () => {
.map((denom) => {
const { symbol, chain, icon } = getTokenInfo(denom)
const borrowRate = Number(marketsData?.[denom].borrow_rate) || 0
const marketLiquidity = BigNumber(marketsData?.[denom].deposit_cap ?? '')
const marketLiquidity = BigNumber(redbankBalances?.[denom] ?? '')
.div(10 ** getTokenDecimals(denom))
.toNumber()
@ -101,7 +103,7 @@ const Borrow = () => {
return rowData
}) ?? [],
}
}, [allowedCoinsData, borrowedAssetsMap, marketsData, tokenPrices])
}, [allowedCoinsData, borrowedAssetsMap, marketsData, redbankBalances, tokenPrices])
const handleBorrowClick = (denom: string) => {
setModuleState({ show: 'borrow', data: { tokenDenom: denom } })

View File

@ -1,6 +1,7 @@
export const queryKeys = {
allBalances: (address: string) => ['allBalances', address],
allowedCoins: () => ['allowedCoins'],
redbankBalances: () => ['redbankBalances'],
creditAccounts: (address: string) => ['creditAccounts', address],
creditAccountsPositions: (accountId: string) => ['creditAccountPositions', accountId],
tokenBalance: (address: string, denom: string) => ['tokenBalance', address, denom],

View File

@ -42,6 +42,7 @@ export const chainsInfo = {
chainId: 'osmo-test-4',
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',
hive: 'https://osmosis-delphi-testnet-1.simply-vc.com.mt/XF32UOOU55CX/osmosis-hive/graphql',
stakeCurrency: {
coinDenom: 'OSMO',
coinMinimalDenom: 'uosmo',