diff --git a/__tests__/components/Earn/Lend/LendingDetails.test.tsx b/__tests__/components/Earn/Lend/LendingDetails.test.tsx index fd62701e..523691d9 100644 --- a/__tests__/components/Earn/Lend/LendingDetails.test.tsx +++ b/__tests__/components/Earn/Lend/LendingDetails.test.tsx @@ -7,7 +7,7 @@ import { BN } from 'utils/helpers' const data: LendingMarketTableData = { asset: ASSETS[0], marketDepositAmount: BN('890546916'), - accountDepositValue: BN('0.5498406009348686811'), + accountLentValue: BN('0.5498406009348686811'), marketLiquidityAmount: BN('629396551'), marketDepositCap: BN('2500000000000'), marketLiquidityRate: 0.017, diff --git a/src/api/cosmwasm-client.ts b/src/api/cosmwasm-client.ts index 62e3038d..52ce9f42 100644 --- a/src/api/cosmwasm-client.ts +++ b/src/api/cosmwasm-client.ts @@ -3,9 +3,9 @@ import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate' import { ENV } from 'constants/env' import { MarsAccountNftQueryClient } from 'types/generated/mars-account-nft/MarsAccountNft.client' import { MarsCreditManagerQueryClient } from 'types/generated/mars-credit-manager/MarsCreditManager.client' +import { MarsIncentivesQueryClient } from 'types/generated/mars-incentives/MarsIncentives.client' import { MarsMockOracleQueryClient } from 'types/generated/mars-mock-oracle/MarsMockOracle.client' import { MarsMockRedBankQueryClient } from 'types/generated/mars-mock-red-bank/MarsMockRedBank.client' -import { MarsMockRedBankReactQuery } from 'types/generated/mars-mock-red-bank/MarsMockRedBank.react-query' import { MarsMockVaultQueryClient } from 'types/generated/mars-mock-vault/MarsMockVault.client' import { MarsParamsQueryClient } from 'types/generated/mars-params/MarsParams.client' @@ -15,6 +15,7 @@ let _creditManagerQueryClient: MarsCreditManagerQueryClient let _oracleQueryClient: MarsMockOracleQueryClient let _redBankQueryClient: MarsMockRedBankQueryClient let _paramsQueryClient: MarsParamsQueryClient +let _incentivesQueryClient: MarsIncentivesQueryClient const getClient = async () => { try { @@ -105,6 +106,19 @@ const getVaultQueryClient = async (address: string) => { } } +const getIncentivesQueryClient = async () => { + try { + if (!_incentivesQueryClient) { + const client = await getClient() + _incentivesQueryClient = new MarsIncentivesQueryClient(client, ENV.ADDRESS_INCENTIVES) + } + + return _incentivesQueryClient + } catch (error) { + throw error + } +} + export { getClient, getAccountNftQueryClient, @@ -113,4 +127,5 @@ export { getOracleQueryClient, getRedBankQueryClient, getVaultQueryClient, + getIncentivesQueryClient, } diff --git a/src/api/incentives/calculateAssetIncentivesApy.ts b/src/api/incentives/calculateAssetIncentivesApy.ts new file mode 100644 index 00000000..fb06441c --- /dev/null +++ b/src/api/incentives/calculateAssetIncentivesApy.ts @@ -0,0 +1,46 @@ +import getMarket from 'api/markets/getMarket' +import getAssetIncentive from 'api/incentives/getAssetIncentive' +import getUnderlyingLiquidityAmount from 'api/markets/getMarketUnderlyingLiquidityAmount' +import { BN } from 'utils/helpers' +import { SECONDS_IN_A_YEAR } from 'utils/constants' +import getPrice from 'api/prices/getPrice' +import getMarsPrice from 'api/prices/getMarsPrice' +import { ASSETS } from 'constants/assets' +import { byDenom } from 'utils/array' + +export default async function calculateAssetIncentivesApy(denom: string): Promise { + try { + const [assetIncentive, market] = await Promise.all([getAssetIncentive(denom), getMarket(denom)]) + + if (!assetIncentive) return null + + const [marketLiquidityAmount, assetPriceResponse, marsPrice] = await Promise.all([ + getUnderlyingLiquidityAmount(market), + getPrice(denom), + getMarsPrice(), + ]) + + const assetDecimals = (ASSETS.find(byDenom(denom)) as Asset).decimals + const marsDecimals = 6, + priceFeedDecimals = 6 + + const assetPrice = BN(assetPriceResponse.price).shiftedBy(assetDecimals - priceFeedDecimals) + const marketLiquidityValue = BN(marketLiquidityAmount) + .shiftedBy(-assetDecimals) + .multipliedBy(assetPrice) + + const marketReturns = BN(market.liquidityRate).multipliedBy(marketLiquidityValue) + const annualEmission = BN(assetIncentive.emission_per_second) + .multipliedBy(SECONDS_IN_A_YEAR) + .shiftedBy(-marsDecimals) + .multipliedBy(marsPrice) + + const totalAnnualReturnsValue = annualEmission.plus(marketReturns) + const incentivesApy = totalAnnualReturnsValue.dividedBy(marketLiquidityValue).multipliedBy(100) + + return incentivesApy.toNumber() + } catch (ex) { + console.error(ex) + return null + } +} diff --git a/src/api/incentives/getAssetIncentive.ts b/src/api/incentives/getAssetIncentive.ts new file mode 100644 index 00000000..a04b5a4e --- /dev/null +++ b/src/api/incentives/getAssetIncentive.ts @@ -0,0 +1,27 @@ +import moment from 'moment' + +import { getIncentivesQueryClient } from 'api/cosmwasm-client' +import { AssetIncentiveResponse } from 'types/generated/mars-incentives/MarsIncentives.types' + +export default async function getAssetIncentive( + denom: string, +): Promise { + try { + const client = await getIncentivesQueryClient() + const assetIncentive = await client.assetIncentive({ + denom, + }) + + const { duration, start_time } = assetIncentive + const isValid = moment(start_time + duration).isBefore(moment.now()) + + if (!isValid) { + throw 'Asset incentive duration is end.' + } + + return assetIncentive + } catch (ex) { + console.error(ex) + return null + } +} diff --git a/src/api/markets/getMarket.ts b/src/api/markets/getMarket.ts new file mode 100644 index 00000000..eddbcd93 --- /dev/null +++ b/src/api/markets/getMarket.ts @@ -0,0 +1,14 @@ +import { ENV } from 'constants/env' +import { resolveMarketResponse } from 'utils/resolvers' +import { getClient, getRedBankQueryClient } from 'api/cosmwasm-client' + +export default async function getMarket(denom: string): Promise { + try { + const client = await getRedBankQueryClient() + const market = await client.market({ denom }) + + return resolveMarketResponse(market) + } catch (ex) { + throw ex + } +} diff --git a/src/api/markets/getMarketDeposits.ts b/src/api/markets/getMarketDeposits.ts index 6a21b154..af0d3f62 100644 --- a/src/api/markets/getMarketDeposits.ts +++ b/src/api/markets/getMarketDeposits.ts @@ -1,17 +1,10 @@ import getMarkets from 'api/markets/getMarkets' -import { getRedBankQueryClient } from 'api/cosmwasm-client' +import getUnderlyingLiquidityAmount from 'api/markets/getMarketUnderlyingLiquidityAmount' export default async function getMarketDeposits(): Promise { try { const markets: Market[] = await getMarkets() - const redBankQueryClient = await getRedBankQueryClient() - - const depositQueries = markets.map((asset) => - redBankQueryClient.underlyingLiquidityAmount({ - denom: asset.denom, - amountScaled: asset.collateralTotalScaled, - }), - ) + const depositQueries = markets.map(getUnderlyingLiquidityAmount) const depositsResults = await Promise.all(depositQueries) return depositsResults.map((deposit, index) => ({ diff --git a/src/api/markets/getMarketUnderlyingLiquidityAmount.ts b/src/api/markets/getMarketUnderlyingLiquidityAmount.ts new file mode 100644 index 00000000..d5b3efb7 --- /dev/null +++ b/src/api/markets/getMarketUnderlyingLiquidityAmount.ts @@ -0,0 +1,15 @@ +import { getRedBankQueryClient } from 'api/cosmwasm-client' + +export default async function getUnderlyingLiquidityAmount(market: Market): Promise { + try { + const client = await getRedBankQueryClient() + const marketLiquidityAmount: string = await client.underlyingLiquidityAmount({ + denom: market.denom, + amountScaled: market.collateralTotalScaled, + }) + + return marketLiquidityAmount + } catch (ex) { + throw ex + } +} diff --git a/src/api/markets/getMarkets.ts b/src/api/markets/getMarkets.ts index 6841a63b..f22b1d8c 100644 --- a/src/api/markets/getMarkets.ts +++ b/src/api/markets/getMarkets.ts @@ -1,18 +1,12 @@ import { getEnabledMarketAssets } from 'utils/assets' -import { resolveMarketResponses } from 'utils/resolvers' -import { getRedBankQueryClient } from 'api/cosmwasm-client' +import getMarket from 'api/markets/getMarket' export default async function getMarkets(): Promise { try { const enabledAssets = getEnabledMarketAssets() - const redBankQueryClient = await getRedBankQueryClient() + const marketQueries = enabledAssets.map((asset) => getMarket(asset.denom)) - const marketQueries = enabledAssets.map((asset) => - redBankQueryClient.market({ denom: asset.denom }), - ) - const marketResults = await Promise.all(marketQueries) - - return resolveMarketResponses(marketResults) + return await Promise.all(marketQueries) } catch (ex) { throw ex } diff --git a/src/api/prices/getMarsPrice.ts b/src/api/prices/getMarsPrice.ts new file mode 100644 index 00000000..ef50a2ed --- /dev/null +++ b/src/api/prices/getMarsPrice.ts @@ -0,0 +1,45 @@ +import { BN } from 'utils/helpers' +import getPrice from 'api/prices/getPrice' + +const MARS_MAINNET_DENOM = 'ibc/573FCD90FACEE750F55A8864EF7D38265F07E5A9273FA0E8DAFD39951332B580' +const MARS_OSMO_POOL_URL = 'https://lcd-osmosis.blockapsis.com/osmosis/gamm/v1beta1/pools/907' + +interface PoolToken { + denom: string + amount: string +} + +interface PoolAsset { + token: PoolToken + weight: string +} + +const findPoolAssetByTokenDenom = (assets: PoolAsset[], denom: string) => + assets.find((a) => a.token.denom === denom) + +async function getMarsPrice() { + const marsOsmoRate = await getMarsOsmoRate() + const osmoPrice = await getPrice('uosmo') + + return marsOsmoRate.multipliedBy(osmoPrice.price) +} + +const getMarsOsmoRate = async () => { + const resp = await fetch(MARS_OSMO_POOL_URL).then((res) => res.json()) + const spotPrice = calculateSpotPrice(resp.pool.pool_assets) + + return BN(1).dividedBy(spotPrice) +} + +const calculateSpotPrice = (poolAssets: PoolAsset[]) => { + const assetIn = findPoolAssetByTokenDenom(poolAssets, MARS_MAINNET_DENOM) as PoolAsset + + const assetOut = findPoolAssetByTokenDenom(poolAssets, 'uosmo') as PoolAsset + + const numerator = BN(assetIn.token.amount).div(assetIn.weight) + const denominator = BN(assetOut.token.amount).div(assetOut.weight) + + return numerator.dividedBy(denominator) +} + +export default getMarsPrice diff --git a/src/components/Account/AccountList.tsx b/src/components/Account/AccountList.tsx index f09a3b98..16e8e939 100644 --- a/src/components/Account/AccountList.tsx +++ b/src/components/Account/AccountList.tsx @@ -12,7 +12,7 @@ import Text from 'components/Text' import useToggle from 'hooks/useToggle' import useStore from 'store' import { calculateAccountDeposits } from 'utils/accounts' -import { hardcodedFee } from 'utils/contants' +import { hardcodedFee } from 'utils/constants' import { BN } from 'utils/helpers' import { getPage, getRoute } from 'utils/route' import usePrices from 'hooks/usePrices' diff --git a/src/components/Account/AccountMenuContent.tsx b/src/components/Account/AccountMenuContent.tsx index 9b273da4..cf7780a4 100644 --- a/src/components/Account/AccountMenuContent.tsx +++ b/src/components/Account/AccountMenuContent.tsx @@ -12,7 +12,7 @@ import Overlay from 'components/Overlay' import Text from 'components/Text' import useToggle from 'hooks/useToggle' import useStore from 'store' -import { hardcodedFee } from 'utils/contants' +import { hardcodedFee } from 'utils/constants' import { isNumber } from 'utils/parsers' const menuClasses = 'absolute isolate flex w-full flex-wrap scrollbar-hide' diff --git a/src/components/Account/CurrentAccountSummary.tsx b/src/components/Account/CurrentAccountSummary.tsx new file mode 100644 index 00000000..95eee3ca --- /dev/null +++ b/src/components/Account/CurrentAccountSummary.tsx @@ -0,0 +1,10 @@ +import useCurrentAccount from 'hooks/useCurrentAccount' +import AccountSummary from 'components/Account/AccountSummary' + +function CurrentAccountSummary({ change }: { change?: AccountChange }) { + const account = useCurrentAccount() + + return +} + +export default CurrentAccountSummary diff --git a/src/components/Account/FundAccount.tsx b/src/components/Account/FundAccount.tsx index 80096f0d..7de6a3f7 100644 --- a/src/components/Account/FundAccount.tsx +++ b/src/components/Account/FundAccount.tsx @@ -11,7 +11,7 @@ import { ASSETS } from 'constants/assets' import useToggle from 'hooks/useToggle' import useStore from 'store' import { getAmount } from 'utils/accounts' -import { hardcodedFee } from 'utils/contants' +import { hardcodedFee } from 'utils/constants' import { BN } from 'utils/helpers' interface Props { diff --git a/src/components/Earn/Lend/LendingActionButtons.tsx b/src/components/Earn/Lend/LendingActionButtons.tsx index 59d4cfa9..32871faf 100644 --- a/src/components/Earn/Lend/LendingActionButtons.tsx +++ b/src/components/Earn/Lend/LendingActionButtons.tsx @@ -4,6 +4,7 @@ import Text from 'components/Text' import { Tooltip } from 'components/Tooltip' import ConditionalWrapper from 'hocs/ConditionalWrapper' import useCurrentAccountDeposits from 'hooks/useCurrentAccountDeposits' +import useLendAndReclaimModal from 'hooks/useLendAndReclaimModal' import { byDenom } from 'utils/array' interface Props { @@ -14,18 +15,19 @@ const buttonClassnames = 'm-0 flex w-40 text-lg' const iconClassnames = 'ml-0 mr-1 w-4 h-4' function LendingActionButtons(props: Props) { - const { asset, accountDepositValue } = props.data + const { asset, accountLentValue: accountLendValue } = props.data const accountDeposits = useCurrentAccountDeposits() + const { openLend, openReclaim } = useLendAndReclaimModal() const assetDepositAmount = accountDeposits.find(byDenom(asset.denom))?.amount return (
- {accountDepositValue && ( + {accountLendValue && (
+ + ) +} diff --git a/src/components/Modals/Borrow/BorrowModal.tsx b/src/components/Modals/Borrow/BorrowModal.tsx index 521ac774..a0f09256 100644 --- a/src/components/Modals/Borrow/BorrowModal.tsx +++ b/src/components/Modals/Borrow/BorrowModal.tsx @@ -14,7 +14,7 @@ import { ASSETS } from 'constants/assets' import useCurrentAccount from 'hooks/useCurrentAccount' import useToggle from 'hooks/useToggle' import useStore from 'store' -import { hardcodedFee } from 'utils/contants' +import { hardcodedFee } from 'utils/constants' import { formatPercent, formatValue } from 'utils/formatters' import { BN } from 'utils/helpers' import AssetImage from 'components/AssetImage' diff --git a/src/components/Modals/FundWithdraw/FundAndWithdrawModalContent.tsx b/src/components/Modals/FundWithdraw/FundAndWithdrawModalContent.tsx index f35ddbf4..b2bc2d10 100644 --- a/src/components/Modals/FundWithdraw/FundAndWithdrawModalContent.tsx +++ b/src/components/Modals/FundWithdraw/FundAndWithdrawModalContent.tsx @@ -10,7 +10,7 @@ import TokenInputWithSlider from 'components/TokenInputWithSlider' import useToggle from 'hooks/useToggle' import useStore from 'store' import { getAmount } from 'utils/accounts' -import { hardcodedFee } from 'utils/contants' +import { hardcodedFee } from 'utils/constants' import { BN } from 'utils/helpers' interface Props { diff --git a/src/components/Modals/LendAndReclaim/DetailsHeader.tsx b/src/components/Modals/LendAndReclaim/DetailsHeader.tsx new file mode 100644 index 00000000..0eb7ee7d --- /dev/null +++ b/src/components/Modals/LendAndReclaim/DetailsHeader.tsx @@ -0,0 +1,61 @@ +import DisplayCurrency from 'components/DisplayCurrency' +import TitleAndSubCell from 'components/TitleAndSubCell' +import { FormattedNumber } from 'components/FormattedNumber' +import useAssetIncentivesApy from 'hooks/useAssetIncentiveApy' +import useCurrentWalletBalance from 'hooks/useCurrentWalletBalance' + +interface Props { + data: LendingMarketTableData +} + +function DetailsHeader({ data }: Props) { + const { asset, marketDepositCap, accountLentAmount: accountLendAmount } = data + const { data: assetApy } = useAssetIncentivesApy(asset.denom) + const balanceInWallet = useCurrentWalletBalance(asset.denom) + + return ( +
+ {assetApy && ( + <> + + + + + } + sub={'APY'} + /> +
+ + )} + {accountLendAmount && ( + <> + } + sub={'Deposited'} + /> +
+ + )} + {balanceInWallet && ( + <> + } sub={'In Wallet'} /> +
+ + )} + + } + sub={'Deposit Cap'} + /> +
+ ) +} + +export default DetailsHeader diff --git a/src/components/Modals/LendAndReclaim/index.tsx b/src/components/Modals/LendAndReclaim/index.tsx new file mode 100644 index 00000000..19eefdce --- /dev/null +++ b/src/components/Modals/LendAndReclaim/index.tsx @@ -0,0 +1,80 @@ +import { useState } from 'react' + +import useStore from 'store' +import useToggle from 'hooks/useToggle' +import { hardcodedFee } from 'utils/constants' +import useCurrentAccount from 'hooks/useCurrentAccount' +import useLendAndReclaimModal from 'hooks/useLendAndReclaimModal' +import DetailsHeader from 'components/Modals/LendAndReclaim/DetailsHeader' +import AssetAmountSelectActionModal from 'components/Modals/AssetAmountSelectActionModal' + +const getAccountChange = (isLend: boolean, value: BigNumber, denom: string): AccountChange => { + const makeCoin = (denom: string, shouldNegate: boolean) => [ + { + amount: (shouldNegate ? value.negated() : value).toString(), + denom, + }, + ] + + return { + deposits: makeCoin(denom, isLend), + lends: makeCoin(denom, !isLend), + } +} + +function LendAndReclaimModal() { + const lend = useStore((s) => s.lend) + const reclaim = useStore((s) => s.reclaim) + const currentAccount = useCurrentAccount() + const { config, close } = useLendAndReclaimModal() + const [isConfirming, setIsConfirming] = useToggle() + const [accountChange, setAccountChange] = useState() + + if (!config || !currentAccount) return null + + const { data, action } = config + const { asset } = data + + const isLendAction = action === 'lend' + const actionText = isLendAction ? 'Lend' : 'Withdraw' + const coinBalances = currentAccount[isLendAction ? 'deposits' : 'lends'] ?? [] + + const handleAmountChange = (value: BigNumber) => { + setAccountChange(getAccountChange(isLendAction, value, asset.denom)) + } + + const handleAction = async (value: BigNumber) => { + setIsConfirming(true) + + const options = { + fee: hardcodedFee, + accountId: currentAccount.id, + coin: { + denom: asset.denom, + amount: value.toString(), + }, + } + await (isLendAction ? lend : reclaim)(options) + + setIsConfirming(false) + close() + } + + return ( + } + coinBalances={coinBalances} + actionButtonText={actionText} + showLoaderInButton={isConfirming} + accountSummaryChange={accountChange} + title={`${actionText} ${asset.symbol}`} + onClose={close} + onAction={handleAction} + onChange={handleAmountChange} + /> + ) +} + +export default LendAndReclaimModal diff --git a/src/components/Modals/ModalsContainer.tsx b/src/components/Modals/ModalsContainer.tsx index c06efcb8..72acb066 100644 --- a/src/components/Modals/ModalsContainer.tsx +++ b/src/components/Modals/ModalsContainer.tsx @@ -3,15 +3,18 @@ import BorrowModal from 'components/Modals/Borrow/BorrowModal' import FundAndWithdrawModal from 'components/Modals/FundWithdraw/FundAndWithdrawModal' import AddVaultBorrowAssetsModal from 'components/Modals/AddVaultAssets/AddVaultBorrowAssetsModal' import UnlockModal from 'components/Modals/Unlock/UnlockModal' +import LendAndReclaimModal from 'components/Modals/LendAndReclaim' export default function ModalsContainer() { return ( <> + + ) } diff --git a/src/components/Modals/Unlock/UnlockModalContent.tsx b/src/components/Modals/Unlock/UnlockModalContent.tsx index 609b9242..e0afde09 100644 --- a/src/components/Modals/Unlock/UnlockModalContent.tsx +++ b/src/components/Modals/Unlock/UnlockModalContent.tsx @@ -1,8 +1,8 @@ +import { hardcodedFee } from 'utils/constants' import Button from 'components/Button' import { Enter } from 'components/Icons' import Text from 'components/Text' import useStore from 'store' -import { hardcodedFee } from 'utils/contants' interface Props { depositedVault: DepositedVault diff --git a/src/components/TokenInputWithSlider.tsx b/src/components/TokenInputWithSlider.tsx index 4ddb598c..5e5571a2 100644 --- a/src/components/TokenInputWithSlider.tsx +++ b/src/components/TokenInputWithSlider.tsx @@ -24,7 +24,7 @@ export default function TokenInputWithSlider(props: Props) { const [percentage, setPercentage] = useState(0) function onChangeSlider(percentage: number) { - const newAmount = BN(percentage).div(100).times(props.max) + const newAmount = BN(percentage).div(100).times(props.max).integerValue() setPercentage(percentage) setAmount(newAmount) props.onChange(newAmount) diff --git a/src/hooks/useAssetIncentiveApy.ts b/src/hooks/useAssetIncentiveApy.ts new file mode 100644 index 00000000..4be3e852 --- /dev/null +++ b/src/hooks/useAssetIncentiveApy.ts @@ -0,0 +1,7 @@ +import useSWR from 'swr' + +import calculateAssetIncentivesApy from 'api/incentives/calculateAssetIncentivesApy' + +export default function useAssetIncentivesApy(denom: string) { + return useSWR(`assetIncentiveApy-${denom}`, () => calculateAssetIncentivesApy(denom)) +} diff --git a/src/hooks/useCurrentAccountLends.ts b/src/hooks/useCurrentAccountLends.ts new file mode 100644 index 00000000..548357bb --- /dev/null +++ b/src/hooks/useCurrentAccountLends.ts @@ -0,0 +1,6 @@ +import useCurrentAccount from 'hooks/useCurrentAccount' + +export default function useCurrentAccountLends() { + const account = useCurrentAccount() + return account?.lends ?? [] +} diff --git a/src/hooks/useCurrentWalletBalance.ts b/src/hooks/useCurrentWalletBalance.ts new file mode 100644 index 00000000..af53aadc --- /dev/null +++ b/src/hooks/useCurrentWalletBalance.ts @@ -0,0 +1,12 @@ +import useStore from 'store' +import useWalletBalances from 'hooks/useWalletBalances' +import { byDenom } from 'utils/array' + +function useCurrentWalletBalance(denom: string) { + const address = useStore((s) => s.address) + const { data: walletBalances } = useWalletBalances(address) + + return walletBalances.find(byDenom(denom)) +} + +export default useCurrentWalletBalance diff --git a/src/hooks/useLendAndReclaimModal.ts b/src/hooks/useLendAndReclaimModal.ts new file mode 100644 index 00000000..8c754539 --- /dev/null +++ b/src/hooks/useLendAndReclaimModal.ts @@ -0,0 +1,27 @@ +import { useCallback } from 'react' + +import useStore from 'store' + +function useLendAndReclaimModal() { + const config = useStore((s) => s.lendAndReclaimModal) + + const open = useCallback((action: LendAndReclaimModalAction, data: LendingMarketTableData) => { + const _config: LendAndReclaimModalConfig = { + action, + data, + } + + useStore.setState({ lendAndReclaimModal: _config }) + }, []) + + const close = useCallback(() => { + useStore.setState({ lendAndReclaimModal: null }) + }, []) + + const openLend = useCallback((data: LendingMarketTableData) => open('lend', data), [open]) + const openReclaim = useCallback((data: LendingMarketTableData) => open('reclaim', data), [open]) + + return { config, openLend, openReclaim, close } +} + +export default useLendAndReclaimModal diff --git a/src/hooks/useLendingMarketAssetsTableData.ts b/src/hooks/useLendingMarketAssetsTableData.ts index 400e23ef..f0a5bede 100644 --- a/src/hooks/useLendingMarketAssetsTableData.ts +++ b/src/hooks/useLendingMarketAssetsTableData.ts @@ -7,36 +7,36 @@ import useMarketDeposits from 'hooks/useMarketDeposits' import useMarketLiquidities from 'hooks/useMarketLiquidities' import useDisplayCurrencyPrice from 'hooks/useDisplayCurrencyPrice' import useDepositEnabledMarkets from 'hooks/useDepositEnabledMarkets' -import useCurrentAccountDeposits from 'hooks/useCurrentAccountDeposits' +import useCurrentAccountLends from 'hooks/useCurrentAccountLends' function useLendingMarketAssetsTableData(): { - lentAssets: LendingMarketTableData[] + accountLentAssets: LendingMarketTableData[] availableAssets: LendingMarketTableData[] } { const markets = useDepositEnabledMarkets() - const accountDeposits = useCurrentAccountDeposits() - // TODO: replace market deposits with account.lends when credit manager contract has lend feature + const accountLentAmounts = useCurrentAccountLends() const { data: marketDeposits } = useMarketDeposits() const { data: marketLiquidities } = useMarketLiquidities() const { convertAmount } = useDisplayCurrencyPrice() return useMemo(() => { - const lentAssets: LendingMarketTableData[] = [], + const accountLentAssets: LendingMarketTableData[] = [], availableAssets: LendingMarketTableData[] = [] markets.forEach(({ denom, depositCap, liquidityRate, liquidationThreshold, maxLtv }) => { const asset = getAssetByDenom(denom) as Asset const marketDepositAmount = BN(marketDeposits.find(byDenom(denom))?.amount ?? 0) const marketLiquidityAmount = BN(marketLiquidities.find(byDenom(denom))?.amount ?? 0) - const accountDepositAmount = accountDeposits.find(byDenom(denom))?.amount - const accountDepositValue = accountDepositAmount - ? convertAmount(asset, accountDepositAmount) + const accountLentAmount = accountLentAmounts.find(byDenom(denom))?.amount + const accountLentValue = accountLentAmount + ? convertAmount(asset, accountLentAmount) : undefined const lendingMarketAsset: LendingMarketTableData = { asset, marketDepositAmount, - accountDepositValue, + accountLentValue, + accountLentAmount, marketLiquidityAmount, marketDepositCap: BN(depositCap), marketLiquidityRate: liquidityRate, @@ -44,13 +44,13 @@ function useLendingMarketAssetsTableData(): { marketMaxLtv: maxLtv, } - ;(lendingMarketAsset.accountDepositValue ? lentAssets : availableAssets).push( + ;(lendingMarketAsset.accountLentValue ? accountLentAssets : availableAssets).push( lendingMarketAsset, ) }) - return { lentAssets, availableAssets } - }, [markets, marketDeposits, marketLiquidities, accountDeposits, convertAmount]) + return { accountLentAssets, availableAssets } + }, [markets, marketDeposits, marketLiquidities, accountLentAmounts, convertAmount]) } export default useLendingMarketAssetsTableData diff --git a/src/hooks/useWalletBalances.tsx b/src/hooks/useWalletBalances.tsx index 8ba1e303..b6c3185c 100644 --- a/src/hooks/useWalletBalances.tsx +++ b/src/hooks/useWalletBalances.tsx @@ -5,5 +5,6 @@ import getWalletBalances from 'api/wallets/getWalletBalances' export default function useWalletBalances(address?: string) { return useSWR(`walletBalances${address}`, () => getWalletBalances(address || ''), { isPaused: () => !address, + fallbackData: [], }) } diff --git a/src/pages/LendPage.tsx b/src/pages/LendPage.tsx index 08b87294..97fc4df1 100644 --- a/src/pages/LendPage.tsx +++ b/src/pages/LendPage.tsx @@ -3,12 +3,12 @@ import LendingMarketsTable from 'components/Earn/Lend/LendingMarketsTable' import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData' export default function LendPage() { - const { lentAssets, availableAssets } = useLendingMarketAssetsTableData() + const { accountLentAssets, availableAssets } = useLendingMarketAssetsTableData() return ( <> - + ) diff --git a/src/store/slices/broadcast.ts b/src/store/slices/broadcast.ts index 73d8764b..e157b6f7 100644 --- a/src/store/slices/broadcast.ts +++ b/src/store/slices/broadcast.ts @@ -6,11 +6,34 @@ import { ENV } from 'constants/env' import { Store } from 'store' import { getSingleValueFromBroadcastResult } from 'utils/broadcast' import { formatAmountWithSymbol } from 'utils/formatters' +import { BN } from 'utils/helpers' export default function createBroadcastSlice( set: SetState, get: GetState, ): BroadcastSlice { + const handleResponseMessages = ( + response: BroadcastResult, + successMessage: string, + errorMessage?: string, + ) => { + if (response.result?.response.code === 0) { + set({ + toast: { + message: successMessage, + }, + }) + } else { + const error = response.error ? response.error : response.result?.rawLogs + set({ + toast: { + message: errorMessage ?? `Transaction failed: ${error}`, + isError: true, + }, + }) + } + } + return { toast: null, borrow: async (options: { fee: StdFee; accountId: string; coin: Coin }) => { @@ -23,24 +46,10 @@ export default function createBroadcastSlice( const response = await get().executeMsg({ msg, fee: options.fee }) - if (response.result?.response.code === 0) { - set({ - toast: { - message: `Borrowed ${formatAmountWithSymbol(options.coin)} to Account ${ - options.accountId - }`, - }, - }) - } else { - const error = response.error ? response.error : response.result?.rawLogs - set({ - toast: { - message: `Transaction failed: ${error}`, - isError: true, - }, - }) - } - + handleResponseMessages( + response, + `Borrowed ${formatAmountWithSymbol(options.coin)} to Account ${options.accountId}`, + ) return !!response.result }, createAccount: async (options: { fee: StdFee }) => { @@ -76,16 +85,9 @@ export default function createBroadcastSlice( const response = await get().executeMsg({ msg, fee: options.fee }) set({ deleteAccountModal: false }) - if (response.result) { - set({ toast: { message: `Account ${options.accountId} deleted` } }) - } else { - set({ - toast: { - message: response.error ?? `Transaction failed: ${response.error}`, - isError: true, - }, - }) - } + + handleResponseMessages(response, `Account ${options.accountId} deleted`) + return !!response.result }, deposit: async (options: { fee: StdFee; accountId: string; coin: Coin }) => { @@ -101,22 +103,11 @@ export default function createBroadcastSlice( } const response = await get().executeMsg({ msg, fee: options.fee, funds: [options.coin] }) - if (response.result) { - set({ - toast: { - message: `Deposited ${formatAmountWithSymbol(options.coin)} to Account ${ - options.accountId - }`, - }, - }) - } else { - set({ - toast: { - message: response.error ?? `Transaction failed: ${response.error}`, - isError: true, - }, - }) - } + + handleResponseMessages( + response, + `Deposited ${formatAmountWithSymbol(options.coin)} to Account ${options.accountId}`, + ) return !!response.result }, unlock: async (options: { fee: StdFee; vault: Vault; amount: string }) => { @@ -133,20 +124,7 @@ export default function createBroadcastSlice( funds: [], }) - if (response.result) { - set({ - toast: { - message: `Requested unlock for ${options.vault.name}`, - }, - }) - } else { - set({ - toast: { - message: response.error ?? `Request unlocked failed: ${response.error}`, - isError: true, - }, - }) - } + handleResponseMessages(response, `Requested unlock for ${options.vault.name}`) return !!response.result }, withdraw: async (options: { fee: StdFee; accountId: string; coin: Coin }) => { @@ -162,22 +140,11 @@ export default function createBroadcastSlice( } const response = await get().executeMsg({ msg, fee: options.fee }) - if (response.result) { - set({ - toast: { - message: `Withdrew ${formatAmountWithSymbol(options.coin)} from Account ${ - options.accountId - }`, - }, - }) - } else { - set({ - toast: { - message: response.error ?? `Transaction failed: ${response.error}`, - isError: true, - }, - }) - } + + handleResponseMessages( + response, + `Withdrew ${formatAmountWithSymbol(options.coin)} from Account ${options.accountId}`, + ) return !!response.result }, executeMsg: async (options: { @@ -239,22 +206,52 @@ export default function createBroadcastSlice( } const response = await get().executeMsg({ msg, fee: options.fee, funds: [] }) - if (response.result?.response.code === 0) { - set({ - toast: { - message: `Repayed ${formatAmountWithSymbol(options.coin)} to Account ${ - options.accountId - }`, - }, - }) - } else { - set({ - toast: { - message: response.error ?? `Transaction failed: ${response.error}`, - isError: true, - }, - }) + + handleResponseMessages( + response, + `Repayed ${formatAmountWithSymbol(options.coin)} to Account ${options.accountId}`, + ) + return !!response.result + }, + lend: async (options: { fee: StdFee; accountId: string; coin: Coin }) => { + const msg = { + update_credit_account: { + account_id: options.accountId, + actions: [ + { + lend: options.coin, + }, + ], + }, } + + const response = await get().executeMsg({ msg, fee: options.fee }) + + handleResponseMessages( + response, + `Successfully deposited ${formatAmountWithSymbol(options.coin)}`, + ) + return !!response.result + }, + reclaim: async (options: { fee: StdFee; accountId: string; coin: Coin }) => { + const reclaim = { denom: options.coin.denom, amount: { exact: BN(options.coin.amount) } } + const msg = { + update_credit_account: { + account_id: options.accountId, + actions: [ + { + reclaim, + }, + ], + }, + } + + const response = await get().executeMsg({ msg, fee: options.fee }) + + handleResponseMessages( + response, + `Successfully deposited ${formatAmountWithSymbol(options.coin)}`, + ) return !!response.result }, } diff --git a/src/store/slices/modal.ts b/src/store/slices/modal.ts index 63c06fc6..c2015eb9 100644 --- a/src/store/slices/modal.ts +++ b/src/store/slices/modal.ts @@ -9,6 +9,7 @@ export default function createModalSlice(set: SetState, get: GetStat fundAccountModal: false, fundAndWithdrawModal: null, unlockModal: null, + lendAndReclaimModal: null, vaultModal: null, } } diff --git a/src/types/generated/mars-incentives/MarsIncentives.client.ts b/src/types/generated/mars-incentives/MarsIncentives.client.ts new file mode 100644 index 00000000..05c545e8 --- /dev/null +++ b/src/types/generated/mars-incentives/MarsIncentives.client.ts @@ -0,0 +1,284 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { Coin, StdFee } from '@cosmjs/amino' +import { + InstantiateMsg, + ExecuteMsg, + Uint128, + Addr, + OwnerUpdate, + QueryMsg, + Decimal, + AssetIncentiveResponse, + ArrayOfAssetIncentiveResponse, + ConfigResponse, +} from './MarsIncentives.types' +export interface MarsIncentivesReadOnlyInterface { + contractAddress: string + config: () => Promise + assetIncentive: ({ denom }: { denom: string }) => Promise + assetIncentives: ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string + }) => Promise + userUnclaimedRewards: ({ user }: { user: string }) => Promise +} +export class MarsIncentivesQueryClient implements MarsIncentivesReadOnlyInterface { + client: CosmWasmClient + contractAddress: string + + constructor(client: CosmWasmClient, contractAddress: string) { + this.client = client + this.contractAddress = contractAddress + this.config = this.config.bind(this) + this.assetIncentive = this.assetIncentive.bind(this) + this.assetIncentives = this.assetIncentives.bind(this) + this.userUnclaimedRewards = this.userUnclaimedRewards.bind(this) + } + + config = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + config: {}, + }) + } + assetIncentive = async ({ denom }: { denom: string }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + asset_incentive: { + denom, + }, + }) + } + assetIncentives = async ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + asset_incentives: { + limit, + start_after: startAfter, + }, + }) + } + userUnclaimedRewards = async ({ user }: { user: string }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + user_unclaimed_rewards: { + user, + }, + }) + } +} +export interface MarsIncentivesInterface extends MarsIncentivesReadOnlyInterface { + contractAddress: string + sender: string + setAssetIncentive: ( + { + denom, + duration, + emissionPerSecond, + startTime, + }: { + denom: string + duration?: number + emissionPerSecond?: Uint128 + startTime?: number + }, + fee?: number | StdFee | 'auto', + memo?: string, + _funds?: Coin[], + ) => Promise + balanceChange: ( + { + denom, + totalAmountScaledBefore, + userAddr, + userAmountScaledBefore, + }: { + denom: string + totalAmountScaledBefore: Uint128 + userAddr: Addr + userAmountScaledBefore: Uint128 + }, + fee?: number | StdFee | 'auto', + memo?: string, + _funds?: Coin[], + ) => Promise + claimRewards: ( + fee?: number | StdFee | 'auto', + memo?: string, + _funds?: Coin[], + ) => Promise + updateConfig: ( + { + addressProvider, + marsDenom, + }: { + addressProvider?: string + marsDenom?: string + }, + fee?: number | StdFee | 'auto', + memo?: string, + _funds?: Coin[], + ) => Promise + updateOwner: ( + ownerUpdate: OwnerUpdate, + fee?: number | StdFee | 'auto', + memo?: string, + _funds?: Coin[], + ) => Promise +} +export class MarsIncentivesClient + extends MarsIncentivesQueryClient + implements MarsIncentivesInterface +{ + client: SigningCosmWasmClient + sender: string + contractAddress: string + + constructor(client: SigningCosmWasmClient, sender: string, contractAddress: string) { + super(client, contractAddress) + this.client = client + this.sender = sender + this.contractAddress = contractAddress + this.setAssetIncentive = this.setAssetIncentive.bind(this) + this.balanceChange = this.balanceChange.bind(this) + this.claimRewards = this.claimRewards.bind(this) + this.updateConfig = this.updateConfig.bind(this) + this.updateOwner = this.updateOwner.bind(this) + } + + setAssetIncentive = async ( + { + denom, + duration, + emissionPerSecond, + startTime, + }: { + denom: string + duration?: number + emissionPerSecond?: Uint128 + startTime?: number + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + _funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + set_asset_incentive: { + denom, + duration, + emission_per_second: emissionPerSecond, + start_time: startTime, + }, + }, + fee, + memo, + _funds, + ) + } + balanceChange = async ( + { + denom, + totalAmountScaledBefore, + userAddr, + userAmountScaledBefore, + }: { + denom: string + totalAmountScaledBefore: Uint128 + userAddr: Addr + userAmountScaledBefore: Uint128 + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + _funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + balance_change: { + denom, + total_amount_scaled_before: totalAmountScaledBefore, + user_addr: userAddr, + user_amount_scaled_before: userAmountScaledBefore, + }, + }, + fee, + memo, + _funds, + ) + } + claimRewards = async ( + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + _funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + claim_rewards: {}, + }, + fee, + memo, + _funds, + ) + } + updateConfig = async ( + { + addressProvider, + marsDenom, + }: { + addressProvider?: string + marsDenom?: string + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + _funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + update_config: { + address_provider: addressProvider, + mars_denom: marsDenom, + }, + }, + fee, + memo, + _funds, + ) + } + updateOwner = async ( + ownerUpdate: OwnerUpdate, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + _funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + update_owner: ownerUpdate, + }, + fee, + memo, + _funds, + ) + } +} diff --git a/src/types/generated/mars-incentives/MarsIncentives.react-query.ts b/src/types/generated/mars-incentives/MarsIncentives.react-query.ts new file mode 100644 index 00000000..bbdf620f --- /dev/null +++ b/src/types/generated/mars-incentives/MarsIncentives.react-query.ts @@ -0,0 +1,254 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' +import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee, Coin } from '@cosmjs/amino' +import { + InstantiateMsg, + ExecuteMsg, + Uint128, + Addr, + OwnerUpdate, + QueryMsg, + Decimal, + AssetIncentiveResponse, + ArrayOfAssetIncentiveResponse, + ConfigResponse, +} from './MarsIncentives.types' +import { MarsIncentivesQueryClient, MarsIncentivesClient } from './MarsIncentives.client' +export const marsIncentivesQueryKeys = { + contract: [ + { + contract: 'marsIncentives', + }, + ] as const, + address: (contractAddress: string | undefined) => + [{ ...marsIncentivesQueryKeys.contract[0], address: contractAddress }] as const, + config: (contractAddress: string | undefined, args?: Record) => + [{ ...marsIncentivesQueryKeys.address(contractAddress)[0], method: 'config', args }] as const, + assetIncentive: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsIncentivesQueryKeys.address(contractAddress)[0], method: 'asset_incentive', args }, + ] as const, + assetIncentives: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsIncentivesQueryKeys.address(contractAddress)[0], method: 'asset_incentives', args }, + ] as const, + userUnclaimedRewards: (contractAddress: string | undefined, args?: Record) => + [ + { + ...marsIncentivesQueryKeys.address(contractAddress)[0], + method: 'user_unclaimed_rewards', + args, + }, + ] as const, +} +export interface MarsIncentivesReactQuery { + client: MarsIncentivesQueryClient | undefined + options?: Omit< + UseQueryOptions, + "'queryKey' | 'queryFn' | 'initialData'" + > & { + initialData?: undefined + } +} +export interface MarsIncentivesUserUnclaimedRewardsQuery + extends MarsIncentivesReactQuery { + args: { + user: string + } +} +export function useMarsIncentivesUserUnclaimedRewardsQuery({ + client, + args, + options, +}: MarsIncentivesUserUnclaimedRewardsQuery) { + return useQuery( + marsIncentivesQueryKeys.userUnclaimedRewards(client?.contractAddress, args), + () => + client + ? client.userUnclaimedRewards({ + user: args.user, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsIncentivesAssetIncentivesQuery + extends MarsIncentivesReactQuery { + args: { + limit?: number + startAfter?: string + } +} +export function useMarsIncentivesAssetIncentivesQuery({ + client, + args, + options, +}: MarsIncentivesAssetIncentivesQuery) { + return useQuery( + marsIncentivesQueryKeys.assetIncentives(client?.contractAddress, args), + () => + client + ? client.assetIncentives({ + limit: args.limit, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsIncentivesAssetIncentiveQuery + extends MarsIncentivesReactQuery { + args: { + denom: string + } +} +export function useMarsIncentivesAssetIncentiveQuery({ + client, + args, + options, +}: MarsIncentivesAssetIncentiveQuery) { + return useQuery( + marsIncentivesQueryKeys.assetIncentive(client?.contractAddress, args), + () => + client + ? client.assetIncentive({ + denom: args.denom, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsIncentivesConfigQuery + extends MarsIncentivesReactQuery {} +export function useMarsIncentivesConfigQuery({ + client, + options, +}: MarsIncentivesConfigQuery) { + return useQuery( + marsIncentivesQueryKeys.config(client?.contractAddress), + () => (client ? client.config() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsIncentivesUpdateOwnerMutation { + client: MarsIncentivesClient + msg: OwnerUpdate + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsIncentivesUpdateOwnerMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.updateOwner(msg, fee, memo, funds), + options, + ) +} +export interface MarsIncentivesUpdateConfigMutation { + client: MarsIncentivesClient + msg: { + addressProvider?: string + marsDenom?: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsIncentivesUpdateConfigMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.updateConfig(msg, fee, memo, funds), + options, + ) +} +export interface MarsIncentivesClaimRewardsMutation { + client: MarsIncentivesClient + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsIncentivesClaimRewardsMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, args: { fee, memo, funds } = {} }) => client.claimRewards(fee, memo, funds), + options, + ) +} +export interface MarsIncentivesBalanceChangeMutation { + client: MarsIncentivesClient + msg: { + denom: string + totalAmountScaledBefore: Uint128 + userAddr: Addr + userAmountScaledBefore: Uint128 + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsIncentivesBalanceChangeMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.balanceChange(msg, fee, memo, funds), + options, + ) +} +export interface MarsIncentivesSetAssetIncentiveMutation { + client: MarsIncentivesClient + msg: { + denom: string + duration?: number + emissionPerSecond?: Uint128 + startTime?: number + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsIncentivesSetAssetIncentiveMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.setAssetIncentive(msg, fee, memo, funds), + options, + ) +} diff --git a/src/types/generated/mars-incentives/MarsIncentives.types.ts b/src/types/generated/mars-incentives/MarsIncentives.types.ts new file mode 100644 index 00000000..f1328167 --- /dev/null +++ b/src/types/generated/mars-incentives/MarsIncentives.types.ts @@ -0,0 +1,94 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export interface InstantiateMsg { + address_provider: string + mars_denom: string + owner: string +} +export type ExecuteMsg = + | { + set_asset_incentive: { + denom: string + duration?: number | null + emission_per_second?: Uint128 | null + start_time?: number | null + } + } + | { + balance_change: { + denom: string + total_amount_scaled_before: Uint128 + user_addr: Addr + user_amount_scaled_before: Uint128 + } + } + | { + claim_rewards: {} + } + | { + update_config: { + address_provider?: string | null + mars_denom?: string | null + } + } + | { + update_owner: OwnerUpdate + } +export type Uint128 = string +export type Addr = string +export type OwnerUpdate = + | { + propose_new_owner: { + proposed: string + } + } + | 'clear_proposed' + | 'accept_proposed' + | 'abolish_owner_role' + | { + set_emergency_owner: { + emergency_owner: string + } + } + | 'clear_emergency_owner' +export type QueryMsg = + | { + config: {} + } + | { + asset_incentive: { + denom: string + } + } + | { + asset_incentives: { + limit?: number | null + start_after?: string | null + } + } + | { + user_unclaimed_rewards: { + user: string + } + } +export type Decimal = string +export interface AssetIncentiveResponse { + denom: string + duration: number + emission_per_second: Uint128 + index: Decimal + last_updated: number + start_time: number +} +export type ArrayOfAssetIncentiveResponse = AssetIncentiveResponse[] +export interface ConfigResponse { + address_provider: Addr + mars_denom: string + owner?: string | null + proposed_new_owner?: string | null +} diff --git a/src/types/generated/mars-incentives/bundle.ts b/src/types/generated/mars-incentives/bundle.ts new file mode 100644 index 00000000..96ab4693 --- /dev/null +++ b/src/types/generated/mars-incentives/bundle.ts @@ -0,0 +1,13 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import * as _3 from './MarsIncentives.types' +import * as _4 from './MarsIncentives.client' +import * as _5 from './MarsIncentives.react-query' +export namespace contracts { + export const MarsIncentives = { ..._3, ..._4, ..._5 } +} diff --git a/src/types/interfaces/asset.d.ts b/src/types/interfaces/asset.d.ts index 1a8085a6..492a1439 100644 --- a/src/types/interfaces/asset.d.ts +++ b/src/types/interfaces/asset.d.ts @@ -42,8 +42,9 @@ interface LendingMarketTableData { marketMaxLtv: number marketLiquidityRate: number marketDepositCap: BigNumber + accountLentAmount?: string marketDepositAmount: BigNumber - accountDepositValue?: BigNumber + accountLentValue?: BigNumber marketLiquidityAmount: BigNumber marketLiquidationThreshold: number } diff --git a/src/types/interfaces/store/broadcast.d.ts b/src/types/interfaces/store/broadcast.d.ts index 8d67a83b..38524275 100644 --- a/src/types/interfaces/store/broadcast.d.ts +++ b/src/types/interfaces/store/broadcast.d.ts @@ -16,6 +16,8 @@ interface BroadcastSlice { deposit: (options: { fee: StdFee; accountId: string; coin: Coin }) => Promise unlock: (options: { fee: StdFee; vault: Vault; amount: string }) => Promise withdraw: (options: { fee: StdFee; accountId: string; coin: Coin }) => Promise + lend: (options: { fee: StdFee; accountId: string; coin: Coin }) => Promise + reclaim: (options: { fee: StdFee; accountId: string; coin: Coin }) => Promise repay: (options: { fee: StdFee accountId: string diff --git a/src/types/interfaces/store/modals.d.ts b/src/types/interfaces/store/modals.d.ts index 4841502a..a605db35 100644 --- a/src/types/interfaces/store/modals.d.ts +++ b/src/types/interfaces/store/modals.d.ts @@ -7,6 +7,13 @@ interface ModalSlice { fundAndWithdrawModal: 'fund' | 'withdraw' | null vaultModal: VaultModal | null unlockModal: UnlockModal | null + lendAndReclaimModal: LendAndReclaimModalConfig | null +} + +type LendAndReclaimModalAction = 'lend' | 'reclaim' +interface LendAndReclaimModalConfig { + data: LendingMarketTableData + action: LendAndReclaimModalAction } interface BorrowModal { diff --git a/src/utils/contants.ts b/src/utils/constants.ts similarity index 80% rename from src/utils/contants.ts rename to src/utils/constants.ts index b98a0cc8..493820fa 100644 --- a/src/utils/contants.ts +++ b/src/utils/constants.ts @@ -9,3 +9,5 @@ export const hardcodedFee = { ], gas: '5000000', } + +export const SECONDS_IN_A_YEAR = 31540000 diff --git a/src/utils/resolvers.ts b/src/utils/resolvers.ts index 5b91659c..e2ac99bc 100644 --- a/src/utils/resolvers.ts +++ b/src/utils/resolvers.ts @@ -16,7 +16,11 @@ export function resolvePositionResponse(response: CreditManagerPosition): Accoun } export function resolveMarketResponses(responses: RedBankMarket[]): Market[] { - return responses.map((response) => ({ + return responses.map(resolveMarketResponse) +} + +export function resolveMarketResponse(response: RedBankMarket): Market { + return { denom: response.denom, borrowRate: Number(response.borrow_rate), debtTotalScaled: response.debt_total_scaled, @@ -27,5 +31,5 @@ export function resolveMarketResponses(responses: RedBankMarket[]): Market[] { maxLtv: Number(response.max_loan_to_value), liquidityRate: Number(response.liquidity_rate), liquidationThreshold: Number(response.liquidation_threshold), - })) + } }