diff --git a/src/apis/kord-fi/index.ts b/src/apis/kord-fi/index.ts index f3f0bd65a..50f4fa39a 100644 --- a/src/apis/kord-fi/index.ts +++ b/src/apis/kord-fi/index.ts @@ -1,5 +1,6 @@ import axios from 'axios'; import { BigNumber } from 'bignumber.js'; +import { isEqual } from 'lodash-es'; import { Observable, catchError, from, map, of } from 'rxjs'; import { EarnOpportunityTokenStandardEnum } from 'src/enums/earn-opportunity-token-standard.enum'; @@ -61,7 +62,9 @@ const getKordFiStats$ = (): Observable => ) ); -export const getKordFiUserDeposits$ = (address: string): Observable<{ [key: string]: UserStakeValueInterface }> => +export const getKordFiUserDeposits$ = ( + address: string +): Observable<{ [key: string]: UserStakeValueInterface | null }> => from(kordFiApi.post('/llb-api/user-deposits/', { address })).pipe( map(({ data: { xtz_deposit, tzbtc_deposit } }) => { const tezosDepositAmountAtomic = tzToMutez(new BigNumber(xtz_deposit), TEZ_TOKEN_METADATA.decimals).toFixed(); @@ -88,9 +91,17 @@ export const getKordFiUserDeposits$ = (address: string): Observable<{ [key: stri }; }), catchError(error => { + if ( + axios.isAxiosError(error) && + error.response?.status === 404 && + isEqual(error.response.data, { detail: 'User not found' }) + ) { + return of({ [KORDFI_TEZOS_CONTRACT_ADDRESS]: null, [TZBTC_CONTRACT_ADDRESS]: null }); + } + console.error('Error getting Kord.Fi user deposits: ', error); - return of({}); + throw error; }) ); diff --git a/src/apis/youves/index.ts b/src/apis/youves/index.ts index 36de2794b..eab3d8a24 100644 --- a/src/apis/youves/index.ts +++ b/src/apis/youves/index.ts @@ -5,6 +5,7 @@ import { catchError, firstValueFrom, forkJoin, from, map, Observable, of } from import { EarnOpportunityTypeEnum } from 'src/enums/earn-opportunity-type.enum'; import { AccountInterface } from 'src/interfaces/account.interface'; import { SavingsItem } from 'src/interfaces/earn-opportunity/savings-item.interface'; +import { UserStakeValueInterface } from 'src/interfaces/user-stake-value.interface'; import { ExchangeRateRecord } from 'src/store/currency/currency-state'; import { KNOWN_TOKENS_SLUGS } from 'src/token/data/token-slugs'; import { toTokenSlug } from 'src/token/utils/token.utils'; @@ -15,17 +16,12 @@ import { isString } from 'src/utils/is-string'; import { tzktUrl } from 'src/utils/linking'; import { fractionToPercentage } from 'src/utils/percentage.utils'; import { getReadOnlyContract } from 'src/utils/rpc/contract.utils'; +import { createReadOnlyTezosToolkit } from 'src/utils/rpc/tezos-toolkit.utils'; import { mutezToTz } from 'src/utils/tezos.util'; import { INITIAL_APR_VALUE } from './constants'; import { SavingsPoolStorage } from './types'; -import { - createEngineMemoized, - createUnifiedSavings, - createUnifiedStaking, - getTezosToolkit, - toEarnOpportunityToken -} from './utils'; +import { createEngineMemoized, createUnifiedSavings, createUnifiedStaking, toEarnOpportunityToken } from './utils'; export const getYOUTokenApr$ = ( assetToUsdExchangeRate: BigNumber, @@ -62,7 +58,7 @@ const getYOUTokenSavingItem = async ( rpcUrl: string ): Promise => { try { - const tezos = getTezosToolkit(rpcUrl); + const tezos = createReadOnlyTezosToolkit(rpcUrl); const unifiedStaking = createUnifiedStaking(rpcUrl); const apr = await firstValueFrom(getYOUTokenApr$(youToUsdExchangeRate, youToUsdExchangeRate, rpcUrl)); const savingsContract = await getReadOnlyContract(unifiedStaking.stakingContract, tezos); @@ -108,7 +104,7 @@ const getSavingsItemByAssetDefinition = async ( rpcUrl: string ): Promise => { try { - const tezos = getTezosToolkit(rpcUrl); + const tezos = createReadOnlyTezosToolkit(rpcUrl); const { id, token, SAVINGS_V3_POOL_ADDRESS } = assetDefinition; const { decimals: tokenDecimals, contractAddress: tokenAddress, tokenId } = token; const apr = await firstValueFrom(getYouvesTokenApr$(assetDefinition, rpcUrl)); @@ -163,7 +159,7 @@ export const getUserStake = async ( stakingOrSavingId: string, type: EarnOpportunityTypeEnum, rpcUrl: string -) => { +): Promise => { const assetDefinition = contracts.mainnet.find(({ id }) => id === stakingOrSavingId); let lastStake: UnifiedStakeExtendedItem | undefined; @@ -196,13 +192,13 @@ export const getUserStake = async ( throw new Error('Unsupported savings type'); } - return ( - lastStake && { - lastStakeId: lastStake.id.toFixed(), - depositAmountAtomic: lastStake.token_amount.toFixed(), - claimableRewards: lastStake.rewardNow.toFixed(), - fullReward: lastStake.rewardTotal.toFixed(), - rewardsDueDate: new Date(lastStake.endTimestamp).getTime() - } - ); + return lastStake + ? { + lastStakeId: lastStake.id.toFixed(), + depositAmountAtomic: lastStake.token_amount.toFixed(), + claimableRewards: lastStake.rewardNow.toFixed(), + fullReward: lastStake.rewardTotal.toFixed(), + rewardsDueDate: new Date(lastStake.endTimestamp).getTime() + } + : null; }; diff --git a/src/apis/youves/utils.ts b/src/apis/youves/utils.ts index 79ac12870..db713278c 100644 --- a/src/apis/youves/utils.ts +++ b/src/apis/youves/utils.ts @@ -1,4 +1,3 @@ -import { TezosToolkit } from '@taquito/taquito'; import { createEngine, contracts, @@ -17,8 +16,6 @@ import memoize from 'memoizee'; import { EarnOpportunityTokenStandardEnum } from 'src/enums/earn-opportunity-token-standard.enum'; import { AccountInterface } from 'src/interfaces/account.interface'; -import { isDefined } from 'src/utils/is-defined'; -import { getFastRpcClient } from 'src/utils/rpc/fast-rpc'; import { createReadOnlyTezosToolkit } from 'src/utils/rpc/tezos-toolkit.utils'; import { INDEXER_CONFIG, YOUVES_TOKENS_ICONS } from './constants'; @@ -57,19 +54,13 @@ class MemoryStorage implements Storage { } } -export const getTezosToolkit = memoize( - (rpcUrl: string, account?: AccountInterface) => - isDefined(account) ? createReadOnlyTezosToolkit(rpcUrl, account) : new TezosToolkit(getFastRpcClient(rpcUrl)), - { normalizer: ([rpcUrl, account]) => [rpcUrl, account?.publicKey].join('_') } -); - const getCreateEngineCacheKey = (rpcUrl: string, token: AssetDefinition, account?: AccountInterface) => [rpcUrl, token.id, account?.publicKey].join('_'); export const createEngineMemoized = memoize( (rpcUrl: string, token: AssetDefinition, account?: AccountInterface) => createEngine({ - tezos: getTezosToolkit(rpcUrl, account), + tezos: createReadOnlyTezosToolkit(rpcUrl, account), contracts: token, storage: new MemoryStorage(), indexerConfig: INDEXER_CONFIG, @@ -109,7 +100,7 @@ const getCreateUnifiedStakingCacheKey = (rpcUrl: string, account?: AccountInterf export const createUnifiedStaking = memoize( (rpcUrl: string, account?: AccountInterface) => - new UnifiedStaking(getTezosToolkit(rpcUrl, account), INDEXER_CONFIG, mainnetNetworkConstants), + new UnifiedStaking(createReadOnlyTezosToolkit(rpcUrl, account), INDEXER_CONFIG, mainnetNetworkConstants), { normalizer: ([rpcUrl, account]) => getCreateUnifiedStakingCacheKey(rpcUrl, account) } diff --git a/src/components/earn-opportunities-main-info/index.tsx b/src/components/earn-opportunities-main-info/index.tsx index 5d5e26f18..a946173af 100644 --- a/src/components/earn-opportunities-main-info/index.tsx +++ b/src/components/earn-opportunities-main-info/index.tsx @@ -1,16 +1,17 @@ import { BigNumber } from 'bignumber.js'; import { noop } from 'lodash-es'; -import React, { FC } from 'react'; +import React, { memo } from 'react'; import { View, Text } from 'react-native'; import { Button } from 'src/components/button/button'; import { Divider } from 'src/components/divider/divider'; -import { FormattedAmount } from 'src/components/formatted-amount'; import { PERCENTAGE_DECIMALS } from 'src/config/earn-opportunities'; import { DEFAULT_AMOUNT, PENNY } from 'src/config/earn-opportunities-main-info'; -import { EmptyFn } from 'src/config/general'; import { useCurrentFiatCurrencyMetadataSelector } from 'src/store/settings/settings-selectors'; import { formatSize } from 'src/styles/format-size'; +import { formatOptionalPercentage } from 'src/utils/earn-opportunities/format.utils'; + +import { OptionalFormattedAmount } from '../optional-formatted-amount'; import { useEarnOpportunitiesMainInfoStyles, useButtonPrimaryStyleConfig } from './styles'; @@ -18,54 +19,59 @@ interface Props { claimAllRewards?: EmptyFn; shouldShowClaimRewardsButton?: boolean; totalClaimableRewardsInFiat?: BigNumber; - netApr: BigNumber; - totalStakedAmountInFiat: BigNumber; + netApr?: BigNumber; + totalStakedAmountInFiat?: BigNumber; areRewardsClaimable?: boolean; } -export const EarnOpportunitiesMainInfo: FC = ({ - claimAllRewards = noop, - shouldShowClaimRewardsButton = false, - totalClaimableRewardsInFiat = DEFAULT_AMOUNT, - netApr, - totalStakedAmountInFiat, - areRewardsClaimable = false -}) => { - const styles = useEarnOpportunitiesMainInfoStyles(); - const buttonPrimaryStylesConfig = useButtonPrimaryStyleConfig(); - const { symbol: fiatSymbol } = useCurrentFiatCurrencyMetadataSelector(); - const roundingMode = totalClaimableRewardsInFiat?.isLessThan(PENNY) ? BigNumber.ROUND_UP : undefined; +export const EarnOpportunitiesMainInfo = memo( + ({ + claimAllRewards = noop, + shouldShowClaimRewardsButton = false, + totalClaimableRewardsInFiat = DEFAULT_AMOUNT, + netApr, + totalStakedAmountInFiat, + areRewardsClaimable = false + }) => { + const styles = useEarnOpportunitiesMainInfoStyles(); + const buttonPrimaryStylesConfig = useButtonPrimaryStyleConfig(); + const { symbol: fiatSymbol } = useCurrentFiatCurrencyMetadataSelector(); + const roundingMode = totalClaimableRewardsInFiat?.isLessThan(PENNY) ? BigNumber.ROUND_UP : undefined; - return ( - - - - - CURRENT DEPOSIT AMOUNT - - - - - NET APR - {netApr.toFixed(PERCENTAGE_DECIMALS)}% + return ( + + + + + CURRENT DEPOSIT AMOUNT + + + + + NET APR + {formatOptionalPercentage(netApr)} + + {shouldShowClaimRewardsButton && ( + <> + +