diff --git a/android/app/build.gradle b/android/app/build.gradle index 60defb1fd..2f5427531 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -91,5 +91,4 @@ dependencies { testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' - implementation 'com.unstoppabledomains:resolution:5.0.0' } diff --git a/android/app/src/main/java/com/cakewallet/cake_wallet/MainActivity.java b/android/app/src/main/java/com/cakewallet/cake_wallet/MainActivity.java index 29b37c46c..df3f6be01 100644 --- a/android/app/src/main/java/com/cakewallet/cake_wallet/MainActivity.java +++ b/android/app/src/main/java/com/cakewallet/cake_wallet/MainActivity.java @@ -20,14 +20,10 @@ import android.net.Uri; import android.os.PowerManager; import android.provider.Settings; -import com.unstoppabledomains.resolution.DomainResolution; -import com.unstoppabledomains.resolution.Resolution; - import java.security.SecureRandom; public class MainActivity extends FlutterFragmentActivity { final String UTILS_CHANNEL = "com.cake_wallet/native_utils"; - final int UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK = 24; boolean isAppSecure = false; @Override @@ -53,14 +49,6 @@ public class MainActivity extends FlutterFragmentActivity { random.nextBytes(bytes); handler.post(() -> result.success(bytes)); break; - case "getUnstoppableDomainAddress": - int version = Build.VERSION.SDK_INT; - if (version >= UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK) { - getUnstoppableDomainAddress(call, result); - } else { - handler.post(() -> result.success("")); - } - break; case "setIsAppSecure": isAppSecure = call.argument("isAppSecure"); if (isAppSecure) { @@ -85,23 +73,6 @@ public class MainActivity extends FlutterFragmentActivity { } } - private void getUnstoppableDomainAddress(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { - DomainResolution resolution = new Resolution(); - Handler handler = new Handler(Looper.getMainLooper()); - String domain = call.argument("domain"); - String ticker = call.argument("ticker"); - - AsyncTask.execute(() -> { - try { - String address = resolution.getAddress(domain, ticker); - handler.post(() -> result.success(address)); - } catch (Exception e) { - System.out.println("Expected Address, but got " + e.getMessage()); - handler.post(() -> result.success("")); - } - }); - } - private void disableBatteryOptimization() { String packageName = getPackageName(); PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); diff --git a/android/app/src/main/java/com/cakewallet/haven/MainActivity.java b/android/app/src/main/java/com/cakewallet/haven/MainActivity.java index d0a465d22..83a790683 100644 --- a/android/app/src/main/java/com/cakewallet/haven/MainActivity.java +++ b/android/app/src/main/java/com/cakewallet/haven/MainActivity.java @@ -19,14 +19,10 @@ import android.net.Uri; import android.os.PowerManager; import android.provider.Settings; -import com.unstoppabledomains.resolution.DomainResolution; -import com.unstoppabledomains.resolution.Resolution; - import java.security.SecureRandom; public class MainActivity extends FlutterFragmentActivity { final String UTILS_CHANNEL = "com.cake_wallet/native_utils"; - final int UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK = 24; @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { @@ -51,14 +47,6 @@ public class MainActivity extends FlutterFragmentActivity { random.nextBytes(bytes); handler.post(() -> result.success(bytes)); break; - case "getUnstoppableDomainAddress": - int version = Build.VERSION.SDK_INT; - if (version >= UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK) { - getUnstoppableDomainAddress(call, result); - } else { - handler.post(() -> result.success("")); - } - break; case "disableBatteryOptimization": disableBatteryOptimization(); handler.post(() -> result.success(null)); @@ -75,23 +63,6 @@ public class MainActivity extends FlutterFragmentActivity { } } - private void getUnstoppableDomainAddress(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { - DomainResolution resolution = new Resolution(); - Handler handler = new Handler(Looper.getMainLooper()); - String domain = call.argument("domain"); - String ticker = call.argument("ticker"); - - AsyncTask.execute(() -> { - try { - String address = resolution.getAddress(domain, ticker); - handler.post(() -> result.success(address)); - } catch (Exception e) { - System.out.println("Expected Address, but got " + e.getMessage()); - handler.post(() -> result.success("")); - } - }); - } - private void disableBatteryOptimization() { String packageName = getPackageName(); PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); diff --git a/android/app/src/main/java/com/monero/app/MainActivity.java b/android/app/src/main/java/com/monero/app/MainActivity.java index 49c368ec7..e6306d27b 100644 --- a/android/app/src/main/java/com/monero/app/MainActivity.java +++ b/android/app/src/main/java/com/monero/app/MainActivity.java @@ -19,14 +19,10 @@ import android.net.Uri; import android.os.PowerManager; import android.provider.Settings; -import com.unstoppabledomains.resolution.DomainResolution; -import com.unstoppabledomains.resolution.Resolution; - import java.security.SecureRandom; public class MainActivity extends FlutterFragmentActivity { final String UTILS_CHANNEL = "com.cake_wallet/native_utils"; - final int UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK = 24; boolean isAppSecure = false; @Override @@ -52,14 +48,6 @@ public class MainActivity extends FlutterFragmentActivity { random.nextBytes(bytes); handler.post(() -> result.success(bytes)); break; - case "getUnstoppableDomainAddress": - int version = Build.VERSION.SDK_INT; - if (version >= UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK) { - getUnstoppableDomainAddress(call, result); - } else { - handler.post(() -> result.success("")); - } - break; case "setIsAppSecure": isAppSecure = call.argument("isAppSecure"); if (isAppSecure) { @@ -84,23 +72,6 @@ public class MainActivity extends FlutterFragmentActivity { } } - private void getUnstoppableDomainAddress(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { - DomainResolution resolution = new Resolution(); - Handler handler = new Handler(Looper.getMainLooper()); - String domain = call.argument("domain"); - String ticker = call.argument("ticker"); - - AsyncTask.execute(() -> { - try { - String address = resolution.getAddress(domain, ticker); - handler.post(() -> result.success(address)); - } catch (Exception e) { - System.out.println("Expected Address, but got " + e.getMessage()); - handler.post(() -> result.success("")); - } - }); - } - private void disableBatteryOptimization() { String packageName = getPackageName(); PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); diff --git a/android/build.gradle b/android/build.gradle index aa9f5005d..7ddb75179 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -2,7 +2,7 @@ buildscript { ext.kotlin_version = '1.8.21' repositories { google() - jcenter() + mavenCentral() } dependencies { @@ -15,7 +15,7 @@ buildscript { allprojects { repositories { google() - jcenter() + mavenCentral() } } diff --git a/assets/images/dfx_dark.png b/assets/images/dfx_dark.png index cbba87372..6ac112eae 100644 Binary files a/assets/images/dfx_dark.png and b/assets/images/dfx_dark.png differ diff --git a/assets/images/dfx_light.png b/assets/images/dfx_light.png index e4836be3e..a045d3e68 100644 Binary files a/assets/images/dfx_light.png and b/assets/images/dfx_light.png differ diff --git a/assets/images/moonpay.png b/assets/images/moonpay.png index b02af6c00..088c93d59 100644 Binary files a/assets/images/moonpay.png and b/assets/images/moonpay.png differ diff --git a/assets/images/moonpay_dark.png b/assets/images/moonpay_dark.png index 872e322e2..21de98eb4 100644 Binary files a/assets/images/moonpay_dark.png and b/assets/images/moonpay_dark.png differ diff --git a/assets/images/moonpay_light.png b/assets/images/moonpay_light.png index c76ae6e74..3d3de2e4f 100644 Binary files a/assets/images/moonpay_light.png and b/assets/images/moonpay_light.png differ diff --git a/assets/images/trocador.png b/assets/images/trocador.png index 67c9f221c..37e643de4 100644 Binary files a/assets/images/trocador.png and b/assets/images/trocador.png differ diff --git a/cw_haven/android/build.gradle b/cw_haven/android/build.gradle index 1319e4ad4..d29c31d4e 100644 --- a/cw_haven/android/build.gradle +++ b/cw_haven/android/build.gradle @@ -5,7 +5,7 @@ buildscript { ext.kotlin_version = '1.7.10' repositories { google() - jcenter() + mavenCentral() } dependencies { @@ -17,7 +17,7 @@ buildscript { rootProject.allprojects { repositories { google() - jcenter() + mavenCentral() } } diff --git a/cw_shared_external/android/build.gradle b/cw_shared_external/android/build.gradle index 8db51f0e6..64b550364 100644 --- a/cw_shared_external/android/build.gradle +++ b/cw_shared_external/android/build.gradle @@ -5,7 +5,7 @@ buildscript { ext.kotlin_version = '1.7.10' repositories { google() - jcenter() + mavenCentral() } dependencies { @@ -17,7 +17,7 @@ buildscript { rootProject.allprojects { repositories { google() - jcenter() + mavenCentral() } } diff --git a/ios/Podfile b/ios/Podfile index 51622ff10..f0a0721a6 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -36,7 +36,6 @@ target 'Runner' do # Cake Wallet (Legacy) pod 'CryptoSwift' - pod 'UnstoppableDomainsResolution', '~> 4.0.0' end post_install do |installer| diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index acdfa4346..0cc4eebe8 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,6 +1,5 @@ import UIKit import Flutter -import UnstoppableDomainsResolution import workmanager @UIApplicationMain @@ -87,27 +86,7 @@ import workmanager } result(secRandom(count: count)) - case "getUnstoppableDomainAddress": - guard let args = call.arguments as? Dictionary, - let domain = args["domain"], - let ticker = args["ticker"], - let resolution = self?.resolution else { - result(nil) - return - } - - resolution.addr(domain: domain, ticker: ticker) { addrResult in - var address : String = "" - - switch addrResult { - case .success(let returnValue): - address = returnValue - case .failure(let error): - print("Expected Address, but got \(error)") - } - - result(address) - } + case "setIsAppSecure": guard let args = call.arguments as? Dictionary, let isAppSecure = args["isAppSecure"] else { diff --git a/lib/entities/unstoppable_domain_address.dart b/lib/entities/unstoppable_domain_address.dart index c5ec71ab5..0f56517b8 100644 --- a/lib/entities/unstoppable_domain_address.dart +++ b/lib/entities/unstoppable_domain_address.dart @@ -1,5 +1,7 @@ -import 'package:cake_wallet/utils/device_info.dart'; +import 'dart:convert'; + import 'package:flutter/services.dart'; +import 'package:http/http.dart' as http; const channel = MethodChannel('com.cake_wallet/native_utils'); @@ -7,18 +9,19 @@ Future fetchUnstoppableDomainAddress(String domain, String ticker) async var address = ''; try { - if (DeviceInfo.instance.isMobile) { - address = await channel.invokeMethod( - 'getUnstoppableDomainAddress', - { - 'domain' : domain, - 'ticker' : ticker - } - ) ?? ''; - } else { - // TODO: Integrate with Unstoppable domains resolution API - return address; + final uri = Uri.parse("https://api.unstoppabledomains.com/profile/public/${Uri.encodeQueryComponent(domain)}?fields=records"); + final jsonString = await http.read(uri); + final jsonParsed = json.decode(jsonString) as Map; + if (jsonParsed["records"] == null) { + throw Exception(".records response from $uri is empty"); + }; + final records = jsonParsed["records"] as Map; + final key = "crypto.${ticker.toUpperCase()}.address"; + if (records[key] == null) { + throw Exception(".records.${key} response from $uri is empty"); } + + return records[key] as String? ?? ''; } catch (e) { print('Unstoppable domain error: ${e.toString()}'); address = ''; diff --git a/lib/src/screens/new_wallet/new_wallet_page.dart b/lib/src/screens/new_wallet/new_wallet_page.dart index b66aab4cf..cd5a7ce8d 100644 --- a/lib/src/screens/new_wallet/new_wallet_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_page.dart @@ -25,6 +25,7 @@ import 'package:cake_wallet/themes/extensions/new_wallet_theme.dart'; import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; import 'package:cake_wallet/entities/seed_type.dart'; + class NewWalletPage extends BasePage { NewWalletPage(this._walletNewVM, this._seedTypeViewModel); @@ -74,6 +75,7 @@ class _WalletNameFormState extends State { _walletNewVM.hasWalletPassword ? TextEditingController() : null; static const aspectRatioImage = 1.22; + static bool formProcessing = false; final GlobalKey _formKey; final GlobalKey _languageSelectorKey; @@ -347,26 +349,35 @@ class _WalletNameFormState extends State { ); } - void _confirmForm() { - if (_formKey.currentState != null && !_formKey.currentState!.validate()) { - return; - } - if (_walletNewVM.nameExists(_walletNewVM.name)) { - showPopUp( - context: context, - builder: (_) { - return AlertWithOneAction( - alertTitle: '', - alertContent: S.of(context).wallet_name_exists, - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop()); - }); - } else { - _walletNewVM.create( - options: _walletNewVM.hasLanguageSelector - ? [_languageSelectorKey.currentState!.selected, isPolyseed] - : null); + void _confirmForm() async { + if (formProcessing) return; + formProcessing = true; + try { + if (_formKey.currentState != null && !_formKey.currentState!.validate()) { + formProcessing = false; + return; + } + if (_walletNewVM.nameExists(_walletNewVM.name)) { + await showPopUp( + context: context, + builder: (_) { + return AlertWithOneAction( + alertTitle: '', + alertContent: S.of(context).wallet_name_exists, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + } else { + await _walletNewVM.create( + options: _walletNewVM.hasLanguageSelector + ? [_languageSelectorKey.currentState!.selected, isPolyseed] + : null); + } + } catch (e) { + formProcessing = false; + rethrow; } + formProcessing = false; } bool get isPolyseed => widget._seedTypeViewModel.moneroSeedType == SeedType.polyseed; diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart index cd6383c0d..29bc29986 100644 --- a/lib/src/screens/restore/wallet_restore_page.dart +++ b/lib/src/screens/restore/wallet_restore_page.dart @@ -2,6 +2,7 @@ import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/new_wallet/new_wallet_page.dart'; import 'package:cake_wallet/src/screens/restore/wallet_restore_from_keys_form.dart'; import 'package:cake_wallet/src/screens/restore/wallet_restore_from_seed_form.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; @@ -80,6 +81,8 @@ class WalletRestorePage extends BasePage { }); } + static bool formProcessing = false; + @override Widget middle(BuildContext context) => Observer( builder: (_) => Text( @@ -350,75 +353,85 @@ class WalletRestorePage extends BasePage { } Future _confirmForm(BuildContext context) async { - // Dismissing all visible keyboard to provide context for navigation - FocusManager.instance.primaryFocus?.unfocus(); + if (formProcessing) return; + formProcessing = true; + try { + // Dismissing all visible keyboard to provide context for navigation + FocusManager.instance.primaryFocus?.unfocus(); - late BuildContext? formContext; - late GlobalKey? formKey; - late String name; - if (walletRestoreViewModel.mode == WalletRestoreMode.seed) { - formContext = walletRestoreFromSeedFormKey.currentContext; - formKey = walletRestoreFromSeedFormKey.currentState!.formKey; - name = walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.value.text; - } else if (walletRestoreViewModel.mode == WalletRestoreMode.keys) { - formContext = walletRestoreFromKeysFormKey.currentContext; - formKey = walletRestoreFromKeysFormKey.currentState!.formKey; - name = walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.value.text; - } - - if (!formKey!.currentState!.validate()) { - return; - } - - if (walletRestoreViewModel.nameExists(name)) { - showNameExistsAlert(formContext!); - return; - } - - walletRestoreViewModel.state = IsExecutingState(); - - DerivationInfo? dInfo; - - // get info about the different derivations: - List derivations = - await walletRestoreViewModel.getDerivationInfo(_credentials()); - - int derivationsWithHistory = 0; - int derivationWithHistoryIndex = 0; - for (int i = 0; i < derivations.length; i++) { - if (derivations[i].transactionsCount > 0) { - derivationsWithHistory++; - derivationWithHistoryIndex = i; + late BuildContext? formContext; + late GlobalKey? formKey; + late String name; + if (walletRestoreViewModel.mode == WalletRestoreMode.seed) { + formContext = walletRestoreFromSeedFormKey.currentContext; + formKey = walletRestoreFromSeedFormKey.currentState!.formKey; + name = walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.value.text; + } else if (walletRestoreViewModel.mode == WalletRestoreMode.keys) { + formContext = walletRestoreFromKeysFormKey.currentContext; + formKey = walletRestoreFromKeysFormKey.currentState!.formKey; + name = walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.value.text; } - } - if (derivationsWithHistory > 1) { - dInfo = await Navigator.of(context).pushNamed( - Routes.restoreWalletChooseDerivation, - arguments: derivations, - ) as DerivationInfo?; - } else if (derivationsWithHistory == 1) { - dInfo = derivations[derivationWithHistoryIndex]; - } - - // get the default derivation for this wallet type: - if (dInfo == null) { - // we only return 1 derivation if we're pretty sure we know which one to use: - if (derivations.length == 1) { - dInfo = derivations.first; - } else { - // if we have multiple possible derivations, and none have histories - // we just default to the most common one: - dInfo = walletRestoreViewModel.getCommonRestoreDerivation(); + if (!formKey!.currentState!.validate()) { + formProcessing = false; + return; } - } - this.derivationInfo = dInfo; - if (this.derivationInfo == null) { - this.derivationInfo = walletRestoreViewModel.getDefaultDerivation(); - } + if (walletRestoreViewModel.nameExists(name)) { + showNameExistsAlert(formContext!); + formProcessing = false; + return; + } - walletRestoreViewModel.create(options: _credentials()); + walletRestoreViewModel.state = IsExecutingState(); + + DerivationInfo? dInfo; + + // get info about the different derivations: + List derivations = + await walletRestoreViewModel.getDerivationInfo(_credentials()); + + int derivationsWithHistory = 0; + int derivationWithHistoryIndex = 0; + for (int i = 0; i < derivations.length; i++) { + if (derivations[i].transactionsCount > 0) { + derivationsWithHistory++; + derivationWithHistoryIndex = i; + } + } + + if (derivationsWithHistory > 1) { + dInfo = await Navigator.of(context).pushNamed( + Routes.restoreWalletChooseDerivation, + arguments: derivations, + ) as DerivationInfo?; + } else if (derivationsWithHistory == 1) { + dInfo = derivations[derivationWithHistoryIndex]; + } + + // get the default derivation for this wallet type: + if (dInfo == null) { + // we only return 1 derivation if we're pretty sure we know which one to use: + if (derivations.length == 1) { + dInfo = derivations.first; + } else { + // if we have multiple possible derivations, and none have histories + // we just default to the most common one: + dInfo = walletRestoreViewModel.getCommonRestoreDerivation(); + } + } + + this.derivationInfo = dInfo; + if (this.derivationInfo == null) { + this.derivationInfo = walletRestoreViewModel.getDefaultDerivation(); + } + + await walletRestoreViewModel.create(options: _credentials()); + } catch (e) { + formProcessing = false; + rethrow; + } + formProcessing = false; } Future showNameExistsAlert(BuildContext context) { diff --git a/lib/src/screens/support_other_links/support_other_links_page.dart b/lib/src/screens/support_other_links/support_other_links_page.dart index 681a44f8f..7a1a945ca 100644 --- a/lib/src/screens/support_other_links/support_other_links_page.dart +++ b/lib/src/screens/support_other_links/support_other_links_page.dart @@ -7,6 +7,7 @@ import 'package:cake_wallet/view_model/settings/regular_list_item.dart'; import 'package:cake_wallet/view_model/support_view_model.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/themes/extensions/option_tile_theme.dart'; import 'package:cake_wallet/generated/i18n.dart'; class SupportOtherLinksPage extends BasePage { @@ -22,8 +23,11 @@ class SupportOtherLinksPage extends BasePage { @override Widget body(BuildContext context) { + final iconColor = Theme.of(context).extension()!.iconColor; + final isLightMode = Theme.of(context).extension()?.useDarkImage ?? false; + return Container( child: Center( child: ConstrainedBox( @@ -37,16 +41,16 @@ class SupportOtherLinksPage extends BasePage { if (item is RegularListItem) { return SettingsCellWithArrow(title: item.title, handler: item.handler); } - if (item is LinkListItem) { + bool hasLightIcon = false; + if (item.lightIcon != null) hasLightIcon = true; return SettingsLinkProviderCell( title: item.title, - icon: item.icon, + icon: isLightMode && hasLightIcon ? item.lightIcon : item.icon, iconColor: item.hasIconColor ? iconColor : null, link: item.link, linkTitle: item.linkTitle); } - return Container(); }), ), diff --git a/lib/view_model/settings/link_list_item.dart b/lib/view_model/settings/link_list_item.dart index 4ee4162a9..8d7607eb5 100644 --- a/lib/view_model/settings/link_list_item.dart +++ b/lib/view_model/settings/link_list_item.dart @@ -8,10 +8,12 @@ class LinkListItem extends SettingsListItem { required this.link, required this.linkTitle, this.icon, + this.lightIcon, this.hasIconColor = false}) : super(title); final String? icon; + final String? lightIcon; final String link; final String linkTitle; final bool hasIconColor; diff --git a/lib/view_model/support_view_model.dart b/lib/view_model/support_view_model.dart index ccef76154..2bb749b42 100644 --- a/lib/view_model/support_view_model.dart +++ b/lib/view_model/support_view_model.dart @@ -33,11 +33,6 @@ abstract class SupportViewModelBase with Store { icon: 'assets/images/Telegram.png', linkTitle: '@cakewallet_bot', link: 'https://t.me/cakewallet_bot'), - LinkListItem( - title: 'Twitter', - icon: 'assets/images/Twitter.png', - linkTitle: '@cakewallet', - link: 'https://twitter.com/cakewallet'), LinkListItem( title: 'ChangeNow', icon: 'assets/images/change_now.png', @@ -46,7 +41,7 @@ abstract class SupportViewModelBase with Store { LinkListItem( title: 'SideShift', icon: 'assets/images/sideshift.png', - linkTitle: S.current.help, + linkTitle: 'help.sideshift.ai', link: 'https://help.sideshift.ai/en/'), LinkListItem( title: 'SimpleSwap', @@ -58,19 +53,41 @@ abstract class SupportViewModelBase with Store { icon: 'assets/images/exolix.png', linkTitle: 'support@exolix.com', link: 'mailto:support@exolix.com'), - if (!isMoneroOnly) ... [ - LinkListItem( - title: 'Wyre', - icon: 'assets/images/wyre.png', - linkTitle: S.current.submit_request, - link: 'https://wyre-support.zendesk.com/hc/en-us/requests/new'), + LinkListItem( + title: 'Quantex', + icon: 'assets/images/quantex.png', + linkTitle: 'help.myquantex.com', + link: 'mailto:support@exolix.com'), + LinkListItem( + title: 'Trocador', + icon: 'assets/images/trocador.png', + linkTitle: 'mail@trocador.app', + link: 'mailto:mail@trocador.app'), + LinkListItem( + title: 'Onramper', + icon: 'assets/images/onramper_dark.png', + lightIcon: 'assets/images/onramper_light.png', + linkTitle: 'View exchanges', + link: 'https://guides.cakewallet.com/docs/service-support/buy/#onramper'), + LinkListItem( + title: 'DFX', + icon: 'assets/images/dfx_dark.png', + lightIcon: 'assets/images/dfx_light.png', + linkTitle: 'support@dfx.swiss', + link: 'mailto:support@dfx.swiss'), + if (!isMoneroOnly) ... [ LinkListItem( title: 'MoonPay', icon: 'assets/images/moonpay.png', - hasIconColor: true, linkTitle: S.current.submit_request, - link: 'https://support.moonpay.com/hc/en-gb/requests/new') - ] + link: 'https://support.moonpay.com/hc/en-gb/requests/new'), + LinkListItem( + title: 'Robinhood Connect', + icon: 'assets/images/robinhood_dark.png', + lightIcon: 'assets/images/robinhood_light.png', + linkTitle: S.current.submit_request, + link: 'https://robinhood.com/contact') + ] //LinkListItem( // title: 'Yat', // icon: 'assets/images/yat_mini_logo.png',