Skip to content

Commit

Permalink
Merge pull request #5319 from EdgeApp/jon/fix/uk-compliance
Browse files Browse the repository at this point in the history
Jon/fix/uk-compliance
  • Loading branch information
Jon-edge authored Oct 23, 2024
2 parents 3a2aab3 + c6816cf commit c63fe78
Show file tree
Hide file tree
Showing 36 changed files with 191 additions and 98 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

## Unreleased

- changed: Various strings updated to UK compliance spec
- changed: Wording in light account persistent notification
- removed: Bank Wire Transfer Buy for Florida
- removed: Paypal Sell for Canada
- removed: Moonpay, Simplex, and Paybis for UK
- removed: UK persistent investment risk banner

## 4.15.1

Expand Down
12 changes: 10 additions & 2 deletions src/actions/FirstOpenActions.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { asNumber, asObject, asString, asValue } from 'cleaners'
import { asNumber, asObject, asOptional, asString, asValue } from 'cleaners'
import { makeReactNativeDisklet } from 'disklet'

import { FIRST_OPEN } from '../constants/constantSettings'
import { makeUuid } from '../util/rnUtils'
import { getCountryCodeByIp } from './AccountReferralActions'

const firstOpenDisklet = makeReactNativeDisklet()

const asFirstOpenInfo = asObject({
isFirstOpen: asValue('true', 'false'),
deviceId: asString,
firstOpenEpoch: asNumber
firstOpenEpoch: asNumber,
countryCode: asOptional(asString)
})
type FirstOpenInfo = ReturnType<typeof asFirstOpenInfo>

Expand All @@ -27,9 +29,15 @@ export const getFirstOpenInfo = async (): Promise<FirstOpenInfo> => {
firstOpenText = await firstOpenDisklet.getText(FIRST_OPEN)
firstOpenInfo = asFirstOpenInfo(JSON.parse(firstOpenText))
firstOpenInfo.isFirstOpen = 'false'

if (firstOpenInfo.countryCode == null) {
// Not critical if we can't get the country code
firstOpenInfo.countryCode = await getCountryCodeByIp().catch(() => undefined)
}
} catch (error: any) {
// Generate new values.
firstOpenInfo = {
countryCode: await getCountryCodeByIp(),
deviceId: await makeUuid(),
firstOpenEpoch: Date.now(),
// If firstOpen != null: This is not the first time they opened the app,
Expand Down
19 changes: 2 additions & 17 deletions src/actions/LoginActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { readSyncedSettings } from '../actions/SettingsActions'
import { ConfirmContinueModal } from '../components/modals/ConfirmContinueModal'
import { FioCreateHandleModal } from '../components/modals/FioCreateHandleModal'
import { SurveyModal } from '../components/modals/SurveyModal'
import { AlertDropdown } from '../components/navigation/AlertDropdown'
import { Airship, showError } from '../components/services/AirshipInstance'
import { ENV } from '../env'
import { getExperimentConfig } from '../experimentConfig'
Expand All @@ -23,8 +22,8 @@ import { NavigationBase, NavigationProp } from '../types/routerTypes'
import { currencyCodesToEdgeAssets } from '../util/CurrencyInfoHelpers'
import { logActivity } from '../util/logger'
import { logEvent, trackError } from '../util/tracking'
import { openLink, runWithTimeout } from '../util/utils'
import { getCountryCodeByIp, loadAccountReferral, refreshAccountReferral } from './AccountReferralActions'
import { runWithTimeout } from '../util/utils'
import { loadAccountReferral, refreshAccountReferral } from './AccountReferralActions'
import { getUniqueWalletName } from './CreateWalletActions'
import { getDeviceSettings, writeIsSurveyDiscoverShown } from './DeviceSettingsActions'
import { readLocalAccountSettings } from './LocalSettingsActions'
Expand Down Expand Up @@ -267,20 +266,6 @@ export function initializeAccount(navigation: NavigationBase, account: EdgeAccou
await Airship.show(bridge => <SurveyModal bridge={bridge} />)
await writeIsSurveyDiscoverShown(true)
}

if ((await getCountryCodeByIp()) === 'GB') {
await Airship.show(bridge => (
<AlertDropdown
bridge={bridge}
message={lstrings.warning_uk_risk}
persistent
warning
onPress={async () => {
await openLink('https://edge.app/due-diligence/')
}}
/>
))
}
}
}

Expand Down
5 changes: 4 additions & 1 deletion src/actions/ScanActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ import { getCurrencyCode } from '../util/CurrencyInfoHelpers'
import { parseDeepLink } from '../util/DeepLinkParser'
import { logActivity } from '../util/logger'
import { makeCurrencyCodeTable, upgradeCurrencyCodes } from '../util/tokenIdTools'
import { getUkCompliantString } from '../util/ukComplianceUtils'
import { getPluginIdFromChainCode, toListString, zeroString } from '../util/utils'
import { cleanQueryFlags, openBrowserUri } from '../util/WebUtils'
import { checkAndShowLightBackupModal } from './BackupModalActions'
import { getFirstOpenInfo } from './FirstOpenActions'

/**
* Handle Request for Address Links (WIP - pending refinement).
Expand Down Expand Up @@ -340,6 +342,7 @@ const shownWalletGetCryptoModals: string[] = []
export function checkAndShowGetCryptoModal(navigation: NavigationBase, wallet: EdgeCurrencyWallet, tokenId: EdgeTokenId): ThunkAction<Promise<void>> {
return async dispatch => {
try {
const { countryCode } = await getFirstOpenInfo()
const currencyCode = getCurrencyCode(wallet, tokenId)
// check if balance is zero
const balance = wallet.balanceMap.get(tokenId)
Expand All @@ -355,7 +358,7 @@ export function checkAndShowGetCryptoModal(navigation: NavigationBase, wallet: E
title={lstrings.buy_crypto_modal_title}
message={messageSyntax}
buttons={{
buy: { label: sprintf(lstrings.buy_crypto_modal_buy_action, currencyCode) },
buy: { label: getUkCompliantString(countryCode, 'buy_1s', currencyCode) },
exchange: { label: lstrings.buy_crypto_modal_exchange, type: 'primary' },
decline: { label: lstrings.buy_crypto_decline }
}}
Expand Down
14 changes: 13 additions & 1 deletion src/components/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as React from 'react'
import { Platform } from 'react-native'

import { getDeviceSettings } from '../actions/DeviceSettingsActions'
import { getFirstOpenInfo } from '../actions/FirstOpenActions'
import { SwapCreateScene as SwapCreateSceneComponent } from '../components/scenes/SwapCreateScene'
import { ENV } from '../env'
import { DEFAULT_EXPERIMENT_CONFIG, ExperimentConfig, getExperimentConfig } from '../experimentConfig'
Expand Down Expand Up @@ -33,6 +34,7 @@ import {
} from '../types/routerTypes'
import { isMaestro } from '../util/maestro'
import { logEvent } from '../util/tracking'
import { getUkCompliantString } from '../util/ukComplianceUtils'
import { ifLoggedIn } from './hoc/IfLoggedIn'
import { BackButton } from './navigation/BackButton'
import { CurrencySettingsTitle } from './navigation/CurrencySettingsTitle'
Expand Down Expand Up @@ -423,6 +425,16 @@ const EdgeTabs = () => {
// -------------------------------------------------------------------------

const EdgeAppStack = () => {
const [countryCode, setCountryCode] = React.useState<string | undefined>()

useAsyncEffect(
async () => {
setCountryCode((await getFirstOpenInfo()).countryCode)
},
[],
'EdgeAppStack'
)

return (
<AppStack.Navigator initialRouteName="edgeTabs" screenOptions={defaultScreenOptions}>
<AppStack.Screen
Expand Down Expand Up @@ -572,7 +584,7 @@ const EdgeAppStack = () => {
name="earnScene"
component={EarnScene}
options={{
title: lstrings.stake_earn_button_label
title: getUkCompliantString(countryCode, 'stake_earn_button_label')
}}
/>
<AppStack.Screen name="fioAddressDetails" component={FioAddressDetailsScene} />
Expand Down
8 changes: 6 additions & 2 deletions src/components/cards/StakingReturnsCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { toPercentString } from '../../locales/intl'
import { lstrings } from '../../locales/strings'
import { StakePolicy } from '../../plugins/stake-plugins/types'
import { getPolicyIconUris } from '../../util/stakeUtils'
import { getUkCompliantString } from '../../util/ukComplianceUtils'
import { PairIcons } from '../icons/PairIcons'
import { cacheStyles, Theme, useTheme } from '../services/ThemeContext'
import { TitleText } from '../text/TitleText'
Expand All @@ -17,6 +18,7 @@ interface Props {
stakePolicy: StakePolicy
wallet: EdgeCurrencyWallet

countryCode?: string
/** If false, show "Stake"/"Earn"
* If true, show "Staked"/"Earned" */
isOpenPosition?: boolean
Expand All @@ -27,15 +29,17 @@ export function StakingReturnsCard(props: Props) {
const theme = useTheme()
const styles = getStyles(theme)

const { stakePolicy, wallet, isOpenPosition, onPress } = props
const { stakePolicy, wallet, isOpenPosition, countryCode, onPress } = props
const { apy, yieldType, stakeProviderInfo } = stakePolicy

const { stakeAssets, rewardAssets } = stakePolicy
const stakeCurrencyCodes = stakeAssets.map(asset => asset.currencyCode).join(' + ')
const rewardCurrencyCodes = rewardAssets.map(asset => asset.currencyCode).join(', ')

const stakeText = sprintf(isOpenPosition ? lstrings.stake_staked_1s : lstrings.stake_stake_1s, stakeCurrencyCodes)
const rewardText = sprintf(isOpenPosition ? lstrings.stake_earning_1s : lstrings.stake_earn_1s, rewardCurrencyCodes)
const rewardText = isOpenPosition
? sprintf(lstrings.stake_earning_1s, rewardCurrencyCodes)
: getUkCompliantString(countryCode, 'stake_earn_1s', rewardCurrencyCodes)

const policyIcons = getPolicyIconUris(wallet.currencyInfo, stakePolicy)

Expand Down
6 changes: 4 additions & 2 deletions src/components/modals/InsufficientFeesModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useHandler } from '../../hooks/useHandler'
import { lstrings } from '../../locales/strings'
import { NavigationBase } from '../../types/routerTypes'
import { getCurrencyCode } from '../../util/CurrencyInfoHelpers'
import { getUkCompliantString } from '../../util/ukComplianceUtils'
import { roundedFee } from '../../util/utils'
import { ButtonsView } from '../buttons/ButtonsView'
import { Paragraph } from '../themed/EdgeText'
Expand All @@ -19,6 +20,7 @@ interface Props {
navigation: NavigationBase
wallet: EdgeCurrencyWallet

countryCode?: string
// Called when the user wants to swap.
// The default behavior is to navigate to the swap scene,
// but the swap scene itself needs a different behavior here.
Expand All @@ -29,7 +31,7 @@ interface Props {
* Show this modal when the wallet doesn't have enough funds to cover fees.
*/
export function InsufficientFeesModal(props: Props) {
const { bridge, coreError, navigation, wallet, onSwap } = props
const { bridge, countryCode, coreError, navigation, wallet, onSwap } = props

// Get the display amount:
const { tokenId, networkFee = '' } = coreError
Expand Down Expand Up @@ -59,7 +61,7 @@ export function InsufficientFeesModal(props: Props) {
<EdgeModal bridge={bridge} title={lstrings.buy_crypto_modal_title} onCancel={handleCancel}>
<Paragraph>{message}</Paragraph>
<ButtonsView
primary={{ label: sprintf(lstrings.buy_crypto_modal_buy_action, currencyCode), onPress: handleBuy }}
primary={{ label: getUkCompliantString(countryCode, 'transaction_details_bought_1s', currencyCode), onPress: handleBuy }}
secondary={{ label: lstrings.buy_crypto_modal_exchange, onPress: handleSwap }}
tertiary={{ label: lstrings.buy_crypto_decline, onPress: handleCancel }}
/>
Expand Down
6 changes: 3 additions & 3 deletions src/components/scenes/GuiPluginListScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { Image, ListRenderItemInfo, Platform, View } from 'react-native'
import { getBuildNumber, getVersion } from 'react-native-device-info'
import FastImage from 'react-native-fast-image'
import Animated from 'react-native-reanimated'
import { sprintf } from 'sprintf-js'

import { checkAndShowLightBackupModal } from '../../actions/BackupModalActions'
import { checkAndSetRegion, showCountrySelectionModal } from '../../actions/CountryListActions'
Expand Down Expand Up @@ -37,6 +36,7 @@ import { filterGuiPluginJson } from '../../util/GuiPluginTools'
import { infoServerData } from '../../util/network'
import { bestOfPlugins } from '../../util/ReferralHelpers'
import { logEvent, OnLogEvent } from '../../util/tracking'
import { getUkCompliantString } from '../../util/ukComplianceUtils'
import { base58ToUuid, getOsVersion } from '../../util/utils'
import { EdgeCard } from '../cards/EdgeCard'
import { filterInfoCards } from '../cards/InfoCardCarousel'
Expand Down Expand Up @@ -404,7 +404,7 @@ class GuiPluginList extends React.PureComponent<Props, State> {
const titleAsset =
forcedWalletResult == null || forcedWalletResult.type !== 'wallet' || forcedWallet == null
? lstrings.cryptocurrency
: getCurrencyCodeWithAccount(account, forcedWallet.currencyInfo.pluginId, forcedWalletResult.tokenId ?? null)
: getCurrencyCodeWithAccount(account, forcedWallet.currencyInfo.pluginId, forcedWalletResult.tokenId) ?? ''

const countryCard =
stateProvinceData == null ? (
Expand All @@ -419,7 +419,7 @@ class GuiPluginList extends React.PureComponent<Props, State> {
<>
<EdgeAnim style={styles.header} enter={fadeInUp90}>
<SceneHeader
title={direction === 'buy' ? sprintf(lstrings.title_plugin_buy_s, titleAsset) : sprintf(lstrings.title_plugin_sell_s, titleAsset)}
title={direction === 'buy' ? getUkCompliantString(countryCode, 'buy_1s', titleAsset) : getUkCompliantString(countryCode, 'sell_1s', titleAsset)}
underline
withTopMargin
/>
Expand Down
4 changes: 3 additions & 1 deletion src/components/scenes/MigrateWalletCalculateFeeScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as React from 'react'
import { ActivityIndicator, ListRenderItemInfo, View } from 'react-native'
import { FlatList } from 'react-native-gesture-handler'

import { getFirstOpenInfo } from '../../actions/FirstOpenActions'
import { SCROLL_INDICATOR_INSET_FIX } from '../../constants/constantSettings'
import { SPECIAL_CURRENCY_INFO } from '../../constants/WalletAndCurrencyConstants'
import { useAsyncEffect } from '../../hooks/useAsyncEffect'
Expand Down Expand Up @@ -124,7 +125,8 @@ const MigrateWalletCalculateFeeComponent = (props: Props) => {
})

const handleInsufficientFunds = useHandler(async (wallet, error) => {
await Airship.show(bridge => <InsufficientFeesModal bridge={bridge} coreError={error} navigation={navigation} wallet={wallet} />)
const { countryCode } = await getFirstOpenInfo()
await Airship.show(bridge => <InsufficientFeesModal bridge={bridge} countryCode={countryCode} coreError={error} navigation={navigation} wallet={wallet} />)
})

const handleSlidingComplete = useHandler(() => {
Expand Down
6 changes: 5 additions & 1 deletion src/components/scenes/SendScene2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { ActivityIndicator, TextInput, View } from 'react-native'
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'
import { sprintf } from 'sprintf-js'

import { getFirstOpenInfo } from '../../actions/FirstOpenActions'
import { showSendScamWarningModal } from '../../actions/ScamWarningActions'
import { checkAndShowGetCryptoModal } from '../../actions/ScanActions'
import { playSendSound } from '../../actions/SoundActions'
Expand Down Expand Up @@ -215,7 +216,10 @@ const SendComponent = (props: Props) => {
const pendingInsufficientFees = React.useRef<InsufficientFundsError | undefined>(undefined)

async function showInsufficientFeesModal(error: InsufficientFundsError): Promise<void> {
await Airship.show(bridge => <InsufficientFeesModal bridge={bridge} coreError={error} navigation={navigation} wallet={coreWallet} />)
const { countryCode } = await getFirstOpenInfo()
await Airship.show(bridge => (
<InsufficientFeesModal bridge={bridge} countryCode={countryCode} coreError={error} navigation={navigation} wallet={coreWallet} />
))
}

const handleChangeAddress =
Expand Down
14 changes: 13 additions & 1 deletion src/components/scenes/Staking/StakeOptionsScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { View } from 'react-native'
import { FlatList } from 'react-native-gesture-handler'
import { sprintf } from 'sprintf-js'

import { getFirstOpenInfo } from '../../../actions/FirstOpenActions'
import { SCROLL_INDICATOR_INSET_FIX } from '../../../constants/constantSettings'
import { useAsyncEffect } from '../../../hooks/useAsyncEffect'
import { useIconColor } from '../../../hooks/useIconColor'
import { lstrings } from '../../../locales/strings'
import { StakePlugin, StakePolicy, StakePositionMap } from '../../../plugins/stake-plugins/types'
Expand Down Expand Up @@ -46,6 +48,16 @@ const StakeOptionsSceneComponent = (props: Props) => {
const tokenId = pluginId ? getTokenIdForced(account, pluginId, currencyCode) : null
const iconColor = useIconColor({ pluginId, tokenId })

const [countryCode, setCountryCode] = React.useState<string | undefined>()

useAsyncEffect(
async () => {
setCountryCode((await getFirstOpenInfo()).countryCode)
},
[],
'StakeOptionsSceneComponent'
)

//
// Handlers
//
Expand All @@ -64,7 +76,7 @@ const StakeOptionsSceneComponent = (props: Props) => {

const renderOptions = ({ item }: { item: StakePolicy }) => {
const primaryText = getPolicyAssetName(item, 'stakeAssets')
const secondaryText = getPolicyTitleName(item)
const secondaryText = getPolicyTitleName(item, countryCode)
const key = [primaryText, secondaryText].join()
const policyIcons = getPolicyIconUris(wallet.currencyInfo, item)
return (
Expand Down
8 changes: 6 additions & 2 deletions src/components/scenes/Staking/StakeOverviewScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { View } from 'react-native'
import { FlatList } from 'react-native-gesture-handler'
import { sprintf } from 'sprintf-js'

import { getFirstOpenInfo } from '../../../actions/FirstOpenActions'
import { SCROLL_INDICATOR_INSET_FIX } from '../../../constants/constantSettings'
import { useAsyncEffect } from '../../../hooks/useAsyncEffect'
import { lstrings } from '../../../locales/strings'
Expand Down Expand Up @@ -60,6 +61,7 @@ const StakeOverviewSceneComponent = (props: Props) => {
const [rewardAllocations, setRewardAllocations] = React.useState<PositionAllocation[]>([])
const [unstakedAllocations, setUnstakedAllocations] = React.useState<PositionAllocation[]>([])
const [stakePosition, setStakePosition] = React.useState<StakePosition | undefined>(startingStakePosition)
const [countryCode, setCountryCode] = React.useState<string | undefined>()

// Background loop to force fetchStakePosition updates
const [updateCounter, setUpdateCounter] = React.useState<number>(0)
Expand All @@ -73,6 +75,8 @@ const StakeOverviewSceneComponent = (props: Props) => {

useAsyncEffect(
async () => {
setCountryCode((await getFirstOpenInfo()).countryCode)

let sp: StakePosition
try {
if (stakePosition == null) {
Expand All @@ -97,7 +101,7 @@ const StakeOverviewSceneComponent = (props: Props) => {
// Handlers
const handleModifyPress = (modification: ChangeQuoteRequest['action'] | 'unstakeAndClaim') => () => {
const sceneTitleMap = {
stake: getPolicyTitleName(stakePolicy),
stake: getPolicyTitleName(stakePolicy, countryCode),
claim: lstrings.stake_claim_rewards,
unstake: lstrings.stake_unstake,
unstakeAndClaim: lstrings.stake_unstake_claim,
Expand Down Expand Up @@ -146,7 +150,7 @@ const StakeOverviewSceneComponent = (props: Props) => {
return (
<SceneWrapper padding={theme.rem(0.5)} scroll>
<SceneHeader title={title} withTopMargin />
<StakingReturnsCard wallet={wallet} stakePolicy={stakePolicy} />
<StakingReturnsCard countryCode={countryCode} wallet={wallet} stakePolicy={stakePolicy} />
{stakePosition == null ? (
<>
<View style={styles.shimmer}>
Expand Down
Loading

0 comments on commit c63fe78

Please sign in to comment.