Skip to content

Commit 1b38595

Browse files
committed
Share the swap price-impact calculation and display
Extract the swap confirmation scene's price-impact computation and the quote card's colored percentage text into PriceImpactText, so the send scene's quote row can reuse the same delta UI.
1 parent 25ae577 commit 1b38595

3 files changed

Lines changed: 123 additions & 69 deletions

File tree

src/components/scenes/SwapConfirmationScene.tsx

Lines changed: 10 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useIsFocused } from '@react-navigation/native'
2-
import { add, div, gt, gte, lte, sub, toFixed } from 'biggystring'
2+
import { add, div, gt, gte, toFixed } from 'biggystring'
33
import type { EdgeSwapQuote, EdgeSwapResult } from 'edge-core-js'
44
import React, { useState } from 'react'
55
import { SectionList, type ViewStyle } from 'react-native'
@@ -51,11 +51,13 @@ import { cacheStyles, type Theme, useTheme } from '../services/ThemeContext'
5151
import { ExchangeQuote } from '../themed/ExchangeQuoteComponent'
5252
import { LineTextDivider } from '../themed/LineTextDivider'
5353
import { ModalFooter } from '../themed/ModalParts'
54+
import {
55+
calculateQuotePriceImpact,
56+
PRICE_IMPACT_WARNING_THRESHOLD
57+
} from '../themed/PriceImpactText'
5458
import { SafeSlider } from '../themed/SafeSlider'
5559
import { WalletListSectionHeader } from '../themed/WalletListSectionHeader'
5660

57-
const PRICE_IMPACT_WARNING_THRESHOLD = 0.05
58-
5961
export interface SwapConfirmationParams {
6062
selectedQuote: EdgeSwapQuote
6163
quotes: EdgeSwapQuote[]
@@ -126,48 +128,11 @@ export const SwapConfirmationScene: React.FC<Props> = (props: Props) => {
126128
const { request } = selectedQuote
127129
const { quoteFor } = request
128130

129-
const priceImpact = React.useMemo(() => {
130-
const { fromWallet, fromTokenId, toTokenId } = request
131-
const toWallet = request.toWallet
132-
if (toWallet == null) {
133-
throw new Error('Swap quote is missing a destination wallet')
134-
}
135-
136-
const fromExchangeDenom = getExchangeDenom(
137-
fromWallet.currencyConfig,
138-
fromTokenId
139-
)
140-
const toExchangeDenom = getExchangeDenom(toWallet.currencyConfig, toTokenId)
141-
142-
const fromExchangeAmount = convertNativeToExchange(
143-
fromExchangeDenom.multiplier
144-
)(selectedQuote.fromNativeAmount)
145-
const toExchangeAmount = convertNativeToExchange(
146-
toExchangeDenom.multiplier
147-
)(selectedQuote.toNativeAmount)
148-
149-
const fromFiatValue = convertCurrency(
150-
exchangeRates,
151-
fromWallet.currencyInfo.pluginId,
152-
fromTokenId,
153-
defaultIsoFiat,
154-
fromExchangeAmount
155-
)
156-
const toFiatValue = convertCurrency(
157-
exchangeRates,
158-
toWallet.currencyInfo.pluginId,
159-
toTokenId,
160-
defaultIsoFiat,
161-
toExchangeAmount
162-
)
163-
164-
if (lte(fromFiatValue, '0')) return undefined
165-
166-
const impact = parseFloat(
167-
div(sub(fromFiatValue, toFiatValue), fromFiatValue, 8)
168-
)
169-
return impact > 0 ? impact : undefined
170-
}, [selectedQuote, exchangeRates, defaultIsoFiat, request])
131+
const priceImpact = React.useMemo(
132+
() =>
133+
calculateQuotePriceImpact(selectedQuote, exchangeRates, defaultIsoFiat),
134+
[selectedQuote, exchangeRates, defaultIsoFiat]
135+
)
171136

172137
const showPriceImpact =
173138
priceImpact != null && priceImpact >= PRICE_IMPACT_WARNING_THRESHOLD

src/components/themed/ExchangeQuoteComponent.tsx

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { View } from 'react-native'
66
import { useCryptoText } from '../../hooks/useCryptoText'
77
import { formatFiatString, useFiatText } from '../../hooks/useFiatText'
88
import { useTokenDisplayData } from '../../hooks/useTokenDisplayData'
9-
import { formatNumber } from '../../locales/intl'
109
import { lstrings } from '../../locales/strings'
1110
import { convertCurrency } from '../../selectors/WalletSelectors'
1211
import { useSelector } from '../../types/reactRedux'
@@ -16,6 +15,7 @@ import { EdgeCard } from '../cards/EdgeCard'
1615
import { CurrencyRow } from '../rows/CurrencyRow'
1716
import { cacheStyles, type Theme, useTheme } from '../services/ThemeContext'
1817
import { EdgeText } from './EdgeText'
18+
import { PriceImpactText } from './PriceImpactText'
1919

2020
interface Props {
2121
fromTo: 'from' | 'to'
@@ -194,17 +194,7 @@ export const ExchangeQuote: React.FC<Props> = props => {
194194

195195
const priceImpactNode =
196196
!isFrom && priceImpact != null && priceImpact > 0 ? (
197-
<EdgeText
198-
style={
199-
priceImpact >= 0.15
200-
? styles.priceImpactHigh
201-
: priceImpact >= 0.05
202-
? styles.priceImpactMedium
203-
: styles.priceImpactLow
204-
}
205-
>
206-
{` (${formatNumber(priceImpact * 100, { toFixed: 2 })}%)`}
207-
</EdgeText>
197+
<PriceImpactText priceImpact={priceImpact} />
208198
) : undefined
209199

210200
return (
@@ -247,17 +237,5 @@ const getStyles = cacheStyles((theme: Theme) => ({
247237
bottomWarningText: {
248238
fontSize: theme.rem(0.75),
249239
color: theme.warningText
250-
},
251-
priceImpactLow: {
252-
fontSize: theme.rem(0.75),
253-
color: theme.deactivatedText
254-
},
255-
priceImpactMedium: {
256-
fontSize: theme.rem(0.75),
257-
color: theme.warningText
258-
},
259-
priceImpactHigh: {
260-
fontSize: theme.rem(0.75),
261-
color: theme.dangerText
262240
}
263241
}))
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { div, lte, sub } from 'biggystring'
2+
import type { EdgeSwapQuote } from 'edge-core-js'
3+
import * as React from 'react'
4+
5+
import type { GuiExchangeRates } from '../../actions/ExchangeRateActions'
6+
import { formatNumber } from '../../locales/intl'
7+
import { getExchangeDenom } from '../../selectors/DenominationSelectors'
8+
import { convertCurrency } from '../../selectors/WalletSelectors'
9+
import { convertNativeToExchange } from '../../util/utils'
10+
import { cacheStyles, type Theme, useTheme } from '../services/ThemeContext'
11+
import { EdgeText } from './EdgeText'
12+
13+
/** Price impacts at or above this warrant a warning. */
14+
export const PRICE_IMPACT_WARNING_THRESHOLD = 0.05
15+
16+
/**
17+
* The fiat-value fraction a swap quote loses between its from and to sides
18+
* (0.05 = 5%), or undefined when it cannot be computed or is not a loss.
19+
* Works for wallet-to-wallet and swap-to-address quotes alike: the quote's
20+
* destination wallet (synthetic for swap-to-address) carries the real
21+
* `currencyConfig`.
22+
*/
23+
export function calculateQuotePriceImpact(
24+
quote: EdgeSwapQuote,
25+
exchangeRates: GuiExchangeRates,
26+
defaultIsoFiat: string
27+
): number | undefined {
28+
const { request, fromNativeAmount, toNativeAmount } = quote
29+
const { fromWallet, fromTokenId, toWallet, toTokenId } = request
30+
if (toWallet == null) return undefined
31+
32+
const fromExchangeDenom = getExchangeDenom(
33+
fromWallet.currencyConfig,
34+
fromTokenId
35+
)
36+
const toExchangeDenom = getExchangeDenom(toWallet.currencyConfig, toTokenId)
37+
38+
const fromExchangeAmount = convertNativeToExchange(
39+
fromExchangeDenom.multiplier
40+
)(fromNativeAmount)
41+
const toExchangeAmount = convertNativeToExchange(toExchangeDenom.multiplier)(
42+
toNativeAmount
43+
)
44+
45+
const fromFiatValue = convertCurrency(
46+
exchangeRates,
47+
fromWallet.currencyInfo.pluginId,
48+
fromTokenId,
49+
defaultIsoFiat,
50+
fromExchangeAmount
51+
)
52+
const toFiatValue = convertCurrency(
53+
exchangeRates,
54+
toWallet.currencyInfo.pluginId,
55+
toTokenId,
56+
defaultIsoFiat,
57+
toExchangeAmount
58+
)
59+
60+
if (lte(fromFiatValue, '0')) return undefined
61+
62+
const impact = parseFloat(
63+
div(sub(fromFiatValue, toFiatValue), fromFiatValue, 8)
64+
)
65+
return impact > 0 ? impact : undefined
66+
}
67+
68+
interface Props {
69+
priceImpact: number | undefined
70+
}
71+
72+
/**
73+
* The colored ` (x.xx%)` price-delta suffix shared by the swap confirmation
74+
* quote card and the send scene's quote row.
75+
*/
76+
export const PriceImpactText: React.FC<Props> = props => {
77+
const { priceImpact } = props
78+
const theme = useTheme()
79+
const styles = getStyles(theme)
80+
81+
if (priceImpact == null || priceImpact <= 0) return null
82+
83+
return (
84+
<EdgeText
85+
style={
86+
priceImpact >= 0.15
87+
? styles.priceImpactHigh
88+
: priceImpact >= PRICE_IMPACT_WARNING_THRESHOLD
89+
? styles.priceImpactMedium
90+
: styles.priceImpactLow
91+
}
92+
>
93+
{` (${formatNumber(priceImpact * 100, { toFixed: 2 })}%)`}
94+
</EdgeText>
95+
)
96+
}
97+
98+
const getStyles = cacheStyles((theme: Theme) => ({
99+
priceImpactLow: {
100+
color: theme.deactivatedText,
101+
fontSize: theme.rem(0.75)
102+
},
103+
priceImpactMedium: {
104+
color: theme.warningText,
105+
fontSize: theme.rem(0.75)
106+
},
107+
priceImpactHigh: {
108+
color: theme.dangerText,
109+
fontSize: theme.rem(0.75)
110+
}
111+
}))

0 commit comments

Comments
 (0)