Skip to content

Commit

Permalink
Merge pull request #4422 from EdgeApp/jon/fio-req-denom
Browse files Browse the repository at this point in the history
- fixed: Properly factor user currency display denomination settings in FIO requests.
  • Loading branch information
Jon-edge authored Aug 31, 2023
2 parents 4df8879 + 5954b75 commit 855c3a9
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 107 deletions.
30 changes: 1 addition & 29 deletions src/components/text/CryptoText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ import { EdgeCurrencyWallet } from 'edge-core-js'
import * as React from 'react'

import { useCryptoText } from '../../hooks/useCryptoText'
import { useTokenDisplayData } from '../../hooks/useTokenDisplayData'
import { getDisplayDenomination } from '../../selectors/DenominationSelectors'
import { useSelector } from '../../types/reactRedux'
import { EdgeText } from '../themed/EdgeText'

interface Props {
Expand All @@ -23,32 +20,7 @@ interface Props {
* 3. Localization: commas, decimals, spaces
**/
export const CryptoText = React.memo(({ wallet, tokenId, nativeAmount, withSymbol }: Props) => {
const cryptoText = useCryptoTextSimple({ wallet, tokenId, nativeAmount, withSymbol })
const cryptoText = useCryptoText({ wallet, tokenId, nativeAmount, withSymbol })

return <EdgeText>{cryptoText}</EdgeText>
})

export const useCryptoTextSimple = ({ wallet, tokenId, nativeAmount, withSymbol }: Props): string => {
const {
denomination: exchangeDenomination,
fiatDenomination,
assetToFiatRate
} = useTokenDisplayData({
tokenId,
wallet
})
const displayDenomination = useSelector(state =>
getDisplayDenomination(state, wallet.currencyInfo.pluginId, exchangeDenomination.name ?? wallet.currencyInfo.currencyCode)
)

const cryptoText = useCryptoText({
displayDenomination,
exchangeDenomination,
exchangeRate: assetToFiatRate,
fiatDenomination,
nativeAmount,
currencyCode: withSymbol ? undefined : displayDenomination.name
})

return cryptoText
}
27 changes: 18 additions & 9 deletions src/components/themed/FioRequestRow.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { mul } from 'biggystring'
import { mul, toFixed } from 'biggystring'
import { EdgeDenomination } from 'edge-core-js'
import * as React from 'react'
import { StyleSheet, TouchableOpacity, View } from 'react-native'
Expand All @@ -10,10 +10,11 @@ import { formatNumber, formatTime } from '../../locales/intl'
import { lstrings } from '../../locales/strings'
import { convertEdgeToFIOCodes, convertFIOToEdgeCodes } from '../../modules/FioAddress/util'
import { isRejectedFioRequest, isSentFioRequest } from '../../modules/FioRequest/util'
import { getDisplayDenomination } from '../../selectors/DenominationSelectors'
import { getDisplayDenomination, getExchangeDenomination } from '../../selectors/DenominationSelectors'
import { getSelectedCurrencyWallet } from '../../selectors/WalletSelectors'
import { connect } from '../../types/reactRedux'
import { FioRequest } from '../../types/types'
import { getCryptoText } from '../../util/cryptoTextUtils'
import { SwipeableRowIcon } from '../icons/SwipeableRowIcon'
import { showError } from '../services/AirshipInstance'
import { cacheStyles, Theme, ThemeProps, withTheme } from '../services/ThemeContext'
Expand All @@ -34,6 +35,7 @@ interface StateProps {
fiatSymbol: string
fiatAmount: string
displayDenomination: EdgeDenomination
exchangeDenomination: EdgeDenomination
}

type Props = OwnProps & StateProps & ThemeProps
Expand Down Expand Up @@ -85,11 +87,14 @@ class FioRequestRowComponent extends React.PureComponent<Props> {
}

render() {
const { displayDenomination, fiatAmount, fiatSymbol, fioRequest, isSent, theme } = this.props
const { displayDenomination, exchangeDenomination, fiatAmount, fiatSymbol, fioRequest, isSent, theme } = this.props
const styles = getStyles(theme)

const fiatValue = `${fiatSymbol} ${fiatAmount}`
const currencyValue = `${displayDenomination.symbol || ''} ${fioRequest.content.amount}`
const fiatText = `${fiatSymbol} ${fiatAmount}`
let nativeAmount = mul(fioRequest.content.amount, exchangeDenomination.multiplier)
nativeAmount = toFixed(nativeAmount, 0, 0)
const cryptoText = `${getCryptoText({ displayDenomination, exchangeDenomination, nativeAmount })}`

// time_stamp is returned as UTC but doesn't always include the zulu
const safeDate = fioRequest.time_stamp.includes('Z') ? fioRequest.time_stamp : `${fioRequest.time_stamp}Z`
const dateValue = `${formatTime(new Date(safeDate))} ${fioRequest.content.memo ? `- ${fioRequest.content.memo}` : ''}`
Expand Down Expand Up @@ -118,13 +123,13 @@ class FioRequestRowComponent extends React.PureComponent<Props> {
<View style={styles.requestRight}>
<View style={styles.requestDetailsRow}>
<EdgeText style={styles.name}>{isSent ? fioRequest.payer_fio_address : fioRequest.payee_fio_address}</EdgeText>
<EdgeText style={styles.requestAmount}>{currencyValue}</EdgeText>
<EdgeText style={styles.requestAmount}>{cryptoText}</EdgeText>
</View>
<View style={styles.requestDetailsRow}>
<EdgeText ellipsizeMode="tail" numberOfLines={1} style={[styles.requestPendingTime, styles.requestTime]}>
{dateValue}
</EdgeText>
<EdgeText style={styles.requestFiat}>{fiatValue}</EdgeText>
<EdgeText style={styles.requestFiat}>{fiatText}</EdgeText>
</View>
<View style={styles.requestDetailsRow}>{isSent ? this.showStatus(fioRequest.status) : this.requestedField()}</View>
</View>
Expand Down Expand Up @@ -189,15 +194,17 @@ const getStyles = cacheStyles((theme: Theme) => ({
}
}))

const emptyDisplayDenomination = { name: '', multiplier: '0' }
const emptyDenomination = { name: '', multiplier: '0' }

export const FioRequestRow = connect<StateProps, {}, OwnProps>(
(state, ownProps) => {
const { fioRequest } = ownProps
let displayDenomination = emptyDisplayDenomination
let displayDenomination = emptyDenomination
let exchangeDenomination = emptyDenomination
const wallet = getSelectedCurrencyWallet(state)
if (!wallet) {
return {
exchangeDenomination,
displayDenomination,
fiatSymbol: '',
fiatAmount: ''
Expand All @@ -217,6 +224,7 @@ export const FioRequestRow = connect<StateProps, {}, OwnProps>(
const { tokenCode: edgeTokenCode } = convertFIOToEdgeCodes(pluginId, fioRequest.content.chain_code.toUpperCase(), tokenCode)
tokenCode = edgeTokenCode
displayDenomination = getDisplayDenomination(state, pluginId, tokenCode)
exchangeDenomination = getExchangeDenomination(state, pluginId, tokenCode)
} catch (e: any) {
console.log('No denomination for this Token Code -', tokenCode)
}
Expand All @@ -229,6 +237,7 @@ export const FioRequestRow = connect<StateProps, {}, OwnProps>(
const fiatAmount = formatNumber(mul(fiatPerCrypto, fioRequest.content.amount), { toFixed: 2 }) || '0'

return {
exchangeDenomination,
displayDenomination,
fiatSymbol,
fiatAmount
Expand Down
4 changes: 2 additions & 2 deletions src/components/tiles/NetworkFeeTile.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { EdgeCurrencyWallet } from 'edge-core-js'
import * as React from 'react'

import { useCryptoText } from '../../hooks/useCryptoText'
import { useFiatText } from '../../hooks/useFiatText'
import { lstrings } from '../../locales/strings'
import { getDisplayDenomination, getExchangeDenomination } from '../../selectors/DenominationSelectors'
import { useSelector } from '../../types/reactRedux'
import { getCryptoText } from '../../util/cryptoTextUtils'
import { getDenomFromIsoCode } from '../../util/utils'
import { Tile } from './Tile'

Expand All @@ -30,7 +30,7 @@ export const NetworkFeeTile = (props: { wallet: EdgeCurrencyWallet; nativeAmount
const displayDenominationName = useSelector(state => getDisplayDenomination(state, pluginId, currencyCode).name)
const displayDenominationSymbol = useSelector(state => getDisplayDenomination(state, pluginId, currencyCode).symbol ?? '')

const feeCryptoAmount = useCryptoText({
const feeCryptoAmount = getCryptoText({
displayDenomination: {
multiplier: displayDenominationMultiplier,
name: displayDenominationName,
Expand Down
99 changes: 32 additions & 67 deletions src/hooks/useCryptoText.ts
Original file line number Diff line number Diff line change
@@ -1,73 +1,38 @@
import { div, log10 } from 'biggystring'
import { EdgeDenomination } from 'edge-core-js'
import { EdgeCurrencyWallet } from 'edge-core-js'

import { formatNumber } from '../locales/intl'
import {
DECIMAL_PRECISION,
decimalOrZero,
DEFAULT_TRUNCATE_PRECISION,
maxPrimaryCurrencyConversionDecimals,
precisionAdjust,
truncateDecimals as nonLocalTruncateDecimals,
zeroString
} from '../util/utils'
import { getDisplayDenomination } from '../selectors/DenominationSelectors'
import { useSelector } from '../types/reactRedux'
import { getCryptoText } from '../util/cryptoTextUtils'
import { useTokenDisplayData } from './useTokenDisplayData'

interface UseCryptoTextParams {
displayDenomination: EdgeDenomination
exchangeDenomination: EdgeDenomination
exchangeRate?: string
fiatDenomination: EdgeDenomination
interface Props {
nativeAmount: string
currencyCode?: string
tokenId?: string
wallet: EdgeCurrencyWallet
withSymbol?: boolean
}

/**
* Get the numeric crypto text string value
* TODO: Break this up once crypto & fiat text display logic is centralized into the appropriate text hooks/components. The order of operations should always be as follows:
* 1. Numeric calculations
* 2. Display Denomination
* 3. Localization: commas, decimals, spaces
*/
export const useCryptoText = ({
displayDenomination,
exchangeDenomination,
fiatDenomination,
exchangeRate,
nativeAmount,
currencyCode
}: UseCryptoTextParams) => {
const { multiplier: displayMultiplier, symbol } = displayDenomination
const { multiplier: exchangeMultiplier } = exchangeDenomination
if (zeroString(nativeAmount)) return `${symbol ? symbol + ' ' : ''}0${currencyCode ? ' ' + currencyCode : ''}`
let maxConversionDecimals = DEFAULT_TRUNCATE_PRECISION

if (exchangeRate != null && parseFloat(exchangeRate) > 0) {
const precisionAdjustValue = precisionAdjust({
primaryExchangeMultiplier: exchangeMultiplier,
secondaryExchangeMultiplier: fiatDenomination.multiplier,
exchangeSecondaryToPrimaryRatio: exchangeRate
})
maxConversionDecimals = maxPrimaryCurrencyConversionDecimals(log10(displayMultiplier), precisionAdjustValue)
}

try {
const preliminaryCryptoAmount = nonLocalTruncateDecimals(div(nativeAmount, displayMultiplier, DECIMAL_PRECISION), maxConversionDecimals)
const finalCryptoAmount = formatNumber(decimalOrZero(preliminaryCryptoAmount, maxConversionDecimals)) // check if infinitesimal (would display as zero), cut off trailing zeroes

if (currencyCode != null) {
// Display with currency code if provided
return `${finalCryptoAmount} ${currencyCode}`
}

// Display with symbol (if available)
return `${symbol != null ? symbol + ' ' : ''}${finalCryptoAmount}`
} catch (error: any) {
if (error.message === 'Cannot operate on base16 float values') {
const errorMessage = `${error.message}: Currency - ${exchangeDenomination.name}, amount - ${nativeAmount}, demonination multiplier: ${displayMultiplier}, exchange multiplier: ${exchangeMultiplier}`
console.error(errorMessage)
}
console.error(error)
}

return ''
export const useCryptoText = ({ wallet, tokenId, nativeAmount, withSymbol }: Props): string => {
const {
denomination: exchangeDenomination,
fiatDenomination,
assetToFiatRate
} = useTokenDisplayData({
tokenId,
wallet
})
const displayDenomination = useSelector(state =>
getDisplayDenomination(state, wallet.currencyInfo.pluginId, exchangeDenomination.name ?? wallet.currencyInfo.currencyCode)
)

const cryptoText = getCryptoText({
displayDenomination,
exchangeDenomination,
exchangeRate: assetToFiatRate,
fiatDenomination,
nativeAmount,
currencyCode: withSymbol ? undefined : displayDenomination.name
})

return cryptoText
}
74 changes: 74 additions & 0 deletions src/util/cryptoTextUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { div, log10 } from 'biggystring'
import { EdgeDenomination } from 'edge-core-js'

import { formatNumber } from '../locales/intl'
import {
DECIMAL_PRECISION,
decimalOrZero,
DEFAULT_TRUNCATE_PRECISION,
maxPrimaryCurrencyConversionDecimals,
precisionAdjust,
truncateDecimals as nonLocalTruncateDecimals,
zeroString
} from './utils'

interface GetCryptoTextParams {
currencyCode?: string
displayDenomination: EdgeDenomination
exchangeDenomination: EdgeDenomination
exchangeRate?: string
fiatDenomination?: EdgeDenomination
nativeAmount: string
}

/**
* Get the numeric crypto text string value, factoring in exchange rate, denominations, etc.
* TODO: Break this up once crypto & fiat text display logic is centralized into the appropriate text hooks/components. The order of operations should always be as follows:
* 1. Numeric calculations
* 2. Display Denomination
* 3. Localization: commas, decimals, spaces
*/
export const getCryptoText = ({
displayDenomination,
exchangeDenomination,
fiatDenomination,
exchangeRate,
nativeAmount,
currencyCode
}: GetCryptoTextParams) => {
const { multiplier: displayMultiplier, symbol } = displayDenomination
const { multiplier: exchangeMultiplier } = exchangeDenomination

if (zeroString(nativeAmount)) return `${symbol ? symbol + ' ' : ''}0${currencyCode ? ' ' + currencyCode : ''}`

let maxConversionDecimals = DEFAULT_TRUNCATE_PRECISION
if (exchangeRate != null && fiatDenomination != null && parseFloat(exchangeRate) > 0) {
const precisionAdjustValue = precisionAdjust({
primaryExchangeMultiplier: exchangeMultiplier,
secondaryExchangeMultiplier: fiatDenomination.multiplier,
exchangeSecondaryToPrimaryRatio: exchangeRate
})
maxConversionDecimals = maxPrimaryCurrencyConversionDecimals(log10(displayMultiplier), precisionAdjustValue)
}

try {
const truncatedCryptoAmount = nonLocalTruncateDecimals(div(nativeAmount, displayMultiplier, DECIMAL_PRECISION), maxConversionDecimals)
const finalCryptoAmount = formatNumber(decimalOrZero(truncatedCryptoAmount, maxConversionDecimals)) // check if infinitesimal (would display as zero), cut off trailing zeroes

if (currencyCode != null) {
// Display with currency code if provided
return `${finalCryptoAmount} ${currencyCode}`
}

// Display with symbol (if available)
return `${symbol != null ? symbol + ' ' : ''}${finalCryptoAmount}`
} catch (error: any) {
if (error.message === 'Cannot operate on base16 float values') {
const errorMessage = `${error.message}: Currency - ${exchangeDenomination.name}, amount - ${nativeAmount}, demonination multiplier: ${displayMultiplier}, exchange multiplier: ${exchangeMultiplier}`
console.error(errorMessage)
}
console.error(error)
}

return ''
}

0 comments on commit 855c3a9

Please sign in to comment.