Skip to content

Commit 849a0e3

Browse files
authored
Merge pull request #5452 from EdgeApp/sam/earn-staking-button-bug
Earn button fixes
2 parents bb2cb1d + 5a23422 commit 849a0e3

File tree

6 files changed

+75
-109
lines changed

6 files changed

+75
-109
lines changed

src/__tests__/components/__snapshots__/TransactionListTop.test.tsx.snap

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -742,15 +742,6 @@ exports[`TransactionListTop should render (with ENABLE_VISA_PROGRAM) 1`] = `
742742
>
743743
<ActivityIndicator
744744
color="#00f1a2"
745-
style={
746-
{
747-
"alignItems": "center",
748-
"flexDirection": "row",
749-
"justifyContent": "center",
750-
"minWidth": 67,
751-
"paddingRight": 22,
752-
}
753-
}
754745
/>
755746
</View>
756747
</View>
@@ -1805,15 +1796,6 @@ exports[`TransactionListTop should render 1`] = `
18051796
>
18061797
<ActivityIndicator
18071798
color="#00f1a2"
1808-
style={
1809-
{
1810-
"alignItems": "center",
1811-
"flexDirection": "row",
1812-
"justifyContent": "center",
1813-
"minWidth": 67,
1814-
"paddingRight": 22,
1815-
}
1816-
}
18171799
/>
18181800
</View>
18191801
</View>

src/components/charts/SwipeChart.tsx

Lines changed: 52 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { CursorProps, GradientProps, SlideAreaChart, ToolTipProps, ToolTipTextRenderersInput, YAxisProps } from '@connectedcars/react-native-slide-charts'
2-
import { asArray, asEither, asNumber, asObject, asString, asTuple } from 'cleaners'
2+
import { asArray, asMaybe, asNumber, asObject, asString, asTuple } from 'cleaners'
33
import * as React from 'react'
44
import { Dimensions, LayoutChangeEvent, Platform, View } from 'react-native'
55
import { cacheStyles } from 'react-native-patina'
@@ -14,9 +14,9 @@ import { formatFiatString } from '../../hooks/useFiatText'
1414
import { useHandler } from '../../hooks/useHandler'
1515
import { formatDate } from '../../locales/intl'
1616
import { lstrings } from '../../locales/strings'
17+
import { snooze } from '../../util/utils'
1718
import { MinimalButton } from '../buttons/MinimalButton'
1819
import { FillLoader } from '../progress-indicators/FillLoader'
19-
import { showWarning } from '../services/AirshipInstance'
2020
import { Theme, useTheme } from '../services/ThemeContext'
2121
import { ReText } from '../text/ReText'
2222
import { EdgeText } from '../themed/EdgeText'
@@ -53,8 +53,6 @@ const asCoinGeckoMarketChartRange = asObject<CoinGeckoMarketChartRange>({
5353
total_volumes: asArray(asCoinGeckoDataPair)
5454
})
5555

56-
const asCoinGeckoMarketApi = asEither(asCoinGeckoMarketChartRange, asCoinGeckoError)
57-
5856
const COINGECKO_URL = 'https://api.coingecko.com'
5957
const COINGECKO_URL_PRO = 'https://pro-api.coingecko.com'
6058
const MARKET_CHART_ENDPOINT_4S = '/api/v3/coins/%1$s/market_chart/range?vs_currency=%2$s&from=%3$s&to=%4$s'
@@ -130,7 +128,7 @@ const reduceChartData = (chartData: ChartDataPoint[], timespan: Timespan): Chart
130128
const SwipeChartComponent = (params: Props) => {
131129
const theme = useTheme()
132130
const styles = getStyles(theme)
133-
const { assetId, currencyCode, fiatCurrencyCode } = params
131+
const { assetId, fiatCurrencyCode } = params
134132

135133
// #region Chart setup
136134

@@ -190,59 +188,57 @@ const SwipeChartComponent = (params: Props) => {
190188
sMinMaxOpacity.value = withDelay(ANIMATION_DURATION.maxMinFadeInDelay, withTiming(1, { duration: ANIMATION_DURATION.maxMinFadeIn }))
191189
}
192190

193-
try {
194-
if (cachedChartData != null) {
195-
// The chart price line animation is slow when transitioning directly
196-
// between datasets.
197-
// Add a delay so the component can get re-mounted with fresh data
198-
// instead.
199-
setTimeout(() => {
200-
setChartData(cachedChartData)
201-
setIsLoading(false)
202-
delayShowMinMaxLabels()
203-
}, 10)
204-
} else {
205-
const unixNow = Math.trunc(new Date().getTime() / 1000)
206-
const fromParam = unixNow - queryFromTimeOffset
207-
const fetchPath = sprintf(MARKET_CHART_ENDPOINT_4S, assetId, fiatCurrencyCode, fromParam, unixNow)
208-
// Start with the free base URL
209-
let fetchUrl = `${COINGECKO_URL}${fetchPath}`
210-
do {
211-
// Construct the dataset query
212-
const response = await fetch(fetchUrl)
213-
const result = await response.json()
214-
const marketChartRange = asCoinGeckoMarketApi(result)
215-
if ('status' in marketChartRange) {
216-
if (marketChartRange.status.error_code === 429) {
217-
// Rate limit error, use our API key as a fallback
218-
if (!fetchUrl.includes('x_cg_pro_api_key')) {
219-
fetchUrl = `${COINGECKO_URL_PRO}${fetchPath}&x_cg_pro_api_key=${ENV.COINGECKO_API_KEY}`
220-
continue
221-
}
222-
} else {
223-
throw new Error(JSON.stringify(marketChartRange))
191+
if (cachedChartData != null) {
192+
// The chart price line animation is slow when transitioning directly
193+
// between datasets.
194+
// Add a delay so the component can get re-mounted with fresh data
195+
// instead.
196+
setTimeout(() => {
197+
setChartData(cachedChartData)
198+
setIsLoading(false)
199+
delayShowMinMaxLabels()
200+
}, 10)
201+
} else {
202+
const unixNow = Math.trunc(new Date().getTime() / 1000)
203+
const fromParam = unixNow - queryFromTimeOffset
204+
const fetchPath = sprintf(MARKET_CHART_ENDPOINT_4S, assetId, fiatCurrencyCode, fromParam, unixNow)
205+
// Start with the free base URL
206+
let fetchUrl = `${COINGECKO_URL}${fetchPath}`
207+
do {
208+
// Construct the dataset query
209+
const response = await fetch(fetchUrl)
210+
const result = await response.json()
211+
const apiError = asMaybe(asCoinGeckoError)(result)
212+
if (apiError != null) {
213+
if (apiError.status.error_code === 429) {
214+
// Rate limit error, use our API key as a fallback
215+
if (!fetchUrl.includes('x_cg_pro_api_key') && ENV.COINGECKO_API_KEY !== '') {
216+
fetchUrl = `${COINGECKO_URL_PRO}${fetchPath}&x_cg_pro_api_key=${ENV.COINGECKO_API_KEY}`
224217
}
225-
} else {
226-
const rawChartData = marketChartRange.prices.map(rawDataPoint => {
227-
return {
228-
x: new Date(rawDataPoint[0]),
229-
y: rawDataPoint[1]
230-
}
231-
})
232-
const reducedChartData = reduceChartData(rawChartData, selectedTimespan)
233-
234-
setChartData(reducedChartData)
235-
cachedTimespanChartData.set(selectedTimespan, reducedChartData)
236-
setCachedChartData(cachedTimespanChartData)
237-
setIsLoading(false)
238-
delayShowMinMaxLabels()
239-
break
218+
// Wait 2 second before retrying. It typically takes 1 minute
219+
// before rate limiting is relieved, so even 2 seconds is hasty.
220+
await snooze(2000)
221+
continue
222+
}
223+
throw new Error(`Failed to fetch market data: ${apiError.status.error_code} ${apiError.status.error_message}`)
224+
}
225+
226+
const marketChartRange = asCoinGeckoMarketChartRange(result)
227+
const rawChartData = marketChartRange.prices.map(rawDataPoint => {
228+
return {
229+
x: new Date(rawDataPoint[0]),
230+
y: rawDataPoint[1]
240231
}
241-
} while (true)
242-
}
243-
} catch (e: any) {
244-
showWarning(`Failed to retrieve market data for ${currencyCode}.`)
245-
console.error(JSON.stringify(e))
232+
})
233+
const reducedChartData = reduceChartData(rawChartData, selectedTimespan)
234+
235+
setChartData(reducedChartData)
236+
cachedTimespanChartData.set(selectedTimespan, reducedChartData)
237+
setCachedChartData(cachedTimespanChartData)
238+
setIsLoading(false)
239+
delayShowMinMaxLabels()
240+
break
241+
} while (true)
246242
}
247243
}
248244
// eslint-disable-next-line react-hooks/exhaustive-deps

src/components/scenes/CoinRankingDetailsScene.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ const CoinRankingDetailsSceneComponent = (props: Props) => {
178178
*/
179179
const stakingWallets = matchingWallets.filter(wallet => {
180180
return (
181-
isStakingSupported(wallet.currencyInfo.pluginId, currencyCode) &&
181+
isStakingSupported(wallet.currencyInfo.pluginId) &&
182182
walletStakingStateMap[wallet.id] != null &&
183183
filterStakePolicies(
184184
Object.values(walletStakingStateMap[wallet.id].stakePolicies).map(stakePolicy => stakePolicy),
@@ -201,7 +201,7 @@ const CoinRankingDetailsSceneComponent = (props: Props) => {
201201
if (coinRankingData != null && matchingWallets.length > 0) {
202202
// Start with a looser filter that does not include the stake policy,
203203
// because it has not yet been initialized
204-
const uninitializedStakingWallets = matchingWallets.filter(wallet => isStakingSupported(wallet.currencyInfo.pluginId, currencyCode))
204+
const uninitializedStakingWallets = matchingWallets.filter(wallet => isStakingSupported(wallet.currencyInfo.pluginId))
205205

206206
for (const wallet of uninitializedStakingWallets) {
207207
if (walletStakingStateMap[wallet.id] != null && Object.keys(walletStakingStateMap[wallet.id].stakePolicies).length > 0) continue

src/components/themed/TransactionListTop.tsx

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,7 @@ export class TransactionListTopComponent extends React.PureComponent<Props, Stat
489489
const styles = getStyles(theme)
490490
const { countryCode } = this.state
491491

492-
const showStaking = this.isStakingAvailable()
492+
const hideStaking = !isStakingSupported(this.props.wallet.currencyInfo.pluginId)
493493
const bestApyText = getBestApyText(Object.values(walletStakingState.stakePolicies))
494494

495495
return (
@@ -500,32 +500,29 @@ export class TransactionListTopComponent extends React.PureComponent<Props, Stat
500500
<IconButton label={lstrings.fragment_send_subtitle} onPress={this.handleSend}>
501501
<Ionicons name="arrow-up" size={theme.rem(2)} color={theme.primaryText} />
502502
</IconButton>
503-
<IconButton
504-
disabled={this.props.walletStakingState.isLoading}
505-
label={getUkCompliantString(countryCode, 'stake_earn_button_label')}
506-
onPress={this.handleStakePress}
507-
superscriptLabel={bestApyText}
508-
>
509-
{this.props.walletStakingState.isLoading ? (
510-
<ActivityIndicator color={theme.textLink} style={styles.stakingButton} />
511-
) : !showStaking ? null : (
512-
<Feather name="percent" size={theme.rem(1.75)} color={theme.primaryText} />
513-
)}
514-
</IconButton>
503+
{hideStaking ? null : (
504+
<IconButton
505+
disabled={this.props.walletStakingState.isLoading}
506+
label={getUkCompliantString(countryCode, 'stake_earn_button_label')}
507+
onPress={this.handleStakePress}
508+
superscriptLabel={bestApyText}
509+
>
510+
{this.props.walletStakingState.isLoading ? (
511+
<ActivityIndicator color={theme.textLink} />
512+
) : (
513+
<Feather name="percent" size={theme.rem(1.75)} color={theme.primaryText} />
514+
)}
515+
</IconButton>
516+
)}
515517
<IconButton label={lstrings.trade_currency} onPress={this.handleTrade}>
516518
<Ionicons name="swap-horizontal" size={theme.rem(2)} color={theme.primaryText} />
517519
</IconButton>
518520
</View>
519521
)
520522
}
521523

522-
isStakingAvailable = (): boolean => {
523-
const { currencyCode, wallet } = this.props
524-
const { pluginId } = wallet.currencyInfo
525-
526-
const isStakingPolicyAvailable = Object.keys(this.props.walletStakingState.stakePolicies).length > 0
527-
528-
return isStakingPolicyAvailable && isStakingSupported(pluginId, currencyCode)
524+
isStakingPolicyAvailable = (): boolean => {
525+
return Object.keys(this.props.walletStakingState.stakePolicies).length > 0
529526
}
530527

531528
/** Return the best APY found, defaulting to 1 decimal place, rounding to the
@@ -601,7 +598,7 @@ export class TransactionListTopComponent extends React.PureComponent<Props, Stat
601598

602599
render() {
603600
const { wallet, isEmpty, searching, theme, tokenId, navigation } = this.props
604-
const showStakedBalance = this.isStakingAvailable()
601+
const showStakedBalance = this.isStakingPolicyAvailable()
605602
const styles = getStyles(theme)
606603

607604
return (
@@ -732,13 +729,6 @@ const getStyles = cacheStyles((theme: Theme) => ({
732729
maxWidth: '70%',
733730
fontSize: theme.rem(1)
734731
},
735-
stakingButton: {
736-
flexDirection: 'row',
737-
justifyContent: 'center',
738-
alignItems: 'center',
739-
minWidth: theme.rem(3),
740-
paddingRight: theme.rem(1)
741-
},
742732

743733
// TODO: Fix SceneHeader to be UI4 compatible
744734
// This negative margin will cause the SceneHeader's divider-line to touch

src/envConfig.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export const asEnvConfig = asObject({
3434
EDGE_API_KEY: asOptional(asString, ''),
3535
EDGE_API_SECRET: asOptional(asBase16),
3636

37-
COINGECKO_API_KEY: asOptional(asString, 'a0000000000000000000000000000000'),
37+
COINGECKO_API_KEY: asOptional(asString, ''),
3838
IP_API_KEY: asOptional(asString, ''),
3939
SENTRY_DSN_URL: asOptional(asString, 'SENTRY_DSN_URL'),
4040
SENTRY_MAP_UPLOAD_URL: asOptional(asString, 'SENTRY_MAP_UPLOAD_URL'),

src/util/stakeUtils.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,7 @@ export const getBestApyText = (stakePolicies?: StakePolicy[]): string | undefine
162162
/**
163163
* Returns true if staking is supported for the given currency code and
164164
* pluginId.
165-
* NOTE: currencyCode is ONLY checked against 'FIO'!
166165
*/
167-
export const isStakingSupported = (pluginId: string, currencyCode: string): boolean => {
168-
// Special case for FIO because it uses it's own staking plugin
169-
return currencyCode.toUpperCase() === 'FIO' || SPECIAL_CURRENCY_INFO[pluginId]?.isStakingSupported === true
166+
export const isStakingSupported = (pluginId: string): boolean => {
167+
return SPECIAL_CURRENCY_INFO[pluginId]?.isStakingSupported === true
170168
}

0 commit comments

Comments
 (0)