Scope Funding to actions, enable fund for USDC (and others) (#426)

* fix vault test and vaulttable pos value

* fix vault test and vaulttable pos value updated

* Scope lend for deposit to action only

* fix build error

* fix build error
This commit is contained in:
Bob van der Helm 2023-09-05 12:45:55 +02:00 committed by GitHub
parent 19e06aa7d4
commit 46d4113d98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 96 additions and 51 deletions

View File

@ -22,13 +22,12 @@ describe('getVaultMetaData()', () => {
expect(getVaultMetaData(testAddress)?.name).toBe(testVaultName) expect(getVaultMetaData(testAddress)?.name).toBe(testVaultName)
}) })
// TODO: Update the following test suite accordingly after new testnet vaults placed in constants it('returns the TESTNET vault of given address WHEN environment configured to testnet', () => {
// it('returns the TESTNET vault of given address WHEN environment configured to testnet', () => { jest.spyOn(constants, 'IS_TESTNET', 'get').mockReturnValue(true)
// jest.spyOn(constants, 'IS_TESTNET', 'get').mockReturnValue(true)
// const testAddress = 'osmo1q40xvrzpldwq5he4ftsf7zm2jf80tj373qaven38yqrvhex8r9rs8n94kv' const testAddress = 'osmo1m45ap4rq4m2mfjkcqu9ks9mxmyx2hvx0cdca9sjmrg46q7lghzqqhxxup5'
// const testVaultName = 'OSMO-USDC.n' const testVaultName = 'OSMO-ATOM'
// expect(getVaultMetaData(testAddress)?.name).toBe(testVaultName) expect(getVaultMetaData(testAddress)?.name).toBe(testVaultName)
// }) })
}) })

View File

@ -3,13 +3,15 @@ import moment from 'moment'
import { getClient, getCreditManagerQueryClient, getVaultQueryClient } from 'api/cosmwasm-client' import { getClient, getCreditManagerQueryClient, getVaultQueryClient } from 'api/cosmwasm-client'
import getPrice from 'api/prices/getPrice' import getPrice from 'api/prices/getPrice'
import getVaults from 'api/vaults/getVaults' import getVaults from 'api/vaults/getVaults'
import { BN_ZERO } from 'constants/math'
import { BNCoin } from 'types/classes/BNCoin'
import { VaultStatus } from 'types/enums/vault' import { VaultStatus } from 'types/enums/vault'
import { import {
VaultPosition, VaultPosition,
VaultPositionAmount, VaultPositionAmount,
} from 'types/generated/mars-credit-manager/MarsCreditManager.types' } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
import { getCoinValue } from 'utils/formatters'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
import { BN_ZERO } from 'constants/math'
async function getUnlocksAtTimestamp(unlockingId: number, vaultAddress: string) { async function getUnlocksAtTimestamp(unlockingId: number, vaultAddress: string) {
try { try {
@ -130,8 +132,12 @@ async function getVaultValuesAndAmounts(
secondary: BN(secondaryLpToken.amount), secondary: BN(secondaryLpToken.amount),
}, },
values: { values: {
primary: BN(primaryLpToken.amount).multipliedBy(primaryPrice), primary: getCoinValue(new BNCoin(primaryLpToken), [
secondary: BN(secondaryLpToken.amount).multipliedBy(secondaryPrice), BNCoin.fromDenomAndBigNumber(primaryLpToken.denom, primaryPrice),
]),
secondary: getCoinValue(new BNCoin(secondaryLpToken), [
BNCoin.fromDenomAndBigNumber(secondaryLpToken.denom, secondaryPrice),
]),
}, },
} }
} catch (ex) { } catch (ex) {
@ -173,4 +179,4 @@ async function getDepositedVaults(accountId: string): Promise<DepositedVault[]>
} }
} }
export default getDepositedVaults export default getDepositedVaults

View File

@ -40,6 +40,7 @@ export default function AccountFund() {
const hasFundingAssets = const hasFundingAssets =
fundingAssets.length > 0 && fundingAssets.every((a) => a.toCoin().amount !== '0') fundingAssets.length > 0 && fundingAssets.every((a) => a.toCoin().amount !== '0')
const { data: marketAssets } = useMarketAssets() const { data: marketAssets } = useMarketAssets()
const [isLending, toggleIsLending] = useToggle(false)
const baseBalance = useMemo( const baseBalance = useMemo(
() => walletBalances.find(byDenom(baseAsset.denom))?.amount ?? '0', () => walletBalances.find(byDenom(baseAsset.denom))?.amount ?? '0',
@ -58,10 +59,11 @@ export default function AccountFund() {
const result = await deposit({ const result = await deposit({
accountId, accountId,
coins: fundingAssets, coins: fundingAssets,
lend: isLending,
}) })
setIsFunding(false) setIsFunding(false)
if (result) useStore.setState({ focusComponent: null, walletAssetsModal: null }) if (result) useStore.setState({ focusComponent: null, walletAssetsModal: null })
}, [fundingAssets, accountId, setIsFunding, deposit]) }, [setIsFunding, accountId, deposit, fundingAssets, isLending])
const handleSelectAssetsClick = useCallback(() => { const handleSelectAssetsClick = useCallback(() => {
useStore.setState({ useStore.setState({
@ -174,6 +176,8 @@ export default function AccountFund() {
<SwitchAutoLend <SwitchAutoLend
className='pt-4 mt-4 border border-transparent border-t-white/10' className='pt-4 mt-4 border border-transparent border-t-white/10'
accountId={selectedAccountId} accountId={selectedAccountId}
value={isLending}
onChange={toggleIsLending}
/> />
<Button <Button
className='w-full mt-4' className='w-full mt-4'
@ -187,4 +191,4 @@ export default function AccountFund() {
</Card> </Card>
</FullOverlayContent> </FullOverlayContent>
) )
} }

View File

@ -22,6 +22,7 @@ import { defaultFee } from 'utils/constants'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
import { isNumber } from 'utils/parsers' import { isNumber } from 'utils/parsers'
import { getPage, getRoute } from 'utils/route' import { getPage, getRoute } from 'utils/route'
const menuClasses = 'absolute isolate flex w-full flex-wrap scrollbar-hide' const menuClasses = 'absolute isolate flex w-full flex-wrap scrollbar-hide'
const ACCOUNT_MENU_BUTTON_ID = 'account-menu-button' const ACCOUNT_MENU_BUTTON_ID = 'account-menu-button'
@ -73,7 +74,16 @@ export default function AccountMenuContent(props: Props) {
}, },
}) })
} }
}, [createAccount, navigate, pathname, address, setShowMenu, setIsCreating]) }, [
setShowMenu,
setIsCreating,
createAccount,
navigate,
pathname,
address,
lendAssets,
enableAutoLendAccountId,
])
const handleCreateAccountClick = useCallback(() => { const handleCreateAccountClick = useCallback(() => {
setShowMenu(!showMenu) setShowMenu(!showMenu)
@ -154,4 +164,4 @@ export default function AccountMenuContent(props: Props) {
) )
} }
export { ACCOUNT_MENU_BUTTON_ID } export { ACCOUNT_MENU_BUTTON_ID }

View File

@ -20,6 +20,7 @@ import { ChevronDown, SortAsc, SortDesc, SortNone } from 'components/Icons'
import Loading from 'components/Loading' import Loading from 'components/Loading'
import Text from 'components/Text' import Text from 'components/Text'
import TitleAndSubCell from 'components/TitleAndSubCell' import TitleAndSubCell from 'components/TitleAndSubCell'
import { ORACLE_DENOM } from 'constants/oracle'
import { VAULT_DEPOSIT_BUFFER } from 'constants/vaults' import { VAULT_DEPOSIT_BUFFER } from 'constants/vaults'
import useStore from 'store' import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
@ -107,7 +108,7 @@ export const VaultTable = (props: Props) => {
cell: ({ row }: { row: Row<DepositedVault | Vault> }) => { cell: ({ row }: { row: Row<DepositedVault | Vault> }) => {
const vault = row.original as DepositedVault const vault = row.original as DepositedVault
const positionValue = vault.values.primary.plus(vault.values.secondary) const positionValue = vault.values.primary.plus(vault.values.secondary)
const coin = BNCoin.fromDenomAndBigNumber(baseCurrency.denom, positionValue) const coin = BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, positionValue)
return <DisplayCurrency coin={coin} className='text-xs' /> return <DisplayCurrency coin={coin} className='text-xs' />
}, },
}, },
@ -229,7 +230,7 @@ export const VaultTable = (props: Props) => {
}, },
}, },
] ]
}, [baseCurrency.denom, props.data, props.isLoading]) }, [props.data, props.isLoading])
const table = useReactTable({ const table = useReactTable({
data: props.data, data: props.data,
@ -305,4 +306,4 @@ export const VaultTable = (props: Props) => {
</tbody> </tbody>
</table> </table>
) )
} }

View File

@ -251,4 +251,4 @@ function BorrowModal(props: Props) {
</div> </div>
</Modal> </Modal>
) )
} }

View File

@ -39,7 +39,7 @@ export default function FundAccount(props: Props) {
const hasFundingAssets = const hasFundingAssets =
fundingAssets.length > 0 && fundingAssets.every((a) => a.toCoin().amount !== '0') fundingAssets.length > 0 && fundingAssets.every((a) => a.toCoin().amount !== '0')
const { autoLendEnabledAccountIds } = useAutoLend() const { autoLendEnabledAccountIds } = useAutoLend()
const isAutoLendEnabled = autoLendEnabledAccountIds.includes(accountId) const [isLending, toggleIsLending] = useToggle(false)
const { simulateDeposits } = useUpdatedAccount(account) const { simulateDeposits } = useUpdatedAccount(account)
const { data: marketAssets } = useMarketAssets() const { data: marketAssets } = useMarketAssets()
const baseBalance = useMemo( const baseBalance = useMemo(
@ -59,10 +59,11 @@ export default function FundAccount(props: Props) {
const result = await deposit({ const result = await deposit({
accountId, accountId,
coins: fundingAssets, coins: fundingAssets,
lend: isLending,
}) })
setIsFunding(false) setIsFunding(false)
if (result) useStore.setState({ fundAndWithdrawModal: null, walletAssetsModal: null }) if (result) useStore.setState({ fundAndWithdrawModal: null, walletAssetsModal: null })
}, [fundingAssets, accountId, setIsFunding, deposit]) }, [setIsFunding, accountId, deposit, fundingAssets, isLending])
const handleSelectAssetsClick = useCallback(() => { const handleSelectAssetsClick = useCallback(() => {
useStore.setState({ useStore.setState({
@ -74,6 +75,10 @@ export default function FundAccount(props: Props) {
}) })
}, [selectedDenoms]) }, [selectedDenoms])
useEffect(() => {
toggleIsLending(autoLendEnabledAccountIds.includes(accountId))
}, [accountId, autoLendEnabledAccountIds, toggleIsLending])
useEffect(() => { useEffect(() => {
const currentSelectedDenom = fundingAssets.map((asset) => asset.denom) const currentSelectedDenom = fundingAssets.map((asset) => asset.denom)
@ -101,8 +106,8 @@ export default function FundAccount(props: Props) {
}, []) }, [])
useEffect(() => { useEffect(() => {
simulateDeposits(isAutoLendEnabled ? 'lend' : 'deposit', fundingAssets) simulateDeposits(isLending ? 'lend' : 'deposit', fundingAssets)
}, [isAutoLendEnabled, fundingAssets, simulateDeposits]) }, [isLending, fundingAssets, simulateDeposits])
useEffect(() => { useEffect(() => {
if (BN(baseBalance).isLessThan(defaultFee.amount[0].amount)) { if (BN(baseBalance).isLessThan(defaultFee.amount[0].amount)) {
@ -165,6 +170,8 @@ export default function FundAccount(props: Props) {
<SwitchAutoLend <SwitchAutoLend
className='pt-4 mt-4 border border-transparent border-t-white/10' className='pt-4 mt-4 border border-transparent border-t-white/10'
accountId={accountId} accountId={accountId}
onChange={toggleIsLending}
value={isLending}
/> />
</div> </div>
<Button <Button
@ -177,4 +184,4 @@ export default function FundAccount(props: Props) {
/> />
</> </>
) )
} }

View File

@ -6,6 +6,8 @@ import useAutoLend from 'hooks/useAutoLend'
interface Props { interface Props {
accountId: string accountId: string
className?: string className?: string
onChange?: () => void
value?: boolean
} }
export default function SwitchAutoLend(props: Props) { export default function SwitchAutoLend(props: Props) {
@ -13,15 +15,21 @@ export default function SwitchAutoLend(props: Props) {
const { autoLendEnabledAccountIds, toggleAutoLend } = useAutoLend() const { autoLendEnabledAccountIds, toggleAutoLend } = useAutoLend()
const isAutoLendEnabled = autoLendEnabledAccountIds.includes(accountId) const isAutoLendEnabled = autoLendEnabledAccountIds.includes(accountId)
function handleToggle() {
if (props.onChange) return props.onChange()
toggleAutoLend(accountId)
}
return ( return (
<div className={classNames('w-full', className)}> <div className={classNames('w-full', className)}>
<SwitchWithLabel <SwitchWithLabel
name='isLending' name='isLending'
label='Lend assets to earn yield' label='Lend assets to earn yield'
value={isAutoLendEnabled} value={props.value !== undefined ? props.value : isAutoLendEnabled}
onChange={() => toggleAutoLend(accountId)} onChange={handleToggle}
tooltip={`Fund your account and lend assets effortlessly! By lending, you'll earn attractive interest (APY) without impacting your LTV. It's a win-win situation - don't miss out on this easy opportunity to grow your holdings!`} tooltip={`Fund your account and lend assets effortlessly! By lending, you'll earn attractive interest (APY) without impacting your LTV. It's a win-win situation - don't miss out on this easy opportunity to grow your holdings!`}
/> />
</div> </div>
) )
} }

View File

@ -21,7 +21,16 @@ export default function SwitchWithLabel(props: Props) {
<Text className='mr-2 text-white/70' size='sm'> <Text className='mr-2 text-white/70' size='sm'>
{props.label} {props.label}
</Text> </Text>
{props.tooltip && <Tooltip type='info' content={<Text size='sm'>{props.tooltip}</Text>} />} {props.tooltip && (
<Tooltip
type='info'
content={
<Text size='sm' className='px-2 py-3'>
{props.tooltip}
</Text>
}
/>
)}
</div> </div>
<Switch <Switch
name={props.name} name={props.name}
@ -31,4 +40,4 @@ export default function SwitchWithLabel(props: Props) {
/> />
</div> </div>
) )
} }

View File

@ -67,6 +67,7 @@ export const ASSETS: Asset[] = [
isEnabled: !IS_TESTNET, isEnabled: !IS_TESTNET,
isMarket: !IS_TESTNET, isMarket: !IS_TESTNET,
isDisplayCurrency: !IS_TESTNET, isDisplayCurrency: !IS_TESTNET,
isAutoLendEnabled: true,
poolId: 712, poolId: 712,
pythPriceFeedId: 'e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43', pythPriceFeedId: 'e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43',
}, },
@ -83,6 +84,7 @@ export const ASSETS: Asset[] = [
isEnabled: !IS_TESTNET, isEnabled: !IS_TESTNET,
isMarket: !IS_TESTNET, isMarket: !IS_TESTNET,
isDisplayCurrency: !IS_TESTNET, isDisplayCurrency: !IS_TESTNET,
isAutoLendEnabled: true,
poolId: 704, poolId: 704,
pythPriceFeedId: 'ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace', pythPriceFeedId: 'ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace',
}, },
@ -119,6 +121,7 @@ export const ASSETS: Asset[] = [
isMarket: true, isMarket: true,
isDisplayCurrency: true, isDisplayCurrency: true,
isStable: true, isStable: true,
isAutoLendEnabled: true,
poolId: 678, poolId: 678,
pythPriceFeedId: 'eaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a', pythPriceFeedId: 'eaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a',
}, },
@ -152,4 +155,4 @@ export const ASSETS: Asset[] = [
hasOraclePrice: true, hasOraclePrice: true,
forceFetchPrice: true, forceFetchPrice: true,
}, },
] ]

View File

@ -20,6 +20,7 @@ import { formatAmountWithSymbol } from 'utils/formatters'
import getTokenOutFromSwapResponse from 'utils/getTokenOutFromSwapResponse' import getTokenOutFromSwapResponse from 'utils/getTokenOutFromSwapResponse'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
function generateExecutionMessage( function generateExecutionMessage(
sender: string | undefined = '', sender: string | undefined = '',
contract: string, contract: string,
@ -209,7 +210,7 @@ export default function createBroadcastSlice(
return { estimateFee, execute } return { estimateFee, execute }
}, },
deposit: async (options: { accountId: string; coins: BNCoin[] }) => { deposit: async (options: { accountId: string; coins: BNCoin[]; lend: boolean }) => {
const msg: CreditManagerExecuteMsg = { const msg: CreditManagerExecuteMsg = {
update_credit_account: { update_credit_account: {
account_id: options.accountId, account_id: options.accountId,
@ -219,11 +220,11 @@ export default function createBroadcastSlice(
}, },
} }
if (checkAutoLendEnabled(options.accountId)) { if (options.lend) {
msg.update_credit_account.actions.push( msg.update_credit_account.actions.push(
...options.coins ...options.coins
.filter((coin) => getAssetByDenom(coin.denom)?.isAutoLendEnabled) .filter((coin) => getAssetByDenom(coin.denom)?.isAutoLendEnabled)
.map((coin) => ({ lend: coin.toActionCoin(true) })), .map((coin) => ({ lend: coin.toActionCoin(options.lend) })),
) )
} }
@ -238,7 +239,9 @@ export default function createBroadcastSlice(
.join(' and ') .join(' and ')
handleResponseMessages( handleResponseMessages(
response, response,
`Deposited ${depositString} to Credit Credit Account ${options.accountId}`, `Deposited ${options.lend ? 'and lent ' : ''}${depositString} to Credit Credit Account ${
options.accountId
}`,
) )
return !!response.result return !!response.result
}, },
@ -524,4 +527,4 @@ export default function createBroadcastSlice(
} }
}, },
} }
} }

View File

@ -20,7 +20,7 @@ interface BroadcastSlice {
claimRewards: (options: { accountId: string }) => ExecutableTx claimRewards: (options: { accountId: string }) => ExecutableTx
createAccount: () => Promise<string | null> createAccount: () => Promise<string | null>
deleteAccount: (options: { accountId: string; lends: BNCoin[] }) => Promise<boolean> deleteAccount: (options: { accountId: string; lends: BNCoin[] }) => Promise<boolean>
deposit: (options: { accountId: string; coins: BNCoin[] }) => Promise<boolean> deposit: (options: { accountId: string; coins: BNCoin[]; lend: boolean }) => Promise<boolean>
depositIntoVault: (options: { accountId: string; actions: Action[] }) => Promise<boolean> depositIntoVault: (options: { accountId: string; actions: Action[] }) => Promise<boolean>
executeMsg: (options: { messages: MsgExecuteContract[] }) => Promise<BroadcastResult> executeMsg: (options: { messages: MsgExecuteContract[] }) => Promise<BroadcastResult>
lend: (options: { accountId: string; coin: BNCoin; isMax?: boolean }) => Promise<boolean> lend: (options: { accountId: string; coin: BNCoin; isMax?: boolean }) => Promise<boolean>
@ -52,4 +52,4 @@ interface BroadcastSlice {
borrow: BNCoin[] borrow: BNCoin[]
reclaims: ActionCoin[] reclaims: ActionCoin[]
}) => Promise<boolean> }) => Promise<boolean>
} }

View File

@ -16,12 +16,7 @@ export const getTokenIcon = (denom: string, marketAssets: Asset[]) =>
export const getTokenInfo = (denom: string, marketAssets: Asset[]) => export const getTokenInfo = (denom: string, marketAssets: Asset[]) =>
marketAssets.find((asset) => asset.denom.toLowerCase() === denom.toLowerCase()) || getBaseAsset() marketAssets.find((asset) => asset.denom.toLowerCase() === denom.toLowerCase()) || getBaseAsset()
export function getTokenValue(coin: BNCoin, prices: BNCoin[]): BigNumber {
const price = prices.find((price) => price.denom === coin.denom)?.amount || '0'
return BN(price).multipliedBy(coin.amount).decimalPlaces(0)
}
export function getTokenPrice(denom: string, prices: BNCoin[]): BigNumber { export function getTokenPrice(denom: string, prices: BNCoin[]): BigNumber {
const price = prices.find((price) => price.denom === denom)?.amount || '0' const price = prices.find((price) => price.denom === denom)?.amount || '0'
return BN(price) return BN(price)
} }

View File

@ -5,9 +5,9 @@ import { TESTNET_VAULTS_META_DATA, VAULTS_META_DATA } from 'constants/vaults'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { Action } from 'types/generated/mars-credit-manager/MarsCreditManager.types' import { Action } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
import { getAssetByDenom } from 'utils/assets' import { getAssetByDenom } from 'utils/assets'
import { getTokenPrice, getTokenValue } from 'utils/tokens' import { getCoinValue } from 'utils/formatters'
import { getValueFromBNCoins, mergeBNCoinArrays } from 'utils/helpers'
import { getValueFromBNCoins, mergeBNCoinArrays } from './helpers' import { getTokenPrice } from 'utils/tokens'
export function getVaultsMetaData() { export function getVaultsMetaData() {
return IS_TESTNET ? TESTNET_VAULTS_META_DATA : VAULTS_META_DATA return IS_TESTNET ? TESTNET_VAULTS_META_DATA : VAULTS_META_DATA
@ -88,7 +88,7 @@ export function getVaultSwapActions(
) )
primaryCoins.forEach((bnCoin) => { primaryCoins.forEach((bnCoin) => {
let value = getTokenValue(bnCoin, prices) let value = getCoinValue(bnCoin, prices)
if (value.isLessThanOrEqualTo(primaryLeftoverValue)) { if (value.isLessThanOrEqualTo(primaryLeftoverValue)) {
primaryLeftoverValue = primaryLeftoverValue.minus(value) primaryLeftoverValue = primaryLeftoverValue.minus(value)
} else { } else {
@ -99,7 +99,7 @@ export function getVaultSwapActions(
}) })
secondaryCoins.forEach((bnCoin) => { secondaryCoins.forEach((bnCoin) => {
let value = getTokenValue(bnCoin, prices) let value = getCoinValue(bnCoin, prices)
if (value.isLessThanOrEqualTo(secondaryLeftoverValue)) { if (value.isLessThanOrEqualTo(secondaryLeftoverValue)) {
secondaryLeftoverValue = secondaryLeftoverValue.minus(value) secondaryLeftoverValue = secondaryLeftoverValue.minus(value)
} else { } else {
@ -110,7 +110,7 @@ export function getVaultSwapActions(
}) })
otherCoins.forEach((bnCoin) => { otherCoins.forEach((bnCoin) => {
let value = getTokenValue(bnCoin, prices) let value = getCoinValue(bnCoin, prices)
let amount = bnCoin.amount let amount = bnCoin.amount
if (primaryLeftoverValue.isGreaterThan(0)) { if (primaryLeftoverValue.isGreaterThan(0)) {
@ -177,4 +177,4 @@ function getSwapAction(denomIn: string, denomOut: string, amount: BigNumber, sli
slippage: slippage.toString(), slippage: slippage.toString(),
}, },
} }
} }