CW-539-DFX-off-ramp-sell-provider (#1229)

* buy_provider_types refactoring

* refactor MoonPay and  sell option flow

* dfx sell flow

* add default sell provider flow

* localization

* Update other_settings_page.dart

* [skip ci] update localization

* [skip ci] providers fixes

* [skip ci] ui fixes

* refactor sell and buy flow

* handle dfx availability by country

* PR fixes
This commit is contained in:
Serhii 2023-12-28 21:20:59 +02:00 committed by GitHub
parent 92914a8532
commit 914565eb72
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 623 additions and 370 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -623,15 +623,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.1" version: "1.0.1"
tor:
dependency: transitive
description:
path: "."
ref: main
resolved-ref: "09ba92cb11d4e3cacf97256e57863b805f79f2e5"
url: "https://github.com/cake-tech/tor.git"
source: git
version: "0.0.1"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:

View file

@ -2,11 +2,11 @@ import 'package:flutter/foundation.dart';
import 'package:cake_wallet/buy/buy_provider_description.dart'; import 'package:cake_wallet/buy/buy_provider_description.dart';
class BuyException implements Exception { class BuyException implements Exception {
BuyException({required this.description, required this.text}); BuyException({required this.title, required this.content});
final BuyProviderDescription description; final String title;
final String text; final String content;
@override @override
String toString() => '${description.title}: $text'; String toString() => '$title: $content';
} }

View file

@ -1,27 +1,35 @@
import 'package:cake_wallet/buy/buy_amount.dart'; import 'package:cake_wallet/buy/buy_amount.dart';
import 'package:cake_wallet/buy/buy_provider_description.dart';
import 'package:cake_wallet/buy/order.dart'; import 'package:cake_wallet/buy/order.dart';
import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart';
abstract class BuyProvider { abstract class BuyProvider {
BuyProvider({required this.wallet, required this.isTestEnvironment}); BuyProvider({
required this.wallet,
required this.isTestEnvironment,
});
final WalletBase wallet; final WalletBase wallet;
final bool isTestEnvironment; final bool isTestEnvironment;
String get title; String get title;
BuyProviderDescription get description;
String get trackUrl;
WalletType get walletType => wallet.type; String get buyOptionDescription;
String get walletAddress => wallet.walletAddresses.address;
String get walletId => wallet.id; String get sellOptionDescription;
String get lightIcon;
String get darkIcon;
@override @override
String toString() => title; String toString() => title;
Future<String> requestUrl(String amount, String sourceCurrency); Future<void> launchProvider(BuildContext context, bool? isBuyAction);
Future<Order> findOrderById(String id);
Future<BuyAmount> calculateAmount(String amount, String sourceCurrency); Future<String> requestUrl(String amount, String sourceCurrency) => throw UnimplementedError();
}
Future<Order> findOrderById(String id) => throw UnimplementedError();
Future<BuyAmount> calculateAmount(String amount, String sourceCurrency) => throw UnimplementedError();
}

View file

@ -1,5 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'package:cake_wallet/buy/buy_provider.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
@ -11,10 +12,9 @@ import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class DFXBuyProvider { class DFXBuyProvider extends BuyProvider {
DFXBuyProvider({required WalletBase wallet}) : this._wallet = wallet; DFXBuyProvider({required WalletBase wallet, bool isTestEnvironment = false})
: super(wallet: wallet, isTestEnvironment: isTestEnvironment);
final WalletBase _wallet;
static const _baseUrl = 'api.dfx.swiss'; static const _baseUrl = 'api.dfx.swiss';
static const _authPath = '/v1/auth/signMessage'; static const _authPath = '/v1/auth/signMessage';
@ -22,8 +22,23 @@ class DFXBuyProvider {
static const _signInPath = '/v1/auth/signIn'; static const _signInPath = '/v1/auth/signIn';
static const walletName = 'CakeWallet'; static const walletName = 'CakeWallet';
@override
String get title => 'DFX Connect';
@override
String get buyOptionDescription => S.current.dfx_option_description;
@override
String get sellOptionDescription => S.current.dfx_option_description;
@override
String get lightIcon => 'assets/images/dfx_light.png';
@override
String get darkIcon => 'assets/images/dfx_dark.png';
String get assetOut { String get assetOut {
switch (_wallet.type) { switch (wallet.type) {
case WalletType.bitcoin: case WalletType.bitcoin:
return 'BTC'; return 'BTC';
case WalletType.bitcoinCash: case WalletType.bitcoinCash:
@ -35,12 +50,12 @@ class DFXBuyProvider {
case WalletType.ethereum: case WalletType.ethereum:
return 'ETH'; return 'ETH';
default: default:
throw Exception("WalletType is not available for DFX ${_wallet.type}"); throw Exception("WalletType is not available for DFX ${wallet.type}");
} }
} }
String get blockchain { String get blockchain {
switch (_wallet.type) { switch (wallet.type) {
case WalletType.bitcoin: case WalletType.bitcoin:
case WalletType.bitcoinCash: case WalletType.bitcoinCash:
case WalletType.litecoin: case WalletType.litecoin:
@ -50,12 +65,12 @@ class DFXBuyProvider {
case WalletType.ethereum: case WalletType.ethereum:
return 'Ethereum'; return 'Ethereum';
default: default:
throw Exception("WalletType is not available for DFX ${_wallet.type}"); throw Exception("WalletType is not available for DFX ${wallet.type}");
} }
} }
Future<String> getSignMessage() async { Future<String> getSignMessage() async {
final walletAddress = _wallet.walletAddresses.address; final walletAddress = wallet.walletAddresses.address;
final uri = Uri.https(_baseUrl, _authPath, {'address': walletAddress}); final uri = Uri.https(_baseUrl, _authPath, {'address': walletAddress});
var response = await http.get(uri, headers: {'accept': 'application/json'}); var response = await http.get(uri, headers: {'accept': 'application/json'});
@ -71,7 +86,7 @@ class DFXBuyProvider {
Future<String> signUp() async { Future<String> signUp() async {
final signMessage = getSignature(await getSignMessage()); final signMessage = getSignature(await getSignMessage());
final walletAddress = _wallet.walletAddresses.address; final walletAddress = wallet.walletAddresses.address;
final requestBody = jsonEncode({ final requestBody = jsonEncode({
'wallet': walletName, 'wallet': walletName,
@ -86,6 +101,10 @@ class DFXBuyProvider {
if (response.statusCode == 201) { if (response.statusCode == 201) {
final responseBody = jsonDecode(response.body); final responseBody = jsonDecode(response.body);
return responseBody['accessToken'] as String; return responseBody['accessToken'] as String;
} else if (response.statusCode == 403) {
final responseBody = jsonDecode(response.body);
final message = responseBody['message'] ?? 'Service unavailable in your country';
throw Exception(message);
} else { } else {
throw Exception( throw Exception(
'Failed to sign up. Status: ${response.statusCode} ${response.body}'); 'Failed to sign up. Status: ${response.statusCode} ${response.body}');
@ -94,7 +113,7 @@ class DFXBuyProvider {
Future<String> signIn() async { Future<String> signIn() async {
final signMessage = getSignature(await getSignMessage()); final signMessage = getSignature(await getSignMessage());
final walletAddress = _wallet.walletAddresses.address; final walletAddress = wallet.walletAddresses.address;
final requestBody = jsonEncode({ final requestBody = jsonEncode({
'address': walletAddress, 'address': walletAddress,
@ -108,6 +127,10 @@ class DFXBuyProvider {
if (response.statusCode == 201) { if (response.statusCode == 201) {
final responseBody = jsonDecode(response.body); final responseBody = jsonDecode(response.body);
return responseBody['accessToken'] as String; return responseBody['accessToken'] as String;
} else if (response.statusCode == 403) {
final responseBody = jsonDecode(response.body);
final message = responseBody['message'] ?? 'Service unavailable in your country';
throw Exception(message);
} else { } else {
throw Exception( throw Exception(
'Failed to sign in. Status: ${response.statusCode} ${response.body}'); 'Failed to sign in. Status: ${response.statusCode} ${response.body}');
@ -115,24 +138,25 @@ class DFXBuyProvider {
} }
String getSignature(String message) { String getSignature(String message) {
switch (_wallet.type) { switch (wallet.type) {
case WalletType.ethereum: case WalletType.ethereum:
return _wallet.signMessage(message); return wallet.signMessage(message);
case WalletType.monero: case WalletType.monero:
case WalletType.litecoin: case WalletType.litecoin:
case WalletType.bitcoin: case WalletType.bitcoin:
case WalletType.bitcoinCash: case WalletType.bitcoinCash:
return _wallet.signMessage(message, return wallet.signMessage(message,
address: _wallet.walletAddresses.address); address: wallet.walletAddresses.address);
default: default:
throw Exception("WalletType is not available for DFX ${_wallet.type}"); throw Exception("WalletType is not available for DFX ${wallet.type}");
} }
} }
Future<void> launchProvider(BuildContext context) async { Future<void> launchProvider(BuildContext context, bool? isBuyAction) async {
try { try {
final assetOut = this.assetOut; final assetOut = this.assetOut;
final blockchain = this.blockchain; final blockchain = this.blockchain;
final actionType = isBuyAction == true ? '/buy' : '/sell';
String accessToken; String accessToken;
@ -146,7 +170,7 @@ class DFXBuyProvider {
} }
} }
final uri = Uri.https('services.dfx.swiss', '/buy', { final uri = Uri.https('services.dfx.swiss', actionType, {
'session': accessToken, 'session': accessToken,
'lang': 'en', 'lang': 'en',
'asset-out': assetOut, 'asset-out': assetOut,
@ -156,8 +180,8 @@ class DFXBuyProvider {
if (await canLaunchUrl(uri)) { if (await canLaunchUrl(uri)) {
if (DeviceInfo.instance.isMobile) { if (DeviceInfo.instance.isMobile) {
Navigator.of(context).pushNamed(Routes.webViewPage, Navigator.of(context)
arguments: [S.of(context).buy, uri]); .pushNamed(Routes.webViewPage, arguments: ["DFX Connect", uri]);
} else { } else {
await launchUrl(uri, mode: LaunchMode.externalApplication); await launchUrl(uri, mode: LaunchMode.externalApplication);
} }

View file

@ -1,9 +1,14 @@
import 'dart:convert'; import 'dart:convert';
import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.dart';
import 'package:cake_wallet/buy/buy_exception.dart'; import 'package:cake_wallet/buy/buy_exception.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:cake_wallet/buy/buy_amount.dart'; import 'package:cake_wallet/buy/buy_amount.dart';
import 'package:cake_wallet/buy/buy_provider.dart'; import 'package:cake_wallet/buy/buy_provider.dart';
@ -14,33 +19,41 @@ import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/exchange/trade_state.dart'; import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
import 'package:url_launcher/url_launcher.dart';
class MoonPaySellProvider { class MoonPaySellProvider {
MoonPaySellProvider({this.isTest = false}) MoonPaySellProvider({required SettingsStore settingsStore,
: baseUrl = isTest ? _baseTestUrl : _baseProductUrl; required WalletBase wallet, this.isTest = false})
: baseUrl = isTest ? _baseTestUrl : _baseProductUrl,
this._settingsStore = settingsStore,
this._wallet = wallet;
final SettingsStore _settingsStore;
final WalletBase _wallet;
static const _baseTestUrl = 'sell-sandbox.moonpay.com'; static const _baseTestUrl = 'sell-sandbox.moonpay.com';
static const _baseProductUrl = 'sell.moonpay.com'; static const _baseProductUrl = 'sell.moonpay.com';
static String themeToMoonPayTheme(ThemeBase theme) { static String themeToMoonPayTheme(ThemeBase theme) {
switch (theme.type) { switch (theme.type) {
case ThemeType.bright: case ThemeType.bright:
return 'light';
case ThemeType.light: case ThemeType.light:
return 'light'; return 'light';
case ThemeType.dark: case ThemeType.dark:
return 'dark'; return 'dark';
} }
} }
static String get _apiKey => secrets.moonPayApiKey;
static String get _secretKey => secrets.moonPaySecretKey; static String get _apiKey => secrets.moonPayApiKey;
static String get _secretKey => secrets.moonPaySecretKey;
final bool isTest; final bool isTest;
final String baseUrl; final String baseUrl;
Future<Uri> requestUrl( Future<Uri> requestUrl({
{required CryptoCurrency currency, required CryptoCurrency currency,
required String refundWalletAddress, required String refundWalletAddress,
required SettingsStore settingsStore}) async { required SettingsStore settingsStore,
}) async {
final customParams = { final customParams = {
'theme': themeToMoonPayTheme(settingsStore.currentTheme), 'theme': themeToMoonPayTheme(settingsStore.currentTheme),
'language': settingsStore.languageCode, 'language': settingsStore.languageCode,
@ -50,11 +63,15 @@ class MoonPaySellProvider {
}; };
final originalUri = Uri.https( final originalUri = Uri.https(
baseUrl, '', <String, dynamic>{ baseUrl,
'',
<String, dynamic>{
'apiKey': _apiKey, 'apiKey': _apiKey,
'defaultBaseCurrencyCode': currency.toString().toLowerCase(), 'defaultBaseCurrencyCode': currency.toString().toLowerCase(),
'refundWalletAddress': refundWalletAddress 'refundWalletAddress': refundWalletAddress,
}..addAll(customParams)); }..addAll(customParams),
);
final messageBytes = utf8.encode('?${originalUri.query}'); final messageBytes = utf8.encode('?${originalUri.query}');
final key = utf8.encode(_secretKey); final key = utf8.encode(_secretKey);
final hmac = Hmac(sha256, key); final hmac = Hmac(sha256, key);
@ -70,6 +87,38 @@ class MoonPaySellProvider {
final signedUri = originalUri.replace(queryParameters: query); final signedUri = originalUri.replace(queryParameters: query);
return signedUri; return signedUri;
} }
Future<void> launchProvider(BuildContext context) async {
try {
final uri = await requestUrl(
currency: _wallet.currency,
refundWalletAddress: _wallet.walletAddresses.address,
settingsStore: _settingsStore,
);
if (await canLaunchUrl(uri)) {
if (DeviceInfo.instance.isMobile) {
Navigator.of(context).pushNamed(Routes.webViewPage, arguments: ['MoonPay', uri]);
} else {
await launchUrl(uri, mode: LaunchMode.externalApplication);
}
} else {
throw Exception('Could not launch URL');
}
} catch (e) {
await showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: 'MoonPay',
alertContent: 'The MoonPay service is currently unavailable: $e',
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop(),
);
},
);
}
}
} }
class MoonPayBuyProvider extends BuyProvider { class MoonPayBuyProvider extends BuyProvider {
@ -88,20 +137,24 @@ class MoonPayBuyProvider extends BuyProvider {
static const _secretKey = secrets.moonPaySecretKey; static const _secretKey = secrets.moonPaySecretKey;
@override @override
String get title => 'MoonPay'; String get title => 'Moon Pay';
@override @override
BuyProviderDescription get description => BuyProviderDescription.moonPay; String get buyOptionDescription => '';
@override
String get lightIcon => 'assets/images/moonpay_light.png';
@override
String get darkIcon => 'assets/images/moonpay_dark.png';
String get currencyCode => String get currencyCode =>
walletTypeToCryptoCurrency(walletType).title.toLowerCase(); walletTypeToCryptoCurrency(wallet.type).title.toLowerCase();
@override
String get trackUrl => baseUrl + '/transaction_receipt?transactionId='; String get trackUrl => baseUrl + '/transaction_receipt?transactionId=';
String baseUrl; String baseUrl;
@override
Future<String> requestUrl(String amount, String sourceCurrency) async { Future<String> requestUrl(String amount, String sourceCurrency) async {
final enabledPaymentMethods = final enabledPaymentMethods =
'credit_debit_card%2Capple_pay%2Cgoogle_pay%2Csamsung_pay' 'credit_debit_card%2Capple_pay%2Cgoogle_pay%2Csamsung_pay'
@ -109,7 +162,7 @@ class MoonPayBuyProvider extends BuyProvider {
final suffix = '?apiKey=' + _apiKey + '&currencyCode=' + final suffix = '?apiKey=' + _apiKey + '&currencyCode=' +
currencyCode + '&enabledPaymentMethods=' + enabledPaymentMethods + currencyCode + '&enabledPaymentMethods=' + enabledPaymentMethods +
'&walletAddress=' + walletAddress + '&walletAddress=' + wallet.walletAddresses.address +
'&baseCurrencyCode=' + sourceCurrency.toLowerCase() + '&baseCurrencyCode=' + sourceCurrency.toLowerCase() +
'&baseCurrencyAmount=' + amount + '&lockAmount=true' + '&baseCurrencyAmount=' + amount + '&lockAmount=true' +
'&showAllCurrencies=false' + '&showWalletAddressForm=false'; '&showAllCurrencies=false' + '&showWalletAddressForm=false';
@ -127,7 +180,6 @@ class MoonPayBuyProvider extends BuyProvider {
return isTestEnvironment ? originalUrl : urlWithSignature; return isTestEnvironment ? originalUrl : urlWithSignature;
} }
@override
Future<BuyAmount> calculateAmount(String amount, String sourceCurrency) async { Future<BuyAmount> calculateAmount(String amount, String sourceCurrency) async {
final url = _apiUrl + _currenciesSuffix + '/$currencyCode' + final url = _apiUrl + _currenciesSuffix + '/$currencyCode' +
_quoteSuffix + '/?apiKey=' + _apiKey + _quoteSuffix + '/?apiKey=' + _apiKey +
@ -138,8 +190,8 @@ class MoonPayBuyProvider extends BuyProvider {
if (response.statusCode != 200) { if (response.statusCode != 200) {
throw BuyException( throw BuyException(
description: description, title: buyOptionDescription,
text: 'Quote is not found!'); content: 'Quote is not found!');
} }
final responseJSON = json.decode(response.body) as Map<String, dynamic>; final responseJSON = json.decode(response.body) as Map<String, dynamic>;
@ -153,7 +205,6 @@ class MoonPayBuyProvider extends BuyProvider {
minAmount: minSourceAmount); minAmount: minSourceAmount);
} }
@override
Future<Order> findOrderById(String id) async { Future<Order> findOrderById(String id) async {
final url = _apiUrl + _transactionsSuffix + '/$id' + final url = _apiUrl + _transactionsSuffix + '/$id' +
'?apiKey=' + _apiKey; '?apiKey=' + _apiKey;
@ -162,8 +213,8 @@ class MoonPayBuyProvider extends BuyProvider {
if (response.statusCode != 200) { if (response.statusCode != 200) {
throw BuyException( throw BuyException(
description: description, title: buyOptionDescription,
text: 'Transaction $id is not found!'); content: 'Transaction $id is not found!');
} }
final responseJSON = json.decode(response.body) as Map<String, dynamic>; final responseJSON = json.decode(response.body) as Map<String, dynamic>;
@ -175,13 +226,13 @@ class MoonPayBuyProvider extends BuyProvider {
return Order( return Order(
id: id, id: id,
provider: description, provider: BuyProviderDescription.moonPay,
transferId: id, transferId: id,
state: state, state: state,
createdAt: createdAt, createdAt: createdAt,
amount: amount.toString(), amount: amount.toString(),
receiveAddress: walletAddress, receiveAddress: wallet.walletAddresses.address,
walletId: walletId walletId: wallet.id
); );
} }
@ -201,4 +252,14 @@ class MoonPayBuyProvider extends BuyProvider {
return isBuyEnable; return isBuyEnable;
} }
@override
// TODO: implement sellOptionDescription
String get sellOptionDescription => throw UnimplementedError();
@override
Future<void> launchProvider(BuildContext context, bool? isBuyAction) {
// TODO: implement launchProvider
throw UnimplementedError();
}
} }

View file

@ -1,4 +1,5 @@
import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/buy/buy_provider.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/settings_store.dart';
@ -9,20 +10,34 @@ import 'package:cw_core/wallet_base.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class OnRamperBuyProvider { class OnRamperBuyProvider extends BuyProvider {
OnRamperBuyProvider({required SettingsStore settingsStore, required WalletBase wallet}) OnRamperBuyProvider(this._settingsStore,
: this._settingsStore = settingsStore, {required WalletBase wallet, bool isTestEnvironment = false})
this._wallet = wallet; : super(wallet: wallet, isTestEnvironment: isTestEnvironment);
final SettingsStore _settingsStore;
final WalletBase _wallet;
static const _baseUrl = 'buy.onramper.com'; static const _baseUrl = 'buy.onramper.com';
final SettingsStore _settingsStore;
@override
String get title => 'Onramper';
@override
String get buyOptionDescription => S.current.onramper_option_description;
@override
String get sellOptionDescription => S.current.onramper_option_description;
@override
String get lightIcon => 'assets/images/onramper_light.png';
@override
String get darkIcon => 'assets/images/onramper_dark.png';
String get _apiKey => secrets.onramperApiKey; String get _apiKey => secrets.onramperApiKey;
String get _normalizeCryptoCurrency { String get _normalizeCryptoCurrency {
switch (_wallet.currency) { switch (wallet.currency) {
case CryptoCurrency.ltc: case CryptoCurrency.ltc:
return "LTC_LITECOIN"; return "LTC_LITECOIN";
case CryptoCurrency.xmr: case CryptoCurrency.xmr:
@ -32,7 +47,7 @@ class OnRamperBuyProvider {
case CryptoCurrency.nano: case CryptoCurrency.nano:
return "XNO_NANO"; return "XNO_NANO";
default: default:
return _wallet.currency.title; return wallet.currency.title;
} }
} }
@ -40,7 +55,7 @@ class OnRamperBuyProvider {
return color.value.toRadixString(16).replaceAll(RegExp(r'^ff'), ""); return color.value.toRadixString(16).replaceAll(RegExp(r'^ff'), "");
} }
Uri requestUrl(BuildContext context) { Uri requestOnramperUrl(BuildContext context) {
String primaryColor, String primaryColor,
secondaryColor, secondaryColor,
primaryTextColor, primaryTextColor,
@ -50,9 +65,10 @@ class OnRamperBuyProvider {
primaryColor = getColorStr(Theme.of(context).primaryColor); primaryColor = getColorStr(Theme.of(context).primaryColor);
secondaryColor = getColorStr(Theme.of(context).colorScheme.background); secondaryColor = getColorStr(Theme.of(context).colorScheme.background);
primaryTextColor = getColorStr(Theme.of(context).extension<CakeTextTheme>()!.titleColor); primaryTextColor =
secondaryTextColor = getColorStr(Theme.of(context).extension<CakeTextTheme>()!.titleColor);
getColorStr(Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor); secondaryTextColor = getColorStr(
Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor);
containerColor = getColorStr(Theme.of(context).colorScheme.background); containerColor = getColorStr(Theme.of(context).colorScheme.background);
cardColor = getColorStr(Theme.of(context).cardColor); cardColor = getColorStr(Theme.of(context).cardColor);
@ -60,12 +76,13 @@ class OnRamperBuyProvider {
cardColor = getColorStr(Colors.white); cardColor = getColorStr(Colors.white);
} }
final networkName = _wallet.currency.fullName?.toUpperCase().replaceAll(" ", ""); final networkName =
wallet.currency.fullName?.toUpperCase().replaceAll(" ", "");
return Uri.https(_baseUrl, '', <String, dynamic>{ return Uri.https(_baseUrl, '', <String, dynamic>{
'apiKey': _apiKey, 'apiKey': _apiKey,
'defaultCrypto': _normalizeCryptoCurrency, 'defaultCrypto': _normalizeCryptoCurrency,
'networkWallets': '${networkName}:${_wallet.walletAddresses.address}', 'networkWallets': '${networkName}:${wallet.walletAddresses.address}',
'supportSell': "false", 'supportSell': "false",
'supportSwap': "false", 'supportSwap': "false",
'primaryColor': primaryColor, 'primaryColor': primaryColor,
@ -77,10 +94,11 @@ class OnRamperBuyProvider {
}); });
} }
Future<void> launchProvider(BuildContext context) async { Future<void> launchProvider(BuildContext context, bool? isBuyAction) async {
final uri = requestUrl(context); final uri = requestOnramperUrl(context);
if (DeviceInfo.instance.isMobile) { if (DeviceInfo.instance.isMobile) {
Navigator.of(context).pushNamed(Routes.webViewPage, arguments: [S.of(context).buy, uri]); Navigator.of(context)
.pushNamed(Routes.webViewPage, arguments: [S.of(context).buy, uri]);
} else { } else {
await launchUrl(uri); await launchUrl(uri);
} }

View file

@ -1,6 +1,10 @@
import 'dart:convert'; import 'dart:convert';
import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/buy/buy_amount.dart';
import 'package:cake_wallet/buy/buy_provider.dart';
import 'package:cake_wallet/buy/buy_provider_description.dart';
import 'package:cake_wallet/buy/order.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/utils/show_pop_up.dart';
@ -10,40 +14,48 @@ import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class RobinhoodBuyProvider { class RobinhoodBuyProvider extends BuyProvider{
RobinhoodBuyProvider({required WalletBase wallet}) : this._wallet = wallet; RobinhoodBuyProvider({required WalletBase wallet, bool isTestEnvironment = false})
: super(wallet: wallet, isTestEnvironment: isTestEnvironment);
final WalletBase _wallet;
static const _baseUrl = 'applink.robinhood.com'; static const _baseUrl = 'applink.robinhood.com';
static const _cIdBaseUrl = 'exchange-helper.cakewallet.com'; static const _cIdBaseUrl = 'exchange-helper.cakewallet.com';
@override
String get title => 'Robinhood Connect';
@override
String get buyOptionDescription => S.current.robinhood_option_description;
@override
String get sellOptionDescription => S.current.robinhood_option_description;
@override
String get lightIcon => 'assets/images/robinhood_light.png';
@override
String get darkIcon => 'assets/images/robinhood_dark.png';
String get _applicationId => secrets.robinhoodApplicationId; String get _applicationId => secrets.robinhoodApplicationId;
String get _apiSecret => secrets.robinhoodCIdApiSecret; String get _apiSecret => secrets.robinhoodCIdApiSecret;
bool get isAvailable => [
WalletType.bitcoin,
WalletType.bitcoinCash,
WalletType.litecoin,
WalletType.ethereum
].contains(_wallet.type);
String getSignature(String message) { String getSignature(String message) {
switch (_wallet.type) { switch (wallet.type) {
case WalletType.ethereum: case WalletType.ethereum:
return _wallet.signMessage(message); return wallet.signMessage(message);
case WalletType.litecoin: case WalletType.litecoin:
case WalletType.bitcoin: case WalletType.bitcoin:
case WalletType.bitcoinCash: case WalletType.bitcoinCash:
return _wallet.signMessage(message, address: _wallet.walletAddresses.address); return wallet.signMessage(message, address: wallet.walletAddresses.address);
default: default:
throw Exception("WalletType is not available for Robinhood ${_wallet.type}"); throw Exception("WalletType is not available for Robinhood ${wallet.type}");
} }
} }
Future<String> getConnectId() async { Future<String> getConnectId() async {
final walletAddress = _wallet.walletAddresses.address; final walletAddress = wallet.walletAddresses.address;
final valid_until = (DateTime.now().millisecondsSinceEpoch / 1000).round() + 10; final valid_until = (DateTime.now().millisecondsSinceEpoch / 1000).round() + 10;
final message = "$_apiSecret:${valid_until}"; final message = "$_apiSecret:${valid_until}";
@ -64,22 +76,22 @@ class RobinhoodBuyProvider {
} }
} }
Future<Uri> requestUrl() async { Future<Uri> requestProviderUrl() async {
final connectId = await getConnectId(); final connectId = await getConnectId();
final networkName = _wallet.currency.fullName?.toUpperCase().replaceAll(" ", "_"); final networkName = wallet.currency.fullName?.toUpperCase().replaceAll(" ", "_");
return Uri.https(_baseUrl, '/u/connect', <String, dynamic>{ return Uri.https(_baseUrl, '/u/connect', <String, dynamic>{
'applicationId': _applicationId, 'applicationId': _applicationId,
'connectId': connectId, 'connectId': connectId,
'walletAddress': _wallet.walletAddresses.address, 'walletAddress': wallet.walletAddresses.address,
'userIdentifier': _wallet.walletAddresses.address, 'userIdentifier': wallet.walletAddresses.address,
'supportedNetworks': networkName 'supportedNetworks': networkName
}); });
} }
Future<void> launchProvider(BuildContext context) async { Future<void> launchProvider(BuildContext context, bool? isBuyAction) async {
try { try {
final uri = await requestUrl(); final uri = await requestProviderUrl();
await launchUrl(uri, mode: LaunchMode.externalApplication); await launchUrl(uri, mode: LaunchMode.externalApplication);
} catch (_) { } catch (_) {
await showPopUp<void>( await showPopUp<void>(
@ -93,4 +105,5 @@ class RobinhoodBuyProvider {
}); });
} }
} }
} }

View file

@ -1,5 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'package:cake_wallet/buy/buy_exception.dart'; import 'package:cake_wallet/buy/buy_exception.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:cake_wallet/buy/buy_amount.dart'; import 'package:cake_wallet/buy/buy_amount.dart';
import 'package:cake_wallet/buy/buy_provider.dart'; import 'package:cake_wallet/buy/buy_provider.dart';
@ -35,16 +36,20 @@ class WyreBuyProvider extends BuyProvider {
String get title => 'Wyre'; String get title => 'Wyre';
@override @override
BuyProviderDescription get description => BuyProviderDescription.wyre; String get buyOptionDescription => '';
@override @override
String get lightIcon => 'assets/images/robinhood_light.png';
@override
String get darkIcon => 'assets/images/robinhood_dark.png';
String get trackUrl => isTestEnvironment String get trackUrl => isTestEnvironment
? _trackTestUrl ? _trackTestUrl
: _trackProductUrl; : _trackProductUrl;
String baseApiUrl; String baseApiUrl;
@override
Future<String> requestUrl(String amount, String sourceCurrency) async { Future<String> requestUrl(String amount, String sourceCurrency) async {
final timestamp = DateTime.now().millisecondsSinceEpoch.toString(); final timestamp = DateTime.now().millisecondsSinceEpoch.toString();
final url = baseApiUrl + _ordersSuffix + _reserveSuffix + final url = baseApiUrl + _ordersSuffix + _reserveSuffix +
@ -53,8 +58,8 @@ class WyreBuyProvider extends BuyProvider {
final body = { final body = {
'amount': amount, 'amount': amount,
'sourceCurrency': sourceCurrency, 'sourceCurrency': sourceCurrency,
'destCurrency': walletTypeToCryptoCurrency(walletType).title, 'destCurrency': walletTypeToCryptoCurrency(wallet.type).title,
'dest': walletTypeToString(walletType).toLowerCase() + ':' + walletAddress, 'dest': walletTypeToString(wallet.type).toLowerCase() + ':' + wallet.walletAddresses.address,
'referrerAccountId': _accountId, 'referrerAccountId': _accountId,
'lockFields': ['amount', 'sourceCurrency', 'destCurrency', 'dest'] 'lockFields': ['amount', 'sourceCurrency', 'destCurrency', 'dest']
}; };
@ -68,8 +73,8 @@ class WyreBuyProvider extends BuyProvider {
if (response.statusCode != 200) { if (response.statusCode != 200) {
throw BuyException( throw BuyException(
description: description, title: buyOptionDescription,
text: 'Url $url is not found!'); content: 'Url $url is not found!');
} }
final responseJSON = json.decode(response.body) as Map<String, dynamic>; final responseJSON = json.decode(response.body) as Map<String, dynamic>;
@ -77,14 +82,13 @@ class WyreBuyProvider extends BuyProvider {
return urlFromResponse; return urlFromResponse;
} }
@override
Future<BuyAmount> calculateAmount(String amount, String sourceCurrency) async { Future<BuyAmount> calculateAmount(String amount, String sourceCurrency) async {
final quoteUrl = _baseProductApiUrl + _ordersSuffix + _quoteSuffix; final quoteUrl = _baseProductApiUrl + _ordersSuffix + _quoteSuffix;
final body = { final body = {
'amount': amount, 'amount': amount,
'sourceCurrency': sourceCurrency, 'sourceCurrency': sourceCurrency,
'destCurrency': walletTypeToCryptoCurrency(walletType).title, 'destCurrency': walletTypeToCryptoCurrency(wallet.type).title,
'dest': walletTypeToString(walletType).toLowerCase() + ':' + walletAddress, 'dest': walletTypeToString(wallet.type).toLowerCase() + ':' + wallet.walletAddresses.address,
'accountId': _accountId, 'accountId': _accountId,
'country': _countryCode 'country': _countryCode
}; };
@ -99,8 +103,8 @@ class WyreBuyProvider extends BuyProvider {
if (response.statusCode != 200) { if (response.statusCode != 200) {
throw BuyException( throw BuyException(
description: description, title: buyOptionDescription,
text: 'Quote is not found!'); content: 'Quote is not found!');
} }
final responseJSON = json.decode(response.body) as Map<String, dynamic>; final responseJSON = json.decode(response.body) as Map<String, dynamic>;
@ -111,7 +115,6 @@ class WyreBuyProvider extends BuyProvider {
return BuyAmount(sourceAmount: sourceAmount, destAmount: destAmount, achSourceAmount: achAmount); return BuyAmount(sourceAmount: sourceAmount, destAmount: destAmount, achSourceAmount: achAmount);
} }
@override
Future<Order> findOrderById(String id) async { Future<Order> findOrderById(String id) async {
final orderUrl = baseApiUrl + _ordersSuffix + '/$id'; final orderUrl = baseApiUrl + _ordersSuffix + '/$id';
final orderUri = Uri.parse(orderUrl); final orderUri = Uri.parse(orderUrl);
@ -119,8 +122,8 @@ class WyreBuyProvider extends BuyProvider {
if (orderResponse.statusCode != 200) { if (orderResponse.statusCode != 200) {
throw BuyException( throw BuyException(
description: description, title: buyOptionDescription,
text: 'Order $id is not found!'); content: 'Order $id is not found!');
} }
final orderResponseJSON = final orderResponseJSON =
@ -141,8 +144,8 @@ class WyreBuyProvider extends BuyProvider {
if (transferResponse.statusCode != 200) { if (transferResponse.statusCode != 200) {
throw BuyException( throw BuyException(
description: description, title: buyOptionDescription,
text: 'Transfer $transferId is not found!'); content: 'Transfer $transferId is not found!');
} }
final transferResponseJSON = final transferResponseJSON =
@ -151,15 +154,25 @@ class WyreBuyProvider extends BuyProvider {
return Order( return Order(
id: id, id: id,
provider: description, provider: BuyProviderDescription.wyre,
transferId: transferId, transferId: transferId,
from: from, from: from,
to: to, to: to,
state: state, state: state,
createdAt: createdAt, createdAt: createdAt,
amount: amount.toString(), amount: amount.toString(),
receiveAddress: walletAddress, receiveAddress: wallet.walletAddresses.address,
walletId: walletId walletId: wallet.id
); );
} }
@override
Future<void> launchProvider(BuildContext context, bool? isBuyAction) {
// TODO: implement launchProvider
throw UnimplementedError();
}
@override
// TODO: implement sellOptionDescription
String get sellOptionDescription => throw UnimplementedError();
} }

View file

@ -1,6 +1,7 @@
import 'package:cake_wallet/anonpay/anonpay_api.dart'; import 'package:cake_wallet/anonpay/anonpay_api.dart';
import 'package:cake_wallet/anonpay/anonpay_info_base.dart'; import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart'; import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart';
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart'; import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/buy/payfura/payfura_buy_provider.dart'; import 'package:cake_wallet/buy/payfura/payfura_buy_provider.dart';
@ -796,8 +797,12 @@ Future<void> setup({
getIt getIt
.registerFactory<DFXBuyProvider>(() => DFXBuyProvider(wallet: getIt.get<AppStore>().wallet!)); .registerFactory<DFXBuyProvider>(() => DFXBuyProvider(wallet: getIt.get<AppStore>().wallet!));
getIt.registerFactory<MoonPaySellProvider>(() => MoonPaySellProvider(
settingsStore: getIt.get<AppStore>().settingsStore,
wallet: getIt.get<AppStore>().wallet!));
getIt.registerFactory<OnRamperBuyProvider>(() => OnRamperBuyProvider( getIt.registerFactory<OnRamperBuyProvider>(() => OnRamperBuyProvider(
settingsStore: getIt.get<AppStore>().settingsStore, getIt.get<AppStore>().settingsStore,
wallet: getIt.get<AppStore>().wallet!, wallet: getIt.get<AppStore>().wallet!,
)); ));
@ -941,7 +946,8 @@ Future<void> setup({
getIt.registerFactory(() => BuyAmountViewModel()); getIt.registerFactory(() => BuyAmountViewModel());
getIt.registerFactory(() => BuyOptionsPage(getIt.get<DashboardViewModel>())); getIt.registerFactoryParam<BuySellOptionsPage, bool, void>(
(isBuyOption, _) => BuySellOptionsPage(getIt.get<DashboardViewModel>(), isBuyOption));
getIt.registerFactory(() { getIt.registerFactory(() {
final wallet = getIt.get<AppStore>().wallet; final wallet = getIt.get<AppStore>().wallet;

View file

@ -1,57 +1,112 @@
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/buy/buy_provider.dart';
import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart';
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart';
import 'package:cake_wallet/di.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
enum BuyProviderType { enum BuyProviderType {
AskEachTime, askEachTime,
Robinhood, robinhood,
Onramper, dfx,
DFX; onramper,
}
@override extension BuyProviderTypeName on BuyProviderType {
String toString() { String get name {
switch (this) { switch (this) {
case BuyProviderType.AskEachTime: case BuyProviderType.askEachTime:
return S.current.ask_each_time; return 'Ask each time';
case BuyProviderType.Robinhood: case BuyProviderType.robinhood:
return "Robinhood"; return 'Robinhood Connect';
case BuyProviderType.Onramper: case BuyProviderType.dfx:
return "Onramper"; return 'DFX Connect';
case BuyProviderType.DFX: case BuyProviderType.onramper:
return "DFX"; return 'Onramper';
default:
return this.toString().split('.').last;
} }
} }
static List<BuyProviderType> getAvailableProviders(WalletType walletType) { String get id {
switch (this) {
case BuyProviderType.askEachTime:
return 'ask_each_time_provider';
case BuyProviderType.robinhood:
return 'robinhood_connect_provider';
case BuyProviderType.dfx:
return 'dfx_connect_provider';
case BuyProviderType.onramper:
return 'onramper_provider';
default:
return this.toString().split('.').last.replaceAll('.', '_')
.toLowerCase() + '_provider';
}
}
}
class BuyProviderHelper {
static List<BuyProviderType> getAvailableBuyProviderTypes(
WalletType walletType) {
switch (walletType) { switch (walletType) {
case WalletType.nano: case WalletType.nano:
case WalletType.banano: case WalletType.banano:
return [ return [BuyProviderType.askEachTime, BuyProviderType.onramper];
BuyProviderType.AskEachTime,
BuyProviderType.Onramper
];
case WalletType.monero: case WalletType.monero:
return [ return [
BuyProviderType.AskEachTime, BuyProviderType.askEachTime,
BuyProviderType.Onramper, BuyProviderType.onramper,
BuyProviderType.DFX BuyProviderType.dfx
]; ];
case WalletType.bitcoin: case WalletType.bitcoin:
case WalletType.ethereum: case WalletType.ethereum:
return [ return [
BuyProviderType.AskEachTime, BuyProviderType.askEachTime,
BuyProviderType.Onramper, BuyProviderType.onramper,
BuyProviderType.DFX, BuyProviderType.dfx,
BuyProviderType.Robinhood BuyProviderType.robinhood
]; ];
case WalletType.litecoin: case WalletType.litecoin:
case WalletType.bitcoinCash: case WalletType.bitcoinCash:
return [ return [
BuyProviderType.AskEachTime, BuyProviderType.askEachTime,
BuyProviderType.Onramper, BuyProviderType.onramper,
BuyProviderType.Robinhood BuyProviderType.robinhood
]; ];
default: default:
return []; return [];
} }
} }
static List<BuyProviderType> getAvailableSellProviderTypes(
WalletType walletType) {
switch (walletType) {
case WalletType.nano:
case WalletType.banano:
return [BuyProviderType.askEachTime];
case WalletType.monero:
return [BuyProviderType.askEachTime, BuyProviderType.dfx];
case WalletType.bitcoin:
case WalletType.ethereum:
return [BuyProviderType.askEachTime, BuyProviderType.dfx];
case WalletType.litecoin:
case WalletType.bitcoinCash:
return [BuyProviderType.askEachTime];
default:
return [];
}
}
static BuyProvider? getProviderByType(BuyProviderType type) {
switch (type) {
case BuyProviderType.robinhood:
return getIt.get<RobinhoodBuyProvider>();
case BuyProviderType.dfx:
return getIt.get<DFXBuyProvider>();
case BuyProviderType.onramper:
return getIt.get<OnRamperBuyProvider>();
case BuyProviderType.askEachTime:
return null;
}
}
} }

View file

@ -1,18 +1,9 @@
import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart';
import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart';
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/buy_provider_types.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
class MainActions { class MainActions {
final String Function(BuildContext context) name; final String Function(BuildContext context) name;
@ -46,53 +37,22 @@ class MainActions {
canShow: (viewModel) => viewModel.hasBuyAction, canShow: (viewModel) => viewModel.hasBuyAction,
onTap: (BuildContext context, DashboardViewModel viewModel) async { onTap: (BuildContext context, DashboardViewModel viewModel) async {
if (!viewModel.isEnabledBuyAction) { if (!viewModel.isEnabledBuyAction) {
await _showErrorDialog(context, S.of(context).unsupported_asset); await _showErrorDialog(
context, S.of(context).buy, S.of(context).unsupported_asset);
return; return;
} }
final defaultBuyProvider = viewModel.defaultBuyProvider; final defaultBuyProvider = viewModel.defaultBuyProvider;
try { try {
await _launchProviderByType(context, defaultBuyProvider); defaultBuyProvider != null
? await defaultBuyProvider.launchProvider(context, true)
: await Navigator.of(context).pushNamed(Routes.buySellPage, arguments: true);
} catch (e) { } catch (e) {
await _showErrorDialog(context, e.toString()); await _showErrorDialog(context, defaultBuyProvider.toString(), e.toString());
} }
}, },
); );
static Future<void> _launchProviderByType(BuildContext context, BuyProviderType providerType) async {
switch (providerType) {
case BuyProviderType.AskEachTime:
Navigator.pushNamed(context, Routes.buy);
break;
case BuyProviderType.Onramper:
await getIt.get<OnRamperBuyProvider>().launchProvider(context);
break;
case BuyProviderType.Robinhood:
await getIt.get<RobinhoodBuyProvider>().launchProvider(context);
break;
case BuyProviderType.DFX:
await getIt.get<DFXBuyProvider>().launchProvider(context);
break;
default:
throw UnsupportedError('Unsupported buy provider type');
}
}
static Future<void> _showErrorDialog(BuildContext context, String errorMessage) async {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).buy,
alertContent: errorMessage,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop(),
);
},
);
}
static MainActions receiveAction = MainActions._( static MainActions receiveAction = MainActions._(
name: (context) => S.of(context).receive, name: (context) => S.of(context).receive,
image: 'assets/images/received.png', image: 'assets/images/received.png',
@ -127,42 +87,35 @@ class MainActions {
isEnabled: (viewModel) => viewModel.isEnabledSellAction, isEnabled: (viewModel) => viewModel.isEnabledSellAction,
canShow: (viewModel) => viewModel.hasSellAction, canShow: (viewModel) => viewModel.hasSellAction,
onTap: (BuildContext context, DashboardViewModel viewModel) async { onTap: (BuildContext context, DashboardViewModel viewModel) async {
final walletType = viewModel.type; if (!viewModel.isEnabledSellAction) {
await _showErrorDialog(
context, S.of(context).sell, S.of(context).unsupported_asset);
return;
}
switch (walletType) { final defaultSellProvider = viewModel.defaultSellProvider;
case WalletType.bitcoin: try {
case WalletType.litecoin: defaultSellProvider != null
case WalletType.ethereum: ? await defaultSellProvider.launchProvider(context, false)
case WalletType.polygon: : await Navigator.of(context).pushNamed(Routes.buySellPage, arguments: false);
case WalletType.bitcoinCash: } catch (e) {
if (viewModel.isEnabledSellAction) { await _showErrorDialog(context, defaultSellProvider.toString(), e.toString());
final moonPaySellProvider = MoonPaySellProvider();
final uri = await moonPaySellProvider.requestUrl(
currency: viewModel.wallet.currency,
refundWalletAddress: viewModel.wallet.walletAddresses.address,
settingsStore: viewModel.settingsStore,
);
if (DeviceInfo.instance.isMobile) {
Navigator.of(context).pushNamed(Routes.webViewPage,
arguments: [S.of(context).sell, uri]);
} else {
await launchUrl(uri);
}
}
break;
default:
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).sell,
alertContent: S.of(context).unsupported_asset,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
},
);
} }
}, },
); );
}
static Future<void> _showErrorDialog(
BuildContext context, String title, String errorMessage) async {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: title,
alertContent: errorMessage,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop(),
);
},
);
}
}

View file

@ -390,8 +390,10 @@ Route<dynamic> createRoute(RouteSettings settings) {
return MaterialPageRoute<void>( return MaterialPageRoute<void>(
builder: (_) => getIt.get<OrderDetailsPage>(param1: settings.arguments as Order)); builder: (_) => getIt.get<OrderDetailsPage>(param1: settings.arguments as Order));
case Routes.buy: case Routes.buySellPage:
return MaterialPageRoute<void>(builder: (_) => getIt.get<BuyOptionsPage>()); final args = settings.arguments as bool;
return MaterialPageRoute<void>(
builder: (_) => getIt.get<BuySellOptionsPage>(param1: args));
case Routes.buyWebView: case Routes.buyWebView:
final args = settings.arguments as List; final args = settings.arguments as List;

View file

@ -55,7 +55,7 @@ class Routes {
static const supportLiveChat = '/support/live_chat'; static const supportLiveChat = '/support/live_chat';
static const supportOtherLinks = '/support/other'; static const supportOtherLinks = '/support/other';
static const orderDetails = '/order_details'; static const orderDetails = '/order_details';
static const buy = '/buy'; static const buySellPage = '/buy_sell_page';
static const buyWebView = '/buy_web_view'; static const buyWebView = '/buy_web_view';
static const unspentCoinsList = '/unspent_coins_list'; static const unspentCoinsList = '/unspent_coins_list';
static const unspentCoinsDetails = '/unspent_coins_details'; static const unspentCoinsDetails = '/unspent_coins_details';

View file

@ -1,7 +1,4 @@
import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart'; import 'package:cake_wallet/buy/buy_provider.dart';
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/buy_provider_types.dart'; import 'package:cake_wallet/entities/buy_provider_types.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/base_page.dart';
@ -11,19 +8,14 @@ import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class BuyOptionsPage extends BasePage { class BuySellOptionsPage extends BasePage {
BuyOptionsPage(this.dashboardViewModel); BuySellOptionsPage(this.dashboardViewModel, this.isBuyAction);
final DashboardViewModel dashboardViewModel; final DashboardViewModel dashboardViewModel;
final iconDarkRobinhood = 'assets/images/robinhood_dark.png'; final bool isBuyAction;
final iconLightRobinhood = 'assets/images/robinhood_light.png';
final iconDarkOnramper = 'assets/images/onramper_dark.png';
final iconLightOnramper = 'assets/images/onramper_light.png';
final iconDarkDFX = 'assets/images/dfx_dark.png';
final iconLightDFX = 'assets/images/dfx_light.png';
@override @override
String get title => S.current.buy; String get title => isBuyAction ? S.current.buy : S.current.sell;
@override @override
AppBarStyle get appBarStyle => AppBarStyle.regular; AppBarStyle get appBarStyle => AppBarStyle.regular;
@ -32,17 +24,9 @@ class BuyOptionsPage extends BasePage {
Widget body(BuildContext context) { Widget body(BuildContext context) {
final isLightMode = final isLightMode =
Theme.of(context).extension<OptionTileTheme>()?.useDarkImage ?? false; Theme.of(context).extension<OptionTileTheme>()?.useDarkImage ?? false;
final iconRobinhood = Image.asset( final availableProviders = isBuyAction
isLightMode ? iconLightRobinhood : iconDarkRobinhood, ? dashboardViewModel.availableBuyProviders
height: 40, : dashboardViewModel.availableSellProviders;
width: 40);
final iconOnramper = Image.asset(
isLightMode ? iconLightOnramper : iconDarkOnramper,
height: 40,
width: 40);
final iconDFX = Image.asset(isLightMode ? iconLightDFX : iconDarkDFX,
height: 40, width: 40);
final availableProviders = dashboardViewModel.availableProviders;
return Container( return Container(
child: Center( child: Center(
@ -50,57 +34,43 @@ class BuyOptionsPage extends BasePage {
constraints: BoxConstraints(maxWidth: 330), constraints: BoxConstraints(maxWidth: 330),
child: Column( child: Column(
children: [ children: [
if (availableProviders.contains(BuyProviderType.Onramper)) ...availableProviders.map((provider) {
Padding( final icon = Image.asset(
isLightMode ? provider.lightIcon : provider.darkIcon,
height: 40,
width: 40,
);
return Padding(
padding: EdgeInsets.only(top: 24), padding: EdgeInsets.only(top: 24),
child: OptionTile( child: OptionTile(
image: iconOnramper, image: icon,
title: "Onramper", title: provider.toString(),
description: S.of(context).onramper_option_description, description: isBuyAction
onPressed: () async => await getIt ? provider.buyOptionDescription
.get<OnRamperBuyProvider>() : provider.sellOptionDescription,
.launchProvider(context), onPressed: () =>
provider.launchProvider(context, isBuyAction),
), ),
), );
if (availableProviders.contains(BuyProviderType.Robinhood)) }).toList(),
Padding(
padding: EdgeInsets.only(top: 24),
child: OptionTile(
image: iconRobinhood,
title: "Robinhood Connect",
description: S.of(context).robinhood_option_description,
onPressed: () async => await getIt
.get<RobinhoodBuyProvider>()
.launchProvider(context),
),
),
if (availableProviders.contains(BuyProviderType.DFX))
Padding(
padding: EdgeInsets.only(top: 24),
child: OptionTile(
image: iconDFX,
title: "DFX Connect",
description: S.of(context).dfx_option_description,
onPressed: () async => await getIt
.get<DFXBuyProvider>()
.launchProvider(context),
),
),
Spacer(), Spacer(),
Padding( Padding(
padding: EdgeInsets.fromLTRB(24, 24, 24, 32), padding: EdgeInsets.fromLTRB(24, 24, 24, 32),
child: Text( child: Text(
S.of(context).select_buy_provider_notice, isBuyAction
textAlign: TextAlign.center, ? S.of(context).select_buy_provider_notice
style: TextStyle( : S.of(context).select_sell_provider_notice,
fontSize: 14, textAlign: TextAlign.center,
fontWeight: FontWeight.normal, style: TextStyle(
color: Theme.of(context) fontSize: 14,
.extension<TransactionTradeTheme>()! fontWeight: FontWeight.normal,
.detailsTitlesColor, color: Theme.of(context)
.extension<TransactionTradeTheme>()!
.detailsTitlesColor,
),
), ),
), ),
),
], ],
), ),
), ),

View file

@ -29,11 +29,10 @@ class BuyListItem extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isSelected = selectedProvider?.description == provider.description; final isSelected = selectedProvider?.buyOptionDescription == provider.buyOptionDescription;
final iconColor = isSelected ? Colors.white : Colors.black; final iconColor = isSelected ? Colors.white : Colors.black;
final providerIcon = getBuyProviderIcon(provider.description, final providerIcon = Image.asset('assets/images/wyre-icon.png', width: 36, height: 36);
iconColor: iconColor)!;
final backgroundColor = isSelected final backgroundColor = isSelected
? Palette.greyBlueCraiola ? Palette.greyBlueCraiola
@ -76,7 +75,7 @@ class BuyListItem extends StatelessWidget {
padding: EdgeInsets.only(right: 10), padding: EdgeInsets.only(right: 10),
child: providerIcon), child: providerIcon),
Text( Text(
provider.description.title, provider.title,
style: TextStyle( style: TextStyle(
color: secondaryTextColor, color: secondaryTextColor,
fontSize: 20, fontSize: 20,

View file

@ -43,10 +43,18 @@ class OtherSettingsPage extends BasePage {
if(_otherSettingsViewModel.isEnabledBuyAction) if(_otherSettingsViewModel.isEnabledBuyAction)
SettingsPickerCell( SettingsPickerCell(
title: S.current.default_buy_provider, title: S.current.default_buy_provider,
items: _otherSettingsViewModel.availableBuyProviders, items: _otherSettingsViewModel.availableBuyProvidersTypes,
displayItem: _otherSettingsViewModel.getBuyProviderType, displayItem: _otherSettingsViewModel.getBuyProviderType,
selectedItem: _otherSettingsViewModel.buyProviderType, selectedItem: _otherSettingsViewModel.buyProviderType,
onItemSelected: _otherSettingsViewModel.onBuyProviderTypeSelected, onItemSelected: _otherSettingsViewModel.onBuyProviderTypeSelected
),
if(_otherSettingsViewModel.isEnabledSellAction)
SettingsPickerCell(
title: S.current.default_sell_provider,
items: _otherSettingsViewModel.availableSellProvidersTypes,
displayItem: _otherSettingsViewModel.getSellProviderType,
selectedItem: _otherSettingsViewModel.sellProviderType,
onItemSelected: _otherSettingsViewModel.onSellProviderTypeSelected,
), ),
SettingsCellWithArrow( SettingsCellWithArrow(
title: S.current.settings_terms_and_conditions, title: S.current.settings_terms_and_conditions,

View file

@ -2,6 +2,7 @@ import 'dart:io';
import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/buy/buy_provider.dart';
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
import 'package:cake_wallet/entities/buy_provider_types.dart'; import 'package:cake_wallet/entities/buy_provider_types.dart';
import 'package:cake_wallet/entities/cake_2fa_preset_options.dart'; import 'package:cake_wallet/entities/cake_2fa_preset_options.dart';
@ -147,7 +148,9 @@ abstract class SettingsStoreBase with Store {
initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings, initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings,
currentSyncMode = initialSyncMode, currentSyncMode = initialSyncMode,
currentSyncAll = initialSyncAll, currentSyncAll = initialSyncAll,
priority = ObservableMap<WalletType, TransactionPriority>() { priority = ObservableMap<WalletType, TransactionPriority>(),
defaultBuyProviders = ObservableMap<WalletType, BuyProviderType>(),
defaultSellProviders = ObservableMap<WalletType, BuyProviderType>() {
//this.nodes = ObservableMap<WalletType, Node>.of(nodes); //this.nodes = ObservableMap<WalletType, Node>.of(nodes);
if (initialMoneroTransactionPriority != null) { if (initialMoneroTransactionPriority != null) {
@ -181,9 +184,25 @@ abstract class SettingsStoreBase with Store {
initializeTrocadorProviderStates(); initializeTrocadorProviderStates();
WalletType.values.forEach((walletType) { WalletType.values.forEach((walletType) {
final key = 'defaultBuyProvider_${walletType.toString()}'; final key = 'buyProvider_${walletType.toString()}';
final providerIndex = sharedPreferences.getInt(key); final providerId = sharedPreferences.getString(key);
defaultBuyProviders[walletType] = providerIndex != null ? BuyProviderType.values[providerIndex] : BuyProviderType.AskEachTime; if (providerId != null) {
defaultBuyProviders[walletType] = BuyProviderType.values
.firstWhere((provider) => provider.id == providerId, orElse: () => BuyProviderType.askEachTime);
} else {
defaultBuyProviders[walletType] = BuyProviderType.askEachTime;
}
});
WalletType.values.forEach((walletType) {
final key = 'sellProvider_${walletType.toString()}';
final providerId = sharedPreferences.getString(key);
if (providerId != null) {
defaultSellProviders[walletType] = BuyProviderType.values
.firstWhere((provider) => provider.id == providerId, orElse: () => BuyProviderType.askEachTime);
} else {
defaultSellProviders[walletType] = BuyProviderType.askEachTime;
}
}); });
reaction( reaction(
@ -196,6 +215,20 @@ abstract class SettingsStoreBase with Store {
(bool shouldShowYatPopup) => (bool shouldShowYatPopup) =>
sharedPreferences.setBool(PreferencesKey.shouldShowYatPopup, shouldShowYatPopup)); sharedPreferences.setBool(PreferencesKey.shouldShowYatPopup, shouldShowYatPopup));
defaultBuyProviders.observe((change) {
final String key = 'buyProvider_${change.key.toString()}';
if (change.newValue != null) {
sharedPreferences.setString(key, change.newValue!.id);
}
});
defaultSellProviders.observe((change) {
final String key = 'sellProvider_${change.key.toString()}';
if (change.newValue != null) {
sharedPreferences.setString(key, change.newValue!.id);
}
});
priority.observe((change) { priority.observe((change) {
final String? key; final String? key;
switch (change.key) { switch (change.key) {
@ -251,16 +284,6 @@ abstract class SettingsStoreBase with Store {
(bool disableSell) => (bool disableSell) =>
sharedPreferences.setBool(PreferencesKey.disableSellKey, disableSell)); sharedPreferences.setBool(PreferencesKey.disableSellKey, disableSell));
reaction(
(_) => defaultBuyProviders.asObservable(),
(ObservableMap<WalletType, BuyProviderType> providers) {
providers.forEach((walletType, provider) {
final key = 'defaultBuyProvider_${walletType.toString()}';
sharedPreferences.setInt(key, provider.index);
});
}
);
reaction( reaction(
(_) => walletListOrder, (_) => walletListOrder,
(WalletListOrderType walletListOrder) => (WalletListOrderType walletListOrder) =>
@ -594,7 +617,10 @@ abstract class SettingsStoreBase with Store {
ObservableMap<String, bool> trocadorProviderStates = ObservableMap<String, bool>(); ObservableMap<String, bool> trocadorProviderStates = ObservableMap<String, bool>();
@observable @observable
ObservableMap<WalletType, BuyProviderType> defaultBuyProviders = ObservableMap<WalletType, BuyProviderType>(); ObservableMap<WalletType, BuyProviderType> defaultBuyProviders;
@observable
ObservableMap<WalletType, BuyProviderType> defaultSellProviders;
@observable @observable
SortBalanceBy sortBalanceBy; SortBalanceBy sortBalanceBy;

View file

@ -61,8 +61,7 @@ abstract class BuyViewModelBase with Store {
String _url = ''; String _url = '';
try { try {
_url = await selectedProvider _url = await selectedProvider!.requestUrl(doubleAmount.toString(), fiatCurrency.title);
!.requestUrl(doubleAmount.toString(), fiatCurrency.title);
} catch (e) { } catch (e) {
print(e.toString()); print(e.toString());
} }

View file

@ -1,5 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'package:cake_wallet/buy/buy_provider.dart';
import 'package:cake_wallet/core/key_service.dart'; import 'package:cake_wallet/core/key_service.dart';
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
import 'package:cake_wallet/entities/balance_display_mode.dart'; import 'package:cake_wallet/entities/balance_display_mode.dart';
@ -41,6 +42,7 @@ import 'package:cw_core/wallet_type.dart';
import 'package:eth_sig_util/util/utils.dart'; import 'package:eth_sig_util/util/utils.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:cake_wallet/entities/buy_provider_types.dart';
part 'dashboard_view_model.g.dart'; part 'dashboard_view_model.g.dart';
@ -295,13 +297,31 @@ abstract class DashboardViewModelBase with Store {
Map<String, List<FilterItem>> filterItems; Map<String, List<FilterItem>> filterItems;
BuyProviderType get defaultBuyProvider => BuyProvider? get defaultBuyProvider => BuyProviderHelper.getProviderByType(
settingsStore.defaultBuyProviders[wallet.type] ?? BuyProviderType.AskEachTime; settingsStore.defaultBuyProviders[wallet.type] ?? BuyProviderType.askEachTime);
BuyProvider? get defaultSellProvider => BuyProviderHelper.getProviderByType(
settingsStore.defaultSellProviders[wallet.type] ?? BuyProviderType.askEachTime);
bool get isBuyEnabled => settingsStore.isBitcoinBuyEnabled; bool get isBuyEnabled => settingsStore.isBitcoinBuyEnabled;
List<BuyProviderType> get availableProviders => List<BuyProvider> get availableBuyProviders {
BuyProviderType.getAvailableProviders(wallet.type); final providerTypes = BuyProviderHelper.getAvailableBuyProviderTypes(wallet.type);
return providerTypes
.map((type) => BuyProviderHelper.getProviderByType(type))
.where((provider) => provider != null)
.cast<BuyProvider>()
.toList();
}
List<BuyProvider> get availableSellProviders {
final providerTypes = BuyProviderHelper.getAvailableSellProviderTypes(wallet.type);
return providerTypes
.map((type) => BuyProviderHelper.getProviderByType(type))
.where((provider) => provider != null)
.cast<BuyProvider>()
.toList();
}
bool get shouldShowYatPopup => settingsStore.shouldShowYatPopup; bool get shouldShowYatPopup => settingsStore.shouldShowYatPopup;
@ -315,16 +335,15 @@ abstract class DashboardViewModelBase with Store {
bool hasExchangeAction; bool hasExchangeAction;
@computed @computed
bool get isEnabledBuyAction => !settingsStore.disableBuy && wallet.type != WalletType.haven; bool get isEnabledBuyAction =>
!settingsStore.disableBuy && availableBuyProviders.isNotEmpty;
@observable @observable
bool hasBuyAction; bool hasBuyAction;
@computed @computed
bool get isEnabledSellAction => bool get isEnabledSellAction =>
!settingsStore.disableSell && !settingsStore.disableSell && availableSellProviders.isNotEmpty;
wallet.type != WalletType.haven &&
wallet.type != WalletType.monero;
@observable @observable
bool hasSellAction; bool hasSellAction;

View file

@ -50,8 +50,10 @@ abstract class OrderDetailsViewModelBase with Store {
@action @action
Future<void> _updateOrder() async { Future<void> _updateOrder() async {
try { try {
if (_provider != null) { if (_provider != null && (_provider is MoonPayBuyProvider || _provider is WyreBuyProvider)) {
final updatedOrder = await _provider!.findOrderById(order.id); final updatedOrder = _provider is MoonPayBuyProvider
? await (_provider as MoonPayBuyProvider).findOrderById(order.id)
: await (_provider as WyreBuyProvider).findOrderById(order.id);
updatedOrder.from = order.from; updatedOrder.from = order.from;
updatedOrder.to = order.to; updatedOrder.to = order.to;
updatedOrder.receiveAddress = order.receiveAddress; updatedOrder.receiveAddress = order.receiveAddress;
@ -87,19 +89,26 @@ abstract class OrderDetailsViewModelBase with Store {
value: order.provider.title) value: order.provider.title)
); );
if (_provider?.trackUrl.isNotEmpty ?? false) { if (_provider != null && (_provider is MoonPayBuyProvider || _provider is WyreBuyProvider)) {
final buildURL = _provider!.trackUrl + '${order.transferId}';
items.add( final trackUrl = _provider is MoonPayBuyProvider
TrackTradeListItem( ? (_provider as MoonPayBuyProvider).trackUrl
title: 'Track', : (_provider as WyreBuyProvider).trackUrl;
value: buildURL,
onTap: () { if (trackUrl.isNotEmpty ?? false) {
try { final buildURL = trackUrl + '${order.transferId}';
launch(buildURL); items.add(
} catch (e) {} TrackTradeListItem(
} title: 'Track',
) value: buildURL,
); onTap: () {
try {
launch(buildURL);
} catch (e) {}
}
)
);
}
} }
items.add( items.add(

View file

@ -1,6 +1,7 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/buy_provider_types.dart'; import 'package:cake_wallet/entities/buy_provider_types.dart';
import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/balance.dart'; import 'package:cw_core/balance.dart';
import 'package:cw_core/transaction_history.dart'; import 'package:cw_core/transaction_history.dart';
@ -59,12 +60,24 @@ abstract class OtherSettingsViewModelBase with Store {
bool get isEnabledBuyAction => bool get isEnabledBuyAction =>
!_settingsStore.disableBuy && _wallet.type != WalletType.haven; !_settingsStore.disableBuy && _wallet.type != WalletType.haven;
List<BuyProviderType> get availableBuyProviders => @computed
BuyProviderType.getAvailableProviders(walletType); bool get isEnabledSellAction =>
!_settingsStore.disableSell && _wallet.type != WalletType.haven;
List<BuyProviderType> get availableBuyProvidersTypes {
return BuyProviderHelper.getAvailableBuyProviderTypes(walletType);
}
List<BuyProviderType> get availableSellProvidersTypes =>
BuyProviderHelper.getAvailableSellProviderTypes(walletType);
BuyProviderType get buyProviderType => BuyProviderType get buyProviderType =>
_settingsStore.defaultBuyProviders[walletType] ?? _settingsStore.defaultBuyProviders[walletType] ??
BuyProviderType.AskEachTime; BuyProviderType.askEachTime;
BuyProviderType get sellProviderType =>
_settingsStore.defaultSellProviders[walletType] ??
BuyProviderType.askEachTime;
String getDisplayPriority(dynamic priority) { String getDisplayPriority(dynamic priority) {
final _priority = priority as TransactionPriority; final _priority = priority as TransactionPriority;
@ -81,13 +94,27 @@ abstract class OtherSettingsViewModelBase with Store {
String getBuyProviderType(dynamic buyProviderType) { String getBuyProviderType(dynamic buyProviderType) {
final _buyProviderType = buyProviderType as BuyProviderType; final _buyProviderType = buyProviderType as BuyProviderType;
return _buyProviderType == BuyProviderType.askEachTime
? S.current.ask_each_time
: _buyProviderType.name;
}
return _buyProviderType.toString(); String getSellProviderType(dynamic sellProviderType) {
final _sellProviderType = sellProviderType as BuyProviderType;
return _sellProviderType == BuyProviderType.askEachTime
? S.current.ask_each_time
: _sellProviderType.name;
} }
void onDisplayPrioritySelected(TransactionPriority priority) => void onDisplayPrioritySelected(TransactionPriority priority) =>
_settingsStore.priority[_wallet.type] = priority; _settingsStore.priority[_wallet.type] = priority;
void onBuyProviderTypeSelected(BuyProviderType buyProviderType) => @action
BuyProviderType onBuyProviderTypeSelected(BuyProviderType buyProviderType) =>
_settingsStore.defaultBuyProviders[walletType] = buyProviderType; _settingsStore.defaultBuyProviders[walletType] = buyProviderType;
@action
BuyProviderType onSellProviderTypeSelected(
BuyProviderType sellProviderType) =>
_settingsStore.defaultSellProviders[walletType] = sellProviderType;
} }

View file

@ -754,6 +754,8 @@
"dfx_option_description": "ﺎﺑﻭﺭﻭﺃ ﻲﻓ ﺕﺎﻛﺮﺸﻟﺍﻭ ﺔﺋﺰﺠﺘﻟﺍ ءﻼﻤﻌﻟ .ﻲﻓﺎﺿﺇ KYC ﻥﻭﺪﺑ ﻭﺭﻮﻳ 990 ﻰﻟﺇ ﻞﺼﻳ ﺎﻣ .ﻱﺮﺴﻳﻮﺴﻟﺍ", "dfx_option_description": "ﺎﺑﻭﺭﻭﺃ ﻲﻓ ﺕﺎﻛﺮﺸﻟﺍﻭ ﺔﺋﺰﺠﺘﻟﺍ ءﻼﻤﻌﻟ .ﻲﻓﺎﺿﺇ KYC ﻥﻭﺪﺑ ﻭﺭﻮﻳ 990 ﻰﻟﺇ ﻞﺼﻳ ﺎﻣ .ﻱﺮﺴﻳﻮﺴﻟﺍ",
"polygonscan_history": "ﻥﺎﻜﺴﻧﻮﺠﻴﻟﻮﺑ ﺦﻳﺭﺎﺗ", "polygonscan_history": "ﻥﺎﻜﺴﻧﻮﺠﻴﻟﻮﺑ ﺦﻳﺭﺎﺗ",
"wallet_seed_legacy": "بذرة محفظة قديمة", "wallet_seed_legacy": "بذرة محفظة قديمة",
"default_sell_provider": " ﻲﺿﺍﺮﺘﻓﻻﺍ ﻊﻴﺒﻟﺍ ﺩﻭﺰﻣ",
"select_sell_provider_notice": ".ﻖﻴﺒﻄﺘﻟﺍ ﺕﺍﺩﺍﺪﻋﺇ ﻲﻓ ﻚﺑ ﺹﺎﺨﻟﺍ ﻲﺿﺍﺮﺘﻓﻻﺍ ﻊﻴﺒﻟﺍ ﺩﻭﺰﻣ ﻦﻴﻴﻌﺗ ﻖﻳﺮﻃ ﻦﻋ ﺔﺷﺎﺸﻟﺍ ﻩﺬﻫ ﻲﻄﺨﺗ",
"custom_drag": "مخصص (عقد وسحب)", "custom_drag": "مخصص (عقد وسحب)",
"switchToEVMCompatibleWallet": " (Ethereum، Polygon) ﻯﺮﺧﺃ ﺓﺮﻣ ﺔﻟﻭﺎﺤﻤﻟﺍﻭ EVM ﻊﻣ ﺔﻘﻓﺍﻮﺘﻣ ﺔﻈﻔﺤﻣ ﻰﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻰﺟﺮﻳ" "switchToEVMCompatibleWallet": " (Ethereum، Polygon) ﻯﺮﺧﺃ ﺓﺮﻣ ﺔﻟﻭﺎﺤﻤﻟﺍﻭ EVM ﻊﻣ ﺔﻘﻓﺍﻮﺘﻣ ﺔﻈﻔﺤﻣ ﻰﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻰﺟﺮﻳ"
} }

View file

@ -750,6 +750,8 @@
"dfx_option_description": "Купете крипто с EUR и CHF. До 990 € без допълнителен KYC. За клиенти на дребно и корпоративни клиенти в Европа", "dfx_option_description": "Купете крипто с EUR и CHF. До 990 € без допълнителен KYC. За клиенти на дребно и корпоративни клиенти в Европа",
"polygonscan_history": "История на PolygonScan", "polygonscan_history": "История на PolygonScan",
"wallet_seed_legacy": "Наследено портфейл семе", "wallet_seed_legacy": "Наследено портфейл семе",
"default_sell_provider": "Доставчик за продажба по подразбиране",
"select_sell_provider_notice": "Изберете доставчик на продажба по-горе. Можете да пропуснете този екран, като зададете своя доставчик на продажба по подразбиране в настройките на приложението.",
"custom_drag": "Персонализиране (задръжте и плъзнете)", "custom_drag": "Персонализиране (задръжте и плъзнете)",
"switchToEVMCompatibleWallet": "Моля, превключете към портфейл, съвместим с EVM, и опитайте отново (Ethereum, Polygon)" "switchToEVMCompatibleWallet": "Моля, превключете към портфейл, съвместим с EVM, и опитайте отново (Ethereum, Polygon)"
} }

View file

@ -750,6 +750,8 @@
"dfx_option_description": "Nakupujte kryptoměny za EUR a CHF. Až 990 € bez dalších KYC. Pro maloobchodní a firemní zákazníky v Evropě", "dfx_option_description": "Nakupujte kryptoměny za EUR a CHF. Až 990 € bez dalších KYC. Pro maloobchodní a firemní zákazníky v Evropě",
"polygonscan_history": "Historie PolygonScan", "polygonscan_history": "Historie PolygonScan",
"wallet_seed_legacy": "Starší semeno peněženky", "wallet_seed_legacy": "Starší semeno peněženky",
"default_sell_provider": "Výchozí poskytovatel prodeje",
"select_sell_provider_notice": "Výše vyberte poskytovatele prodeje. Tuto obrazovku můžete přeskočit nastavením výchozího poskytovatele prodeje v nastavení aplikace.",
"custom_drag": "Custom (Hold and Drag)", "custom_drag": "Custom (Hold and Drag)",
"switchToEVMCompatibleWallet": "Přepněte na peněženku kompatibilní s EVM a zkuste to znovu (Ethereum, Polygon)" "switchToEVMCompatibleWallet": "Přepněte na peněženku kompatibilní s EVM a zkuste to znovu (Ethereum, Polygon)"
} }

View file

@ -758,6 +758,8 @@
"dfx_option_description": "Krypto mit EUR und CHF kaufen. Bis zu 990€ ohne zusätzliches KYC. Für Privat- und Firmenkunden in Europa", "dfx_option_description": "Krypto mit EUR und CHF kaufen. Bis zu 990€ ohne zusätzliches KYC. Für Privat- und Firmenkunden in Europa",
"polygonscan_history": "PolygonScan-Verlauf", "polygonscan_history": "PolygonScan-Verlauf",
"wallet_seed_legacy": "Legacy Wallet Seed", "wallet_seed_legacy": "Legacy Wallet Seed",
"default_sell_provider": "Standard-Verkaufsanbieter",
"select_sell_provider_notice": "Wählen Sie oben einen Verkaufsanbieter aus. Sie können diesen Bildschirm überspringen, indem Sie in den App-Einstellungen Ihren Standard-Verkaufsanbieter festlegen.",
"custom_drag": "Custom (Hold and Drag)", "custom_drag": "Custom (Hold and Drag)",
"switchToEVMCompatibleWallet": "Bitte wechseln Sie zu einem EVM-kompatiblen Wallet und versuchen Sie es erneut (Ethereum, Polygon)" "switchToEVMCompatibleWallet": "Bitte wechseln Sie zu einem EVM-kompatiblen Wallet und versuchen Sie es erneut (Ethereum, Polygon)"
} }

View file

@ -759,6 +759,8 @@
"dfx_option_description": "Buy crypto with EUR & CHF. Up to 990€ without additional KYC. For retail and corporate customers in Europe", "dfx_option_description": "Buy crypto with EUR & CHF. Up to 990€ without additional KYC. For retail and corporate customers in Europe",
"polygonscan_history": "PolygonScan history", "polygonscan_history": "PolygonScan history",
"wallet_seed_legacy": "Legacy wallet seed", "wallet_seed_legacy": "Legacy wallet seed",
"default_sell_provider": "Default Sell Provider",
"select_sell_provider_notice": "Select a sell provider above. You can skip this screen by setting your default sell provider in app settings.",
"custom_drag": "Custom (Hold and Drag)", "custom_drag": "Custom (Hold and Drag)",
"switchToEVMCompatibleWallet": "Please switch to an EVM compatible wallet and try again (Ethereum, Polygon)" "switchToEVMCompatibleWallet": "Please switch to an EVM compatible wallet and try again (Ethereum, Polygon)"
} }

View file

@ -758,6 +758,8 @@
"dfx_option_description": "Compre criptomonedas con EUR y CHF. Hasta 990€ sin KYC adicional. Para clientes minoristas y corporativos en Europa", "dfx_option_description": "Compre criptomonedas con EUR y CHF. Hasta 990€ sin KYC adicional. Para clientes minoristas y corporativos en Europa",
"polygonscan_history": "Historial de PolygonScan", "polygonscan_history": "Historial de PolygonScan",
"wallet_seed_legacy": "Semilla de billetera heredada", "wallet_seed_legacy": "Semilla de billetera heredada",
"default_sell_provider": "Proveedor de venta predeterminado",
"select_sell_provider_notice": "Seleccione un proveedor de venta arriba. Puede omitir esta pantalla configurando su proveedor de venta predeterminado en la configuración de la aplicación.",
"custom_drag": "Custom (mantenía y arrastre)", "custom_drag": "Custom (mantenía y arrastre)",
"switchToEVMCompatibleWallet": "Cambie a una billetera compatible con EVM e inténtelo nuevamente (Ethereum, Polygon)" "switchToEVMCompatibleWallet": "Cambie a una billetera compatible con EVM e inténtelo nuevamente (Ethereum, Polygon)"
} }

View file

@ -758,6 +758,8 @@
"dfx_option_description": "Achetez des crypto-monnaies avec EUR et CHF. Jusqu'à 990€ sans KYC supplémentaire. Pour les clients particuliers et entreprises en Europe", "dfx_option_description": "Achetez des crypto-monnaies avec EUR et CHF. Jusqu'à 990€ sans KYC supplémentaire. Pour les clients particuliers et entreprises en Europe",
"polygonscan_history": "Historique de PolygonScan", "polygonscan_history": "Historique de PolygonScan",
"wallet_seed_legacy": "Graine de portefeuille hérité", "wallet_seed_legacy": "Graine de portefeuille hérité",
"default_sell_provider": "Fournisseur de vente par défaut",
"select_sell_provider_notice": "Sélectionnez un fournisseur de vente ci-dessus. Vous pouvez ignorer cet écran en définissant votre fournisseur de vente par défaut dans les paramètres de l'application.",
"custom_drag": "Custom (maintenir et traîner)", "custom_drag": "Custom (maintenir et traîner)",
"switchToEVMCompatibleWallet": "Veuillez passer à un portefeuille compatible EVM et réessayer (Ethereum, Polygon)" "switchToEVMCompatibleWallet": "Veuillez passer à un portefeuille compatible EVM et réessayer (Ethereum, Polygon)"
} }

View file

@ -736,6 +736,8 @@
"dfx_option_description": "Sayi crypto tare da EUR & CHF. Har zuwa € 990 ba tare da ƙarin KYC ba. Don 'yan kasuwa da abokan ciniki na kamfanoni a Turai", "dfx_option_description": "Sayi crypto tare da EUR & CHF. Har zuwa € 990 ba tare da ƙarin KYC ba. Don 'yan kasuwa da abokan ciniki na kamfanoni a Turai",
"polygonscan_history": "PolygonScan tarihin kowane zamani", "polygonscan_history": "PolygonScan tarihin kowane zamani",
"wallet_seed_legacy": "Tallarin walat walat", "wallet_seed_legacy": "Tallarin walat walat",
"default_sell_provider": "Tsohuwar Mai Bayar Siyarwa",
"select_sell_provider_notice": "Zaɓi mai bada siyarwa a sama. Kuna iya tsallake wannan allon ta saita mai bada siyar da ku a cikin saitunan app.",
"custom_drag": "Al'ada (riƙe da ja)", "custom_drag": "Al'ada (riƙe da ja)",
"switchToEVMCompatibleWallet": "Da fatan za a canza zuwa walat ɗin EVM mai jituwa kuma a sake gwadawa (Ethereum, Polygon)" "switchToEVMCompatibleWallet": "Da fatan za a canza zuwa walat ɗin EVM mai jituwa kuma a sake gwadawa (Ethereum, Polygon)"
} }

View file

@ -758,6 +758,8 @@
"dfx_option_description": "EUR और CHF के साथ क्रिप्टो खरीदें। अतिरिक्त केवाईसी के बिना 990€ तक। यूरोप में खुदरा और कॉर्पोरेट ग्राहकों के लिए", "dfx_option_description": "EUR और CHF के साथ क्रिप्टो खरीदें। अतिरिक्त केवाईसी के बिना 990€ तक। यूरोप में खुदरा और कॉर्पोरेट ग्राहकों के लिए",
"polygonscan_history": "पॉलीगॉनस्कैन इतिहास", "polygonscan_history": "पॉलीगॉनस्कैन इतिहास",
"wallet_seed_legacy": "विरासत बटुए बीज", "wallet_seed_legacy": "विरासत बटुए बीज",
"default_sell_provider": "डिफ़ॉल्ट विक्रय प्रदाता",
"select_sell_provider_notice": "ऊपर एक विक्रय प्रदाता का चयन करें। आप ऐप सेटिंग में अपना डिफ़ॉल्ट विक्रय प्रदाता सेट करके इस स्क्रीन को छोड़ सकते हैं।",
"custom_drag": "कस्टम (पकड़ और खींचें)", "custom_drag": "कस्टम (पकड़ और खींचें)",
"switchToEVMCompatibleWallet": "कृपया ईवीएम संगत वॉलेट पर स्विच करें और पुनः प्रयास करें (एथेरियम, पॉलीगॉन)" "switchToEVMCompatibleWallet": "कृपया ईवीएम संगत वॉलेट पर स्विच करें और पुनः प्रयास करें (एथेरियम, पॉलीगॉन)"
} }

View file

@ -756,6 +756,8 @@
"dfx_option_description": "Kupujte kripto s EUR i CHF. Do 990 € bez dodatnog KYC-a. Za maloprodajne i poslovne korisnike u Europi", "dfx_option_description": "Kupujte kripto s EUR i CHF. Do 990 € bez dodatnog KYC-a. Za maloprodajne i poslovne korisnike u Europi",
"polygonscan_history": "Povijest PolygonScan", "polygonscan_history": "Povijest PolygonScan",
"wallet_seed_legacy": "Sjeme naslijeđenog novčanika", "wallet_seed_legacy": "Sjeme naslijeđenog novčanika",
"default_sell_provider": "Zadani dobavljač prodaje",
"select_sell_provider_notice": "Gore odaberite pružatelja usluga prodaje. Ovaj zaslon možete preskočiti postavljanjem zadanog pružatelja usluga prodaje u postavkama aplikacije.",
"custom_drag": "Prilagođeni (držite i povucite)", "custom_drag": "Prilagođeni (držite i povucite)",
"switchToEVMCompatibleWallet": "Prijeđite na novčanik kompatibilan s EVM-om i pokušajte ponovno (Ethereum, Polygon)" "switchToEVMCompatibleWallet": "Prijeđite na novčanik kompatibilan s EVM-om i pokušajte ponovno (Ethereum, Polygon)"
} }

View file

@ -746,6 +746,8 @@
"dfx_option_description": "Beli kripto dengan EUR & CHF. Hingga 990€ tanpa KYC tambahan. Untuk pelanggan ritel dan korporat di Eropa", "dfx_option_description": "Beli kripto dengan EUR & CHF. Hingga 990€ tanpa KYC tambahan. Untuk pelanggan ritel dan korporat di Eropa",
"polygonscan_history": "Sejarah PolygonScan", "polygonscan_history": "Sejarah PolygonScan",
"wallet_seed_legacy": "Biji dompet warisan", "wallet_seed_legacy": "Biji dompet warisan",
"default_sell_provider": "Penyedia Penjualan Default",
"select_sell_provider_notice": "Pilih penyedia jual di atas. Anda dapat melewati layar ini dengan mengatur penyedia penjualan default Anda di pengaturan aplikasi.",
"custom_drag": "Khusus (tahan dan seret)", "custom_drag": "Khusus (tahan dan seret)",
"switchToEVMCompatibleWallet": "Silakan beralih ke dompet yang kompatibel dengan EVM dan coba lagi (Ethereum, Polygon)" "switchToEVMCompatibleWallet": "Silakan beralih ke dompet yang kompatibel dengan EVM dan coba lagi (Ethereum, Polygon)"
} }

View file

@ -758,6 +758,8 @@
"dfx_option_description": "Acquista criptovalute con EUR e CHF. Fino a 990€ senza KYC aggiuntivi. Per clienti al dettaglio e aziendali in Europa", "dfx_option_description": "Acquista criptovalute con EUR e CHF. Fino a 990€ senza KYC aggiuntivi. Per clienti al dettaglio e aziendali in Europa",
"polygonscan_history": "Cronologia PolygonScan", "polygonscan_history": "Cronologia PolygonScan",
"wallet_seed_legacy": "Seme di portafoglio legacy", "wallet_seed_legacy": "Seme di portafoglio legacy",
"default_sell_provider": "Fornitore di vendita predefinito",
"select_sell_provider_notice": "Seleziona un fornitore di vendita sopra. Puoi saltare questa schermata impostando il tuo fornitore di vendita predefinito nelle impostazioni dell'app.",
"custom_drag": "Custom (Hold and Drag)", "custom_drag": "Custom (Hold and Drag)",
"switchToEVMCompatibleWallet": "Passa a un portafoglio compatibile con EVM e riprova (Ethereum, Polygon)" "switchToEVMCompatibleWallet": "Passa a un portafoglio compatibile con EVM e riprova (Ethereum, Polygon)"
} }

View file

@ -758,6 +758,8 @@
"dfx_option_description": "EUR と CHF で暗号通貨を購入します。追加のKYCなしで最大990ユーロ。ヨーロッパの小売および法人顧客向け", "dfx_option_description": "EUR と CHF で暗号通貨を購入します。追加のKYCなしで最大990ユーロ。ヨーロッパの小売および法人顧客向け",
"polygonscan_history": "ポリゴンスキャン履歴", "polygonscan_history": "ポリゴンスキャン履歴",
"wallet_seed_legacy": "レガシーウォレットシード", "wallet_seed_legacy": "レガシーウォレットシード",
"default_sell_provider": "デフォルトの販売プロバイダー",
"select_sell_provider_notice": "上記の販売プロバイダーを選択してください。アプリ設定でデフォルトの販売プロバイダーを設定することで、この画面をスキップできます。",
"custom_drag": "カスタム(ホールドとドラッグ)", "custom_drag": "カスタム(ホールドとドラッグ)",
"switchToEVMCompatibleWallet": "EVM 互換のウォレットに切り替えて再試行してください (イーサリアム、ポリゴン)" "switchToEVMCompatibleWallet": "EVM 互換のウォレットに切り替えて再試行してください (イーサリアム、ポリゴン)"
} }

View file

@ -756,6 +756,8 @@
"dfx_option_description": "EUR 및 CHF로 암호화폐를 구매하세요. 추가 KYC 없이 최대 990€. 유럽의 소매 및 기업 고객용", "dfx_option_description": "EUR 및 CHF로 암호화폐를 구매하세요. 추가 KYC 없이 최대 990€. 유럽의 소매 및 기업 고객용",
"polygonscan_history": "다각형 스캔 기록", "polygonscan_history": "다각형 스캔 기록",
"wallet_seed_legacy": "레거시 지갑 시드", "wallet_seed_legacy": "레거시 지갑 시드",
"default_sell_provider": "기본 판매 공급자",
"select_sell_provider_notice": "위에서 판매 공급자를 선택하세요. 앱 설정에서 기본 판매 공급자를 설정하면 이 화면을 건너뛸 수 있습니다.",
"custom_drag": "사용자 정의 (홀드 앤 드래그)", "custom_drag": "사용자 정의 (홀드 앤 드래그)",
"switchToEVMCompatibleWallet": "EVM 호환 지갑으로 전환 후 다시 시도해 주세요. (이더리움, 폴리곤)" "switchToEVMCompatibleWallet": "EVM 호환 지갑으로 전환 후 다시 시도해 주세요. (이더리움, 폴리곤)"
} }

View file

@ -756,6 +756,8 @@
"dfx_option_description": "EUR & CHF ဖြင့် crypto ကိုဝယ်ပါ။ အပို KYC မပါဘဲ 990€ အထိ။ ဥရောပရှိ လက်လီရောင်းချသူများနှင့် ကော်ပိုရိတ်ဖောက်သည်များအတွက်", "dfx_option_description": "EUR & CHF ဖြင့် crypto ကိုဝယ်ပါ။ အပို KYC မပါဘဲ 990€ အထိ။ ဥရောပရှိ လက်လီရောင်းချသူများနှင့် ကော်ပိုရိတ်ဖောက်သည်များအတွက်",
"polygonscan_history": "PolygonScan မှတ်တမ်း", "polygonscan_history": "PolygonScan မှတ်တမ်း",
"wallet_seed_legacy": "အမွေအနှစ်ပိုက်ဆံအိတ်မျိုးစေ့", "wallet_seed_legacy": "အမွေအနှစ်ပိုက်ဆံအိတ်မျိုးစေ့",
"default_sell_provider": "ပုံသေရောင်းချပေးသူ",
"select_sell_provider_notice": "အထက်ဖော်ပြပါ အရောင်းဝန်ဆောင်မှုပေးသူကို ရွေးပါ။ အက်ပ်ဆက်တင်များတွင် သင်၏မူလရောင်းချပေးသူကို သတ်မှတ်ခြင်းဖြင့် ဤစခရင်ကို ကျော်နိုင်သည်။",
"custom_drag": "စိတ်ကြိုက် (Drag)", "custom_drag": "စိတ်ကြိုက် (Drag)",
"switchToEVMCompatibleWallet": "ကျေးဇူးပြု၍ EVM တွဲဖက်သုံးနိုင်သော ပိုက်ဆံအိတ်သို့ ပြောင်းပြီး ထပ်စမ်းကြည့်ပါ (Ethereum၊ Polygon)" "switchToEVMCompatibleWallet": "ကျေးဇူးပြု၍ EVM တွဲဖက်သုံးနိုင်သော ပိုက်ဆံအိတ်သို့ ပြောင်းပြီး ထပ်စမ်းကြည့်ပါ (Ethereum၊ Polygon)"
} }

View file

@ -758,6 +758,8 @@
"dfx_option_description": "Koop crypto met EUR & CHF. Tot 990€ zonder extra KYC. Voor particuliere en zakelijke klanten in Europa", "dfx_option_description": "Koop crypto met EUR & CHF. Tot 990€ zonder extra KYC. Voor particuliere en zakelijke klanten in Europa",
"polygonscan_history": "PolygonScan-geschiedenis", "polygonscan_history": "PolygonScan-geschiedenis",
"wallet_seed_legacy": "Legacy portemonnee zaad", "wallet_seed_legacy": "Legacy portemonnee zaad",
"default_sell_provider": "Standaard verkoopaanbieder",
"select_sell_provider_notice": "Selecteer hierboven een verkoopaanbieder. U kunt dit scherm overslaan door uw standaardverkoopprovider in te stellen in de app-instellingen.",
"custom_drag": "Custom (vasthouden en slepen)", "custom_drag": "Custom (vasthouden en slepen)",
"switchToEVMCompatibleWallet": "Schakel over naar een EVM-compatibele portemonnee en probeer het opnieuw (Ethereum, Polygon)" "switchToEVMCompatibleWallet": "Schakel over naar een EVM-compatibele portemonnee en probeer het opnieuw (Ethereum, Polygon)"
} }

View file

@ -758,6 +758,8 @@
"dfx_option_description": "Kupuj kryptowaluty za EUR i CHF. Do 990 € bez dodatkowego KYC. Dla klientów detalicznych i korporacyjnych w Europie", "dfx_option_description": "Kupuj kryptowaluty za EUR i CHF. Do 990 € bez dodatkowego KYC. Dla klientów detalicznych i korporacyjnych w Europie",
"polygonscan_history": "Historia PolygonScan", "polygonscan_history": "Historia PolygonScan",
"wallet_seed_legacy": "Dziedziczne ziarno portfela", "wallet_seed_legacy": "Dziedziczne ziarno portfela",
"default_sell_provider": "Domyślny dostawca sprzedaży",
"select_sell_provider_notice": "Wybierz dostawcę sprzedaży powyżej. Możesz pominąć ten ekran, ustawiając domyślnego dostawcę sprzedaży w ustawieniach aplikacji.",
"custom_drag": "Niestandardowe (trzymaj i przeciągnij)", "custom_drag": "Niestandardowe (trzymaj i przeciągnij)",
"switchToEVMCompatibleWallet": "Przejdź na portfel zgodny z EVM i spróbuj ponownie (Ethereum, Polygon)" "switchToEVMCompatibleWallet": "Przejdź na portfel zgodny z EVM i spróbuj ponownie (Ethereum, Polygon)"
} }

View file

@ -757,6 +757,8 @@
"dfx_option_description": "Compre criptografia com EUR e CHF. Até 990€ sem KYC adicional. Para clientes de varejo e corporativos na Europa", "dfx_option_description": "Compre criptografia com EUR e CHF. Até 990€ sem KYC adicional. Para clientes de varejo e corporativos na Europa",
"polygonscan_history": "História do PolygonScan", "polygonscan_history": "História do PolygonScan",
"wallet_seed_legacy": "Semente de carteira herdada", "wallet_seed_legacy": "Semente de carteira herdada",
"default_sell_provider": "Provedor de venda padrão",
"select_sell_provider_notice": "Selecione um fornecedor de venda acima. Você pode pular esta tela definindo seu provedor de venda padrão nas configurações do aplicativo.",
"custom_drag": "Personalizado (segure e arraste)", "custom_drag": "Personalizado (segure e arraste)",
"switchToEVMCompatibleWallet": "Mude para uma carteira compatível com EVM e tente novamente (Ethereum, Polygon)" "switchToEVMCompatibleWallet": "Mude para uma carteira compatível com EVM e tente novamente (Ethereum, Polygon)"
} }

View file

@ -758,6 +758,8 @@
"dfx_option_description": "Покупайте криптовалюту за EUR и CHF. До 990€ без дополнительного KYC. Для розничных и корпоративных клиентов в Европе", "dfx_option_description": "Покупайте криптовалюту за EUR и CHF. До 990€ без дополнительного KYC. Для розничных и корпоративных клиентов в Европе",
"polygonscan_history": "История PolygonScan", "polygonscan_history": "История PolygonScan",
"wallet_seed_legacy": "Наследие семя кошелька", "wallet_seed_legacy": "Наследие семя кошелька",
"default_sell_provider": "Поставщик продаж по умолчанию",
"select_sell_provider_notice": "Выберите поставщика услуг продажи выше. Вы можете пропустить этот экран, установив поставщика услуг продаж по умолчанию в настройках приложения.",
"custom_drag": "Пользователь (удерживайте и перетаскивайте)", "custom_drag": "Пользователь (удерживайте и перетаскивайте)",
"switchToEVMCompatibleWallet": "Пожалуйста, переключитесь на кошелек, совместимый с EVM, и повторите попытку (Ethereum, Polygon)." "switchToEVMCompatibleWallet": "Пожалуйста, переключитесь на кошелек, совместимый с EVM, и повторите попытку (Ethereum, Polygon)."
} }

View file

@ -756,6 +756,8 @@
"dfx_option_description": "ซื้อ crypto ด้วย EUR และ CHF สูงถึง 990€ โดยไม่มี KYC เพิ่มเติม สำหรับลูกค้ารายย่อยและลูกค้าองค์กรในยุโรป", "dfx_option_description": "ซื้อ crypto ด้วย EUR และ CHF สูงถึง 990€ โดยไม่มี KYC เพิ่มเติม สำหรับลูกค้ารายย่อยและลูกค้าองค์กรในยุโรป",
"polygonscan_history": "ประวัติ PolygonScan", "polygonscan_history": "ประวัติ PolygonScan",
"wallet_seed_legacy": "เมล็ดกระเป๋าเงินมรดก", "wallet_seed_legacy": "เมล็ดกระเป๋าเงินมรดก",
"default_sell_provider": "ผู้ให้บริการการขายเริ่มต้น",
"select_sell_provider_notice": "เลือกผู้ให้บริการการขายด้านบน คุณสามารถข้ามหน้าจอนี้ได้โดยการตั้งค่าผู้ให้บริการการขายเริ่มต้นในการตั้งค่าแอป",
"custom_drag": "กำหนดเอง (ค้างและลาก)", "custom_drag": "กำหนดเอง (ค้างและลาก)",
"switchToEVMCompatibleWallet": "โปรดเปลี่ยนไปใช้กระเป๋าเงินที่รองรับ EVM แล้วลองอีกครั้ง (Ethereum, Polygon)" "switchToEVMCompatibleWallet": "โปรดเปลี่ยนไปใช้กระเป๋าเงินที่รองรับ EVM แล้วลองอีกครั้ง (Ethereum, Polygon)"
} }

View file

@ -752,6 +752,8 @@
"dfx_option_description": "Bumili ng crypto gamit ang EUR at CHF. Hanggang 990€ nang walang karagdagang KYC. Para sa retail at corporate na mga customer sa Europe", "dfx_option_description": "Bumili ng crypto gamit ang EUR at CHF. Hanggang 990€ nang walang karagdagang KYC. Para sa retail at corporate na mga customer sa Europe",
"polygonscan_history": "Kasaysayan ng PolygonScan", "polygonscan_history": "Kasaysayan ng PolygonScan",
"wallet_seed_legacy": "Legacy wallet seed", "wallet_seed_legacy": "Legacy wallet seed",
"default_sell_provider": "Default na Sell Provider",
"select_sell_provider_notice": "Pumili ng provider ng nagbebenta sa itaas. Maaari mong laktawan ang screen na ito sa pamamagitan ng pagtatakda ng iyong default na sell provider sa mga setting ng app.",
"custom_drag": "Pasadyang (hawakan at i -drag)", "custom_drag": "Pasadyang (hawakan at i -drag)",
"switchToEVMCompatibleWallet": "Mangyaring lumipat sa isang EVM compatible na wallet at subukang muli (Ethereum, Polygon)" "switchToEVMCompatibleWallet": "Mangyaring lumipat sa isang EVM compatible na wallet at subukang muli (Ethereum, Polygon)"
} }

View file

@ -756,6 +756,8 @@
"dfx_option_description": "EUR ve CHF ile kripto satın alın. Ek KYC olmadan 990 €'ya kadar. Avrupa'daki perakende ve kurumsal müşteriler için", "dfx_option_description": "EUR ve CHF ile kripto satın alın. Ek KYC olmadan 990 €'ya kadar. Avrupa'daki perakende ve kurumsal müşteriler için",
"polygonscan_history": "PolygonScan geçmişi", "polygonscan_history": "PolygonScan geçmişi",
"wallet_seed_legacy": "Eski cüzdan tohumu", "wallet_seed_legacy": "Eski cüzdan tohumu",
"default_sell_provider": "Varsayılan Satış Sağlayıcısı",
"select_sell_provider_notice": "Yukarıdan bir satış sağlayıcısı seçin. Uygulama ayarlarında varsayılan satış sağlayıcınızı ayarlayarak bu ekranı atlayabilirsiniz.",
"custom_drag": "Özel (Bekle ve Sürükle)", "custom_drag": "Özel (Bekle ve Sürükle)",
"switchToEVMCompatibleWallet": "Lütfen EVM uyumlu bir cüzdana geçin ve tekrar deneyin (Ethereum, Polygon)" "switchToEVMCompatibleWallet": "Lütfen EVM uyumlu bir cüzdana geçin ve tekrar deneyin (Ethereum, Polygon)"
} }

View file

@ -758,6 +758,8 @@
"seed_language_chinese_traditional": "Китайський (традиційний)", "seed_language_chinese_traditional": "Китайський (традиційний)",
"polygonscan_history": "Історія PolygonScan", "polygonscan_history": "Історія PolygonScan",
"wallet_seed_legacy": "Спадець насіння гаманця", "wallet_seed_legacy": "Спадець насіння гаманця",
"default_sell_provider": "Постачальник продажу за замовчуванням",
"select_sell_provider_notice": "Виберіть вище постачальника послуг продажу. Ви можете пропустити цей екран, встановивши постачальника послуг продажу за умовчанням у налаштуваннях програми.",
"custom_drag": "На замовлення (утримуйте та перетягується)", "custom_drag": "На замовлення (утримуйте та перетягується)",
"switchToEVMCompatibleWallet": "Перейдіть на гаманець, сумісний з EVM, і повторіть спробу (Ethereum, Polygon)" "switchToEVMCompatibleWallet": "Перейдіть на гаманець, сумісний з EVM, і повторіть спробу (Ethereum, Polygon)"
} }

View file

@ -750,6 +750,8 @@
"dfx_option_description": "EUR ﺭﻭﺍ CHF ﯽﻓﺎﺿﺍ ۔ﮟﯾﺪﯾﺮﺧ ﻮﭩﭘﺮﮐ ﮫﺗﺎﺳ ﮯﮐ KYC ﮯﯿﻟ ﮯﮐ ﻦﯿﻓﺭﺎﺻ ﭧﯾﺭﻮﭘﺭﺎﮐ ﺭﻭﺍ ﮦﺩﺭﻮﺧ ﮟ", "dfx_option_description": "EUR ﺭﻭﺍ CHF ﯽﻓﺎﺿﺍ ۔ﮟﯾﺪﯾﺮﺧ ﻮﭩﭘﺮﮐ ﮫﺗﺎﺳ ﮯﮐ KYC ﮯﯿﻟ ﮯﮐ ﻦﯿﻓﺭﺎﺻ ﭧﯾﺭﻮﭘﺭﺎﮐ ﺭﻭﺍ ﮦﺩﺭﻮﺧ ﮟ",
"polygonscan_history": "ﺦﯾﺭﺎﺗ ﯽﮐ ﻦﯿﮑﺳﺍ ﻥﻮﮔ ﯽﻟﻮﭘ", "polygonscan_history": "ﺦﯾﺭﺎﺗ ﯽﮐ ﻦﯿﮑﺳﺍ ﻥﻮﮔ ﯽﻟﻮﭘ",
"wallet_seed_legacy": "میراثی پرس کا بیج", "wallet_seed_legacy": "میراثی پرس کا بیج",
"default_sell_provider": " ﮦﺪﻨﻨﮐ ﻢﮨﺍﺮﻓ ﻞﯿﺳ ﭧﻟﺎﻔﯾﮈ",
"select_sell_provider_notice": "۔ﮟﯿﮨ ﮯﺘﮑﺳ ﮌﻮﮭﭼ ﻮﮐ ﻦﯾﺮﮑﺳﺍ ﺱﺍ ﺮﮐ ﮮﺩ ﺐﯿﺗﺮﺗ ﻮﮐ ﮦﺪﻨﻨﮐ ﻢﮨﺍﺮﻓ ﻞﯿﺳ ﭧﻟﺎﻔﯾﮈ ﮯﻨﭘﺍ ﮟﯿﻣ ﺕﺎﺒ",
"custom_drag": "کسٹم (ہولڈ اینڈ ڈریگ)", "custom_drag": "کسٹم (ہولڈ اینڈ ڈریگ)",
"switchToEVMCompatibleWallet": "(Ethereum, Polygon) ﮟﯾﺮﮐ ﺶﺷﻮﮐ ﮦﺭﺎﺑﻭﺩ ﺭﻭﺍ ﮟﯾﺮﮐ ﭻﺋﻮﺳ ﺮﭘ ﭧﯿﻟﺍﻭ ﮯﻟﺍﻭ ﮯﻨﮭﮐﺭ ﺖﻘﺑﺎﻄﻣ " "switchToEVMCompatibleWallet": "(Ethereum, Polygon) ﮟﯾﺮﮐ ﺶﺷﻮﮐ ﮦﺭﺎﺑﻭﺩ ﺭﻭﺍ ﮟﯾﺮﮐ ﭻﺋﻮﺳ ﺮﭘ ﭧﯿﻟﺍﻭ ﮯﻟﺍﻭ ﮯﻨﮭﮐﺭ ﺖﻘﺑﺎﻄﻣ "
} }

View file

@ -752,6 +752,8 @@
"dfx_option_description": "Ra crypto pẹlu EUR & CHF. Titi di 990 € laisi afikun KYC. Fun soobu ati awọn onibara ile-iṣẹ ni Yuroopu", "dfx_option_description": "Ra crypto pẹlu EUR & CHF. Titi di 990 € laisi afikun KYC. Fun soobu ati awọn onibara ile-iṣẹ ni Yuroopu",
"polygonscan_history": "PolygonScan itan", "polygonscan_history": "PolygonScan itan",
"wallet_seed_legacy": "Irugbin akole", "wallet_seed_legacy": "Irugbin akole",
"default_sell_provider": "Aiyipada Olupese Tita",
"select_sell_provider_notice": "Yan olupese ti o ta loke. O le foju iboju yii nipa tito olupese iṣẹ tita aiyipada rẹ ni awọn eto app.",
"custom_drag": "Aṣa (mu ati fa)", "custom_drag": "Aṣa (mu ati fa)",
"switchToEVMCompatibleWallet": "Jọwọ yipada si apamọwọ ibaramu EVM ki o tun gbiyanju lẹẹkansi (Ethereum, Polygon)" "switchToEVMCompatibleWallet": "Jọwọ yipada si apamọwọ ibaramu EVM ki o tun gbiyanju lẹẹkansi (Ethereum, Polygon)"
} }

View file

@ -757,6 +757,8 @@
"dfx_option_description": "用欧元和瑞士法郎购买加密货币。高达 990 欧元,无需额外 KYC。对于欧洲的零售和企业客户", "dfx_option_description": "用欧元和瑞士法郎购买加密货币。高达 990 欧元,无需额外 KYC。对于欧洲的零售和企业客户",
"polygonscan_history": "多边形扫描历史", "polygonscan_history": "多边形扫描历史",
"wallet_seed_legacy": "旧的钱包种子", "wallet_seed_legacy": "旧的钱包种子",
"default_sell_provider": "默认销售提供商",
"select_sell_provider_notice": "选择上面的销售提供商。您可以通过在应用程序设置中设置默认销售提供商来跳过此屏幕。",
"custom_drag": "定制(保持和拖动)", "custom_drag": "定制(保持和拖动)",
"switchToEVMCompatibleWallet": "请切换到 EVM 兼容钱包并重试以太坊、Polygon" "switchToEVMCompatibleWallet": "请切换到 EVM 兼容钱包并重试以太坊、Polygon"
} }