Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jon/fix/uk-compliance #5319

Merged
merged 6 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to re-save here, so it's available on the next run?

Copy link
Collaborator Author

@Jon-edge Jon-edge Oct 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, then they're stuck as a UK-designated device if they happened to open the app in the UK when this change goes live. Could see issues with people visiting the UK or VPN-ing.

} 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
Loading