diff --git a/CHANGELOG.md b/CHANGELOG.md index 7eb4b1f8767..f419e0696df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,25 +2,46 @@ ## Unreleased -- added: Add 'Free Talk Live' and 'Crypto Canal' options to survey modal -- added: Include custom tokens within wallet data in log output -- added: Warning message about risks of investing to all UK IPs +## 4.14.0 + +- added: `ExpandableList` component, replacing the address hint dropdown in `AddressFormScene` +- added: Add 'Cris Cyborg,' 'Free Talk Live,' and 'Crypto Canal' options to survey modal +- added: Buy with Revolut +- added: Buy/sell with Paypal +- added: Foreground push notifications displayed in app +- added: Minimum receive amount to `SwapConfirmationScene` +- added: New analytics tracking param: `accountAgeMonths` +- added: Sell with Moonpay +- changed: Enable Cardano staking +- changed: Added Iraq to list of Visa/MC supported countries +- changed: Display Asset Status cards in the same style as Promo Cards +- changed: Updated ACH supported US states +- changed: Use new platform-specific `assetStatsCards2` info server data +- fixed: Missing ellipses for long usernames displayed in the `SideMenu` +- fixed: "Apple Pay" renamed to "Pay with Apple Pay" to align with branding guidelines +- fixed: "Most Recent Wallets" do not show those chosen through `fiatPlugin` +- fixed: Crash on HomeScene when logging while in airplane mode +- fixed: Inconsistent content of address hint dropdown between iOS and Android in `AddressFormScene` +- fixed: Inconsistent corners in `SideMenu` +- fixed: Round Kado-provided amounts during sell -## 4.13.0 +## 4.13.0 (2024-09-18) +- added: Add 'Free Talk Live' and 'Crypto Canal' options to survey modal - added: Cardano staking through Kiln staking pools +- added: Include custom tokens within wallet data in log output - added: Support for `isLiquidStaking` field on staking policies -- changed: Some unecessary `showError` dropdowns demoted to hidden `showDevError` -- changed: Restrict Bity buy/sell to no-KYC asset -- changed: Determine Moonpay asset support using chainCode/contractAddress +- added: Warning message about risks of investing to all UK IPs - changed: Allow private key sweep in light accounts for amounts less than $50 - changed: Allow reverse quotes for Kado - changed: Credit card allowed countries to add Botswana, Cambodia, Panama, and Sri Lanka -- fixed: Crash on HomeScene when logging while in airplane mode -- fixed: Default swap pair logic from the trade modal was not prioritizing mainnet assets +- changed: Determine Moonpay asset support using chainCode/contractAddress +- changed: Restrict Bity buy/sell to no-KYC asset +- changed: Some unecessary `showError` dropdowns demoted to hidden `showDevError` - fixed: "FIO Address does not exist" error after transferring a FIO name -- fixed: Fix incorrect string comparison resulting in wrong rate used for 24 change calculation - fixed: Crypto amount display bug after flipping the wallet input on `SwapCreateScne` +- fixed: Default swap pair logic from the trade modal was not prioritizing mainnet assets +- fixed: Fix incorrect string comparison resulting in wrong rate used for 24 change calculation - removed: 'fasterpayments' payment type - removed: Turking bank transfer support diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 24918d60505..0dd829cb9e2 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -15,15 +15,15 @@ PODS: - disklet (0.5.2): - React - DoubleConversion (1.1.6) - - edge-core-js (2.14.0): + - edge-core-js (2.18.0): - React-Core - - edge-currency-accountbased (4.24.1-2): + - edge-currency-accountbased (4.24.6): - React-Core - edge-currency-plugins (3.3.2): - React-Core - - edge-exchange-plugins (2.7.5): + - edge-exchange-plugins (2.9.1): - React-Core - - edge-login-ui-rn (3.19.2): + - edge-login-ui-rn (3.22.3): - React-Core - EXApplication (5.1.1): - ExpoModulesCore @@ -430,7 +430,7 @@ PODS: - react-native-webview (13.8.4): - RCT-Folly (= 2021.07.22.00) - React-Core - - react-native-zcash (0.8.0): + - react-native-zcash (0.8.1): - gRPC-Swift (~> 1.8) - MnemonicSwift (~> 2.2) - React-Core @@ -1078,12 +1078,12 @@ SPEC CHECKSUMS: CNIOLinux: 62e3505f50de558c393dc2f273dde71dcce518da CNIOWindows: 3047f2d8165848a3936a0a755fee27c6b5ee479b disklet: e7ed3e673ccad9d175a1675f9f3589ffbf69a5fd - DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 - edge-core-js: 9e7f9dc8b064c7be712e2988feb78db0ed5dbd77 - edge-currency-accountbased: a4e78ccd42797465cc7025934ab76301597bd916 + DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953 + edge-core-js: 36f9eccd1b3e22ad3e1ca14773c9754c2bf83b63 + edge-currency-accountbased: 86f333e5789a79adb7b47519104d72db21e1eb72 edge-currency-plugins: d2d7466f1215ffed6ff18d056fb5470dd2f86ab3 - edge-exchange-plugins: 6ed47d7adb64b432e787a9036f46ce947d488ad6 - edge-login-ui-rn: d4b91425b848a18c302f31c2ff16e07d66bf2d04 + edge-exchange-plugins: 5a0502c8ecc7691fe1b4ef682038ee7905620b41 + edge-login-ui-rn: 42c2652b2bd8b9a98b7208368ecc2827eea23955 EXApplication: d8f53a7eee90a870a75656280e8d4b85726ea903 EXConstants: f348da07e21b23d2b085e270d7b74f282df1a7d9 EXFileSystem: 844e86ca9b5375486ecc4ef06d3838d5597d895d @@ -1100,7 +1100,7 @@ SPEC CHECKSUMS: FirebaseInstallations: 766dabca09fd94aef922538aaf144cc4a6fb6869 FirebaseMessaging: 585984d0a1df120617eb10b44cad8968b859815e fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 - glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b + glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15 gRPC-Swift: 74adcaaa62ac5e0a018938840328cb1fdfb09e7b @@ -1140,7 +1140,7 @@ SPEC CHECKSUMS: react-native-safari-view: 955d7160d159241b8e9395d12d10ea0ef863dcdd react-native-safe-area-context: dcab599c527c2d7de2d76507a523d20a0b83823d react-native-webview: fa228e55c53372c2b361d2fa5e415844fa83eabf - react-native-zcash: e9be77ab00cc04fe3c19125e12817c00d9356bc8 + react-native-zcash: 2959ff77ba393d08bcd65aa582fef8445c99453c React-perflogger: 0cc42978a483a47f3696171dac2e7033936fc82d React-RCTActionSheet: ea922b476d24f6d40b8e02ac3228412bd3637468 React-RCTAnimation: 7be2c148398eaa5beac950b2b5ec7102389ec3ad diff --git a/package.json b/package.json index a728c554b36..b77311cfad8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "edge-react-gui", - "version": "4.13.0", + "version": "4.14.0", "private": true, "description": "Edge Wallet React GUI", "homepage": "https://edge.app", @@ -97,13 +97,13 @@ "deepmerge": "^4.3.1", "detect-bundler": "^1.1.0", "disklet": "^0.5.2", - "edge-core-js": "^2.14.0", - "edge-currency-accountbased": "4.24.1-2", + "edge-core-js": "^2.18.0", + "edge-currency-accountbased": "^4.24.6", "edge-currency-monero": "^1.3.1", "edge-currency-plugins": "^3.3.2", - "edge-exchange-plugins": "^2.7.5", - "edge-info-server": "^2.6.0", - "edge-login-ui-rn": "3.19.2", + "edge-exchange-plugins": "^2.9.1", + "edge-info-server": "^2.7.0", + "edge-login-ui-rn": "^3.22.3", "ethers": "^5.7.2", "expo": "^48.0.0", "jsrsasign": "^11.1.0", @@ -141,7 +141,7 @@ "react-native-mymonero-core": "^0.3.1", "react-native-patina": "^0.1.6", "react-native-permissions": "^4.1.5", - "react-native-piratechain": "^0.5.0", + "react-native-piratechain": "^0.5.1", "react-native-reanimated": "^3.14.0", "react-native-safari-view": "^2.1.0", "react-native-safe-area-context": "^4.10.1", @@ -154,7 +154,7 @@ "react-native-svg": "^15.3.0", "react-native-vector-icons": "^10.1.0", "react-native-webview": "^13.8.4", - "react-native-zcash": "^0.8.0", + "react-native-zcash": "^0.8.1", "react-redux": "^8.1.1", "redux": "^4.2.1", "redux-thunk": "^2.3.0", diff --git a/src/__tests__/__snapshots__/GuiPlugins.test.ts.snap b/src/__tests__/__snapshots__/GuiPlugins.test.ts.snap index e8b346e1fd6..3555fe4005c 100644 --- a/src/__tests__/__snapshots__/GuiPlugins.test.ts.snap +++ b/src/__tests__/__snapshots__/GuiPlugins.test.ts.snap @@ -133,6 +133,20 @@ Settlement: 1 - 48 hours", "pluginId": "bitsofgold", "title": "Israeli Bank Transfer", }, + { + "cryptoCodes": [], + "deepPath": "", + "deepQuery": {}, + "description": "Fee: ~5% +Settlement: 5 min - 24 hours", + "paymentType": "paypal", + "paymentTypeLogoKey": "paypal", + "paymentTypes": [ + "paypal", + ], + "pluginId": "paypal", + "title": "Paypal", + }, { "cryptoCodes": [], "deepPath": "", @@ -181,6 +195,20 @@ Limit $1000", "pluginId": "iach", "title": "Instant ACH Bank Transfer", }, + { + "cryptoCodes": [], + "deepPath": "", + "deepQuery": {}, + "description": "Fee: ~5% +Settlement: 5 min - 24 hours", + "paymentType": "paypal", + "paymentTypeLogoKey": "paypal", + "paymentTypes": [ + "paypal", + ], + "pluginId": "paypal", + "title": "Paypal", + }, { "cryptoCodes": [], "deepPath": "", @@ -193,7 +221,7 @@ Settlement: 10 - 30 minutes", "credit", ], "pluginId": "creditcard", - "title": "Apple Pay", + "title": "Pay with Apple Pay", }, { "cryptoCodes": [], @@ -209,6 +237,20 @@ Settlement: 10 - 30 minutes", "pluginId": "creditcard", "title": "Credit and Debit Card", }, + { + "cryptoCodes": [], + "deepPath": "", + "deepQuery": {}, + "description": "Fee: ~5% +Settlement: 5 min - 24 hours", + "paymentType": "revolut", + "paymentTypeLogoKey": "revolut", + "paymentTypes": [ + "revolut", + ], + "pluginId": "revolut", + "title": "Revolut", + }, { "cryptoCodes": [], "deepPath": "", @@ -260,6 +302,20 @@ Settlement: 2 - 3 days", "pluginId": "ach", "title": "ACH Bank Transfer", }, + { + "cryptoCodes": [], + "deepPath": "", + "deepQuery": {}, + "description": "Fee: ~5% +Settlement: 5 min - 24 hours", + "paymentType": "paypal", + "paymentTypeLogoKey": "paypal", + "paymentTypes": [ + "paypal", + ], + "pluginId": "paypal", + "title": "Paypal", + }, { "cryptoCodes": [], "deepPath": "", diff --git a/src/__tests__/components/PromoCards.test.tsx b/src/__tests__/components/PromoCards.test.tsx index 145ce6176b2..efbe9e5fdd2 100644 --- a/src/__tests__/components/PromoCards.test.tsx +++ b/src/__tests__/components/PromoCards.test.tsx @@ -1,7 +1,7 @@ import { describe, expect, test } from '@jest/globals' import { PromoCard2 } from 'edge-info-server' -import { filterPromoCards } from '../../components/cards/PromoCards' +import { filterInfoCards } from '../../components/cards/InfoCardCarousel' const dummyCard: PromoCard2 = { localeMessages: { en: 'hello' }, @@ -33,12 +33,12 @@ const currentDate = new Date('2024-06-13T20:53:33.013Z') describe('filterPromoCards', () => { test('No cards', () => { const cards: PromoCard2[] = [] - const result = filterPromoCards({ cards, countryCode: 'US', buildNumber, osType, version, osVersion, currentDate }) + const result = filterInfoCards({ cards, countryCode: 'US', buildNumber, osType, version, osVersion, currentDate }) expect(result.length).toBe(0) }) test('Card no filters', () => { const cards: PromoCard2[] = [{ ...dummyCard }] - const result = filterPromoCards({ cards, countryCode: 'US', buildNumber, osType, version, osVersion, currentDate }) + const result = filterInfoCards({ cards, countryCode: 'US', buildNumber, osType, version, osVersion, currentDate }) expect(result.length).toBe(1) expect(result[0].localeMessages.en).toBe('hello') }) @@ -49,7 +49,7 @@ describe('filterPromoCards', () => { { ...dummyCard, osTypes: ['web'], localeMessages: { en: 'Web Message' } }, { ...dummyCard, osTypes: ['ios'], localeMessages: { en: 'Another iOS Message' } } ] - const result = filterPromoCards({ cards, countryCode: 'US', buildNumber, osType: 'ios', version, osVersion, currentDate }) + const result = filterInfoCards({ cards, countryCode: 'US', buildNumber, osType: 'ios', version, osVersion, currentDate }) expect(result.length).toBe(2) expect(result[0].localeMessages.en).toBe('iOS Message') expect(result[1].localeMessages.en).toBe('Another iOS Message') @@ -61,7 +61,7 @@ describe('filterPromoCards', () => { { ...dummyCard, exactBuildNum: '432', osTypes: ['web'], localeMessages: { en: 'Web Message' } }, { ...dummyCard, exactBuildNum: '432', osTypes: ['android'], localeMessages: { en: 'Another Android Message' } } ] - const result = filterPromoCards({ cards, countryCode: 'US', buildNumber: '432', osType: 'android', version: '1.2.3', osVersion, currentDate }) + const result = filterInfoCards({ cards, countryCode: 'US', buildNumber: '432', osType: 'android', version: '1.2.3', osVersion, currentDate }) expect(result.length).toBe(1) expect(result[0].localeMessages.en).toBe('Another Android Message') }) @@ -72,7 +72,7 @@ describe('filterPromoCards', () => { { ...dummyCard, osTypes: ['web'], localeMessages: { en: 'Web Message' } }, { ...dummyCard, osTypes: ['android'], localeMessages: { en: 'Another Android Message' }, appVersion: '1.2.4' } ] - const result = filterPromoCards({ cards, countryCode: 'US', buildNumber: '432', osType: 'android', version: '1.2.3', osVersion, currentDate }) + const result = filterInfoCards({ cards, countryCode: 'US', buildNumber: '432', osType: 'android', version: '1.2.3', osVersion, currentDate }) expect(result.length).toBe(1) expect(result[0].localeMessages.en).toBe('Android message') }) @@ -83,7 +83,7 @@ describe('filterPromoCards', () => { { ...dummyCard, minBuildNum: '4', maxBuildNum: '5', localeMessages: { en: '4-5' } }, { ...dummyCard, minBuildNum: '1', maxBuildNum: '4', localeMessages: { en: '1-4' } } ] - const result = filterPromoCards({ cards, countryCode: 'US', buildNumber: '4', osType, version: '1.2.3', osVersion, currentDate }) + const result = filterInfoCards({ cards, countryCode: 'US', buildNumber: '4', osType, version: '1.2.3', osVersion, currentDate }) expect(result.length).toBe(3) expect(result[0].localeMessages.en).toBe('3-5') expect(result[1].localeMessages.en).toBe('4-5') @@ -97,7 +97,7 @@ describe('filterPromoCards', () => { { ...dummyCard, countryCodes: ['uk'], localeMessages: { en: 'UK message' } }, { ...dummyCard, countryCodes: ['es'], localeMessages: { en: 'ES message' } } ] - const result = filterPromoCards({ cards, buildNumber, osType, version, osVersion, currentDate }) + const result = filterInfoCards({ cards, buildNumber, osType, version, osVersion, currentDate }) expect(result.length).toBe(1) expect(result[0].localeMessages.en).toBe('no country') }) @@ -108,7 +108,7 @@ describe('filterPromoCards', () => { { ...dummyCard, excludeCountryCodes: ['uk'], localeMessages: { en: 'UK message' } }, { ...dummyCard, excludeCountryCodes: ['es'], localeMessages: { en: 'ES message' } } ] - const result = filterPromoCards({ cards, countryCode: 'us', buildNumber: '4', osType, version, osVersion, currentDate }) + const result = filterInfoCards({ cards, countryCode: 'us', buildNumber: '4', osType, version, osVersion, currentDate }) expect(result.length).toBe(2) expect(result[0].localeMessages.en).toBe('UK message') expect(result[1].localeMessages.en).toBe('ES message') @@ -120,7 +120,7 @@ describe('filterPromoCards', () => { { ...dummyCard, promoId: 'bob3', localeMessages: { en: 'Bob3 Message' } }, { ...dummyCard, promoId: 'bob4', localeMessages: { en: 'Bob4 Message' } } ] - const result = filterPromoCards({ + const result = filterInfoCards({ cards, countryCode: 'US', buildNumber, @@ -140,7 +140,7 @@ describe('filterPromoCards', () => { { ...dummyCard, promoId: 'bob3', localeMessages: { en: 'Bob3 Message' } }, { ...dummyCard, promoId: 'bob4', localeMessages: { en: 'Bob4 Message' } } ] - const result = filterPromoCards({ + const result = filterInfoCards({ cards, countryCode: 'US', buildNumber, diff --git a/src/actions/LogActions.tsx b/src/actions/LogActions.tsx index 63af0d76416..8ed0fe4b5fc 100644 --- a/src/actions/LogActions.tsx +++ b/src/actions/LogActions.tsx @@ -1,5 +1,5 @@ import { uncleaner } from 'cleaners' -import { EdgeDataDump } from 'edge-core-js' +import { EdgeDataDump, EdgeTokenMap } from 'edge-core-js' import * as React from 'react' import { Platform } from 'react-native' import { getBrand, getBuildNumber, getDeviceId, getVersion } from 'react-native-device-info' @@ -50,6 +50,7 @@ interface LoggedInUser { interface WalletData { currencyCode?: string + customTokens?: EdgeTokenMap imported?: boolean repoId?: string pluginDump?: EdgeDataDump @@ -180,6 +181,7 @@ export function getLogOutput(): ThunkAction> { const currencyCode = wallet.currencyInfo.currencyCode ?? '' logOutput.loggedInUser.wallets.push({ currencyCode, + customTokens: wallet.currencyConfig.customTokens, imported, repoId: getRepoId(syncKey), pluginDump: await wallet.dumpData().catch(error => ({ diff --git a/src/actions/LoginActions.tsx b/src/actions/LoginActions.tsx index 929189e114a..2106fa927c9 100644 --- a/src/actions/LoginActions.tsx +++ b/src/actions/LoginActions.tsx @@ -10,6 +10,7 @@ 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' @@ -22,8 +23,8 @@ import { NavigationBase, NavigationProp } from '../types/routerTypes' import { currencyCodesToEdgeAssets } from '../util/CurrencyInfoHelpers' import { logActivity } from '../util/logger' import { logEvent, trackError } from '../util/tracking' -import { runWithTimeout } from '../util/utils' -import { loadAccountReferral, refreshAccountReferral } from './AccountReferralActions' +import { openLink, runWithTimeout } from '../util/utils' +import { getCountryCodeByIp, loadAccountReferral, refreshAccountReferral } from './AccountReferralActions' import { getUniqueWalletName } from './CreateWalletActions' import { getDeviceSettings, writeIsSurveyDiscoverShown } from './DeviceSettingsActions' import { readLocalAccountSettings } from './LocalSettingsActions' @@ -249,6 +250,7 @@ export function initializeAccount(navigation: NavigationBase, account: EdgeAccou showError(error) } + // Post login stuff: if (!newAccount && !hideSurvey && !getDeviceSettings().isSurveyDiscoverShown && config.disableSurveyModal !== true) { // Show the survey modal once per app install, only if this isn't the // first login of a newly created account and the user didn't get any @@ -256,6 +258,20 @@ export function initializeAccount(navigation: NavigationBase, account: EdgeAccou await Airship.show(bridge => ) await writeIsSurveyDiscoverShown(true) } + + if ((await getCountryCodeByIp()) === 'GB') { + await Airship.show(bridge => ( + { + await openLink('https://edge.app/due-diligence/') + }} + /> + )) + } } } diff --git a/src/assets/images/paymentTypes/paymentTypeLogoPaypal.png b/src/assets/images/paymentTypes/paymentTypeLogoPaypal.png new file mode 100644 index 00000000000..514fd057b73 Binary files /dev/null and b/src/assets/images/paymentTypes/paymentTypeLogoPaypal.png differ diff --git a/src/assets/images/paymentTypes/paymentTypeLogoRevolut-dark.png b/src/assets/images/paymentTypes/paymentTypeLogoRevolut-dark.png new file mode 100644 index 00000000000..1b8be63ccaf Binary files /dev/null and b/src/assets/images/paymentTypes/paymentTypeLogoRevolut-dark.png differ diff --git a/src/assets/images/paymentTypes/paymentTypeLogoRevolut-light.png b/src/assets/images/paymentTypes/paymentTypeLogoRevolut-light.png new file mode 100644 index 00000000000..19c938b7faa Binary files /dev/null and b/src/assets/images/paymentTypes/paymentTypeLogoRevolut-light.png differ diff --git a/src/components/cards/AssetStatusCard.tsx b/src/components/cards/AssetStatusCard.tsx deleted file mode 100644 index 7d72e50be0b..00000000000 --- a/src/components/cards/AssetStatusCard.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { AssetStatus } from 'edge-info-server' -import * as React from 'react' -import IonIcon from 'react-native-vector-icons/Ionicons' - -import { getLocaleOrDefaultString } from '../../locales/intl' -import { openBrowserUri } from '../../util/WebUtils' -import { cacheStyles, Theme, useTheme } from '../services/ThemeContext' -import { IconMessageCard } from './IconMessageCard' -export const AssetStatusCard = (props: { assetStatus: AssetStatus }) => { - const { statusType, localeStatusTitle, localeStatusBody, iconUrl, statusUrl } = props.assetStatus - const theme = useTheme() - const styles = getStyles(theme) - - const title = getLocaleOrDefaultString(localeStatusTitle) - const message = getLocaleOrDefaultString(localeStatusBody) - const isValidText = title != null && message != null - - return isValidText ? ( - - ) : ( - // statusType === 'info' - - ) - ) : ( - iconUrl - ) - } - onPress={statusUrl ? () => openBrowserUri(statusUrl) : undefined} - /> - ) : null -} - -const getStyles = cacheStyles((theme: Theme) => ({ - icon: { - width: theme.rem(3), - height: theme.rem(3), - marginRight: theme.rem(0.5) - } -})) diff --git a/src/components/cards/PromoCard.tsx b/src/components/cards/InfoCard.tsx similarity index 95% rename from src/components/cards/PromoCard.tsx rename to src/components/cards/InfoCard.tsx index 646f3a619ac..18800e669b3 100644 --- a/src/components/cards/PromoCard.tsx +++ b/src/components/cards/InfoCard.tsx @@ -15,7 +15,7 @@ import { cacheStyles, Theme, useTheme } from '../services/ThemeContext' import { EdgeText } from '../themed/EdgeText' import { EdgeCard } from './EdgeCard' -export interface FilteredPromoCard { +export interface FilteredInfoCard { background: PromoCard2['background'] ctaButton: PromoCard2['ctaButton'] dismissable: PromoCard2['dismissable'] @@ -26,11 +26,11 @@ export interface FilteredPromoCard { interface Props { navigation: NavigationBase - promoInfo: FilteredPromoCard + promoInfo: FilteredInfoCard onClose: () => Promise } -export function PromoCard(props: Props) { +export function InfoCard(props: Props) { const theme = useTheme() const styles = getStyles(theme) const dispatch = useDispatch() @@ -49,7 +49,7 @@ export function PromoCard(props: Props) { const { localeUrls } = ctaButton const url = getLocaleOrDefaultString(localeUrls) if (url == null) { - showError('No PromoCard URL found') + showError('No Carousel Card URL found') return } diff --git a/src/components/cards/PromoCards.tsx b/src/components/cards/InfoCardCarousel.tsx similarity index 74% rename from src/components/cards/PromoCards.tsx rename to src/components/cards/InfoCardCarousel.tsx index 45a986008fb..207e830878e 100644 --- a/src/components/cards/PromoCards.tsx +++ b/src/components/cards/InfoCardCarousel.tsx @@ -1,5 +1,5 @@ import { asDate } from 'cleaners' -import { PromoCard2 } from 'edge-info-server' +import { AssetStatus2, PromoCard2 } from 'edge-info-server' import * as React from 'react' import { ListRenderItem, Platform } from 'react-native' import { getBuildNumber, getVersion } from 'react-native-device-info' @@ -7,55 +7,40 @@ import shajs from 'sha.js' import { hideMessageTweak } from '../../actions/AccountReferralActions' import { useHandler } from '../../hooks/useHandler' -import { useWatch } from '../../hooks/useWatch' +import { useIsAccountFunded } from '../../hooks/useIsAccountFunded' import { useDispatch, useSelector } from '../../types/reactRedux' import { AccountReferral } from '../../types/ReferralTypes' import { NavigationBase } from '../../types/routerTypes' -import { infoServerData } from '../../util/network' -import { getOsVersion, zeroString } from '../../util/utils' -import { EdgeAnim, fadeInUp110 } from '../common/EdgeAnim' +import { getOsVersion } from '../../util/utils' +import { Anim, EdgeAnim } from '../common/EdgeAnim' import { EdgeCarousel } from '../common/EdgeCarousel' import { useTheme } from '../services/ThemeContext' -import { FilteredPromoCard, PromoCard } from './PromoCard' +import { FilteredInfoCard, InfoCard } from './InfoCard' interface Props { navigation: NavigationBase + enterAnim: Anim screenWidth: number + // TODO: Add info server InfoCard export + cards?: PromoCard2[] | AssetStatus2[] countryCode?: string } -export const PromoCards = (props: Props) => { - const { countryCode, navigation, screenWidth } = props +export const InfoCardCarousel = (props: Props) => { + const { enterAnim, countryCode, navigation, screenWidth, cards } = props const theme = useTheme() const dispatch = useDispatch() - const account = useSelector(state => state.core.account) const accountReferral = useSelector(state => state.account.accountReferral) - const currencyWallets = useWatch(account, 'currencyWallets') - - const [filteredCards, setFilteredCards] = React.useState([]) - const [accountFunded, setAccountFunded] = React.useState() - - const walletsSynced = useSelector(state => { - const { currencyWallets } = state.core.account - const { userPausedWalletsSet } = state.ui.settings - const unPausedWallets = Object.values(currencyWallets).filter(wallet => !userPausedWalletsSet?.has(wallet.id)) - const unSyncedWallets = unPausedWallets.filter(wallet => wallet.syncRatio < 1) - - return unSyncedWallets.length === 0 - }) + const [filteredCards, setFilteredCards] = React.useState([]) // Set account funded status - React.useEffect(() => { - if (!walletsSynced) return - setAccountFunded(Object.values(currencyWallets).some(wallet => [...wallet.balanceMap.values()].some(balanceVal => !zeroString(balanceVal)))) - }, [currencyWallets, walletsSynced]) + const accountFunded = useIsAccountFunded() // Check for PromoCard2 from info server: React.useEffect(() => { - const cards = infoServerData.rollup?.promoCards2 ?? [] - + if (cards == null) return // We want to show cards even if balances aren't ready yet. We'll just // skip over balance-dependent cards until balances are ready const currentDate = new Date() @@ -65,7 +50,7 @@ export const PromoCards = (props: Props) => { const osVersion = getOsVersion() setFilteredCards( - filterPromoCards({ + filterInfoCards({ cards, countryCode, accountFunded, @@ -77,34 +62,34 @@ export const PromoCards = (props: Props) => { currentDate }) ) - }, [accountFunded, accountReferral, countryCode]) + }, [accountFunded, accountReferral, cards, countryCode]) const hiddenAccountMessages = useSelector(state => state.account.accountReferral.hiddenAccountMessages) const activeCards = React.useMemo(() => filteredCards.filter(card => !hiddenAccountMessages[card.messageId]), [filteredCards, hiddenAccountMessages]) // List rendering methods: - const keyExtractor = useHandler((item: FilteredPromoCard) => item.messageId) - const renderItem: ListRenderItem = useHandler(({ item }) => { + const keyExtractor = useHandler((item: FilteredInfoCard) => item.messageId) + const renderItem: ListRenderItem = useHandler(({ item }) => { const handleClose = async (): Promise => { await dispatch(hideMessageTweak(item.messageId, { type: 'account' })) } - return + return }) if (activeCards == null || activeCards.length === 0) return null return ( - + ) } /** - * Finds the promo cards that are relevant to our application version & + * Finds the info server cards that are relevant to our application version & * other factors. */ -export function filterPromoCards(params: { - cards: PromoCard2[] +export function filterInfoCards(params: { + cards: PromoCard2[] | AssetStatus2[] countryCode?: string buildNumber: string osType: string @@ -113,7 +98,7 @@ export function filterPromoCards(params: { currentDate: Date accountFunded?: boolean accountReferral?: Partial -}): FilteredPromoCard[] { +}): FilteredInfoCard[] { const { cards, countryCode, accountFunded, buildNumber, osType, version, osVersion, currentDate, accountReferral } = params let accountPromoIds: string[] | undefined @@ -125,7 +110,7 @@ export function filterPromoCards(params: { // Find relevant cards: const ccLowerCase = countryCode?.toLowerCase() - const filteredCards: FilteredPromoCard[] = [] + const filteredCards: FilteredInfoCard[] = [] for (const card of cards) { const { appVersion, diff --git a/src/components/common/EdgeAnim.tsx b/src/components/common/EdgeAnim.tsx index 8e174564b99..2a65cfb2f79 100644 --- a/src/components/common/EdgeAnim.tsx +++ b/src/components/common/EdgeAnim.tsx @@ -63,7 +63,7 @@ type AnimTypeStretchIns = 'stretchInY' type AnimTypeStretchOuts = 'stretchOutY' type AnimType = AnimTypeFadeIns | AnimTypeFadeOuts | AnimTypeStretchIns | AnimTypeStretchOuts -interface Anim { +export interface Anim { type: AnimType delay?: number duration?: number diff --git a/src/components/common/ExpandableList.tsx b/src/components/common/ExpandableList.tsx new file mode 100644 index 00000000000..9180a2dd12e --- /dev/null +++ b/src/components/common/ExpandableList.tsx @@ -0,0 +1,168 @@ +import * as React from 'react' +import { Platform, ScrollView, View, ViewStyle } from 'react-native' +import { AirshipBridge } from 'react-native-airship' +import { ShadowedView } from 'react-native-fast-shadow' +import { cacheStyles } from 'react-native-patina' +import Animated, { Easing, useAnimatedStyle, useDerivedValue, useSharedValue, withTiming } from 'react-native-reanimated' + +import { SCROLL_INDICATOR_INSET_FIX } from '../../constants/constantSettings' +import { useAsyncEffect } from '../../hooks/useAsyncEffect' +import { GradientFadeOut } from '../modals/GradientFadeout' +import { Airship } from '../services/AirshipInstance' +import { Theme, useTheme } from '../services/ThemeContext' + +interface Props { + isExpanded: boolean + items: React.ReactNode[] + /** Defaults to 4.75 */ + maxDisplayedItems?: number + /** If not provided, defaults to full screen width. Whether set or not, 0.5rem + * margins are applied. */ + widthRem?: number +} + +export const ExpandableList = (props: Props) => { + const { isExpanded, items, maxDisplayedItems = 4.75, widthRem } = props + + const theme = useTheme() + const styles = getStyles(theme) + + const [localBridge, setLocalBridge] = React.useState>() + const [itemHeight, setItemHeight] = React.useState(0) + const [dropdownLayoutStyle, setDropdownLayoutStyle] = React.useState() + + /** To measure the positioning and width of the anchor view */ + const anchorViewRef = React.useRef(null) + + const sAnimationMult = useSharedValue(0) + + const dFinalHeight = useDerivedValue(() => { + return itemHeight * Math.min(items.length, maxDisplayedItems) + }) + + const aContainerHeightStyle = useAnimatedStyle(() => ({ + height: withTiming(isExpanded ? dFinalHeight.value : 0, { + duration: 250, + easing: Easing.inOut(Easing.circle) + }), + opacity: isExpanded && items.length > 0 ? sAnimationMult.value : withTiming(0, { duration: 500 }) + })) + + const aFadeoutStyle = useAnimatedStyle(() => { + const isShowFade = items.length > maxDisplayedItems && isExpanded + return { + opacity: isShowFade ? withTiming(1, { duration: 500 }) : withTiming(0, { duration: 250 }), + height: isShowFade ? withTiming(48, { duration: 500 }) : withTiming(0, { duration: 250 }) + } + }) + + useAsyncEffect( + async () => { + if (dropdownLayoutStyle == null || itemHeight === 0) return + + sAnimationMult.value = withTiming(isExpanded ? 1 : 0, { + duration: 500, + easing: Easing.inOut(Easing.circle) + }) + + if (isExpanded && items.length > 0) { + if (localBridge != null) { + localBridge.resolve(undefined) + } + + await Airship.show(bridge => { + setLocalBridge(bridge) + + return ( + + + + {items} + + {!isExpanded ? null : ( + + + + )} + + + ) + }) + } else { + localBridge?.resolve(undefined) + } + + // Cleanup + return () => { + localBridge?.resolve(undefined) + } + }, + [sAnimationMult, isExpanded, items, dropdownLayoutStyle, itemHeight], + 'ExpandableList' + ) + + // Measure the anchor view position and width + React.useEffect(() => { + if (anchorViewRef.current != null) { + anchorViewRef.current.measureInWindow((x, y, width, height) => { + // iOS and Android return various garbage values initially. + // We expect positive nonzero values. We can always safely omit 0 values + // because we guarantee some amount of margin under correct UI design + if (x <= 0 || y <= 0 || width <= 0) return + + setDropdownLayoutStyle({ left: x, top: Platform.OS === 'android' ? y + 25 : y, width: widthRem == null ? width : theme.rem(widthRem) }) + }) + } + }, [dropdownLayoutStyle, anchorViewRef, theme, isExpanded, widthRem]) + + const handleRowLayout = (event: { nativeEvent: { layout: { height: number } } }) => { + if (event != null && itemHeight === 0) { + const { height } = event.nativeEvent.layout + setItemHeight(height) + } + } + + return ( + + {items.length === 0 ? null : ( + + {items[0]} + + )} + + ) +} + +const getStyles = cacheStyles((theme: Theme) => ({ + anchorContainer: { + marginHorizontal: theme.rem(0.5) + }, + dropdownContainer: { + position: 'absolute', + borderRadius: theme.rem(0.5) + }, + dummyMeasureContainer: { + position: 'absolute', + opacity: 0 + }, + fadeoutContainer: { + position: 'absolute', + right: 0, + left: 0, + bottom: 0, + borderBottomLeftRadius: theme.rem(0.5), + borderBottomRightRadius: theme.rem(0.5), + overflow: 'hidden' + }, + scrollContainer: { + flexGrow: 1 + }, + shadowViewStyle: { + borderRadius: theme.rem(0.5), + backgroundColor: theme.modal, + ...theme.dropdownListShadow + }, + shadowViewStyleAndroidAdjust: { + shadowOpacity: 0.1 + } +})) diff --git a/src/components/modals/SurveyModal.tsx b/src/components/modals/SurveyModal.tsx index 3d47f71bc90..7f1d0a25804 100644 --- a/src/components/modals/SurveyModal.tsx +++ b/src/components/modals/SurveyModal.tsx @@ -26,6 +26,10 @@ const SURVEY_OPTS = [ { label: lstrings.survey_opt_in_person_event, selected: false }, { label: lstrings.survey_opt_personal_referral, selected: false }, { label: lstrings.survey_opt_article, selected: false }, + // Show names intentionally left untranslated: + { label: 'Free Talk Live', selected: false }, + { label: 'Crypto Canal', selected: false }, + { label: 'Cris Cyborg', selected: false }, { label: lstrings.survey_opt_other_specify, selected: false } ] diff --git a/src/components/modals/WalletListModal.tsx b/src/components/modals/WalletListModal.tsx index 2bb02778274..6ece36090ef 100644 --- a/src/components/modals/WalletListModal.tsx +++ b/src/components/modals/WalletListModal.tsx @@ -5,13 +5,14 @@ import { AirshipBridge } from 'react-native-airship' import { FlatList } from 'react-native-gesture-handler' import { sprintf } from 'sprintf-js' +import { updateMostRecentWalletsSelected } from '../../actions/WalletActions' import { SPECIAL_CURRENCY_INFO } from '../../constants/WalletAndCurrencyConstants' import { PaymentMethod, PaymentMethodsMap } from '../../controllers/action-queue/PaymentMethod' import { useAsyncValue } from '../../hooks/useAsyncValue' import { useHandler } from '../../hooks/useHandler' import { lstrings } from '../../locales/strings' import { config } from '../../theme/appConfig' -import { useSelector } from '../../types/reactRedux' +import { useDispatch, useSelector } from '../../types/reactRedux' import { NavigationBase } from '../../types/routerTypes' import { EdgeAsset } from '../../types/types' import { getCurrencyCode, isKeysOnlyPlugin } from '../../util/CurrencyInfoHelpers' @@ -100,6 +101,7 @@ export function WalletListModal(props: Props) { const showCustomAssets = customAssets != null && customAssets.length > 0 + const dispatch = useDispatch() const account = useSelector(state => state.core.account) const theme = useTheme() const styles = getStyles(theme) @@ -144,6 +146,8 @@ export function WalletListModal(props: Props) { } else { const wallet = await account.waitForCurrencyWallet(walletId) const currencyCode = getCurrencyCode(wallet, tokenId) + + dispatch(updateMostRecentWalletsSelected(walletId, tokenId)) bridge.resolve({ type: 'wallet', walletId, currencyCode, tokenId }) } }) diff --git a/src/components/navigation/AlertDropdown.tsx b/src/components/navigation/AlertDropdown.tsx index b45a60a7e66..a5ea77e89a6 100644 --- a/src/components/navigation/AlertDropdown.tsx +++ b/src/components/navigation/AlertDropdown.tsx @@ -4,35 +4,54 @@ import { AirshipBridge } from 'react-native-airship' import AntDesignIcon from 'react-native-vector-icons/AntDesign' import EntypoIcon from 'react-native-vector-icons/Entypo' +import { useHandler } from '../../hooks/useHandler' import { lstrings } from '../../locales/strings' import { textStyle } from '../../styles/common/textStylesThemed' import { AirshipDropdown } from '../common/AirshipDropdown' +import { EdgeTouchableOpacity } from '../common/EdgeTouchableOpacity' import { cacheStyles, Theme, useTheme } from '../services/ThemeContext' interface Props { bridge: AirshipBridge message: string - // True for orange warning, false for red alert: + /** True for orange warning, false for red alert: */ warning?: boolean - devAlert?: boolean + + /** No auto-hide, must dismiss through the tap. */ + persistent?: boolean + + /** If given, pressing the body of the dropdown invokes onPress, while the + * close icon dismisses the dropdown */ + onPress?: () => void | Promise } export function AlertDropdown(props: Props) { - const { bridge, devAlert, message, warning } = props + const { bridge, persistent, message, warning, onPress } = props const theme = useTheme() const styles = getStyles(theme) const color = warning ? theme.dropdownWarning : theme.dropdownError + const handleOnPress = useHandler(async () => { + if (onPress != null) await onPress() + bridge.resolve() + }) + + const handleClose = useHandler(() => { + bridge.resolve() + }) + return ( - + {warning ? lstrings.alert_dropdown_warning : lstrings.alert_dropdown_alert} {message} - + + + ) diff --git a/src/components/navigation/FlashNotification.tsx b/src/components/navigation/FlashNotification.tsx index acaa66c4813..0f7434c613f 100644 --- a/src/components/navigation/FlashNotification.tsx +++ b/src/components/navigation/FlashNotification.tsx @@ -1,28 +1,29 @@ import * as React from 'react' -import { Text, View } from 'react-native' +import { View } from 'react-native' import { AirshipBridge } from 'react-native-airship' import AntDesignIcon from 'react-native-vector-icons/AntDesign' -import { THEME } from '../../theme/variables/airbitz' import { AirshipDropdown } from '../common/AirshipDropdown' import { cacheStyles, Theme, useTheme } from '../services/ThemeContext' +import { Paragraph } from '../themed/EdgeText' interface Props { bridge: AirshipBridge message: string + icon?: React.ReactNode onPress?: () => void } export function FlashNotification(props: Props) { - const { bridge, message, onPress = () => {} } = props + const { bridge, message, icon, onPress = () => {} } = props const theme = useTheme() const styles = getStyles(theme) return ( - - {message} + {icon ?? } + {message} ) @@ -30,19 +31,12 @@ export function FlashNotification(props: Props) { const getStyles = cacheStyles((theme: Theme) => ({ container: { - paddingBottom: theme.rem(1) - }, - text: { - color: THEME.COLORS.WHITE, - flexShrink: 1, - fontFamily: THEME.FONTS.DEFAULT, - fontSize: THEME.rem(1), - padding: theme.rem(0.25), - textAlign: 'center' + margin: theme.rem(0.5), + alignItems: 'center' }, icon: { alignSelf: 'center', color: theme.iconTappable, - paddingTop: theme.rem(0.25) + margin: theme.rem(0.5) } })) diff --git a/src/components/rows/SwapProviderRow.tsx b/src/components/rows/SwapProviderRow.tsx index 924667a8758..83280bf0b77 100644 --- a/src/components/rows/SwapProviderRow.tsx +++ b/src/components/rows/SwapProviderRow.tsx @@ -1,12 +1,13 @@ import { EdgeSwapQuote } from 'edge-core-js' import React from 'react' import FastImage from 'react-native-fast-image' +import { sprintf } from 'sprintf-js' import { useCryptoText } from '../../hooks/useCryptoText' import { lstrings } from '../../locales/strings' import { getSwapPluginIconUri } from '../../util/CdnUris' import { cacheStyles, Theme, useTheme } from '../services/ThemeContext' -import { EdgeText } from '../themed/EdgeText' +import { SmallText, WarningText } from '../themed/EdgeText' import { IconDataRow } from './IconDataRow' export interface Props { @@ -28,13 +29,24 @@ export const SwapProviderRow = (props: Props) => { const costOrReceiveAmount = useCryptoText({ wallet, tokenId, nativeAmount }) + const minCryptoAmountText = useCryptoText({ wallet: toWallet, tokenId: toTokenId, nativeAmount: quote.minReceiveAmount ?? '0', withSymbol: false }) + const minReceiveAmountOrPartial = + quote.minReceiveAmount != null ? ( + sprintf(lstrings.swap_minimum_amount_1s, minCryptoAmountText) + ) : quote.canBePartial ? ( + + {lstrings.quote_partial_settlement} + + ) : undefined + const maybeVariableSymbol = quote.minReceiveAmount || quote.canBePartial ? '~ ' : '' + return ( } leftText={quote.swapInfo.displayName} leftSubtext={quote.swapInfo.isDex ? lstrings.quote_dex_provider : lstrings.quote_centralized_provider} - rightText={costOrReceiveAmount} - rightSubText={quote.canBePartial ? {lstrings.quote_partial_settlement} : ''} + rightText={`${maybeVariableSymbol}${costOrReceiveAmount}`} + rightSubText={minReceiveAmountOrPartial} /> ) } @@ -44,9 +56,5 @@ const getStyles = cacheStyles((theme: Theme) => ({ aspectRatio: 1, width: theme.rem(2), height: theme.rem(2) - }, - partialSettlementText: { - fontSize: theme.rem(0.75), - color: theme.warningText } })) diff --git a/src/components/scenes/DevTestScene.tsx b/src/components/scenes/DevTestScene.tsx index c67c9e427c8..eb044f26ef1 100644 --- a/src/components/scenes/DevTestScene.tsx +++ b/src/components/scenes/DevTestScene.tsx @@ -1,3 +1,4 @@ +import { useNavigation } from '@react-navigation/native' import { addBreadcrumb, captureException } from '@sentry/react-native' import { eq } from 'biggystring' import { InsufficientFundsError } from 'edge-core-js' @@ -11,9 +12,10 @@ import { Fontello } from '../../assets/vector' import { ENV } from '../../env' import { useSelectedWallet } from '../../hooks/useSelectedWallet' import { lstrings } from '../../locales/strings' +import { HomeAddress } from '../../types/FormTypes' import { useState } from '../../types/reactHooks' import { useDispatch } from '../../types/reactRedux' -import { EdgeSceneProps } from '../../types/routerTypes' +import { EdgeSceneProps, NavigationBase } from '../../types/routerTypes' import { parseDeepLink } from '../../util/DeepLinkParser' import { consify } from '../../util/utils' import { ButtonsView } from '../buttons/ButtonsView' @@ -112,6 +114,27 @@ export function DevTestScene(props: Props) { )).catch(error => console.log(error)) } + const navigation2 = useNavigation() + + const handleAddressFormPress = () => { + navigation2.navigate('buyTab', { + screen: 'guiPluginAddressForm', + params: { + // Add any necessary props here + countryCode: 'US', + headerTitle: 'Address Form', + onSubmit: async (homeAddress: HomeAddress) => { + console.log('Address submitted:', homeAddress) + // Handle the submitted address + }, + onClose: () => { + console.log('Address form closed') + // Handle closing the form + } + } + }) + } + const coreWallet = selectedWallet?.wallet let balance = coreWallet?.balanceMap.get(tokenId) ?? '' if (eq(balance, '0')) balance = '' @@ -132,6 +155,10 @@ export function DevTestScene(props: Props) { + <> + + + <> Galore} /> { } if (plugin.nativePlugin != null) { const cards = infoServerData.rollup?.promoCards2 ?? [] - const promoCards = filterPromoCards({ + const promoCards = filterInfoCards({ accountReferral, cards, countryCode, diff --git a/src/components/scenes/HomeScene.tsx b/src/components/scenes/HomeScene.tsx index dbd0b9f01ce..9ee24a1a836 100644 --- a/src/components/scenes/HomeScene.tsx +++ b/src/components/scenes/HomeScene.tsx @@ -19,10 +19,10 @@ import { infoServerData } from '../../util/network' import { BalanceCard } from '../cards/BalanceCard' import { ContentPostCarousel } from '../cards/ContentPostCarousel' import { HomeTileCard } from '../cards/HomeTileCard' +import { InfoCardCarousel } from '../cards/InfoCardCarousel' import { MarketsCard } from '../cards/MarketsCard' -import { PromoCards } from '../cards/PromoCards' import { SupportCard } from '../cards/SupportCard' -import { EdgeAnim, fadeInUp30, fadeInUp60, fadeInUp80, fadeInUp140 } from '../common/EdgeAnim' +import { EdgeAnim, fadeInUp30, fadeInUp60, fadeInUp80, fadeInUp110, fadeInUp140 } from '../common/EdgeAnim' import { SceneWrapper } from '../common/SceneWrapper' import { SectionHeader } from '../common/SectionHeader' import { SectionView } from '../layout/SectionView' @@ -141,7 +141,13 @@ export const HomeScene = (props: Props) => { {/* Animation inside PromoCardsUi4 component */} - + { }) } navigation.setParams(params) - dispatch(updateMostRecentWalletsSelected(walletId, tokenId)) }) const handleMaxPress = useHandler(() => { diff --git a/src/components/scenes/TransactionListScene.tsx b/src/components/scenes/TransactionListScene.tsx index d492da24636..62b379a7855 100644 --- a/src/components/scenes/TransactionListScene.tsx +++ b/src/components/scenes/TransactionListScene.tsx @@ -1,9 +1,8 @@ import { EdgeCurrencyWallet, EdgeTokenId, EdgeTokenMap, EdgeTransaction } from 'edge-core-js' -import { AssetStatus } from 'edge-info-server' import * as React from 'react' import { ListRenderItemInfo, Platform, RefreshControl, View } from 'react-native' -import { getVersion } from 'react-native-device-info' import Animated from 'react-native-reanimated' +import { useSafeAreaFrame } from 'react-native-safe-area-context' import { SCROLL_INDICATOR_INSET_FIX } from '../../constants/constantSettings' import { SPECIAL_CURRENCY_INFO } from '../../constants/WalletAndCurrencyConstants' @@ -11,17 +10,15 @@ import { useHandler } from '../../hooks/useHandler' import { useIconColor } from '../../hooks/useIconColor' import { useTransactionList } from '../../hooks/useTransactionList' import { useWatch } from '../../hooks/useWatch' -import { getLocaleOrDefaultString } from '../../locales/intl' import { lstrings } from '../../locales/strings' import { getExchangeDenomByCurrencyCode } from '../../selectors/DenominationSelectors' import { FooterRender } from '../../state/SceneFooterState' import { useSceneScrollHandler } from '../../state/SceneScrollState' -import { config } from '../../theme/appConfig' import { useSelector } from '../../types/reactRedux' import { EdgeSceneProps } from '../../types/routerTypes' import { infoServerData } from '../../util/network' import { calculateSpamThreshold, darkenHexColor, unixToLocaleDateTime, zeroString } from '../../util/utils' -import { AssetStatusCard } from '../cards/AssetStatusCard' +import { InfoCardCarousel } from '../cards/InfoCardCarousel' import { AccentColors } from '../common/DotsBackground' import { EdgeAnim, fadeInDown10, MAX_LIST_ITEMS_ANIM } from '../common/EdgeAnim' import { SceneWrapper } from '../common/SceneWrapper' @@ -38,6 +35,7 @@ export interface TransactionListParams { walletId: string walletName: string tokenId: EdgeTokenId + countryCode?: string } type ListItem = EdgeTransaction | string | null @@ -50,6 +48,8 @@ function TransactionListComponent(props: Props) { const theme = useTheme() const styles = getStyles(theme) + const { width: screenWidth } = useSafeAreaFrame() + const tokenId = checkToken(route.params.tokenId, wallet.currencyConfig.allTokens) const { pluginId } = wallet.currencyInfo const { currencyCode } = tokenId == null ? wallet.currencyInfo : wallet.currencyConfig.allTokens[tokenId] @@ -139,27 +139,6 @@ function TransactionListComponent(props: Props) { } }, [enabledTokenIds, navigation, tokenId]) - // Check for AssetStatuses from info server (known sync issues, etc): - const assetStatuses = React.useMemo(() => { - const pluginTokenId = `${pluginId}${tokenId == null ? '' : `_${tokenId}`}` - const allAssetStatuses = (infoServerData.rollup?.assetStatusCards ?? {})[pluginTokenId] ?? [] - const version = getVersion() - return allAssetStatuses.filter(assetStatus => { - const { appId, appVersions, localeStatusBody, localeStatusTitle, statusStartIsoDate, statusEndIsoDate } = assetStatus - const curDate = new Date().toISOString() - - const title = getLocaleOrDefaultString(localeStatusTitle) - const message = getLocaleOrDefaultString(localeStatusBody) - - if (title == null || message == null) return false - if (appId != null && appId !== config.appId) return false - if (appVersions != null && !appVersions.includes(version)) return false - if (statusEndIsoDate != null && statusEndIsoDate < curDate) return false - if (statusStartIsoDate != null && statusStartIsoDate > curDate) return false - return true - }) - }, [pluginId, tokenId]) - // // Handlers // @@ -217,16 +196,16 @@ function TransactionListComponent(props: Props) { onSearchingChange={setIsSearching} onSearchTextChange={setSearchText} /> - {assetStatuses.length > 0 && !isSearching - ? assetStatuses.map(assetStatus => ( - - - - )) - : null} + ) - }, [assetStatuses, isLightAccount, listItems.length, navigation, isSearching, tokenId, wallet]) + }, [listItems.length, navigation, isSearching, tokenId, wallet, isLightAccount, pluginId, route.params.countryCode, screenWidth]) const emptyComponent = React.useMemo(() => { if (isTransactionListUnsupported) { diff --git a/src/components/services/AirshipInstance.tsx b/src/components/services/AirshipInstance.tsx index 4ac9ade2785..7f015a5ee0b 100644 --- a/src/components/services/AirshipInstance.tsx +++ b/src/components/services/AirshipInstance.tsx @@ -56,7 +56,7 @@ export async function showDevErrorAsync(error: unknown, options: ShowErrorWarnin if (__DEV__ || appVersion === '99.99.99' || appVersion.includes('-d')) { // Non-production (develop) builds show all errors - await Airship.show(bridge => ) + await Airship.show(bridge => ) } else { // Production/staging builds don't show visible errors, but just saves a // breadcrumb. diff --git a/src/components/services/DeepLinkingManager.ts b/src/components/services/DeepLinkingManager.tsx similarity index 69% rename from src/components/services/DeepLinkingManager.ts rename to src/components/services/DeepLinkingManager.tsx index 0e63f015849..a0246a67645 100644 --- a/src/components/services/DeepLinkingManager.ts +++ b/src/components/services/DeepLinkingManager.tsx @@ -1,6 +1,7 @@ import messaging, { FirebaseMessagingTypes } from '@react-native-firebase/messaging' import * as React from 'react' import { Linking } from 'react-native' +import FontAwesomeIcon from 'react-native-vector-icons/FontAwesome' import { launchDeepLink } from '../../actions/DeepLinkingActions' import { ENV } from '../../env' @@ -12,7 +13,9 @@ import { useDispatch, useSelector } from '../../types/reactRedux' import { NavigationBase } from '../../types/routerTypes' import { parseDeepLink } from '../../util/DeepLinkParser' import { parsePushMessage } from '../../util/PushMessageParser' -import { showDevError, showError } from './AirshipInstance' +import { FlashNotification } from '../navigation/FlashNotification' +import { Airship, showDevError, showError } from './AirshipInstance' +import { cacheStyles, Theme, useTheme } from './ThemeContext' interface Props { navigation: NavigationBase @@ -21,6 +24,8 @@ interface Props { export function DeepLinkingManager(props: Props) { const { navigation } = props const dispatch = useDispatch() + const theme = useTheme() + const styles = getStyles(theme) const [pendingLink, setPendingLink] = React.useState() @@ -64,7 +69,8 @@ export function DeepLinkingManager(props: Props) { } } - function handlePushMessage(message: FirebaseMessagingTypes.RemoteMessage): void { + /** Handler for push messages received while app is in the background. */ + function handleBackgroundPushMessage(message: FirebaseMessagingTypes.RemoteMessage): void { try { const link = parsePushMessage(message) if (link != null) setPendingLink(link) @@ -74,15 +80,37 @@ export function DeepLinkingManager(props: Props) { } } + /** Handler for push messages received while app is in the foreground. */ + const handleForegroundPushMessage = (message: FirebaseMessagingTypes.RemoteMessage) => { + const body = message.notification?.body ?? '' + + if (body === '') { + console.warn('FirebaseMessagingTypes.RemoteMessage (foreground push message) has no body') + return + } + + // Show a FlashNotification: + Airship.show(bridge => ( + { + bridge.resolve() + }} + icon={} + /> + )).catch(error => showDevError(String(error))) + } + // Subscribe to various incoming events: const linkingCleanup = Linking.addEventListener('url', event => { handleDeepLink(event.url) }) const messageCleanup = messaging().onMessage(message => { - // do nothing for now except return the unsubscribe function + handleForegroundPushMessage(message) }) const launchCleanup = messaging().onNotificationOpenedApp(message => { - handlePushMessage(message) + handleBackgroundPushMessage(message) }) // Load any tapped links: @@ -91,7 +119,7 @@ export function DeepLinkingManager(props: Props) { // Load any links sent by push messages: const message = await messaging().getInitialNotification() - if (message != null) handlePushMessage(message) + if (message != null) handleBackgroundPushMessage(message) return () => { if (linkingCleanup != null) linkingCleanup.remove() @@ -105,3 +133,11 @@ export function DeepLinkingManager(props: Props) { return null } + +const getStyles = cacheStyles((theme: Theme) => ({ + icon: { + alignSelf: 'center', + color: theme.iconTappable, + margin: theme.rem(0.5) + } +})) diff --git a/src/components/themed/ExchangeQuoteComponent.tsx b/src/components/themed/ExchangeQuoteComponent.tsx index 960d96dffbb..247976332f4 100644 --- a/src/components/themed/ExchangeQuoteComponent.tsx +++ b/src/components/themed/ExchangeQuoteComponent.tsx @@ -15,6 +15,7 @@ import { DECIMAL_PRECISION, removeIsoPrefix } from '../../util/utils' import { EdgeCard } from '../cards/EdgeCard' import { CurrencyRow } from '../rows/CurrencyRow' import { cacheStyles, Theme, useTheme } from '../services/ThemeContext' +import { FiatText } from '../text/FiatText' import { EdgeText } from './EdgeText' interface Props { @@ -71,6 +72,11 @@ export const ExchangeQuote = (props: Props) => { const fiatCurrencyCode = removeIsoPrefix(isoFiatCurrencyCode) const totalFiatText = `${formatFiatString({ fiatAmount: add(feeFiatAmount, fromFiatAmount) })} ${fiatCurrencyCode}` + const minCryptoAmountText = useCryptoText({ wallet: toWallet, tokenId: toTokenId, nativeAmount: quote.minReceiveAmount ?? '0', withSymbol: false }) + const minFiatAmountText = ( + + ) + const renderRow = (label: React.ReactNode, value: React.ReactNode, style: any = {}) => { return ( @@ -99,8 +105,20 @@ export const ExchangeQuote = (props: Props) => { )} ) + } else if (quote.minReceiveAmount != null) { + // Show the minimum receive amount + return ( + + {renderRow( + {lstrings.swap_minimum_receive_amount}, + {minCryptoAmountText} + )} + {renderRow(<>, ({minFiatAmountText}))} + + ) + } else { + return null } - return null } return ( diff --git a/src/components/themed/SideMenu.tsx b/src/components/themed/SideMenu.tsx index 271e5d8e33a..eb84a1e7a0c 100644 --- a/src/components/themed/SideMenu.tsx +++ b/src/components/themed/SideMenu.tsx @@ -39,6 +39,7 @@ import { Services } from '../services/Services' import { cacheStyles, Theme, useTheme } from '../services/ThemeContext' import { TitleText } from '../text/TitleText' import { DividerLine } from './DividerLine' +import { EdgeText } from './EdgeText' const footerGradientStart = { x: 0, y: 0 } const footerGradientEnd = { x: 0, y: 0.75 } @@ -176,7 +177,6 @@ export function SideMenu(props: DrawerContentComponentProps) { // Track the destination height of the dropdown const userListDesiredHeight = styles.rowContainer.height * sortedUsers.length + theme.rem(1) const userListHeight = Math.min(userListDesiredHeight, bottomPanelHeight) - const isUserListHeightOverflowing = userListDesiredHeight >= bottomPanelHeight // Height value above can change if users are added/removed const sMaxHeight = useSharedValue(userListHeight) @@ -196,11 +196,6 @@ export function SideMenu(props: DrawerContentComponentProps) { /// ---- Dynamic CSS ---- - const themeRem2 = theme.rem(2) // We cannot call theme.rem from within worklet - const aBorderBottomRightRadius = useAnimatedStyle(() => ({ - // Use a easeInCirc easing function for the border transition - borderBottomRightRadius: isUserListHeightOverflowing ? themeRem2 - themeRem2 * (1 - Math.sqrt(1 - sAnimationMult.value ** 4)) : themeRem2 - })) const aDropdown = useAnimatedStyle(() => ({ height: sMaxHeight.value * sAnimationMult.value })) @@ -302,7 +297,9 @@ export function SideMenu(props: DrawerContentComponentProps) { - {displayUsername} + + {displayUsername} + {isMultiUsers ? ( @@ -320,16 +317,16 @@ export function SideMenu(props: DrawerContentComponentProps) { const usernameDropdown = ( <> - + {sortedUsers.map(userInfo => ( {/* This empty container is required to align the row contents properly */} - + {userInfo.username == null ? sprintf(lstrings.guest_account_id_1s, userInfo.loginId.slice(userInfo.loginId.length - 3)) : userInfo.username} - + @@ -472,7 +469,7 @@ const getStyles = cacheStyles((theme: Theme) => ({ // Animation dropContainer: { backgroundColor: theme.modal, - borderBottomLeftRadius: theme.rem(2), + borderBottomLeftRadius: theme.rem(1), zIndex: 2, position: 'absolute', width: '100%' @@ -498,7 +495,7 @@ const getStyles = cacheStyles((theme: Theme) => ({ position: 'absolute', height: '100%', width: '100%', - borderBottomLeftRadius: theme.rem(2), + borderBottomLeftRadius: theme.rem(1), zIndex: 1 } })) diff --git a/src/components/themed/WalletListSwipeable.tsx b/src/components/themed/WalletListSwipeable.tsx index 4d985c2326d..d804f6dfbc6 100644 --- a/src/components/themed/WalletListSwipeable.tsx +++ b/src/components/themed/WalletListSwipeable.tsx @@ -4,6 +4,7 @@ import { useMemo } from 'react' import { FlatList, RefreshControl } from 'react-native' import Animated from 'react-native-reanimated' +import { getCountryCodeByIp } from '../../actions/AccountReferralActions' import { selectWalletToken } from '../../actions/WalletActions' import { SCROLL_INDICATOR_INSET_FIX } from '../../constants/constantSettings' import { useHandler } from '../../hooks/useHandler' @@ -66,6 +67,7 @@ function WalletListSwipeableComponent(props: Props) { const handleCreateWallet = useHandler(async (walletId: string, tokenId: EdgeTokenId) => { const wallet = account.currencyWallets[walletId] + const countryCode = await getCountryCodeByIp().catch(() => '') dispatch(selectWalletToken({ navigation, walletId, tokenId })) .then( activationNotRequired => @@ -73,7 +75,8 @@ function WalletListSwipeableComponent(props: Props) { navigation.navigate('transactionList', { walletId, tokenId, - walletName: wallet.name ?? wallet.currencyInfo.displayName + walletName: wallet.name ?? wallet.currencyInfo.displayName, + countryCode }) ) .finally(onReset) diff --git a/src/components/themed/WalletListSwipeableCurrencyRow.tsx b/src/components/themed/WalletListSwipeableCurrencyRow.tsx index 9796b2dd93a..f8356a19af8 100644 --- a/src/components/themed/WalletListSwipeableCurrencyRow.tsx +++ b/src/components/themed/WalletListSwipeableCurrencyRow.tsx @@ -3,6 +3,7 @@ import * as React from 'react' import { Text } from 'react-native' import { SharedValue } from 'react-native-reanimated' +import { getCountryCodeByIp } from '../../actions/AccountReferralActions' import { checkAndShowLightBackupModal } from '../../actions/BackupModalActions' import { selectWalletToken } from '../../actions/WalletActions' import { Fontello } from '../../assets/vector/index' @@ -69,8 +70,9 @@ function WalletListSwipeableCurrencyRowComponent(props: Props) { closeRow() dispatch(selectWalletToken({ navigation, walletId: wallet.id, tokenId, alwaysActivate: true })) .then(async activated => { + const countryCode = await getCountryCodeByIp().catch(() => '') if (activated) { - navigation.navigate('transactionList', { tokenId, walletId: wallet.id, walletName: wallet.name ?? wallet.currencyInfo.displayName }) + navigation.navigate('transactionList', { tokenId, walletId: wallet.id, walletName: wallet.name ?? wallet.currencyInfo.displayName, countryCode }) } }) .catch(err => showError(err)) diff --git a/src/constants/plugins/GuiPlugins.ts b/src/constants/plugins/GuiPlugins.ts index 57f8c3ab50a..ddf78a2c131 100644 --- a/src/constants/plugins/GuiPlugins.ts +++ b/src/constants/plugins/GuiPlugins.ts @@ -157,6 +157,14 @@ export const guiPlugins: { [pluginId: string]: GuiPlugin } = { forceFiatCurrencyCode: 'iso:AUD', displayName: '' }, + paypal: { + pluginId: 'amountquote', + storeId: '', + baseUri: '', + lockUriPath: true, + nativePlugin: amountQuoteFiatPlugin, + displayName: '' + }, pix: { pluginId: 'amountquote', storeId: '', @@ -187,6 +195,14 @@ export const guiPlugins: { [pluginId: string]: GuiPlugin } = { defaultFiatAmount: '2000', displayName: '' }, + revolut: { + pluginId: 'amountquote', + storeId: '', + baseUri: '', + lockUriPath: true, + nativePlugin: amountQuoteFiatPlugin, + displayName: '' + }, sepa: { pluginId: 'amountquote', storeId: '', diff --git a/src/constants/plugins/buyPluginList.json b/src/constants/plugins/buyPluginList.json index 6672cb18bc2..5239fbee63a 100644 --- a/src/constants/plugins/buyPluginList.json +++ b/src/constants/plugins/buyPluginList.json @@ -7,243 +7,30 @@ "paymentTypes": ["credit"], "title": "Credit and Debit Card", "forCountries": [ - "AF", - "AL", - "DZ", - "AS", - "AD", - "AO", - "AI", - "AQ", - "AG", - "AR", - "AM", - "AW", - "AU", - "AT", - "AZ", - "BH", - "BD", - "BB", - "BY", - "BE", - "BZ", - "BJ", - "BM", - "BT", - "BO", - "BQ", - "BA", - "BV", - "BR", - "BW", - "IO", - "BN", - "BG", - "BF", - "BI", - "CM", - "CA", - "CV", - "KY", - "CF", - "TD", - "CL", - "CN", - "CX", - "CC", - "CO", - "KM", - "CG", - "CD", - "CK", - "CR", - "HR", - "CW", - "CY", - "CZ", - "CI", - "DK", - "DJ", - "DM", - "DO", - "EC", - "EG", - "SV", - "GQ", - "ER", - "EE", - "FK", - "FO", - "FJ", - "FI", - "FR", - "GF", - "GH", - "PF", - "TF", - "GA", - "GM", - "GE", - "DE", - "GI", - "GR", - "GL", - "GD", - "GP", - "GU", - "GT", - "GG", - "GN", - "GW", - "GY", - "KH", - "HT", - "HM", - "VA", - "HN", - "HK", - "HU", - "IS", - "IN", - "ID", - "IE", - "IM", - "IL", - "IT", - "JM", - "JP", - "JE", - "JO", - "KZ", - "KE", - "KI", - "KR", - "KW", - "KG", - "LA", - "LV", - "LB", - "LS", - "LR", - "LY", - "LI", - "LK", - "LT", - "LU", - "MO", - "MK", - "MG", - "MW", - "MY", - "MV", - "ML", - "MT", - "MH", - "MQ", - "MR", - "MU", - "YT", - "MX", - "FM", - "MD", - "MC", - "MN", - "ME", - "MS", - "MA", - "MZ", - "MM", - "NA", - "NR", - "NP", - "NL", - "NC", - "NZ", - "NI", - "NE", - "NG", - "NU", - "NF", - "MP", - "NO", - "OM", - "PA", - "PW", - "PS", - "PG", - "PY", - "PE", - "PH", - "PN", - "PL", - "PT", - "PR", - "QA", - "RO", - "RU", - "RW", - "RE", - "BL", - "SH", - "KN", - "LC", - "MF", - "PM", - "VC", - "WS", - "SM", - "ST", - "SA", - "SN", - "RS", - "SC", - "SL", - "SG", - "SX", - "SK", - "SI", - "SB", - "SO", - "ZA", - "GS", - "SS", - "ES", - "SD", - "SR", - "SJ", - "SZ", - "SE", - "CH", - "TW", - "TJ", - "TZ", - "TH", - "TL", - "TG", - "TK", - "TO", - "TR", - "TM", - "TC", - "TV", - "UG", - "UA", - "AE", - "GB", - "US", - "UM", - "UY", - "UZ", - "VU", - "VE", - "VN", - "VG", - "VI", - "WF", - "EH", - "ZM", - "ZW" + "AD", "AE", "AF", "AG", "AI", "AL", "AM", "AO", "AQ", "AR", + "AS", "AT", "AU", "AW", "AZ", "BA", "BB", "BD", "BE", "BF", + "BG", "BH", "BI", "BJ", "BL", "BM", "BN", "BO", "BQ", "BR", + "BT", "BV", "BW", "BY", "BZ", "CA", "CC", "CD", "CF", "CG", + "CH", "CI", "CK", "CL", "CM", "CN", "CO", "CR", "CV", "CW", + "CX", "CY", "CZ", "DE", "DJ", "DK", "DM", "DO", "DZ", "EC", + "EE", "EG", "EH", "ER", "ES", "FI", "FJ", "FK", "FM", "FO", + "FR", "GA", "GB", "GD", "GE", "GF", "GG", "GH", "GI", "GL", + "GM", "GN", "GP", "GQ", "GR", "GS", "GT", "GU", "GW", "GY", + "HK", "HM", "HN", "HR", "HT", "HU", "ID", "IE", "IL", "IM", + "IN", "IO", "IQ", "IS", "IT", "JE", "JM", "JO", "JP", "KE", "KG", + "KH", "KI", "KM", "KN", "KR", "KW", "KY", "KZ", "LA", "LB", + "LC", "LI", "LK", "LR", "LS", "LT", "LU", "LV", "LY", "MA", + "MC", "MD", "ME", "MF", "MG", "MH", "MK", "ML", "MM", "MN", + "MO", "MP", "MQ", "MR", "MS", "MT", "MU", "MV", "MW", "MX", + "MY", "MZ", "NA", "NC", "NE", "NF", "NG", "NI", "NL", "NO", + "NP", "NR", "NU", "NZ", "OM", "PA", "PE", "PF", "PG", "PH", + "PL", "PM", "PN", "PR", "PS", "PT", "PW", "PY", "QA", "RE", + "RO", "RS", "RU", "RW", "SA", "SB", "SC", "SD", "SE", "SG", + "SH", "SI", "SJ", "SK", "SL", "SM", "SN", "SO", "SR", "SS", + "ST", "SV", "SX", "SZ", "TC", "TD", "TF", "TG", "TH", "TJ", + "TK", "TL", "TM", "TO", "TR", "TV", "TW", "TZ", "UA", "UG", + "UM", "US", "UY", "UZ", "VA", "VC", "VE", "VG", "VI", "VN", + "VU", "WF", "WS", "YT", "ZA", "ZM", "ZW" ], "cryptoCodes": [], "paymentTypeLogoKey": "credit" @@ -267,7 +54,7 @@ "title": "Instant ACH Bank Transfer", "description": "Fee: ~2%\nSettlement: ~5 minutes\nLimit $1000", "forCountries": ["US"], - "notStateProvinces": { "US": ["FL", "NY", "TX"] }, + "notStateProvinces": { "US": ["AK", "AR", "CT", "NC", "NY", "TX"] }, "cryptoCodes": [], "paymentTypeLogoKey": "bank" }, @@ -287,36 +74,9 @@ "id": "sepa", "pluginId": "sepa", "forCountries": [ - "AT", - "BE", - "BG", - "CH", - "CZ", - "DK", - "EE", - "FI", - "FR", - "DE", - "GR", - "HU", - "IE", - "IT", - "LV", - "LT", - "LU", - "NL", - "PL", - "PT", - "RO", - "SK", - "SI", - "ES", - "SE", - "HR", - "LI", - "NO", - "SM", - "GB" + "AT", "BE", "BG", "CH", "CZ", "DE", "DK", "EE", "ES", "FI", + "FR", "GB", "GR", "HR", "HU", "IE", "IT", "LI", "LT", "LU", + "LV", "NL", "NO", "PL", "PT", "RO", "SE", "SI", "SK", "SM" ], "paymentType": "sepa", "paymentTypes": ["sepa"], @@ -369,6 +129,23 @@ "cryptoCodes": [], "paymentTypeLogoKey": "payid" }, + { + "id": "paypal", + "pluginId": "paypal", + "paymentType": "paypal", + "paymentTypes": ["paypal"], + "title": "Paypal", + "description": "Fee: ~5%\nSettlement: 5 min - 24 hours", + "forCountries": [ + "AE", "AR", "AT", "AU", "BE", "BR", "CA", "CH", "CL", "CO", + "CZ", "DE", "DK", "EG", "ES", "FI", "FR", "GH", "GR", "HK", + "HU", "IE", "IL", "IN", "IT", "JP", "KE", "KR", "MX", "MY", + "NG", "NL", "NO", "NZ", "PE", "PH", "PL", "PT", "RU", "SA", + "SE", "SG", "TH", "TR", "TZ", "US", "VN", "ZA" + ], + "cryptoCodes": [], + "paymentTypeLogoKey": "paypal" + }, { "id": "pix", "pluginId": "pix", @@ -392,6 +169,23 @@ "cryptoCodes": [], "paymentTypeLogoKey": "bank" }, + { + "id": "revolut", + "pluginId": "revolut", + "paymentType": "revolut", + "paymentTypes": ["revolut"], + "title": "Revolut", + "description": "Fee: ~5%\nSettlement: 5 min - 24 hours", + "forCountries": [ + "AU", "BR", "AT", "BE", "BG", "CY", "CZ", "DE", "DK", "EE", "ES", "FI", + "FR", "GR", "HR", "HU", "IE", "IS", "IT", "LI", "LT", "LU", "LV", + "MT", "NL", "NO", "PL", "PT", "RO", "SE", "SI", "SK", "JP", "NZ", + "SG", "CH", "GB", "US", "IO", "IM", "JE", "GG", "GF", "GP", "YT", + "MQ", "RE", "MF" + ], + "cryptoCodes": [], + "paymentTypeLogoKey": "revolut" + }, { "id": "turkishbank", "pluginId": "turkishbank", @@ -469,10 +263,18 @@ "id": "pix", "sortIndex": 10 }, + { + "id": "paypal", + "sortIndex": 15 + }, { "id": "pse", "sortIndex": 10 }, + { + "id": "revolut", + "sortIndex": 15 + }, { "id": "turkishbank", "sortIndex": 10 @@ -517,241 +319,32 @@ "paymentType": "applepay", "description": "Fee: 5-7%\nSettlement: 10 - 30 minutes", "paymentTypes": ["credit"], - "title": "Apple Pay", + "title": "Pay with Apple Pay", "forCountries": [ - "AF", - "AL", - "DZ", - "AS", - "AD", - "AO", - "AI", - "AQ", - "AG", - "AR", - "AM", - "AW", - "AU", - "AT", - "AZ", - "BH", - "BD", - "BB", - "BY", - "BE", - "BZ", - "BJ", - "BM", - "BT", - "BO", - "BQ", - "BA", - "BV", - "BR", - "IO", - "BN", - "BG", - "BF", - "BI", - "CM", - "CA", - "CV", - "KY", - "CF", - "TD", - "CL", - "CN", - "CX", - "CC", - "CO", - "KM", - "CG", - "CD", - "CK", - "CR", - "HR", - "CW", - "CY", - "CZ", - "CI", - "DK", - "DJ", - "DM", - "DO", - "EC", - "EG", - "SV", - "GH", - "GQ", - "ER", - "EE", - "FK", - "FO", - "FJ", - "FI", - "FR", - "GF", - "PF", - "TF", - "GA", - "GM", - "GE", - "DE", - "GI", - "GR", - "GL", - "GD", - "GP", - "GU", - "GT", - "GG", - "GN", - "GW", - "GY", - "HT", - "HM", - "VA", - "HN", - "HK", - "HU", - "IS", - "IN", - "ID", - "IE", - "IM", - "IL", - "IT", - "JM", - "JP", - "JE", - "JO", - "KZ", - "KE", - "KI", - "KR", - "KW", - "KG", - "LA", - "LV", - "LB", - "LS", - "LR", - "LY", - "LI", - "LT", - "LU", - "MO", - "MK", - "MG", - "MW", - "MY", - "MV", - "ML", - "MT", - "MH", - "MQ", - "MR", - "MU", - "YT", - "MX", - "FM", - "MD", - "MC", - "MN", - "ME", - "MS", - "MA", - "MZ", - "MM", - "NA", - "NR", - "NP", - "NL", - "NC", - "NZ", - "NI", - "NE", - "NG", - "NU", - "NF", - "MP", - "NO", - "OM", - "PW", - "PS", - "PG", - "PY", - "PE", - "PH", - "PN", - "PL", - "PT", - "PR", - "QA", - "RO", - "RU", - "RW", - "RE", - "BL", - "SH", - "KN", - "LC", - "MF", - "PM", - "VC", - "WS", - "SM", - "ST", - "SA", - "SN", - "RS", - "SC", - "SL", - "SG", - "SX", - "SK", - "SI", - "SB", - "SO", - "ZA", - "GS", - "SS", - "ES", - "SD", - "SR", - "SJ", - "SZ", - "SE", - "CH", - "TW", - "TJ", - "TZ", - "TH", - "TL", - "TG", - "TK", - "TO", - "TR", - "TM", - "TC", - "TV", - "UG", - "UA", - "AE", - "GB", - "US", - "UM", - "UY", - "UZ", - "VU", - "VE", - "VN", - "VG", - "VI", - "WF", - "EH", - "ZM", - "ZW" + "AD", "AE", "AF", "AG", "AI", "AL", "AM", "AO", "AQ", "AR", + "AS", "AT", "AU", "AW", "AZ", "BA", "BB", "BD", "BE", "BF", + "BG", "BH", "BI", "BJ", "BL", "BM", "BN", "BO", "BQ", "BR", + "BT", "BV", "BW", "BY", "BZ", "CA", "CC", "CD", "CF", "CG", + "CH", "CI", "CK", "CL", "CM", "CN", "CO", "CR", "CV", "CW", + "CX", "CY", "CZ", "DE", "DJ", "DK", "DM", "DO", "DZ", "EC", + "EE", "EG", "EH", "ER", "ES", "FI", "FJ", "FK", "FM", "FO", + "FR", "GA", "GB", "GD", "GE", "GF", "GG", "GH", "GI", "GL", + "GM", "GN", "GP", "GQ", "GR", "GS", "GT", "GU", "GW", "GY", + "HK", "HM", "HN", "HR", "HT", "HU", "ID", "IE", "IL", "IM", + "IN", "IO", "IQ", "IS", "IT", "JE", "JM", "JO", "JP", "KE", "KG", + "KH", "KI", "KM", "KN", "KR", "KW", "KY", "KZ", "LA", "LB", + "LC", "LI", "LK", "LR", "LS", "LT", "LU", "LV", "LY", "MA", + "MC", "MD", "ME", "MF", "MG", "MH", "MK", "ML", "MM", "MN", + "MO", "MP", "MQ", "MR", "MS", "MT", "MU", "MV", "MW", "MX", + "MY", "MZ", "NA", "NC", "NE", "NF", "NG", "NI", "NL", "NO", + "NP", "NR", "NU", "NZ", "OM", "PA", "PE", "PF", "PG", "PH", + "PL", "PM", "PN", "PR", "PS", "PT", "PW", "PY", "QA", "RE", + "RO", "RS", "RU", "RW", "SA", "SB", "SC", "SD", "SE", "SG", + "SH", "SI", "SJ", "SK", "SL", "SM", "SN", "SO", "SR", "SS", + "ST", "SV", "SX", "SZ", "TC", "TD", "TF", "TG", "TH", "TJ", + "TK", "TL", "TM", "TO", "TR", "TV", "TW", "TZ", "UA", "UG", + "UM", "US", "UY", "UZ", "VA", "VC", "VE", "VG", "VI", "VN", + "VU", "WF", "WS", "YT", "ZA", "ZM", "ZW" ], "cryptoCodes": [], "paymentTypeLogoKey": "applepay" @@ -766,239 +359,30 @@ "paymentTypes": ["credit"], "title": "Google Pay", "forCountries": [ - "AF", - "AL", - "DZ", - "AS", - "AD", - "AO", - "AI", - "AQ", - "AG", - "AR", - "AM", - "AW", - "AU", - "AT", - "AZ", - "BH", - "BD", - "BB", - "BY", - "BE", - "BZ", - "BJ", - "BM", - "BT", - "BO", - "BQ", - "BA", - "BV", - "BR", - "IO", - "BN", - "BG", - "BF", - "BI", - "CM", - "CA", - "CV", - "KY", - "CF", - "TD", - "CL", - "CN", - "CX", - "CC", - "CO", - "KM", - "CG", - "CD", - "CK", - "CR", - "HR", - "CW", - "CY", - "CZ", - "CI", - "DK", - "DJ", - "DM", - "DO", - "EC", - "EG", - "SV", - "GQ", - "ER", - "EE", - "FK", - "FO", - "FJ", - "FI", - "FR", - "GF", - "GH", - "PF", - "TF", - "GA", - "GM", - "GE", - "DE", - "GI", - "GR", - "GL", - "GD", - "GP", - "GU", - "GT", - "GG", - "GN", - "GW", - "GY", - "HT", - "HM", - "VA", - "HN", - "HK", - "HU", - "IS", - "IN", - "ID", - "IE", - "IM", - "IL", - "IT", - "JM", - "JP", - "JE", - "JO", - "KZ", - "KE", - "KI", - "KR", - "KW", - "KG", - "LA", - "LV", - "LB", - "LS", - "LR", - "LY", - "LI", - "LT", - "LU", - "MO", - "MK", - "MG", - "MW", - "MY", - "MV", - "ML", - "MT", - "MH", - "MQ", - "MR", - "MU", - "YT", - "MX", - "FM", - "MD", - "MC", - "MN", - "ME", - "MS", - "MA", - "MZ", - "MM", - "NA", - "NR", - "NP", - "NL", - "NC", - "NZ", - "NI", - "NE", - "NG", - "NU", - "NF", - "MP", - "NO", - "OM", - "PW", - "PS", - "PG", - "PY", - "PE", - "PH", - "PN", - "PL", - "PT", - "PR", - "QA", - "RO", - "RU", - "RW", - "RE", - "BL", - "SH", - "KN", - "LC", - "MF", - "PM", - "VC", - "WS", - "SM", - "ST", - "SA", - "SN", - "RS", - "SC", - "SL", - "SG", - "SX", - "SK", - "SI", - "SB", - "SO", - "ZA", - "GS", - "SS", - "ES", - "SD", - "SR", - "SJ", - "SZ", - "SE", - "CH", - "TW", - "TJ", - "TZ", - "TH", - "TL", - "TG", - "TK", - "TO", - "TR", - "TM", - "TC", - "TV", - "UG", - "UA", - "AE", - "GB", - "US", - "UM", - "UY", - "UZ", - "VU", - "VE", - "VN", - "VG", - "VI", - "WF", - "EH", - "ZM", - "ZW" + "AD", "AE", "AF", "AG", "AI", "AL", "AM", "AO", "AQ", "AR", + "AS", "AT", "AU", "AW", "AZ", "BA", "BB", "BD", "BE", "BF", + "BG", "BH", "BI", "BJ", "BL", "BM", "BN", "BO", "BQ", "BR", + "BT", "BV", "BW", "BY", "BZ", "CA", "CC", "CD", "CF", "CG", + "CH", "CI", "CK", "CL", "CM", "CN", "CO", "CR", "CV", "CW", + "CX", "CY", "CZ", "DE", "DJ", "DK", "DM", "DO", "DZ", "EC", + "EE", "EG", "EH", "ER", "ES", "FI", "FJ", "FK", "FM", "FO", + "FR", "GA", "GB", "GD", "GE", "GF", "GG", "GH", "GI", "GL", + "GM", "GN", "GP", "GQ", "GR", "GS", "GT", "GU", "GW", "GY", + "HK", "HM", "HN", "HR", "HT", "HU", "ID", "IE", "IL", "IM", + "IN", "IO", "IQ", "IS", "IT", "JE", "JM", "JO", "JP", "KE", "KG", + "KH", "KI", "KM", "KN", "KR", "KW", "KY", "KZ", "LA", "LB", + "LC", "LI", "LK", "LR", "LS", "LT", "LU", "LV", "LY", "MA", + "MC", "MD", "ME", "MF", "MG", "MH", "MK", "ML", "MM", "MN", + "MO", "MP", "MQ", "MR", "MS", "MT", "MU", "MV", "MW", "MX", + "MY", "MZ", "NA", "NC", "NE", "NF", "NG", "NI", "NL", "NO", + "NP", "NR", "NU", "NZ", "OM", "PA", "PE", "PF", "PG", "PH", + "PL", "PM", "PN", "PR", "PS", "PT", "PW", "PY", "QA", "RE", + "RO", "RS", "RU", "RW", "SA", "SB", "SC", "SD", "SE", "SG", + "SH", "SI", "SJ", "SK", "SL", "SM", "SN", "SO", "SR", "SS", + "ST", "SV", "SX", "SZ", "TC", "TD", "TF", "TG", "TH", "TJ", + "TK", "TL", "TM", "TO", "TR", "TV", "TW", "TZ", "UA", "UG", + "UM", "US", "UY", "UZ", "VA", "VC", "VE", "VG", "VI", "VN", + "VU", "WF", "WS", "YT", "ZA", "ZM", "ZW" ], "cryptoCodes": [], "paymentTypeLogoKey": "googlepay" @@ -1008,5 +392,10 @@ "id": "sepa", "forCountries": ["GB"], "sortIndex": 50 + }, + { + "id": "revolut", + "forCountries": ["US"], + "sortIndex": 30 } ] diff --git a/src/constants/plugins/sellPluginList.json b/src/constants/plugins/sellPluginList.json index 684bb1b0ee7..8d12f309ce4 100644 --- a/src/constants/plugins/sellPluginList.json +++ b/src/constants/plugins/sellPluginList.json @@ -12,11 +12,7 @@ "US" ], "notStateProvinces": { - "US": [ - "FL", - "NY", - "TX" - ] + "US": ["AK", "AR", "CT", "NC", "NY", "TX"] }, "cryptoCodes": [], "paymentTypeLogoKey": "bank" @@ -46,38 +42,10 @@ "title": "Bank Transfer via Debit Card", "description": "Fee: 4.0%\nSettlement: 5 min - 24 hours", "forCountries": [ - "US", - "GB", - "AU", - "AT", - "BE", - "BG", - "CH", - "CZ", - "DK", - "EE", - "FI", - "FR", - "DE", - "GR", - "HU", - "IE", - "IT", - "LV", - "LT", - "LU", - "NL", - "PL", - "PT", - "RO", - "SK", - "SI", - "ES", - "SE", - "HR", - "LI", - "NO", - "SM" + "AT", "AU", "BE", "BG", "CH", "CZ", "DE", "DK", "EE", "ES", + "FI", "FR", "GB", "GR", "HR", "HU", "IE", "IT", "LI", "LT", + "LU", "LV", "NL", "NO", "PL", "PT", "RO", "SE", "SI", "SK", + "SM", "US" ], "notStateProvinces": { "US": ["HI", "NY"] }, "cryptoCodes": [], @@ -142,6 +110,23 @@ "cryptoCodes": [], "paymentTypeLogoKey": "bank" }, + { + "id": "paypal", + "pluginId": "paypal", + "paymentType": "paypal", + "paymentTypes": ["paypal"], + "title": "Paypal", + "description": "Fee: ~5%\nSettlement: 5 min - 24 hours", + "forCountries": [ + "AE", "AR", "AT", "AU", "BE", "BR", "CA", "CH", "CL", "CO", + "CZ", "DE", "DK", "EG", "ES", "FI", "FR", "GH", "GR", "HK", + "HU", "IE", "IL", "IN", "IT", "JP", "KE", "KR", "MX", "MY", + "NG", "NL", "NO", "NZ", "PE", "PH", "PL", "PT", "RU", "SA", + "SE", "SG", "TH", "TR", "TZ", "US", "VN", "ZA" + ], + "cryptoCodes": [], + "paymentTypeLogoKey": "paypal" + }, { "id": "pix", "pluginId": "pix", @@ -194,254 +179,31 @@ "title": "Gift Cards", "partnerIconPath": "bitrefill-2.png", "forCountries": [ - "AD", - "AE", - "AF", - "AG", - "AI", - "AL", - "AM", - "AO", - "AQ", - "AR", - "AS", - "AT", - "AU", - "AW", - "AZ", - "BA", - "BB", - "BD", - "BE", - "BF", - "BG", - "BH", - "BI", - "BJ", - "BL", - "BM", - "BN", - "BO", - "BQ", - "BR", - "BS", - "BT", - "BV", - "BW", - "BY", - "BZ", - "CA", - "CC", - "CD", - "CF", - "CG", - "CH", - "CI", - "CK", - "CL", - "CM", - "CN", - "CO", - "CR", - "CU", - "CV", - "CW", - "CX", - "CY", - "CZ", - "DE", - "DJ", - "DK", - "DM", - "DO", - "DZ", - "EC", - "EE", - "EG", - "EH", - "ER", - "ES", - "ET", - "FI", - "FJ", - "FK", - "FM", - "FO", - "FR", - "GA", - "GB", - "GD", - "GE", - "GF", - "GG", - "GH", - "GI", - "GL", - "GM", - "GN", - "GP", - "GQ", - "GR", - "GS", - "GT", - "GU", - "GW", - "GY", - "HK", - "HM", - "HN", - "HR", - "HT", - "HU", - "ID", - "IE", - "IL", - "IM", - "IN", - "IO", - "IQ", - "IR", - "IS", - "IT", - "JE", - "JM", - "JO", - "JP", - "KE", - "KG", - "KH", - "KI", - "KM", - "KN", - "KP", - "KR", - "KW", - "KY", - "KZ", - "LA", - "LB", - "LC", - "LI", - "LK", - "LR", - "LS", - "LT", - "LU", - "LV", - "LY", - "MA", - "MC", - "MD", - "ME", - "MF", - "MG", - "MH", - "MK", - "ML", - "MM", - "MN", - "MO", - "MP", - "MQ", - "MR", - "MS", - "MT", - "MU", - "MV", - "MW", - "MX", - "MY", - "MZ", - "NA", - "NC", - "NE", - "NF", - "NG", - "NI", - "NL", - "NO", - "NP", - "NR", - "NU", - "NZ", - "OM", - "PA", - "PE", - "PF", - "PG", - "PH", - "PK", - "PL", - "PM", - "PN", - "PR", - "PS", - "PT", - "PW", - "PY", - "QA", - "RE", - "RO", - "RS", - "RU", - "RW", - "SA", - "SB", - "SC", - "SD", - "SE", - "SG", - "SH", - "SI", - "SJ", - "SK", - "SL", - "SM", - "SN", - "SO", - "SR", - "SS", - "ST", - "SV", - "SX", - "SY", - "SZ", - "TC", - "TD", - "TF", - "TG", - "TH", - "TJ", - "TK", - "TL", - "TM", - "TN", - "TO", - "TR", - "TT", - "TV", - "TW", - "TZ", - "UA", - "UG", - "UM", - "US", - "UY", - "UZ", - "VA", - "VC", - "VE", - "VG", - "VI", - "VN", - "VU", - "WF", - "WS", - "YE", - "YT", - "ZA", - "ZM", - "ZW" + "AD", "AE", "AF", "AG", "AI", "AL", "AM", "AO", "AQ", "AR", + "AS", "AT", "AU", "AW", "AZ", "BA", "BB", "BD", "BE", "BF", + "BG", "BH", "BI", "BJ", "BL", "BM", "BN", "BO", "BQ", "BR", + "BS", "BT", "BV", "BW", "BY", "BZ", "CA", "CC", "CD", "CF", + "CG", "CH", "CI", "CK", "CL", "CM", "CN", "CO", "CR", "CU", + "CV", "CW", "CX", "CY", "CZ", "DE", "DJ", "DK", "DM", "DO", + "DZ", "EC", "EE", "EG", "EH", "ER", "ES", "ET", "FI", "FJ", + "FK", "FM", "FO", "FR", "GA", "GB", "GD", "GE", "GF", "GG", + "GH", "GI", "GL", "GM", "GN", "GP", "GQ", "GR", "GS", "GT", + "GU", "GW", "GY", "HK", "HM", "HN", "HR", "HT", "HU", "ID", + "IE", "IL", "IM", "IN", "IO", "IQ", "IR", "IS", "IT", "JE", + "JM", "JO", "JP", "KE", "KG", "KH", "KI", "KM", "KN", "KP", + "KR", "KW", "KY", "KZ", "LA", "LB", "LC", "LI", "LK", "LR", + "LS", "LT", "LU", "LV", "LY", "MA", "MC", "MD", "ME", "MF", + "MG", "MH", "MK", "ML", "MM", "MN", "MO", "MP", "MQ", "MR", + "MS", "MT", "MU", "MV", "MW", "MX", "MY", "MZ", "NA", "NC", + "NE", "NF", "NG", "NI", "NL", "NO", "NP", "NR", "NU", "NZ", + "OM", "PA", "PE", "PF", "PG", "PH", "PK", "PL", "PM", "PN", + "PR", "PS", "PT", "PW", "PY", "QA", "RE", "RO", "RS", "RU", + "RW", "SA", "SB", "SC", "SD", "SE", "SG", "SH", "SI", "SJ", + "SK", "SL", "SM", "SN", "SO", "SR", "SS", "ST", "SV", "SX", + "SY", "SZ", "TC", "TD", "TF", "TG", "TH", "TJ", "TK", "TL", + "TM", "TN", "TO", "TR", "TT", "TV", "TW", "TZ", "UA", "UG", + "UM", "US", "UY", "UZ", "VA", "VC", "VE", "VG", "VI", "VN", + "VU", "WF", "WS", "YE", "YT", "ZA", "ZM", "ZW" ], "cryptoCodes": [ "BTC", @@ -456,36 +218,9 @@ "id": "sepa", "pluginId": "sepa", "forCountries": [ - "AT", - "BE", - "BG", - "CH", - "CZ", - "DK", - "EE", - "FI", - "FR", - "DE", - "GR", - "HU", - "IE", - "IT", - "LV", - "LT", - "LU", - "NL", - "PL", - "PT", - "RO", - "SK", - "SI", - "ES", - "SE", - "HR", - "LI", - "NO", - "SM", - "GB" + "AT", "BE", "BG", "CH", "CZ", "DE", "DK", "EE", "ES", "FI", + "FR", "GB", "GR", "HR", "HU", "IE", "IT", "LI", "LT", "LU", + "LV", "NL", "NO", "PL", "PT", "RO", "SE", "SI", "SK", "SM" ], "cryptoCodes": [ "BTC" @@ -501,6 +236,25 @@ "type": "sell" } }, + { + "##": "Disabled for now", + "id": "revolut", + "pluginId": "revolut", + "paymentType": "revolut", + "paymentTypes": ["revolut"], + "title": "Revolut", + "description": "Fee: ~5%\nSettlement: 5 min - 24 hours", + "forCountries": [], + "-forCountries": [ + "AU", "BR", "AT", "BE", "BG", "CY", "CZ", "DE", "DK", "EE", "ES", "FI", + "FR", "GR", "HR", "HU", "IE", "IS", "IT", "LI", "LT", "LU", "LV", + "MT", "NL", "NO", "PL", "PT", "RO", "SE", "SI", "SK", "JP", "NZ", + "SG", "CH", "GB", "US", "IO", "IM", "JE", "GG", "GF", "GP", "YT", + "MQ", "RE", "MF" + ], + "cryptoCodes": [], + "paymentTypeLogoKey": "revolut" + }, { "id": "xanpool", "pluginId": "xanpool", @@ -546,6 +300,14 @@ "id": "pix", "sortIndex": 10 }, + { + "id": "paypal", + "sortIndex": 15 + }, + { + "id": "revolut", + "sortIndex": 15 + }, { "id": "bitsofgold", "sortIndex": 10 diff --git a/src/hooks/useIsAccountFunded.ts b/src/hooks/useIsAccountFunded.ts new file mode 100644 index 00000000000..c25ea611f7a --- /dev/null +++ b/src/hooks/useIsAccountFunded.ts @@ -0,0 +1,32 @@ +import * as React from 'react' + +import { useSelector } from '../types/reactRedux' +import { zeroString } from '../util/utils' +import { useWatch } from './useWatch' + +/** + * Checks if the account has any balances + */ +export function useIsAccountFunded(): boolean { + const account = useSelector(state => state.core.account) + const currencyWallets = useWatch(account, 'currencyWallets') + + const walletsSynced = useSelector(state => { + const { currencyWallets } = state.core.account + const { userPausedWalletsSet } = state.ui.settings + const unPausedWallets = Object.values(currencyWallets).filter(wallet => !userPausedWalletsSet?.has(wallet.id)) + const unSyncedWallets = unPausedWallets.filter(wallet => wallet.syncRatio < 1) + + return unSyncedWallets.length === 0 + }) + + const [accountFunded, setAccountFunded] = React.useState(false) + + // Set account funded status + React.useEffect(() => { + if (!walletsSynced) return + setAccountFunded(Object.values(currencyWallets).some(wallet => [...wallet.balanceMap.values()].some(balanceVal => !zeroString(balanceVal)))) + }, [currencyWallets, walletsSynced]) + + return accountFunded +} diff --git a/src/locales/en_US.ts b/src/locales/en_US.ts index f532eadea5d..c2f24acece1 100644 --- a/src/locales/en_US.ts +++ b/src/locales/en_US.ts @@ -127,6 +127,7 @@ const strings = { warning_token_contract_override_3s: 'The entered token %1$s exists as a built-in token %2$s with the same contract address. Please proceed with caution and verify the contract is legitimate as use of this token can result in loss of funds. If you have questions about this feature please contact %3$s.', warning_token_exists_1s: 'The entered token already exists as a built-in token %1$s', + warning_uk_risk: `Don't invest unless you're prepared to lose all the money you invest. This is a high-risk investment and you should not expect to be protected if something goes wrong. Take 2 min to learn more.`, // Alert component: alert_dropdown_alert: 'Alert! ', @@ -437,6 +438,8 @@ const strings = { swap_preferred_instructions: 'When multiple exchanges can fill an order, prefer:', swap_preferred_promo_instructions: 'When multiple exchanges can fill an order, the current promotion always prefers:', swap_token_no_enabled_exchanges_2s: 'No enabled exchanges support %1$s (on %2$s) at this time', + swap_minimum_receive_amount: 'Min Receive Amount', + swap_minimum_amount_1s: 'Min %1$s', settings_button_clear_logs: 'Clear Logs', settings_button_send_logs: 'Send Logs to Edge', settings_button_export_logs: 'Export Logs', @@ -1444,6 +1447,11 @@ const strings = { fiat_plugin_fetching_assets: 'Fetching supported assets', fiat_plugin_sell_cancelled: 'Sell order cancelled', fiat_plugin_finalizing_quote: 'Finalizing your exchange quote. Please wait as this may take up to a minute', + fiat_plugin_sell_complete_title: 'Sell Order Complete', + fiat_plugin_sell_complete_message_s: 'Your sell order of %1$s %2$s for %3$s %4$s has been completed.', + fiat_plugin_sell_complete_message_2_hour_s: 'Please allow up to %1$s hour for the funds to appear in your account.', + fiat_plugin_sell_complete_message_2_hours_s: 'Please allow up to %1$s hours for the funds to appear in your account.', + fiat_plugin_sell_complete_message_3: 'A confirmation email has been sent to your registered email address.', fiat_plugin_sell_failed_try_again: 'Sell order failed. Please try again.', fiat_plugin_sell_failed_to_send_try_again: 'Failed to send funds for sell transaction. Please try again.', fiat_plugin_cannot_continue_camera_permission: 'Cannot continue. Camera permission needed for ID verifications', diff --git a/src/locales/strings/de.json b/src/locales/strings/de.json index 27540d48d9a..ab650a7b709 100644 --- a/src/locales/strings/de.json +++ b/src/locales/strings/de.json @@ -64,6 +64,7 @@ "error_paymentprotocol_tx_verification_failed": "Überprüfung des Zahlungsprotokolls stimmt nicht überein", "error_spend_amount_less_then_min_s": "Spend amount is less than minimum of %s", "error_amount_too_low_to_stake_s": "The amount %s is too low to stake successfully", + "error_balance_below_minimum_to_stake_2s": "Your balance of %1$s does not meet the minimum %2$s required to stake.", "warning_low_fee_selected": "Niedrige Gebühr ausgewählt", "warning_custom_fee_selected": "Eigene Gebühr ausgewählt", "warning_low_or_custom_fee": "Die Verwendung einer niedrigen Gebühr kann die Dauer der Bestätigung Ihrer Transaktion erhöhen. In seltenen Fällen kann Ihre Transaktion fehlschlagen.", @@ -80,6 +81,7 @@ "warning_token_code_override_2s": "The entered contract address differs from the contract address of built-in token %1$s. Please proceed with caution and verify the contract is legitimate as use of this token can result in loss of funds. If you have questions about this feature or contract please contact %2$s.", "warning_token_contract_override_3s": "The entered token %1$s exists as a built-in token %2$s with the same contract address. Please proceed with caution and verify the contract is legitimate as use of this token can result in loss of funds. If you have questions about this feature please contact %3$s.", "warning_token_exists_1s": "The entered token already exists as a built-in token %1$s", + "warning_uk_risk": "Don't invest unless you're prepared to lose all the money you invest. This is a high-risk investment and you should not expect to be protected if something goes wrong. Take 2 min to learn more.", "alert_dropdown_alert": "Achtung!", "alert_dropdown_warning": "Warnung! ", "azteco_success": "Sie haben eine Azteco-Bitcoin-Karte eingelöst. Das Geld sollte in Kürze eintreffen.", @@ -369,6 +371,8 @@ "swap_preferred_instructions": "Wenn mehrere Börsen eine Order ausfüllen können, bevorzugen sie:", "swap_preferred_promo_instructions": "Wenn mehrere Börsen eine Bestellung ausfüllen können, bevorzugt die aktuelle Promotion immer:", "swap_token_no_enabled_exchanges_2s": "No enabled exchanges support %1$s (on %2$s) at this time", + "swap_minimum_receive_amount": "Min Receive Amount", + "swap_minimum_amount_1s": "Min %1$s", "settings_button_clear_logs": "Clear Logs", "settings_button_send_logs": "Send Logs to Edge", "settings_button_export_logs": "Export Logs", @@ -1202,6 +1206,7 @@ "util_s_and_s": "%1$s and %2$s", "util_truncate_delimeter": "...", "stake_estimated_return": "Estimated Return: %s", + "stake_estimated_apr_s": "%s Estimated APR", "stake_s_staked": "%s Staked", "stake_s_earned": "%s Earned", "stake_s_unstaked": "%s Unstaked", @@ -1272,6 +1277,11 @@ "fiat_plugin_fetching_assets": "Fetching supported assets", "fiat_plugin_sell_cancelled": "Sell order cancelled", "fiat_plugin_finalizing_quote": "Finalizing your exchange quote. Please wait as this may take up to a minute", + "fiat_plugin_sell_complete_title": "Sell Order Complete", + "fiat_plugin_sell_complete_message_s": "Your sell order of %1$s %2$s for %3$s %4$s has been completed.", + "fiat_plugin_sell_complete_message_2_hour_s": "Please allow up to %1$s hour for the funds to appear in your account.", + "fiat_plugin_sell_complete_message_2_hours_s": "Please allow up to %1$s hours for the funds to appear in your account.", + "fiat_plugin_sell_complete_message_3": "A confirmation email has been sent to your registered email address.", "fiat_plugin_sell_failed_try_again": "Sell order failed. Please try again.", "fiat_plugin_sell_failed_to_send_try_again": "Failed to send funds for sell transaction. Please try again.", "fiat_plugin_cannot_continue_camera_permission": "Cannot continue. Camera permission needed for ID verifications", diff --git a/src/locales/strings/enUS.json b/src/locales/strings/enUS.json index 59ca02e61dc..6fb76745248 100644 --- a/src/locales/strings/enUS.json +++ b/src/locales/strings/enUS.json @@ -81,6 +81,7 @@ "warning_token_code_override_2s": "The entered contract address differs from the contract address of built-in token %1$s. Please proceed with caution and verify the contract is legitimate as use of this token can result in loss of funds. If you have questions about this feature or contract please contact %2$s.", "warning_token_contract_override_3s": "The entered token %1$s exists as a built-in token %2$s with the same contract address. Please proceed with caution and verify the contract is legitimate as use of this token can result in loss of funds. If you have questions about this feature please contact %3$s.", "warning_token_exists_1s": "The entered token already exists as a built-in token %1$s", + "warning_uk_risk": "Don't invest unless you're prepared to lose all the money you invest. This is a high-risk investment and you should not expect to be protected if something goes wrong. Take 2 min to learn more.", "alert_dropdown_alert": "Alert! ", "alert_dropdown_warning": "Warning! ", "azteco_success": "You've redeemed an Azteco bitcoin card. Funds should arrive shortly.", @@ -370,6 +371,8 @@ "swap_preferred_instructions": "When multiple exchanges can fill an order, prefer:", "swap_preferred_promo_instructions": "When multiple exchanges can fill an order, the current promotion always prefers:", "swap_token_no_enabled_exchanges_2s": "No enabled exchanges support %1$s (on %2$s) at this time", + "swap_minimum_receive_amount": "Min Receive Amount", + "swap_minimum_amount_1s": "Min %1$s", "settings_button_clear_logs": "Clear Logs", "settings_button_send_logs": "Send Logs to Edge", "settings_button_export_logs": "Export Logs", @@ -1274,6 +1277,11 @@ "fiat_plugin_fetching_assets": "Fetching supported assets", "fiat_plugin_sell_cancelled": "Sell order cancelled", "fiat_plugin_finalizing_quote": "Finalizing your exchange quote. Please wait as this may take up to a minute", + "fiat_plugin_sell_complete_title": "Sell Order Complete", + "fiat_plugin_sell_complete_message_s": "Your sell order of %1$s %2$s for %3$s %4$s has been completed.", + "fiat_plugin_sell_complete_message_2_hour_s": "Please allow up to %1$s hour for the funds to appear in your account.", + "fiat_plugin_sell_complete_message_2_hours_s": "Please allow up to %1$s hours for the funds to appear in your account.", + "fiat_plugin_sell_complete_message_3": "A confirmation email has been sent to your registered email address.", "fiat_plugin_sell_failed_try_again": "Sell order failed. Please try again.", "fiat_plugin_sell_failed_to_send_try_again": "Failed to send funds for sell transaction. Please try again.", "fiat_plugin_cannot_continue_camera_permission": "Cannot continue. Camera permission needed for ID verifications", diff --git a/src/locales/strings/es.json b/src/locales/strings/es.json index c38a3447418..5233ca01c00 100644 --- a/src/locales/strings/es.json +++ b/src/locales/strings/es.json @@ -64,6 +64,7 @@ "error_paymentprotocol_tx_verification_failed": "La verificación de la transacción del protocolo de pago no coincide", "error_spend_amount_less_then_min_s": "Spend amount is less than minimum of %s", "error_amount_too_low_to_stake_s": "The amount %s is too low to stake successfully", + "error_balance_below_minimum_to_stake_2s": "Your balance of %1$s does not meet the minimum %2$s required to stake.", "warning_low_fee_selected": "Tarifa baja seleccionada", "warning_custom_fee_selected": "Tarifa personalizada seleccionada", "warning_low_or_custom_fee": "Usar una comisión baja puede aumentar la cantidad de tiempo que tarda en confirmar tu transacción. En raras ocasiones tu transacción puede fallar.", @@ -80,6 +81,7 @@ "warning_token_code_override_2s": "The entered contract address differs from the contract address of built-in token %1$s. Please proceed with caution and verify the contract is legitimate as use of this token can result in loss of funds. If you have questions about this feature or contract please contact %2$s.", "warning_token_contract_override_3s": "The entered token %1$s exists as a built-in token %2$s with the same contract address. Please proceed with caution and verify the contract is legitimate as use of this token can result in loss of funds. If you have questions about this feature please contact %3$s.", "warning_token_exists_1s": "The entered token already exists as a built-in token %1$s", + "warning_uk_risk": "Don't invest unless you're prepared to lose all the money you invest. This is a high-risk investment and you should not expect to be protected if something goes wrong. Take 2 min to learn more.", "alert_dropdown_alert": "¡Alerta! ", "alert_dropdown_warning": "¡Advertencia! ", "azteco_success": "Has canjeado una tarjeta de bitcoin Azteco. Los fondos deberían llegar pronto.", @@ -369,6 +371,8 @@ "swap_preferred_instructions": "Cuando varias casas de cambio pueden ejecutar una orden, preferir:", "swap_preferred_promo_instructions": "Cuando varias casas de cambio pueden completar un pedido, la promoción actual prefiere:", "swap_token_no_enabled_exchanges_2s": "No enabled exchanges support %1$s (on %2$s) at this time", + "swap_minimum_receive_amount": "Min Receive Amount", + "swap_minimum_amount_1s": "Min %1$s", "settings_button_clear_logs": "Eliminar registros", "settings_button_send_logs": "Enviar registros a Edge", "settings_button_export_logs": "Exportar registros", @@ -1202,6 +1206,7 @@ "util_s_and_s": "%1$s and %2$s", "util_truncate_delimeter": "...", "stake_estimated_return": "Estimated Return: %s", + "stake_estimated_apr_s": "%s Estimated APR", "stake_s_staked": "%s Staked", "stake_s_earned": "%s Earned", "stake_s_unstaked": "%s Unstaked", @@ -1272,6 +1277,11 @@ "fiat_plugin_fetching_assets": "Fetching supported assets", "fiat_plugin_sell_cancelled": "Sell order cancelled", "fiat_plugin_finalizing_quote": "Finalizing your exchange quote. Please wait as this may take up to a minute", + "fiat_plugin_sell_complete_title": "Sell Order Complete", + "fiat_plugin_sell_complete_message_s": "Your sell order of %1$s %2$s for %3$s %4$s has been completed.", + "fiat_plugin_sell_complete_message_2_hour_s": "Please allow up to %1$s hour for the funds to appear in your account.", + "fiat_plugin_sell_complete_message_2_hours_s": "Please allow up to %1$s hours for the funds to appear in your account.", + "fiat_plugin_sell_complete_message_3": "A confirmation email has been sent to your registered email address.", "fiat_plugin_sell_failed_try_again": "Sell order failed. Please try again.", "fiat_plugin_sell_failed_to_send_try_again": "Failed to send funds for sell transaction. Please try again.", "fiat_plugin_cannot_continue_camera_permission": "Cannot continue. Camera permission needed for ID verifications", diff --git a/src/locales/strings/esMX.json b/src/locales/strings/esMX.json index 0ca30ea680c..56e00a17d96 100644 --- a/src/locales/strings/esMX.json +++ b/src/locales/strings/esMX.json @@ -64,6 +64,7 @@ "error_paymentprotocol_tx_verification_failed": "La verificación de la transacción del protocolo de pago no coincide", "error_spend_amount_less_then_min_s": "El importe del gasto es inferior al mínimo de %s", "error_amount_too_low_to_stake_s": "The amount %s is too low to stake successfully", + "error_balance_below_minimum_to_stake_2s": "Your balance of %1$s does not meet the minimum %2$s required to stake.", "warning_low_fee_selected": "Tarifa baja seleccionada", "warning_custom_fee_selected": "Tarifa personalizada seleccionada", "warning_low_or_custom_fee": "Usar una comisión baja puede aumentar la cantidad de tiempo que tarda en confirmar tu transacción. En raras ocasiones tu transacción puede fallar.", @@ -80,6 +81,7 @@ "warning_token_code_override_2s": "La dirección del contrato ingresada difiere de la dirección del contrato del token integrado %1$s. Por favor. Proceda con precaución y verifique que el contrato sea legítimo, ya que el uso de este token puede provocar la pérdida de fondos. Si tiene preguntas sobre esta función o contrato, comuníquese con %2$s.", "warning_token_contract_override_3s": "El token ingresado %1$s existe como un token integrado %2$s con la misma dirección de contrato. Por favor. Proceda con precaución y verifique que el contrato sea legítimo, ya que el uso de este token puede provocar la pérdida de fondos. Si tiene preguntas sobre esta función, comuníquese con %3$s.", "warning_token_exists_1s": "El token ingresado ya existe como token integrado %1$s", + "warning_uk_risk": "Don't invest unless you're prepared to lose all the money you invest. This is a high-risk investment and you should not expect to be protected if something goes wrong. Take 2 min to learn more.", "alert_dropdown_alert": "¡Alerta! ", "alert_dropdown_warning": "¡Advertencia! ", "azteco_success": "Has canjeado una tarjeta de bitcoin Azteco. Los fondos deberían llegar pronto.", @@ -369,6 +371,8 @@ "swap_preferred_instructions": "Cuando varias casas de cambio pueden ejecutar una orden, preferir:", "swap_preferred_promo_instructions": "Cuando varias casas de cambio pueden completar un pedido, la promoción actual prefiere:", "swap_token_no_enabled_exchanges_2s": "No enabled exchanges support %1$s (on %2$s) at this time", + "swap_minimum_receive_amount": "Min Receive Amount", + "swap_minimum_amount_1s": "Min %1$s", "settings_button_clear_logs": "Eliminar registros", "settings_button_send_logs": "Enviar registros a Edge", "settings_button_export_logs": "Exportar registros", @@ -1202,6 +1206,7 @@ "util_s_and_s": "%1$s and %2$s", "util_truncate_delimeter": "...", "stake_estimated_return": "Estimated Return: %s", + "stake_estimated_apr_s": "%s Estimated APR", "stake_s_staked": "%s Staked", "stake_s_earned": "%s Earned", "stake_s_unstaked": "%s Unstaked", @@ -1272,6 +1277,11 @@ "fiat_plugin_fetching_assets": "Fetching supported assets", "fiat_plugin_sell_cancelled": "Sell order cancelled", "fiat_plugin_finalizing_quote": "Finalizing your exchange quote. Please wait as this may take up to a minute", + "fiat_plugin_sell_complete_title": "Sell Order Complete", + "fiat_plugin_sell_complete_message_s": "Your sell order of %1$s %2$s for %3$s %4$s has been completed.", + "fiat_plugin_sell_complete_message_2_hour_s": "Please allow up to %1$s hour for the funds to appear in your account.", + "fiat_plugin_sell_complete_message_2_hours_s": "Please allow up to %1$s hours for the funds to appear in your account.", + "fiat_plugin_sell_complete_message_3": "A confirmation email has been sent to your registered email address.", "fiat_plugin_sell_failed_try_again": "Sell order failed. Please try again.", "fiat_plugin_sell_failed_to_send_try_again": "Failed to send funds for sell transaction. Please try again.", "fiat_plugin_cannot_continue_camera_permission": "Cannot continue. Camera permission needed for ID verifications", diff --git a/src/locales/strings/fr.json b/src/locales/strings/fr.json index 5b0b3a3a839..3a3e174c3ed 100644 --- a/src/locales/strings/fr.json +++ b/src/locales/strings/fr.json @@ -64,6 +64,7 @@ "error_paymentprotocol_tx_verification_failed": "Payment Protocol transaction verification mismatch", "error_spend_amount_less_then_min_s": "Spend amount is less than minimum of %s", "error_amount_too_low_to_stake_s": "The amount %s is too low to stake successfully", + "error_balance_below_minimum_to_stake_2s": "Your balance of %1$s does not meet the minimum %2$s required to stake.", "warning_low_fee_selected": "Low Fee Selected", "warning_custom_fee_selected": "Custom Fee Selected", "warning_low_or_custom_fee": "Using a low fee may increase the amount of time it takes for your transaction to confirm. In rare instances your transaction can fail.", @@ -80,6 +81,7 @@ "warning_token_code_override_2s": "The entered contract address differs from the contract address of built-in token %1$s. Please proceed with caution and verify the contract is legitimate as use of this token can result in loss of funds. If you have questions about this feature or contract please contact %2$s.", "warning_token_contract_override_3s": "The entered token %1$s exists as a built-in token %2$s with the same contract address. Please proceed with caution and verify the contract is legitimate as use of this token can result in loss of funds. If you have questions about this feature please contact %3$s.", "warning_token_exists_1s": "The entered token already exists as a built-in token %1$s", + "warning_uk_risk": "Don't invest unless you're prepared to lose all the money you invest. This is a high-risk investment and you should not expect to be protected if something goes wrong. Take 2 min to learn more.", "alert_dropdown_alert": "Alert! ", "alert_dropdown_warning": "Warning! ", "azteco_success": "You've redeemed an Azteco bitcoin card. Funds should arrive shortly.", @@ -369,6 +371,8 @@ "swap_preferred_instructions": "When multiple exchanges can fill an order, prefer:", "swap_preferred_promo_instructions": "When multiple exchanges can fill an order, the current promotion always prefers:", "swap_token_no_enabled_exchanges_2s": "No enabled exchanges support %1$s (on %2$s) at this time", + "swap_minimum_receive_amount": "Min Receive Amount", + "swap_minimum_amount_1s": "Min %1$s", "settings_button_clear_logs": "Clear Logs", "settings_button_send_logs": "Send Logs to Edge", "settings_button_export_logs": "Export Logs", @@ -1202,6 +1206,7 @@ "util_s_and_s": "%1$s and %2$s", "util_truncate_delimeter": "...", "stake_estimated_return": "Estimated Return: %s", + "stake_estimated_apr_s": "%s Estimated APR", "stake_s_staked": "%s Staked", "stake_s_earned": "%s Earned", "stake_s_unstaked": "%s Unstaked", @@ -1272,6 +1277,11 @@ "fiat_plugin_fetching_assets": "Fetching supported assets", "fiat_plugin_sell_cancelled": "Sell order cancelled", "fiat_plugin_finalizing_quote": "Finalizing your exchange quote. Please wait as this may take up to a minute", + "fiat_plugin_sell_complete_title": "Sell Order Complete", + "fiat_plugin_sell_complete_message_s": "Your sell order of %1$s %2$s for %3$s %4$s has been completed.", + "fiat_plugin_sell_complete_message_2_hour_s": "Please allow up to %1$s hour for the funds to appear in your account.", + "fiat_plugin_sell_complete_message_2_hours_s": "Please allow up to %1$s hours for the funds to appear in your account.", + "fiat_plugin_sell_complete_message_3": "A confirmation email has been sent to your registered email address.", "fiat_plugin_sell_failed_try_again": "Sell order failed. Please try again.", "fiat_plugin_sell_failed_to_send_try_again": "Failed to send funds for sell transaction. Please try again.", "fiat_plugin_cannot_continue_camera_permission": "Cannot continue. Camera permission needed for ID verifications", diff --git a/src/locales/strings/it.json b/src/locales/strings/it.json index 8d25c47254b..86eebaab519 100644 --- a/src/locales/strings/it.json +++ b/src/locales/strings/it.json @@ -63,7 +63,8 @@ "error_paymentprotocol_no_payment_option": "Nessuna valuta disponibile per questa invoice Payment Protocol. Valute supportate: %s", "error_paymentprotocol_tx_verification_failed": "Verifica della transazione Payment Protocol non corrispondente", "error_spend_amount_less_then_min_s": "L'importo che vuoi spendere è inferiore al minimo di %s", - "error_amount_too_low_to_stake_s": "The amount %s is too low to stake successfully", + "error_amount_too_low_to_stake_s": "L'importo %s è troppo basso per lo stake", + "error_balance_below_minimum_to_stake_2s": "Il tuo saldo di %1$s non soddisfa il minimo %2$s richiesto per lo stake.", "warning_low_fee_selected": "Commissione bassa selezionata", "warning_custom_fee_selected": "Hai selezionato Personalizzata", "warning_low_or_custom_fee": "L'utilizzo di una commissione bassa può aumentare il tempo necessario per la conferma della transazione. In rari casi la transazione può fallire.", @@ -80,6 +81,7 @@ "warning_token_code_override_2s": "L'indirizzo del contratto inserito differisce dall'indirizzo del contratto del token integrato %1$s. Si prega di procedere con cautela e verificare che il contratto sia legittimo in quanto l'uso di questo token può comportare la perdita di fondi. Se hai domande su questa funzione o sul contratto contatta %2$s.", "warning_token_contract_override_3s": "Il token %1$s inserito esiste come token %2$s integrato con lo stesso indirizzo del contratto. Si prega di procedere con cautela e verificare che il contratto sia legittimo in quanto l'uso di questo token può comportare la perdita di fondi. Se hai domande su questa funzionalità, contatta %3$s.", "warning_token_exists_1s": "Il token inserito esiste già come token integrato %1$s", + "warning_uk_risk": "Non investire a meno che non sia disposto a perdere tutti i soldi che investi. Questo è un investimento ad alto rischio e non dovresti aspettarti di essere protetto se qualcosa va storto. Prenditi 2 minuti per saperne di più.", "alert_dropdown_alert": "Avviso! ", "alert_dropdown_warning": "Attenzione! ", "azteco_success": "Hai riscattato una carta bitcoin Azteco. I fondi dovrebbero arrivare a breve.", @@ -369,6 +371,8 @@ "swap_preferred_instructions": "Quando più cambi valuta possono eseguire un ordine, preferisci:", "swap_preferred_promo_instructions": "Quando più cambi valuta possono eseguire un ordine, la promozione corrente preferisce sempre:", "swap_token_no_enabled_exchanges_2s": "Nessun cambio valuta abilitato supporta %1$s (su %2$s) al momento", + "swap_minimum_receive_amount": "Min Receive Amount", + "swap_minimum_amount_1s": "Min %1$s", "settings_button_clear_logs": "Cancella i log", "settings_button_send_logs": "Invia i logs a Edge", "settings_button_export_logs": "Esporta i logs", @@ -1202,6 +1206,7 @@ "util_s_and_s": "%1$s e %2$s", "util_truncate_delimeter": "...", "stake_estimated_return": "Ritorno stimato: %s", + "stake_estimated_apr_s": "APR stimato %s", "stake_s_staked": "%s in Stake", "stake_s_earned": "%s Guadagnato", "stake_s_unstaked": "%s tolti dallo stake", @@ -1244,7 +1249,7 @@ "stake_earn_button_label": "Guadagna", "stake_unable_to_query_locked": "Impossibile interrogare il saldo bloccato. Riprova più tardi.", "stake_liquid_staking_warning_title": "Avviso Staking Pool Liquido", - "stake_liquid_staking_warning_header": "This is a liquid-staking pool, meaning the entire wallet's balance is either staked/unstaked in the pool. Wallet funds will remain liquid (transferable), however all current and future funds will be exposed to the risks and rewards of the staking pool while staked.", + "stake_liquid_staking_warning_header": "Questa è una pool di staking liquido, il che significa che l'intero saldo del portafoglio è in stake / unstaked nella pool. I fondi del portafoglio rimarranno liquidi (trasferibili), tuttavia tutti i fondi attuali e futuri saranno esposti ai rischi e ai benefici della pool mentre si trovano in stake.", "stake_resource_display_name": "Risorse TRON", "stake_resource_display_name_v2": "Risorse TRON v2", "stake_resource_bandwidth": "Bandwidth", @@ -1272,6 +1277,11 @@ "fiat_plugin_fetching_assets": "Recupero degli asset supportati", "fiat_plugin_sell_cancelled": "Ordine di vendita annullato", "fiat_plugin_finalizing_quote": "Finalizzazione della quota di scambio. Potrebbe richiedere fino a un minuto", + "fiat_plugin_sell_complete_title": "Ordine di vendita completato", + "fiat_plugin_sell_complete_message_s": "Il tuo ordine di vendita di %1$s %2$s per %3$s %4$s è stato completato.", + "fiat_plugin_sell_complete_message_2_hour_s": "Si prega di attendere fino a %1$s ore perché i fondi appaiano nel tuo account.", + "fiat_plugin_sell_complete_message_2_hours_s": "Si prega di attendere fino a %1$s ore perché i fondi appaiano nel tuo account.", + "fiat_plugin_sell_complete_message_3": "Una email di conferma è stata inviata al tuo indirizzo email registrato.", "fiat_plugin_sell_failed_try_again": "Ordine di vendita non riuscito. Riprova.", "fiat_plugin_sell_failed_to_send_try_again": "Impossibile inviare fondi per la transazione di vendita. Riprova.", "fiat_plugin_cannot_continue_camera_permission": "Impossibile continuare. Autorizzazione fotocamera necessaria per la verifica dell'ID", diff --git a/src/locales/strings/ja.json b/src/locales/strings/ja.json index 19035e00721..b2b63f6d844 100644 --- a/src/locales/strings/ja.json +++ b/src/locales/strings/ja.json @@ -64,6 +64,7 @@ "error_paymentprotocol_tx_verification_failed": "ペイメント・プロトコルのトランザクション検証の不一致", "error_spend_amount_less_then_min_s": "使用金額が最低額の%s未満です", "error_amount_too_low_to_stake_s": "The amount %s is too low to stake successfully", + "error_balance_below_minimum_to_stake_2s": "Your balance of %1$s does not meet the minimum %2$s required to stake.", "warning_low_fee_selected": "低料金が選択されました", "warning_custom_fee_selected": "カスタム料金が選択されました", "warning_low_or_custom_fee": "低い手数料を選択すると、トランザクションの承認にかかる時間が増える可能性がある上、まれにトランザクションが失敗することがあります。", @@ -80,6 +81,7 @@ "warning_token_code_override_2s": "The entered contract address differs from the contract address of built-in token %1$s. Please proceed with caution and verify the contract is legitimate as use of this token can result in loss of funds. If you have questions about this feature or contract please contact %2$s.", "warning_token_contract_override_3s": "The entered token %1$s exists as a built-in token %2$s with the same contract address. Please proceed with caution and verify the contract is legitimate as use of this token can result in loss of funds. If you have questions about this feature please contact %3$s.", "warning_token_exists_1s": "The entered token already exists as a built-in token %1$s", + "warning_uk_risk": "Don't invest unless you're prepared to lose all the money you invest. This is a high-risk investment and you should not expect to be protected if something goes wrong. Take 2 min to learn more.", "alert_dropdown_alert": "警告! ", "alert_dropdown_warning": "注意! ", "azteco_success": "Aztecoビットコインカードを使用しました。資金はまもなく届きます。", @@ -369,6 +371,8 @@ "swap_preferred_instructions": "複数の取引所が注文を発注できる場合は、以下を優先します:", "swap_preferred_promo_instructions": "複数の取引所が注文を成立させることができる場合、現在のプロモーションは常に下記を優先します:", "swap_token_no_enabled_exchanges_2s": "No enabled exchanges support %1$s (on %2$s) at this time", + "swap_minimum_receive_amount": "Min Receive Amount", + "swap_minimum_amount_1s": "Min %1$s", "settings_button_clear_logs": "ログを消去", "settings_button_send_logs": "Edgeにログを送信", "settings_button_export_logs": "ログをエクスポート", @@ -1202,6 +1206,7 @@ "util_s_and_s": "%1$s と %2$s", "util_truncate_delimeter": "...", "stake_estimated_return": "推定リターン: %s", + "stake_estimated_apr_s": "%s Estimated APR", "stake_s_staked": "%s ステーク済み", "stake_s_earned": "%s 獲得", "stake_s_unstaked": "%s Unstaked", @@ -1272,6 +1277,11 @@ "fiat_plugin_fetching_assets": "サポートされている資産を取得中", "fiat_plugin_sell_cancelled": "Sell order cancelled", "fiat_plugin_finalizing_quote": "Finalizing your exchange quote. Please wait as this may take up to a minute", + "fiat_plugin_sell_complete_title": "Sell Order Complete", + "fiat_plugin_sell_complete_message_s": "Your sell order of %1$s %2$s for %3$s %4$s has been completed.", + "fiat_plugin_sell_complete_message_2_hour_s": "Please allow up to %1$s hour for the funds to appear in your account.", + "fiat_plugin_sell_complete_message_2_hours_s": "Please allow up to %1$s hours for the funds to appear in your account.", + "fiat_plugin_sell_complete_message_3": "A confirmation email has been sent to your registered email address.", "fiat_plugin_sell_failed_try_again": "Sell order failed. Please try again.", "fiat_plugin_sell_failed_to_send_try_again": "Failed to send funds for sell transaction. Please try again.", "fiat_plugin_cannot_continue_camera_permission": "Cannot continue. Camera permission needed for ID verifications", diff --git a/src/locales/strings/kaa.json b/src/locales/strings/kaa.json index 29261f23a14..cdffae793c4 100644 --- a/src/locales/strings/kaa.json +++ b/src/locales/strings/kaa.json @@ -64,6 +64,7 @@ "error_paymentprotocol_tx_verification_failed": "Payment Protocol transaction verification mismatch", "error_spend_amount_less_then_min_s": "Spend amount is less than minimum of %s", "error_amount_too_low_to_stake_s": "The amount %s is too low to stake successfully", + "error_balance_below_minimum_to_stake_2s": "Your balance of %1$s does not meet the minimum %2$s required to stake.", "warning_low_fee_selected": "Low Fee Selected", "warning_custom_fee_selected": "Custom Fee Selected", "warning_low_or_custom_fee": "Using a low fee may increase the amount of time it takes for your transaction to confirm. In rare instances your transaction can fail.", @@ -80,6 +81,7 @@ "warning_token_code_override_2s": "The entered contract address differs from the contract address of built-in token %1$s. Please proceed with caution and verify the contract is legitimate as use of this token can result in loss of funds. If you have questions about this feature or contract please contact %2$s.", "warning_token_contract_override_3s": "The entered token %1$s exists as a built-in token %2$s with the same contract address. Please proceed with caution and verify the contract is legitimate as use of this token can result in loss of funds. If you have questions about this feature please contact %3$s.", "warning_token_exists_1s": "The entered token already exists as a built-in token %1$s", + "warning_uk_risk": "Don't invest unless you're prepared to lose all the money you invest. This is a high-risk investment and you should not expect to be protected if something goes wrong. Take 2 min to learn more.", "alert_dropdown_alert": "Dıqqat! ", "alert_dropdown_warning": "Dıqqat! ", "azteco_success": "You've redeemed an Azteco bitcoin card. Funds should arrive shortly.", @@ -369,6 +371,8 @@ "swap_preferred_instructions": "When multiple exchanges can fill an order, prefer:", "swap_preferred_promo_instructions": "When multiple exchanges can fill an order, the current promotion always prefers:", "swap_token_no_enabled_exchanges_2s": "No enabled exchanges support %1$s (on %2$s) at this time", + "swap_minimum_receive_amount": "Min Receive Amount", + "swap_minimum_amount_1s": "Min %1$s", "settings_button_clear_logs": "Clear Logs", "settings_button_send_logs": "Send Logs to Edge", "settings_button_export_logs": "Export Logs", @@ -1202,6 +1206,7 @@ "util_s_and_s": "%1$s and %2$s", "util_truncate_delimeter": "...", "stake_estimated_return": "Estimated Return: %s", + "stake_estimated_apr_s": "%s Estimated APR", "stake_s_staked": "%s Staked", "stake_s_earned": "%s Earned", "stake_s_unstaked": "%s Unstaked", @@ -1272,6 +1277,11 @@ "fiat_plugin_fetching_assets": "Fetching supported assets", "fiat_plugin_sell_cancelled": "Sell order cancelled", "fiat_plugin_finalizing_quote": "Finalizing your exchange quote. Please wait as this may take up to a minute", + "fiat_plugin_sell_complete_title": "Sell Order Complete", + "fiat_plugin_sell_complete_message_s": "Your sell order of %1$s %2$s for %3$s %4$s has been completed.", + "fiat_plugin_sell_complete_message_2_hour_s": "Please allow up to %1$s hour for the funds to appear in your account.", + "fiat_plugin_sell_complete_message_2_hours_s": "Please allow up to %1$s hours for the funds to appear in your account.", + "fiat_plugin_sell_complete_message_3": "A confirmation email has been sent to your registered email address.", "fiat_plugin_sell_failed_try_again": "Sell order failed. Please try again.", "fiat_plugin_sell_failed_to_send_try_again": "Failed to send funds for sell transaction. Please try again.", "fiat_plugin_cannot_continue_camera_permission": "Cannot continue. Camera permission needed for ID verifications", diff --git a/src/locales/strings/ko.json b/src/locales/strings/ko.json index f7b1e942cef..a333db0adf2 100644 --- a/src/locales/strings/ko.json +++ b/src/locales/strings/ko.json @@ -64,6 +64,7 @@ "error_paymentprotocol_tx_verification_failed": "Payment Protocol transaction verification mismatch", "error_spend_amount_less_then_min_s": "Spend amount is less than minimum of %s", "error_amount_too_low_to_stake_s": "The amount %s is too low to stake successfully", + "error_balance_below_minimum_to_stake_2s": "Your balance of %1$s does not meet the minimum %2$s required to stake.", "warning_low_fee_selected": "Low Fee Selected", "warning_custom_fee_selected": "Custom Fee Selected", "warning_low_or_custom_fee": "Using a low fee may increase the amount of time it takes for your transaction to confirm. In rare instances your transaction can fail.", @@ -80,6 +81,7 @@ "warning_token_code_override_2s": "The entered contract address differs from the contract address of built-in token %1$s. Please proceed with caution and verify the contract is legitimate as use of this token can result in loss of funds. If you have questions about this feature or contract please contact %2$s.", "warning_token_contract_override_3s": "The entered token %1$s exists as a built-in token %2$s with the same contract address. Please proceed with caution and verify the contract is legitimate as use of this token can result in loss of funds. If you have questions about this feature please contact %3$s.", "warning_token_exists_1s": "The entered token already exists as a built-in token %1$s", + "warning_uk_risk": "Don't invest unless you're prepared to lose all the money you invest. This is a high-risk investment and you should not expect to be protected if something goes wrong. Take 2 min to learn more.", "alert_dropdown_alert": "Alert! ", "alert_dropdown_warning": "Warning! ", "azteco_success": "You've redeemed an Azteco bitcoin card. Funds should arrive shortly.", @@ -369,6 +371,8 @@ "swap_preferred_instructions": "When multiple exchanges can fill an order, prefer:", "swap_preferred_promo_instructions": "When multiple exchanges can fill an order, the current promotion always prefers:", "swap_token_no_enabled_exchanges_2s": "No enabled exchanges support %1$s (on %2$s) at this time", + "swap_minimum_receive_amount": "Min Receive Amount", + "swap_minimum_amount_1s": "Min %1$s", "settings_button_clear_logs": "Clear Logs", "settings_button_send_logs": "Send Logs to Edge", "settings_button_export_logs": "Export Logs", @@ -1202,6 +1206,7 @@ "util_s_and_s": "%1$s and %2$s", "util_truncate_delimeter": "...", "stake_estimated_return": "Estimated Return: %s", + "stake_estimated_apr_s": "%s Estimated APR", "stake_s_staked": "%s Staked", "stake_s_earned": "%s Earned", "stake_s_unstaked": "%s Unstaked", @@ -1272,6 +1277,11 @@ "fiat_plugin_fetching_assets": "Fetching supported assets", "fiat_plugin_sell_cancelled": "Sell order cancelled", "fiat_plugin_finalizing_quote": "Finalizing your exchange quote. Please wait as this may take up to a minute", + "fiat_plugin_sell_complete_title": "Sell Order Complete", + "fiat_plugin_sell_complete_message_s": "Your sell order of %1$s %2$s for %3$s %4$s has been completed.", + "fiat_plugin_sell_complete_message_2_hour_s": "Please allow up to %1$s hour for the funds to appear in your account.", + "fiat_plugin_sell_complete_message_2_hours_s": "Please allow up to %1$s hours for the funds to appear in your account.", + "fiat_plugin_sell_complete_message_3": "A confirmation email has been sent to your registered email address.", "fiat_plugin_sell_failed_try_again": "Sell order failed. Please try again.", "fiat_plugin_sell_failed_to_send_try_again": "Failed to send funds for sell transaction. Please try again.", "fiat_plugin_cannot_continue_camera_permission": "Cannot continue. Camera permission needed for ID verifications", diff --git a/src/locales/strings/pt.json b/src/locales/strings/pt.json index d11111e876e..545f2b3af19 100644 --- a/src/locales/strings/pt.json +++ b/src/locales/strings/pt.json @@ -64,6 +64,7 @@ "error_paymentprotocol_tx_verification_failed": "Incompatibilidade de verificação da transação do protocolo de pagamento", "error_spend_amount_less_then_min_s": "O valor gasto é menor que o mínimo de %s", "error_amount_too_low_to_stake_s": "The amount %s is too low to stake successfully", + "error_balance_below_minimum_to_stake_2s": "Your balance of %1$s does not meet the minimum %2$s required to stake.", "warning_low_fee_selected": "Taxa baixa selecionada", "warning_custom_fee_selected": "Taxa personalizada selecionada", "warning_low_or_custom_fee": "Usar uma taxa baixa pode aumentar o tempo necessário para a confirmação da sua transação. Em casos raros, sua transação pode falhar.", @@ -80,6 +81,7 @@ "warning_token_code_override_2s": "O endereço do contrato informado difere do endereço do contrato do token %1$sincorporado. Proceda com cuidado e verifique se o contrato é legítimo, já que o uso desse token pode resultar em perda de fundos. Se você tiver dúvidas sobre este recurso ou contrato, entre em contato com %2$s.", "warning_token_contract_override_3s": "O token %1$s informado existe como um token %2$s interno com o mesmo endereço de contrato. Prossiga com cuidado e verifique se o contrato é legítimo, já que o uso desse token pode resultar em perda de fundos. Se você tiver dúvidas sobre este recurso, por favor contate %3$s.", "warning_token_exists_1s": "O token informado já existe como um token interno %1$s", + "warning_uk_risk": "Don't invest unless you're prepared to lose all the money you invest. This is a high-risk investment and you should not expect to be protected if something goes wrong. Take 2 min to learn more.", "alert_dropdown_alert": "Alerta! ", "alert_dropdown_warning": "Atenção! ", "azteco_success": "Você resgatou um cartão bitcoin Azteco. Os fundos devem chegar em breve.", @@ -369,6 +371,8 @@ "swap_preferred_instructions": "Quando várias exchanges puderem preencher a uma ordem, prefira:", "swap_preferred_promo_instructions": "Quando várias exchanges podem preencher a uma ordem, a promoção atual sempre prefere:", "swap_token_no_enabled_exchanges_2s": "No enabled exchanges support %1$s (on %2$s) at this time", + "swap_minimum_receive_amount": "Min Receive Amount", + "swap_minimum_amount_1s": "Min %1$s", "settings_button_clear_logs": "Limpar Logs", "settings_button_send_logs": "Enviar Logs para o Edge", "settings_button_export_logs": "Exportar Logs", @@ -1202,6 +1206,7 @@ "util_s_and_s": "%1$s e %2$s", "util_truncate_delimeter": "...", "stake_estimated_return": "Retorno Estimado: %s", + "stake_estimated_apr_s": "%s Estimated APR", "stake_s_staked": "%s Staked", "stake_s_earned": "%s Ganhos", "stake_s_unstaked": "%s Unstaked", @@ -1272,6 +1277,11 @@ "fiat_plugin_fetching_assets": "Buscando ativos suportados", "fiat_plugin_sell_cancelled": "Sell order cancelled", "fiat_plugin_finalizing_quote": "Finalizing your exchange quote. Please wait as this may take up to a minute", + "fiat_plugin_sell_complete_title": "Sell Order Complete", + "fiat_plugin_sell_complete_message_s": "Your sell order of %1$s %2$s for %3$s %4$s has been completed.", + "fiat_plugin_sell_complete_message_2_hour_s": "Please allow up to %1$s hour for the funds to appear in your account.", + "fiat_plugin_sell_complete_message_2_hours_s": "Please allow up to %1$s hours for the funds to appear in your account.", + "fiat_plugin_sell_complete_message_3": "A confirmation email has been sent to your registered email address.", "fiat_plugin_sell_failed_try_again": "Sell order failed. Please try again.", "fiat_plugin_sell_failed_to_send_try_again": "Failed to send funds for sell transaction. Please try again.", "fiat_plugin_cannot_continue_camera_permission": "Cannot continue. Camera permission needed for ID verifications", diff --git a/src/locales/strings/ru.json b/src/locales/strings/ru.json index 71c1e599955..526ac5aa536 100644 --- a/src/locales/strings/ru.json +++ b/src/locales/strings/ru.json @@ -64,6 +64,7 @@ "error_paymentprotocol_tx_verification_failed": "Payment Protocol transaction verification mismatch", "error_spend_amount_less_then_min_s": "Spend amount is less than minimum of %s", "error_amount_too_low_to_stake_s": "The amount %s is too low to stake successfully", + "error_balance_below_minimum_to_stake_2s": "Your balance of %1$s does not meet the minimum %2$s required to stake.", "warning_low_fee_selected": "Low Fee Selected", "warning_custom_fee_selected": "Custom Fee Selected", "warning_low_or_custom_fee": "Using a low fee may increase the amount of time it takes for your transaction to confirm. In rare instances your transaction can fail.", @@ -80,6 +81,7 @@ "warning_token_code_override_2s": "The entered contract address differs from the contract address of built-in token %1$s. Please proceed with caution and verify the contract is legitimate as use of this token can result in loss of funds. If you have questions about this feature or contract please contact %2$s.", "warning_token_contract_override_3s": "The entered token %1$s exists as a built-in token %2$s with the same contract address. Please proceed with caution and verify the contract is legitimate as use of this token can result in loss of funds. If you have questions about this feature please contact %3$s.", "warning_token_exists_1s": "The entered token already exists as a built-in token %1$s", + "warning_uk_risk": "Don't invest unless you're prepared to lose all the money you invest. This is a high-risk investment and you should not expect to be protected if something goes wrong. Take 2 min to learn more.", "alert_dropdown_alert": "Внимание! ", "alert_dropdown_warning": "Внимание! ", "azteco_success": "You've redeemed an Azteco bitcoin card. Funds should arrive shortly.", @@ -369,6 +371,8 @@ "swap_preferred_instructions": "При выполнении ордера предпочесть следующие биржи:", "swap_preferred_promo_instructions": "При выполнении обмена несколькими биржами предпочтение отдается текущим предложениям:", "swap_token_no_enabled_exchanges_2s": "No enabled exchanges support %1$s (on %2$s) at this time", + "swap_minimum_receive_amount": "Min Receive Amount", + "swap_minimum_amount_1s": "Min %1$s", "settings_button_clear_logs": "Clear Logs", "settings_button_send_logs": "Send Logs to Edge", "settings_button_export_logs": "Export Logs", @@ -1202,6 +1206,7 @@ "util_s_and_s": "%1$s and %2$s", "util_truncate_delimeter": "...", "stake_estimated_return": "Estimated Return: %s", + "stake_estimated_apr_s": "%s Estimated APR", "stake_s_staked": "%s Staked", "stake_s_earned": "%s Earned", "stake_s_unstaked": "%s Unstaked", @@ -1272,6 +1277,11 @@ "fiat_plugin_fetching_assets": "Fetching supported assets", "fiat_plugin_sell_cancelled": "Sell order cancelled", "fiat_plugin_finalizing_quote": "Finalizing your exchange quote. Please wait as this may take up to a minute", + "fiat_plugin_sell_complete_title": "Sell Order Complete", + "fiat_plugin_sell_complete_message_s": "Your sell order of %1$s %2$s for %3$s %4$s has been completed.", + "fiat_plugin_sell_complete_message_2_hour_s": "Please allow up to %1$s hour for the funds to appear in your account.", + "fiat_plugin_sell_complete_message_2_hours_s": "Please allow up to %1$s hours for the funds to appear in your account.", + "fiat_plugin_sell_complete_message_3": "A confirmation email has been sent to your registered email address.", "fiat_plugin_sell_failed_try_again": "Sell order failed. Please try again.", "fiat_plugin_sell_failed_to_send_try_again": "Failed to send funds for sell transaction. Please try again.", "fiat_plugin_cannot_continue_camera_permission": "Cannot continue. Camera permission needed for ID verifications", diff --git a/src/locales/strings/vi.json b/src/locales/strings/vi.json index 19de4e880ce..95b288e7330 100644 --- a/src/locales/strings/vi.json +++ b/src/locales/strings/vi.json @@ -64,6 +64,7 @@ "error_paymentprotocol_tx_verification_failed": "Payment Protocol transaction verification mismatch", "error_spend_amount_less_then_min_s": "Spend amount is less than minimum of %s", "error_amount_too_low_to_stake_s": "The amount %s is too low to stake successfully", + "error_balance_below_minimum_to_stake_2s": "Your balance of %1$s does not meet the minimum %2$s required to stake.", "warning_low_fee_selected": "Low Fee Selected", "warning_custom_fee_selected": "Custom Fee Selected", "warning_low_or_custom_fee": "Using a low fee may increase the amount of time it takes for your transaction to confirm. In rare instances your transaction can fail.", @@ -80,6 +81,7 @@ "warning_token_code_override_2s": "The entered contract address differs from the contract address of built-in token %1$s. Please proceed with caution and verify the contract is legitimate as use of this token can result in loss of funds. If you have questions about this feature or contract please contact %2$s.", "warning_token_contract_override_3s": "The entered token %1$s exists as a built-in token %2$s with the same contract address. Please proceed with caution and verify the contract is legitimate as use of this token can result in loss of funds. If you have questions about this feature please contact %3$s.", "warning_token_exists_1s": "The entered token already exists as a built-in token %1$s", + "warning_uk_risk": "Don't invest unless you're prepared to lose all the money you invest. This is a high-risk investment and you should not expect to be protected if something goes wrong. Take 2 min to learn more.", "alert_dropdown_alert": "Alert! ", "alert_dropdown_warning": "Warning! ", "azteco_success": "You've redeemed an Azteco bitcoin card. Funds should arrive shortly.", @@ -369,6 +371,8 @@ "swap_preferred_instructions": "When multiple exchanges can fill an order, prefer:", "swap_preferred_promo_instructions": "When multiple exchanges can fill an order, the current promotion always prefers:", "swap_token_no_enabled_exchanges_2s": "No enabled exchanges support %1$s (on %2$s) at this time", + "swap_minimum_receive_amount": "Min Receive Amount", + "swap_minimum_amount_1s": "Min %1$s", "settings_button_clear_logs": "Clear Logs", "settings_button_send_logs": "Send Logs to Edge", "settings_button_export_logs": "Export Logs", @@ -1202,6 +1206,7 @@ "util_s_and_s": "%1$s and %2$s", "util_truncate_delimeter": "...", "stake_estimated_return": "Estimated Return: %s", + "stake_estimated_apr_s": "%s Estimated APR", "stake_s_staked": "%s Staked", "stake_s_earned": "%s Earned", "stake_s_unstaked": "%s Unstaked", @@ -1272,6 +1277,11 @@ "fiat_plugin_fetching_assets": "Fetching supported assets", "fiat_plugin_sell_cancelled": "Sell order cancelled", "fiat_plugin_finalizing_quote": "Finalizing your exchange quote. Please wait as this may take up to a minute", + "fiat_plugin_sell_complete_title": "Sell Order Complete", + "fiat_plugin_sell_complete_message_s": "Your sell order of %1$s %2$s for %3$s %4$s has been completed.", + "fiat_plugin_sell_complete_message_2_hour_s": "Please allow up to %1$s hour for the funds to appear in your account.", + "fiat_plugin_sell_complete_message_2_hours_s": "Please allow up to %1$s hours for the funds to appear in your account.", + "fiat_plugin_sell_complete_message_3": "A confirmation email has been sent to your registered email address.", "fiat_plugin_sell_failed_try_again": "Sell order failed. Please try again.", "fiat_plugin_sell_failed_to_send_try_again": "Failed to send funds for sell transaction. Please try again.", "fiat_plugin_cannot_continue_camera_permission": "Cannot continue. Camera permission needed for ID verifications", diff --git a/src/locales/strings/zh.json b/src/locales/strings/zh.json index a661ed5a4fd..e8c7452684d 100644 --- a/src/locales/strings/zh.json +++ b/src/locales/strings/zh.json @@ -64,6 +64,7 @@ "error_paymentprotocol_tx_verification_failed": "Payment Protocol transaction verification mismatch", "error_spend_amount_less_then_min_s": "Spend amount is less than minimum of %s", "error_amount_too_low_to_stake_s": "The amount %s is too low to stake successfully", + "error_balance_below_minimum_to_stake_2s": "Your balance of %1$s does not meet the minimum %2$s required to stake.", "warning_low_fee_selected": "Low Fee Selected", "warning_custom_fee_selected": "Custom Fee Selected", "warning_low_or_custom_fee": "Using a low fee may increase the amount of time it takes for your transaction to confirm. In rare instances your transaction can fail.", @@ -80,6 +81,7 @@ "warning_token_code_override_2s": "The entered contract address differs from the contract address of built-in token %1$s. Please proceed with caution and verify the contract is legitimate as use of this token can result in loss of funds. If you have questions about this feature or contract please contact %2$s.", "warning_token_contract_override_3s": "The entered token %1$s exists as a built-in token %2$s with the same contract address. Please proceed with caution and verify the contract is legitimate as use of this token can result in loss of funds. If you have questions about this feature please contact %3$s.", "warning_token_exists_1s": "The entered token already exists as a built-in token %1$s", + "warning_uk_risk": "Don't invest unless you're prepared to lose all the money you invest. This is a high-risk investment and you should not expect to be protected if something goes wrong. Take 2 min to learn more.", "alert_dropdown_alert": "Alert! ", "alert_dropdown_warning": "Warning! ", "azteco_success": "You've redeemed an Azteco bitcoin card. Funds should arrive shortly.", @@ -369,6 +371,8 @@ "swap_preferred_instructions": "When multiple exchanges can fill an order, prefer:", "swap_preferred_promo_instructions": "When multiple exchanges can fill an order, the current promotion always prefers:", "swap_token_no_enabled_exchanges_2s": "No enabled exchanges support %1$s (on %2$s) at this time", + "swap_minimum_receive_amount": "Min Receive Amount", + "swap_minimum_amount_1s": "Min %1$s", "settings_button_clear_logs": "Clear Logs", "settings_button_send_logs": "Send Logs to Edge", "settings_button_export_logs": "Export Logs", @@ -1202,6 +1206,7 @@ "util_s_and_s": "%1$s and %2$s", "util_truncate_delimeter": "...", "stake_estimated_return": "Estimated Return: %s", + "stake_estimated_apr_s": "%s Estimated APR", "stake_s_staked": "%s Staked", "stake_s_earned": "%s Earned", "stake_s_unstaked": "%s Unstaked", @@ -1272,6 +1277,11 @@ "fiat_plugin_fetching_assets": "Fetching supported assets", "fiat_plugin_sell_cancelled": "Sell order cancelled", "fiat_plugin_finalizing_quote": "Finalizing your exchange quote. Please wait as this may take up to a minute", + "fiat_plugin_sell_complete_title": "Sell Order Complete", + "fiat_plugin_sell_complete_message_s": "Your sell order of %1$s %2$s for %3$s %4$s has been completed.", + "fiat_plugin_sell_complete_message_2_hour_s": "Please allow up to %1$s hour for the funds to appear in your account.", + "fiat_plugin_sell_complete_message_2_hours_s": "Please allow up to %1$s hours for the funds to appear in your account.", + "fiat_plugin_sell_complete_message_3": "A confirmation email has been sent to your registered email address.", "fiat_plugin_sell_failed_try_again": "Sell order failed. Please try again.", "fiat_plugin_sell_failed_to_send_try_again": "Failed to send funds for sell transaction. Please try again.", "fiat_plugin_cannot_continue_camera_permission": "Cannot continue. Camera permission needed for ID verifications", diff --git a/src/plugins/gui/fiatPluginTypes.ts b/src/plugins/gui/fiatPluginTypes.ts index 8aa0c38cc94..66e9e8898b0 100644 --- a/src/plugins/gui/fiatPluginTypes.ts +++ b/src/plugins/gui/fiatPluginTypes.ts @@ -34,8 +34,10 @@ export const asFiatPaymentType = asValue( 'iobank', 'mexicobank', 'payid', + 'paypal', 'pix', 'pse', + 'revolut', 'sepa', 'spei', 'turkishbank', diff --git a/src/plugins/gui/providers/kadoProvider.ts b/src/plugins/gui/providers/kadoProvider.ts index 5b8a3552e1d..4f56e1d2cb7 100644 --- a/src/plugins/gui/providers/kadoProvider.ts +++ b/src/plugins/gui/providers/kadoProvider.ts @@ -1,5 +1,5 @@ -import { gt, lt } from 'biggystring' -import { asArray, asBoolean, asNumber, asObject, asOptional, asString, asValue } from 'cleaners' +import { gt, lt, round } from 'biggystring' +import { asArray, asBoolean, asMaybe, asNumber, asObject, asOptional, asString, asValue } from 'cleaners' import { EdgeAssetAction, EdgeSpendInfo, EdgeTokenId, EdgeTxActionFiat } from 'edge-core-js' import URL from 'url-parse' @@ -383,7 +383,12 @@ const asOrderData = asObject({ // settlementTimeInSeconds: asNumber }) -const asWebviewMessage = asObject({ type: asValue('PLAID_NEW_ACH_LINK'), payload: asObject({ link: asString }) }) +const asWebviewMessage = asObject({ + // We also see "RAMP_ORDER_ID" here, so we should eventually add + // some `asEither` logic to switch between various valid message types: + type: asValue('PLAID_NEW_ACH_LINK' as const), + payload: asObject({ link: asString }) +}) const asOrderInfo = asObject({ success: asBoolean, @@ -647,15 +652,10 @@ export const kadoProvider: FiatProviderFactory = { // webview to prevent some glitchiness. When needed, Kado will send an onMessage // trigger with the url to open. The below code is derived from Kado's sample code const onMessage = (data: string) => { - try { - datelog(`**** Kado onMessage ${data}`) - const message = asWebviewMessage(JSON.parse(data)) - showUi.openExternalWebView({ url: message.payload.link, redirectExternal: true }).catch(e => { - throw e - }) - } catch (error) { - // Handle parsing errors gracefully - showUi.showError(`Error parsing message: ${String(error)}`).catch(e => {}) + datelog(`**** Kado onMessage ${data}`) + const message = asMaybe(asWebviewMessage)(JSON.parse(data)) + if (message?.type === 'PLAID_NEW_ACH_LINK') { + showUi.openExternalWebView({ url: message.payload.link, redirectExternal: true }).catch(async error => await showUi.showError(error)) } } @@ -759,7 +759,7 @@ export const kadoProvider: FiatProviderFactory = { console.log(` blockchain: ${blockchain}`) console.log(` pluginId: ${pluginId}`) console.log(` tokenId: ${tokenId}`) - const nativeAmount = await coreWallet.denominationToNative(paymentExchangeAmount, displayCurrencyCode) + const nativeAmount = round(await coreWallet.denominationToNative(paymentExchangeAmount, displayCurrencyCode), 0) const assetAction: EdgeAssetAction = { assetActionType: 'sell' diff --git a/src/plugins/gui/providers/moonpayProvider.ts b/src/plugins/gui/providers/moonpayProvider.ts index 0fcdf65ea8c..e709e6d2b0d 100644 --- a/src/plugins/gui/providers/moonpayProvider.ts +++ b/src/plugins/gui/providers/moonpayProvider.ts @@ -1,11 +1,16 @@ // import { div, gt, lt, mul, toFixed } from 'biggystring' import { asArray, asBoolean, asEither, asNull, asNumber, asObject, asOptional, asString, asValue } from 'cleaners' -import { EdgeTokenId } from 'edge-core-js' +import { EdgeAssetAction, EdgeSpendInfo, EdgeTokenId, EdgeTxActionFiat } from 'edge-core-js' +import { sprintf } from 'sprintf-js' import URL from 'url-parse' +import { SendScene2Params } from '../../../components/scenes/SendScene2' +import { lstrings } from '../../../locales/strings' import { StringMap } from '../../../types/types' +import { CryptoAmount } from '../../../util/CryptoAmount' import { removeIsoPrefix } from '../../../util/utils' -import { FiatDirection, FiatPaymentType } from '../fiatPluginTypes' +import { SendErrorBackPressed, SendErrorNoTransaction } from '../fiatPlugin' +import { FiatDirection, FiatPaymentType, SaveTxActionParams } from '../fiatPluginTypes' import { FiatProvider, FiatProviderApproveQuoteParams, @@ -18,15 +23,19 @@ import { FiatProviderQuote } from '../fiatProviderTypes' import { addTokenToArray } from '../util/providerUtils' -import { addExactRegion, isDailyCheckDue, validateExactRegion } from './common' +import { addExactRegion, isDailyCheckDue, NOT_SUCCESS_TOAST_HIDE_MS, RETURN_URL_PAYMENT, validateExactRegion } from './common' const providerId = 'moonpay' const storeId = 'com.moonpay' const partnerIcon = 'moonpay_symbol_prp.png' const pluginDisplayName = 'Moonpay' +const supportEmail = 'support@moonpay.com' const allowedCurrencyCodes: Record = { - buy: { credit: { providerId, fiat: {}, crypto: {} } }, - sell: { credit: { providerId, fiat: {}, crypto: {}, requiredAmountType: 'crypto' } } + buy: { credit: { providerId, fiat: {}, crypto: {} }, paypal: { providerId, fiat: {}, crypto: {} } }, + sell: { + credit: { providerId, fiat: {}, crypto: {}, requiredAmountType: 'crypto' }, + paypal: { providerId, fiat: {}, crypto: {}, requiredAmountType: 'crypto' } + } } const allowedCountryCodes: Record = { buy: {}, sell: {} } @@ -99,17 +108,44 @@ const asApiKeys = asString const asMoonpayCountries = asArray(asMoonpayCountry) -type MoonpayPaymentMethod = 'ach_bank_transfer' | 'credit_debit_card' +type MoonpayPaymentMethod = 'ach_bank_transfer' | 'credit_debit_card' | 'paypal' + interface MoonpayWidgetQueryParams { apiKey: string + lockAmount: true + showAllCurrencies: false + paymentMethod: MoonpayPaymentMethod +} + +type MoonpayBuyWidgetQueryParams = MoonpayWidgetQueryParams & { + /** crypto currency code */ currencyCode: string + + /** fiat currency code */ baseCurrencyCode: string - lockAmount: boolean walletAddress: string - showAllCurrencies: boolean - enableRecurringBuys: boolean - paymentMethod: MoonpayPaymentMethod + enableRecurringBuys: false + + /** crypto amount to buy */ + quoteCurrencyAmount?: number + + /** fiat amount to spend */ + baseCurrencyAmount?: number +} + +type MoonpaySellWidgetQueryParams = MoonpayWidgetQueryParams & { + /** fiat currency code */ + quoteCurrencyCode: string + + /** crypto currency code */ + baseCurrencyCode: string + refundWalletAddress: string + redirectURL: string + + /** fiat amount to receive */ quoteCurrencyAmount?: number + + /** crypto amount to sell */ baseCurrencyAmount?: number } @@ -117,7 +153,8 @@ const MOONPAY_PAYMENT_TYPE_MAP: Partial> = { applepay: 'credit', credit: 'credit', - googlepay: 'credit' + googlepay: 'credit', + paypal: 'paypal' } let lastChecked = 0 @@ -163,15 +201,11 @@ export const moonpayProvider: FiatProviderFactory = { getSupportedAssets: async ({ direction, paymentTypes, regionCode }): Promise => { const paymentType = PAYMENT_TYPE_MAP[paymentTypes[0]] ?? paymentTypes[0] - if (direction !== 'buy') { - throw new FiatProviderError({ providerId, errorType: 'paymentUnsupported' }) - } - // Return nothing if paymentTypes are not supported by this provider const assetMap = allowedCurrencyCodes[direction][paymentType] if (assetMap == null) throw new FiatProviderError({ providerId, errorType: 'paymentUnsupported' }) - if (isDailyCheckDue(lastChecked)) { + if (Object.keys(assetMap.crypto).length === 0 || isDailyCheckDue(lastChecked)) { const response = await fetch(`https://api.moonpay.com/v3/currencies?apiKey=${apiKey}`).catch(e => undefined) if (response == null || !response.ok) return assetMap @@ -185,9 +219,9 @@ export const moonpayProvider: FiatProviderFactory = { return assetMap } for (const currency of moonpayCurrencies) { - // if (direction === 'sell' && currency.isSellSupported !== true) { - // continue - // } + if (direction === 'sell' && currency.isSellSupported !== true) { + continue + } if (currency.type === 'crypto') { if (regionCode.countryCode === 'US' && currency.isSupportedInUS !== true) { @@ -223,10 +257,13 @@ export const moonpayProvider: FiatProviderFactory = { for (const country of countries) { if (country.isAllowed) { if (country.states == null) { - if (country.isBuyAllowed) { - allowedCountryCodes.buy[country.alpha2] = true - } else if (country.isSellAllowed) { - allowedCountryCodes.sell[country.alpha2] = true + if (country.isAllowed) { + if (country.isBuyAllowed) { + allowedCountryCodes.buy[country.alpha2] = true + } + if (country.isSellAllowed) { + allowedCountryCodes.sell[country.alpha2] = true + } } } else { const countryCode = country.alpha2 @@ -252,13 +289,9 @@ export const moonpayProvider: FiatProviderFactory = { return assetMap }, getQuote: async (params: FiatProviderGetQuoteParams): Promise => { - const { direction, regionCode, paymentTypes, displayCurrencyCode } = params + const { direction, fiatCurrencyCode, regionCode, paymentTypes, displayCurrencyCode, tokenId } = params validateExactRegion(providerId, regionCode, allowedCountryCodes[direction]) - if (direction !== 'buy') { - throw new FiatProviderError({ providerId, errorType: 'paymentUnsupported' }) - } - const paymentType = PAYMENT_TYPE_MAP[paymentTypes[0]] ?? paymentTypes[0] const assetMap = allowedCurrencyCodes[direction][paymentType] if (assetMap == null) throw new FiatProviderError({ providerId, errorType: 'paymentUnsupported' }) @@ -356,27 +389,194 @@ export const moonpayProvider: FiatProviderFactory = { approveQuote: async (approveParams: FiatProviderApproveQuoteParams): Promise => { const { coreWallet, showUi } = approveParams const receiveAddress = await coreWallet.getReceiveAddress({ tokenId: null }) - const url = new URL('https://buy.moonpay.com?', true) - const queryObj: MoonpayWidgetQueryParams = { - apiKey, - walletAddress: receiveAddress.publicAddress, - currencyCode: cryptoCurrencyObj.code, - paymentMethod, - baseCurrencyCode: fiatCurrencyObj.code, - lockAmount: true, - showAllCurrencies: false, - enableRecurringBuys: true - } - if (params.amountType === 'crypto') { - queryObj.quoteCurrencyAmount = moonpayQuote.quoteCurrencyAmount + if (direction === 'buy') { + const urlObj = new URL('https://buy.moonpay.com?', true) + const queryObj: MoonpayBuyWidgetQueryParams = { + apiKey, + walletAddress: receiveAddress.publicAddress, + currencyCode: cryptoCurrencyObj.code, + paymentMethod, + baseCurrencyCode: fiatCurrencyObj.code, + lockAmount: true, + showAllCurrencies: false, + enableRecurringBuys: false + } + if (params.amountType === 'crypto') { + queryObj.quoteCurrencyAmount = moonpayQuote.quoteCurrencyAmount + } else { + queryObj.baseCurrencyAmount = 'totalAmount' in moonpayQuote ? moonpayQuote.totalAmount : undefined + } + urlObj.set('query', queryObj) + console.log('Approving moonpay buy quote url=' + urlObj.href) + await showUi.openExternalWebView({ url: urlObj.href }) } else { - queryObj.baseCurrencyAmount = 'totalAmount' in moonpayQuote ? moonpayQuote.totalAmount : undefined + const urlObj = new URL('https://sell.moonpay.com?', true) + const queryObj: MoonpaySellWidgetQueryParams = { + apiKey, + refundWalletAddress: receiveAddress.publicAddress, + quoteCurrencyCode: fiatCurrencyObj.code, + paymentMethod, + baseCurrencyCode: cryptoCurrencyObj.code, + lockAmount: true, + showAllCurrencies: false, + redirectURL: RETURN_URL_PAYMENT + } + if (params.amountType === 'crypto') { + queryObj.baseCurrencyAmount = moonpayQuote.baseCurrencyAmount + } else { + queryObj.quoteCurrencyAmount = 'totalAmount' in moonpayQuote ? moonpayQuote.totalAmount : undefined + } + urlObj.set('query', queryObj) + console.log('Approving moonpay sell quote url=' + urlObj.href) + + let inPayment = false + + const openWebView = async () => { + await showUi.openWebView({ + url: urlObj.href, + onUrlChange: async (uri: string) => { + console.log('Moonpay WebView url change: ' + uri) + + if (uri.startsWith(RETURN_URL_PAYMENT)) { + console.log('Moonpay WebView launch payment: ' + uri) + const urlObj = new URL(uri, true) + const { query } = urlObj + const { baseCurrencyAmount, baseCurrencyCode, depositWalletAddress, depositWalletAddressTag, transactionId } = query + if (inPayment) return + inPayment = true + try { + if (baseCurrencyAmount == null || baseCurrencyCode == null || depositWalletAddress == null || transactionId == null) { + throw new Error('Moonpay missing parameters') + } + + const nativeAmount = await coreWallet.denominationToNative(baseCurrencyAmount, displayCurrencyCode) + + const assetAction: EdgeAssetAction = { + assetActionType: 'sell' + } + const savedAction: EdgeTxActionFiat = { + actionType: 'fiat', + orderId: transactionId, + orderUri: `https://sell.moonpay.com/transaction_receipt?transactionId=${transactionId}`, + isEstimate: true, + fiatPlugin: { + providerId, + providerDisplayName: pluginDisplayName, + supportEmail + }, + payinAddress: depositWalletAddress, + cryptoAsset: { + pluginId: coreWallet.currencyInfo.pluginId, + tokenId, + nativeAmount + }, + fiatAsset: { + fiatCurrencyCode, + fiatAmount + } + } + + // Launch the SendScene to make payment + const spendInfo: EdgeSpendInfo = { + tokenId, + assetAction, + savedAction, + spendTargets: [ + { + nativeAmount, + publicAddress: depositWalletAddress + } + ] + } + + if (depositWalletAddressTag != null) { + spendInfo.memos = [ + { + type: 'text', + value: depositWalletAddressTag, + hidden: true + } + ] + } + + const sendParams: SendScene2Params = { + walletId: coreWallet.id, + tokenId, + spendInfo, + dismissAlert: true, + lockTilesMap: { + address: true, + amount: true, + wallet: true + }, + hiddenFeaturesMap: { + address: true + } + } + const tx = await showUi.send(sendParams) + await showUi.trackConversion('Sell_Success', { + conversionValues: { + conversionType: 'sell', + destFiatCurrencyCode: fiatCurrencyCode, + destFiatAmount: fiatAmount, + sourceAmount: new CryptoAmount({ + currencyConfig: coreWallet.currencyConfig, + currencyCode: displayCurrencyCode, + exchangeAmount: baseCurrencyAmount + }), + fiatProviderId: providerId, + orderId: transactionId + } + }) + + // Save separate metadata/action for token transaction fee + if (tokenId != null) { + const params: SaveTxActionParams = { + walletId: coreWallet.id, + tokenId, + txid: tx.txid, + savedAction, + assetAction: { ...assetAction, assetActionType: 'sell' } + } + await showUi.saveTxAction(params) + } + + // Route back to the original URL to show Paybis confirmation screen + await showUi.exitScene() + + const message = + sprintf(lstrings.fiat_plugin_sell_complete_message_s, cryptoAmount, displayCurrencyCode, fiatAmount, displayFiatCurrencyCode, '1') + + '\n\n' + + sprintf(lstrings.fiat_plugin_sell_complete_message_2_hour_s, '1') + + '\n\n' + + lstrings.fiat_plugin_sell_complete_message_3 + await showUi.buttonModal({ + buttons: { + ok: { label: lstrings.string_ok, type: 'primary' } + }, + title: lstrings.fiat_plugin_sell_complete_title, + message + }) + } catch (e: any) { + await showUi.exitScene() + // Reopen the webivew on the Paybis payment screen + await openWebView() + if (e.message === SendErrorNoTransaction) { + await showUi.showToast(lstrings.fiat_plugin_sell_failed_to_send_try_again, NOT_SUCCESS_TOAST_HIDE_MS) + } else if (e.message === SendErrorBackPressed) { + // Do nothing + } else { + await showUi.showError(e) + } + } finally { + inPayment = false + } + } + } + }) + } + await openWebView() } - - url.set('query', queryObj) - - console.log('Approving moonpay quote url=' + url.href) - await showUi.openExternalWebView({ url: url.href }) }, closeQuote: async (): Promise => {} } diff --git a/src/plugins/gui/providers/paybisProvider.ts b/src/plugins/gui/providers/paybisProvider.ts index 6f9848ce3bc..0f922584f37 100644 --- a/src/plugins/gui/providers/paybisProvider.ts +++ b/src/plugins/gui/providers/paybisProvider.ts @@ -42,6 +42,7 @@ const allowedPaymentTypes: AllowedPaymentTypes = { googlepay: true, pix: true, pse: true, + revolut: true, spei: true }, sell: { @@ -64,6 +65,7 @@ const asApiKeys = asObject({ const asPaymentMethodId = asValue( 'method-id-credit-card', 'method-id-credit-card-out', + 'method-id_bridgerpay_revolutpay', // XXX Hack. Fake payment methods for googlepay/applepay 'fake-id-googlepay', @@ -278,6 +280,7 @@ const EDGE_TO_PAYBIS_CURRENCY_MAP: StringMap = Object.entries(PAYBIS_TO_EDGE_CUR const PAYMENT_METHOD_MAP: { [Payment in PaymentMethodId]: FiatPaymentType } = { 'method-id-credit-card': 'credit', 'method-id-credit-card-out': 'credit', + 'method-id_bridgerpay_revolutpay': 'revolut', // XXX Hack. Fake payment methods for googlepay/applepay 'fake-id-googlepay': 'googlepay', @@ -302,6 +305,7 @@ const REVERSE_PAYMENT_METHOD_MAP: Partial<{ [Payment in FiatPaymentType]: Paymen googlepay: 'method-id-credit-card', pix: 'method-id_bridgerpay_directa24_pix', pse: 'method-id_bridgerpay_directa24_pse', + revolut: 'method-id_bridgerpay_revolutpay', spei: 'method-id_bridgerpay_directa24_spei' } @@ -345,6 +349,10 @@ export const paybisProvider: FiatProviderFactory = { partnerIcon, pluginDisplayName, getSupportedAssets: async ({ direction, paymentTypes, regionCode }): Promise => { + // Do not allow sell to debit in US + if (direction === 'sell' && paymentTypes.includes('credit') && regionCode.countryCode === 'US') { + throw new FiatProviderError({ providerId, errorType: 'paymentUnsupported' }) + } validateRegion(providerId, regionCode, SUPPORTED_REGIONS) // Return nothing if paymentTypes are not supported by this provider const paymentType = paymentTypes.find(paymentType => allowedPaymentTypes[direction][paymentType] === true) diff --git a/src/plugins/gui/scenes/AddressFormScene.tsx b/src/plugins/gui/scenes/AddressFormScene.tsx index 1554dce666e..55832cbcf4c 100644 --- a/src/plugins/gui/scenes/AddressFormScene.tsx +++ b/src/plugins/gui/scenes/AddressFormScene.tsx @@ -1,12 +1,12 @@ /* eslint-disable react-hooks/exhaustive-deps */ import { asArray, asObject, asOptional, asString } from 'cleaners' import * as React from 'react' -import { Platform, ScrollView, View, ViewStyle } from 'react-native' +import { Platform, ScrollView, View } from 'react-native' import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view' -import Animated, { Easing, interpolateColor, useAnimatedStyle, useDerivedValue, useSharedValue, withTiming } from 'react-native-reanimated' import { SceneButtons } from '../../../components/buttons/SceneButtons' import { EdgeTouchableOpacity } from '../../../components/common/EdgeTouchableOpacity' +import { ExpandableList } from '../../../components/common/ExpandableList' import { SceneWrapper } from '../../../components/common/SceneWrapper' import { cacheStyles, Theme, useTheme } from '../../../components/services/ThemeContext' import { EdgeText } from '../../../components/themed/EdgeText' @@ -60,7 +60,6 @@ export const AddressFormScene = React.memo((props: Props) => { const { route, navigation } = props const { countryCode, headerTitle, /* headerIconUri, */ onSubmit, onClose } = route.params const disklet = useSelector(state => state.core.disklet) - const dropdownBorderColor = React.useMemo(() => [theme.iconDeactivated, theme.iconTappable], [theme]) const [formData, setFormData] = React.useState({ address: '', @@ -74,31 +73,19 @@ export const AddressFormScene = React.memo((props: Props) => { const [searchResults, setSearchResults] = React.useState([]) const [prevAddressQuery, setPrevAddressQuery] = React.useState(undefined) const [isHintsDropped, setIsHintsDropped] = React.useState(false) - const [isAnimateHintsNumChange, setIsAnimateHintsNumChange] = React.useState(false) const [hintHeight, setHintHeight] = React.useState(0) const rAddressInput = React.createRef() const mounted = React.useRef(true) - const sAnimationMult = useSharedValue(0) - - const dFinalHeight = useDerivedValue(() => { - return hintHeight * Math.min(searchResults.length, MAX_DISPLAYED_HINTS) - }) - - // Further calculations to determine the height. Also add another - // conditional animation based on the number of hints changing as - // the search query changes from user input - const aDropContainerStyle = useAnimatedStyle(() => ({ - height: withTiming(dFinalHeight.value * sAnimationMult.value, { - duration: isAnimateHintsNumChange ? 250 : 0, - easing: Easing.inOut(Easing.circle) - }), - opacity: isHintsDropped && searchResults.length > 0 ? sAnimationMult.value : withTiming(0, { duration: 500 }), - - borderColor: interpolateColor(sAnimationMult.value, [0, 1], dropdownBorderColor) - })) + const addressHintPress = (selectedAddressHint: HomeAddress) => () => { + handleHideAddressHints() + setFormData({ ...selectedAddressHint }) + if (rAddressInput.current != null) { + rAddressInput.current.blur() + } + } const handleHintLayout = useHandler(event => { if (event != null && hintHeight === 0) { @@ -107,16 +94,28 @@ export const AddressFormScene = React.memo((props: Props) => { } }) + const addressHints = React.useMemo(() => { + return searchResults.map(searchResult => { + const displaySearchResult1 = searchResult.address + const displaySearchResult2 = `${searchResult.city}, ${searchResult.state}, ${countryCode}` + + return ( + + + {displaySearchResult1} + {displaySearchResult2} + + + ) + }) + }, [searchResults, countryCode, addressHintPress, handleHintLayout, styles.rowContainer]) + const handleShowAddressHints = useHandler(() => { setIsHintsDropped(true) }) const handleHideAddressHints = useHandler(() => { setIsHintsDropped(false) - - // Avoid stacking multiple animation multipliers the next - // time the dropdown opens - setIsAnimateHintsNumChange(false) }) // Search for address hints @@ -172,14 +171,6 @@ export const AddressFormScene = React.memo((props: Props) => { // Populate the address fields with the values from the selected search // results - const addressHintPress = (selectedAddressHint: HomeAddress) => () => { - setFormData({ ...selectedAddressHint }) // Update address's value with new value - - if (rAddressInput.current != null) { - rAddressInput.current.blur() - } - } - const handleChangeAddress = useHandler((inputValue: string) => { setIsNeedsFuzzySearch(true) setFormData({ ...formData, address: inputValue }) @@ -208,15 +199,6 @@ export const AddressFormScene = React.memo((props: Props) => { await onSubmit(formData) }) - // The main hints dropdown animation depending on focus state of the - // address field - React.useEffect(() => { - sAnimationMult.value = withTiming(isHintsDropped ? 1 : 0, { - duration: 500, - easing: Easing.inOut(Easing.circle) - }) - }, [sAnimationMult, isHintsDropped]) - // Periodically run a fuzzy search on changed address user input React.useEffect(() => { const task = makePeriodicTask(handlePeriodicSearch, FUZZY_SEARCH_INTERVAL) @@ -225,16 +207,6 @@ export const AddressFormScene = React.memo((props: Props) => { return () => task.stop() }, [handlePeriodicSearch]) - // Changes to the number of address hints results should trigger - // another animation if the hints are are open - React.useEffect(() => { - setIsAnimateHintsNumChange(isHintsDropped) - - // Don't want to react on isHintsDropped, only changes to the - // number of results while dropdown is open - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [searchResults]) - // Unmount cleanup React.useEffect(() => { return navigation.addListener('beforeRemove', () => { @@ -272,22 +244,7 @@ export const AddressFormScene = React.memo((props: Props) => { onFocus={handleShowAddressHints} onBlur={handleHideAddressHints} /> - - - {searchResults.map(searchResult => { - const displaySearchResult = `${searchResult.address}\n${searchResult.city}, ${searchResult.state}, ${countryCode}` - return ( - - - - {displaySearchResult} - - - - ) - })} - - + { }) const getStyles = cacheStyles((theme: Theme) => { - const dropContainerCommon: ViewStyle = { - backgroundColor: theme.modal, - borderRadius: theme.rem(0.5), - zIndex: 1, - borderColor: theme.iconTappable, - borderWidth: theme.thinLineWidth, - overflow: 'hidden', - position: 'absolute', - left: theme.rem(0.5), - right: theme.rem(0.5) - } return { - addressHintText: { - marginHorizontal: theme.rem(0.5), - marginVertical: theme.rem(0.25) - }, container: { paddingTop: 0, - marginHorizontal: theme.rem(0.5), + paddingHorizontal: theme.rem(0.5), flexGrow: 1 }, - dropContainer: { - top: theme.rem(3.75), - ...dropContainerCommon - }, - dropContainerAndroid: { - top: theme.rem(4) - 3, - ...dropContainerCommon - }, formSectionTitle: { marginLeft: theme.rem(0.5), marginTop: theme.rem(1), @@ -385,11 +319,12 @@ const getStyles = cacheStyles((theme: Theme) => { fontFamily: theme.fontFaceBold }, rowContainer: { - display: 'flex', - height: theme.rem(2.75), - flexDirection: 'row', - justifyContent: 'flex-start', - alignItems: 'center' + flexGrow: 1, + flexShrink: 1, + justifyContent: 'center', + alignItems: 'flex-start', + marginHorizontal: theme.rem(0.5), + marginVertical: theme.rem(0.25) } } }) diff --git a/src/plugins/stake-plugins/generic/pluginInfo.ts b/src/plugins/stake-plugins/generic/pluginInfo.ts index 4d257d0cc7b..46aa3903532 100644 --- a/src/plugins/stake-plugins/generic/pluginInfo.ts +++ b/src/plugins/stake-plugins/generic/pluginInfo.ts @@ -1,8 +1,8 @@ -// import { kilncardanopool } from './pluginInfo/cardanoKilnPool' +import { kilncardanopool } from './pluginInfo/cardanoKilnPool' import { coreumnative } from './pluginInfo/coreumNativeStaking' // import { kilnpool } from './pluginInfo/ethereumKilnPool' import { glifpoolCalibration } from './pluginInfo/filecoinCalibrationGlifpool' import { glifpool } from './pluginInfo/filecoinGlifpool' import { tarotpool } from './pluginInfo/optimismTarotPool' -export const genericPlugins = [glifpool, glifpoolCalibration, tarotpool, coreumnative] +export const genericPlugins = [glifpool, glifpoolCalibration, tarotpool, coreumnative, kilncardanopool] diff --git a/src/plugins/stake-plugins/generic/policyAdapters/GlifInfinityPoolAdapter.ts b/src/plugins/stake-plugins/generic/policyAdapters/GlifInfinityPoolAdapter.ts index 41aea49817b..ec6109b7d2d 100644 --- a/src/plugins/stake-plugins/generic/policyAdapters/GlifInfinityPoolAdapter.ts +++ b/src/plugins/stake-plugins/generic/policyAdapters/GlifInfinityPoolAdapter.ts @@ -111,9 +111,11 @@ export const makeGlifInfinityPoolAdapter = (policyConfig: StakePolicyConfig { const daysBetween = msBetween / MILLISECONDS_PER_DAY return daysBetween } +export const monthsBetween = (startDate: Date, endDate: Date): number => { + let months + months = (endDate.getFullYear() - startDate.getFullYear()) * 12 + months += endDate.getMonth() - startDate.getMonth() + + // Adjust for days in the month + if (endDate.getDate() < startDate.getDate()) { + months-- + } + + return months <= 0 ? 0 : months +} export async function runWithTimeout(promise: Promise, ms: number, error: Error = new Error(`Timeout of ${ms}ms exceeded`)): Promise { const timeout: Promise = new Promise((resolve, reject) => { diff --git a/yarn.lock b/yarn.lock index d695d14f803..32b2623cf76 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1266,10 +1266,10 @@ dependencies: "@babel/runtime" "^7.21.0" -"@chain-registry/types@^0.45.44": - version "0.45.44" - resolved "https://registry.yarnpkg.com/@chain-registry/types/-/types-0.45.44.tgz#d732643e2c33f1ec5bcff75ff149be9c5188f5ec" - integrity sha512-s59rl13Uvv8yECDRFTx81YHWwH8Kw/X3mZaafe2qGjGVhsCeJvVqFBhxK1Znnx1m6/auL+S4sVhcTYFqb/3ujQ== +"@chain-registry/types@^0.45.73": + version "0.45.73" + resolved "https://registry.yarnpkg.com/@chain-registry/types/-/types-0.45.73.tgz#affdccc8c79070afd2d7e5ccebffc08bda11ae06" + integrity sha512-DXp7vUuAGdL0HgzmQ6GaSgGoI7Ey0ODoBCi+IawDf6uAsB9KkJiEfV4Yb5dW4pj4frJoI+yZExMMGmilFgMWIQ== "@chain-registry/utils@^1.14.0": version "1.14.0" @@ -7455,12 +7455,12 @@ chai-as-promised@^7.1.1: dependencies: check-error "^1.0.2" -chain-registry@^1.63.50: - version "1.63.54" - resolved "https://registry.yarnpkg.com/chain-registry/-/chain-registry-1.63.54.tgz#e34b5f70b7c5bacd6a60b38b55d1c7ee8b2b0f9a" - integrity sha512-qiJnoZqkOQayW5JMTd+vWSohj+CxIxuXd8kCk+u8tDsItNPVA1gltgOvDi00FZL/+j2+xrnxiVR6hVn5H6/8kg== +chain-registry@^1.63.92: + version "1.63.92" + resolved "https://registry.yarnpkg.com/chain-registry/-/chain-registry-1.63.92.tgz#8b049aef4913fcd3e848f608f97025215688aad2" + integrity sha512-IXa/cOGoTYEUDuCByuDNVKsHMYD+krR5scD2XNyLxBHvqCexMkdxZ3XyeZGe/Vr3JKiM6cHyinDNvRQr4j2kpQ== dependencies: - "@chain-registry/types" "^0.45.44" + "@chain-registry/types" "^0.45.73" chainsaw@~0.1.0: version "0.1.0" @@ -9101,10 +9101,10 @@ ed25519@0.0.4: bindings "^1.2.1" nan "^2.0.9" -edge-core-js@^2.14.0: - version "2.14.0" - resolved "https://registry.yarnpkg.com/edge-core-js/-/edge-core-js-2.14.0.tgz#cf67eb72363e60da06e0b14abe22a383d17a82c7" - integrity sha512-Hu9OB6GHVi+oTY4kvIACVA69v+smkK0VKxmrQsu4laQ7+hUksQF2TQdoLIJS0ttgXVZfSa1v7Sxq9y269tvRxg== +edge-core-js@^2.18.0: + version "2.18.0" + resolved "https://registry.yarnpkg.com/edge-core-js/-/edge-core-js-2.18.0.tgz#9188b28bb68bf7646a1d8bdda0e06844ea9dc6e8" + integrity sha512-YqH4zNvk31THl01T6JPZ5oTFWMm7DpKueWlUDNOiLjknArxL9GaQfFMo1m+Yvr2JiPdaBKsrwM1MaN2rYyHRog== dependencies: aes-js "^3.1.0" base-x "^4.0.0" @@ -9126,10 +9126,10 @@ edge-core-js@^2.14.0: yaob "^0.3.12" yavent "^0.1.3" -edge-currency-accountbased@4.24.1-2: - version "4.24.1-2" - resolved "https://registry.yarnpkg.com/edge-currency-accountbased/-/edge-currency-accountbased-4.24.1-2.tgz#7e5e023f0912174147a971f39a24fd182d2ccdc5" - integrity sha512-7BwEdXsY4YEO1XPgdIRp8vgkK5WiAa7O3WT3ZYb2ZC8CZKJyNc4O8aRtUJolXhCW23juB30xfRhDTBtvsPa13g== +edge-currency-accountbased@^4.24.6: + version "4.24.6" + resolved "https://registry.yarnpkg.com/edge-currency-accountbased/-/edge-currency-accountbased-4.24.6.tgz#a2201ce550c2361d93c8f30dca04885e12e05287" + integrity sha512-q0I00j2WMGj5xX3lMWLqZi6FiNatiSNiHL7nAulrygfgnrQbGKKxkR0HPR3kE4wL10aJugAxjak38brUCuAdFQ== dependencies: "@binance-chain/javascript-sdk" "^4.2.0" "@chain-registry/client" "^1.15.0" @@ -9149,7 +9149,7 @@ edge-currency-accountbased@4.24.1-2: biggystring "^4.1.3" bip39 "^3.0.2" bs58 "4.0.1" - chain-registry "^1.63.50" + chain-registry "^1.63.92" cleaners "^0.3.14" ed25519-hd-key "^1.3.0" eth-sig-util "^3.0.1" @@ -9213,10 +9213,10 @@ edge-currency-plugins@^3.3.2: wifgrs "^2.0.6" ws "^7.4.6" -edge-exchange-plugins@^2.7.5: - version "2.7.5" - resolved "https://registry.yarnpkg.com/edge-exchange-plugins/-/edge-exchange-plugins-2.7.5.tgz#adcc53233aff46bcb6dd6df54aa642e3b72b2933" - integrity sha512-nvo2rwA6KSs4dzWd3HblupxSDC2ST/tnyhoRqLO6+IKM3n7sE7b4/UW6taquW57yHqD9+yeL6UwkJwW9gUrGtQ== +edge-exchange-plugins@^2.9.1: + version "2.9.1" + resolved "https://registry.yarnpkg.com/edge-exchange-plugins/-/edge-exchange-plugins-2.9.1.tgz#4cead608d6091355dfa5eae88d63cc9843c5b97a" + integrity sha512-R8tQ7uIb6cM8WkRLTSD3UbNtSlo1x+FqIvMLaJJMX26YrndnZBxdNkSju6WyCBqgXkIlCnY9VjSERd98SmgKJQ== dependencies: "@cosmjs/encoding" "^0.32.2" biggystring "^4.1.3" @@ -9225,17 +9225,17 @@ edge-exchange-plugins@^2.7.5: regenerator-runtime "0.13.11" xrpl "^2.10.0" -edge-info-server@^2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/edge-info-server/-/edge-info-server-2.6.0.tgz#944720cb98e90bf2ce5c8c75517b82677c8e886f" - integrity sha512-tA5MbnkVYihBje4hAr7CCsf5TVmcKMoTjxZDR+ijLSLO5TmWybVQdYVVE4bZabKv+FKq33tTZ0JJYHVDTSwXkA== +edge-info-server@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/edge-info-server/-/edge-info-server-2.7.0.tgz#dd59760a779b922285a3bb6108331f25d87395d0" + integrity sha512-6FecnV0nm1tufSZlai4nd0mRslrquTNMicQf9VTNOR0GJmXD55AZgm28GSjeQx/VIJdnwh+620BNfidQeqGc0w== dependencies: cleaners "^0.3.16" -edge-login-ui-rn@3.19.2: - version "3.19.2" - resolved "https://registry.yarnpkg.com/edge-login-ui-rn/-/edge-login-ui-rn-3.19.2.tgz#074b1787fdcc181717d9298b9075246350ce324e" - integrity sha512-+yjeNFvrvbsVLJ2+DovobHX+GpGeWMFfoawymkjKBK1BWlS0VgLCXyrB4LY3E6RMYIJZebTx4L0lfvzzCVNnzQ== +edge-login-ui-rn@^3.22.3: + version "3.22.3" + resolved "https://registry.yarnpkg.com/edge-login-ui-rn/-/edge-login-ui-rn-3.22.3.tgz#e8f56e415b84e6e51c876bce2288d0bac49d158c" + integrity sha512-EZb18kdRQsOazHJh0nF1n9YetkDV1Hcf8Qx7/IQimFfn1Dllt2uyZU1+NXJPRwMu11MubaQk3aWMUsHad/Q5yA== dependencies: base-x "^4.0.0" cleaners "^0.3.12" @@ -16264,10 +16264,10 @@ react-native-permissions@^4.1.5: resolved "https://registry.yarnpkg.com/react-native-permissions/-/react-native-permissions-4.1.5.tgz#db4d1ddbf076570043f4fd4168f54bb6020aec92" integrity sha512-r6VMRacASmtRHS+GZ+5HQCp9p9kiE+UU9magHOZCXZLTJitdTuVHWZRrb4v4oqZGU+zAp3mZhTQftuMMv+WLUg== -react-native-piratechain@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/react-native-piratechain/-/react-native-piratechain-0.5.0.tgz#74519d6983d6281eda9b0b788081eb2221a08204" - integrity sha512-cCYNGll6Zye+2oIABBLMSg6DEuIEUJomZkODXKnEDvG8SSIByz2w00Crt6Ol3UjIaDlA69Y10Ty4SiKvLTy2EQ== +react-native-piratechain@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/react-native-piratechain/-/react-native-piratechain-0.5.1.tgz#c7acf6f258b67bdaea0bf232c177022df99dcdfb" + integrity sha512-IJAcg44LVzggXhXK/BSVMFPP/71Qv0Dlq1+1R2feaRCT4ljJrJBsDLLxJKjnfu4OWuz2oEWohCy9kmmZMO4wuQ== dependencies: rfc4648 "^1.3.0" @@ -16374,10 +16374,10 @@ react-native-webview@^13.8.4: escape-string-regexp "2.0.0" invariant "2.2.4" -react-native-zcash@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/react-native-zcash/-/react-native-zcash-0.8.0.tgz#ca3dc76c9e4ae9e7b3e4840526f38acb4b139d7b" - integrity sha512-UtgdWTo8ZNAHvYaKiGGlm9jJZ+jGQxUVZWuoEUUjPxgr87+Tb8FpXTpkvzm5ThRxDrgEKS8Dj98mdJnKO06rAA== +react-native-zcash@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/react-native-zcash/-/react-native-zcash-0.8.1.tgz#04c1ce86134558a8563c3d7258ae703bf060c6ee" + integrity sha512-5eH1jbOH/2he+UZn0BbbYOSRfs+9g9kjZBXXU2ozZ0giS1rZCBc0oUIEVYEnQOPw38BJpbeAjlqYz2EuRl/8hg== dependencies: biggystring "^4.1.3" rfc4648 "^1.3.0"