CW-532-DFX-buy-provider (#1209)
* dfx buy provider ui * fix signing flow * fixed provider determination based on wallet type and app settings * update localization files * minor fix * Fix BTC, LTC und BCH signMessages * Add signMessage to monero * open dfx in webview * Update dfx_buy_provider.dart * Revert merge conflict * Update bitcoin_flutter ref --------- Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com>
Serhii committed
Dec 8, 2023 at 16:05 UTC
2138c35e38680f021b2f23b716a65d5e0e9673c5
50 files changed
+456
-109
assets/images/dfx_dark.png
Binary files /dev/null and b/assets/images/dfx_dark.png differ
assets/images/dfx_light.png
Binary files /dev/null and b/assets/images/dfx_light.png differ
cw_bitcoin/lib/electrum_wallet.dart
+2
-3
@@ -725,8 +725,7 @@ abstract class ElectrumWalletBase
725
final index = address != null
726
? walletAddresses.addresses.firstWhere((element) => element.address == address).index
727
: null;
728
- return index == null
729
- ? base64Encode(hd.sign(message))
730
- : base64Encode(hd.derive(index).sign(message));
728
+ final HD = index == null ? hd : hd.derive(index);
729
+ return base64Encode(HD.signMessage(message));
730
}
731
}
cw_bitcoin/pubspec.yaml
+1
-1
@@ -22,7 +22,7 @@ dependencies:
22
bitcoin_flutter:
23
git:
24
url: https://github.com/cake-tech/bitcoin_flutter.git
25
- ref: cake-update-v3
25
+ ref: cake-update-v4
26
bitbox:
27
git:
28
url: https://github.com/cake-tech/bitbox-flutter.git
cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart
+3
-5
@@ -302,10 +302,8 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
302
final index = address != null
303
? walletAddresses.addresses
304
.firstWhere((element) => element.address == AddressUtils.toLegacyAddress(address))
305
- .index
306
- : null;
307
- return index == null
308
- ? base64Encode(hd.sign(message))
309
- : base64Encode(hd.derive(index).sign(message));
305
+ .index : null;
306
+ final HD = index == null ? hd : hd.derive(index);
307
+ return base64Encode(HD.signMessage(message));
308
}
309
}
cw_bitcoin_cash/pubspec.yaml
+1
-1
@@ -24,7 +24,7 @@ dependencies:
24
bitcoin_flutter:
25
git:
26
url: https://github.com/cake-tech/bitcoin_flutter.git
27
- ref: cake-update-v3
27
+ ref: cake-update-v4
28
bitbox:
29
git:
30
url: https://github.com/cake-tech/bitbox-flutter.git
cw_monero/ios/Classes/monero_api.cpp
+9
@@ -926,6 +926,8 @@ extern "C"
926
return m_wallet->trustedDaemon();
927
}
928
929
+ // Coin Control //
930
+
931
CoinsInfoRow* coin(int index)
932
{
933
if (index >= 0 && index < m_coins_info.size()) {
@@ -1020,6 +1022,13 @@ extern "C"
1022
m_coins->thaw(index);
1023
}
1024
1025
+ // Sign Messages //
1026
+
1027
+ char *sign_message(char *message, char *address = "")
1028
+ {
1029
+ return strdup(get_current_wallet()->signMessage(std::string(message), std::string(address)).c_str());
1030
+ }
1031
+
1032
#ifdef __cplusplus
1033
}
1034
#endif
cw_monero/ios/Classes/monero_api.h
+2
-1
@@ -32,7 +32,8 @@ void store(char *path);
32
33
void set_trusted_daemon(bool arg);
34
bool trusted_daemon();
35
+char *sign_message(char *message, char *address);
36
37
#ifdef __cplusplus
38
}
38
-#endif
\ No newline at end of file
39
+#endif
cw_monero/lib/api/signatures.dart
+2
@@ -149,3 +149,5 @@ typedef coin = Pointer<CoinsInfoRow> Function(Int32 index);
149
typedef freeze_coin = Void Function(Int32 index);
150
151
typedef thaw_coin = Void Function(Int32 index);
152
+
153
+typedef sign_message = Pointer<Utf8> Function(Pointer<Utf8> message, Pointer<Utf8> address);
cw_monero/lib/api/types.dart
+2
@@ -149,3 +149,5 @@ typedef GetCoin = Pointer<CoinsInfoRow> Function(int);
149
typedef FreezeCoin = void Function(int);
150
151
typedef ThawCoin = void Function(int);
152
+
153
+typedef SignMessage = Pointer<Utf8> Function(Pointer<Utf8>, Pointer<Utf8>);
cw_monero/lib/api/wallet.dart
+18
-4
@@ -8,7 +8,6 @@ import 'package:cw_monero/api/types.dart';
8
import 'package:cw_monero/api/monero_api.dart';
9
import 'package:cw_monero/api/exceptions/setup_wallet_exception.dart';
10
import 'package:flutter/foundation.dart';
11
-import 'package:flutter/services.dart';
11
12
int _boolToInt(bool value) => value ? 1 : 0;
13
@@ -128,6 +127,10 @@ final trustedDaemonNative = moneroApi
127
.lookup<NativeFunction<trusted_daemon>>('trusted_daemon')
128
.asFunction<TrustedDaemon>();
129
130
+final signMessageNative = moneroApi
131
+ .lookup<NativeFunction<sign_message>>('sign_message')
132
+ .asFunction<SignMessage>();
133
+
134
int getSyncingHeight() => getSyncingHeightNative();
135
136
bool isNeededToRefresh() => isNeededToRefreshNative() != 0;
@@ -296,7 +299,7 @@ class SyncListener {
299
300
final bchHeight = await getNodeHeightOrUpdate(syncHeight);
301
299
- if (_lastKnownBlockHeight == syncHeight || syncHeight == null) {
302
+ if (_lastKnownBlockHeight == syncHeight) {
303
return;
304
}
305
@@ -311,7 +314,7 @@ class SyncListener {
314
}
315
316
// 1. Actual new height; 2. Blocks left to finish; 3. Progress in percents;
314
- onNewBlock?.call(syncHeight, left, ptc);
317
+ onNewBlock.call(syncHeight, left, ptc);
318
});
319
}
320
@@ -382,4 +385,15 @@ String getSubaddressLabel(int accountIndex, int addressIndex) {
385
386
Future setTrustedDaemon(bool trusted) async => setTrustedDaemonNative(_boolToInt(trusted));
387
385
-Future<bool> trustedDaemon() async => trustedDaemonNative() != 0;
\ No newline at end of file
388
+Future<bool> trustedDaemon() async => trustedDaemonNative() != 0;
389
+
390
+String signMessage(String message, {String address = ""}) {
391
+ final messagePointer = message.toNativeUtf8();
392
+ final addressPointer = address.toNativeUtf8();
393
+
394
+ final signature = convertUTF8ToString(pointer: signMessageNative(messagePointer, addressPointer));
395
+ calloc.free(messagePointer);
396
+ calloc.free(addressPointer);
397
+
398
+ return signature;
399
+}
cw_monero/lib/monero_wallet.dart
+6
@@ -651,4 +651,10 @@ abstract class MoneroWalletBase
651
652
@override
653
void setExceptionHandler(void Function(FlutterErrorDetails) onError) => _onError = onError;
654
+
655
+ @override
656
+ String signMessage(String message, {String? address}) {
657
+ final useAddress = address ?? "";
658
+ return monero_wallet.signMessage(message, address: useAddress);
659
+ }
660
}
cw_monero/macos/Classes/monero_api.cpp
+7
@@ -1020,6 +1020,13 @@ extern "C"
1020
m_coins->thaw(index);
1021
}
1022
1023
+ // Sign Messages //
1024
+
1025
+ char *sign_message(char *message, char *address = "")
1026
+ {
1027
+ return strdup(get_current_wallet()->signMessage(std::string(message), std::string(address)).c_str());
1028
+ }
1029
+
1030
#ifdef __cplusplus
1031
}
1032
#endif
cw_monero/macos/Classes/monero_api.h
+2
-1
@@ -32,7 +32,8 @@ void store(char *path);
32
33
void set_trusted_daemon(bool arg);
34
bool trusted_daemon();
35
+char *sign_message(char *message, char *address);
36
37
#ifdef __cplusplus
38
}
38
-#endif
\ No newline at end of file
39
+#endif
lib/buy/dfx/dfx_buy_provider.dart
new
+179
@@ -0,0 +1,179 @@
1
+import 'dart:convert';
2
+
3
+import 'package:cake_wallet/generated/i18n.dart';
4
+import 'package:cake_wallet/routes.dart';
5
+import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
6
+import 'package:cake_wallet/utils/device_info.dart';
7
+import 'package:cake_wallet/utils/show_pop_up.dart';
8
+import 'package:cw_core/wallet_base.dart';
9
+import 'package:cw_core/wallet_type.dart';
10
+import 'package:flutter/material.dart';
11
+import 'package:http/http.dart' as http;
12
+import 'package:url_launcher/url_launcher.dart';
13
+
14
+class DFXBuyProvider {
15
+ DFXBuyProvider({required WalletBase wallet}) : this._wallet = wallet;
16
+
17
+ final WalletBase _wallet;
18
+
19
+ static const _baseUrl = 'api.dfx.swiss';
20
+ static const _authPath = '/v1/auth/signMessage';
21
+ static const _signUpPath = '/v1/auth/signUp';
22
+ static const _signInPath = '/v1/auth/signIn';
23
+ static const walletName = 'CakeWallet';
24
+
25
+ String get assetOut {
26
+ switch (_wallet.type) {
27
+ case WalletType.bitcoin:
28
+ return 'BTC';
29
+ case WalletType.bitcoinCash:
30
+ return 'BCH';
31
+ case WalletType.litecoin:
32
+ return 'LTC';
33
+ case WalletType.monero:
34
+ return 'XMR';
35
+ case WalletType.ethereum:
36
+ return 'ETH';
37
+ default:
38
+ throw Exception("WalletType is not available for DFX ${_wallet.type}");
39
+ }
40
+ }
41
+
42
+ String get blockchain {
43
+ switch (_wallet.type) {
44
+ case WalletType.bitcoin:
45
+ case WalletType.bitcoinCash:
46
+ case WalletType.litecoin:
47
+ return 'Bitcoin';
48
+ case WalletType.monero:
49
+ return 'Monero';
50
+ case WalletType.ethereum:
51
+ return 'Ethereum';
52
+ default:
53
+ throw Exception("WalletType is not available for DFX ${_wallet.type}");
54
+ }
55
+ }
56
+
57
+ Future<String> getSignMessage() async {
58
+ final walletAddress = _wallet.walletAddresses.address;
59
+ final uri = Uri.https(_baseUrl, _authPath, {'address': walletAddress});
60
+
61
+ var response = await http.get(uri, headers: {'accept': 'application/json'});
62
+
63
+ if (response.statusCode == 200) {
64
+ final responseBody = jsonDecode(response.body);
65
+ return responseBody['message'] as String;
66
+ } else {
67
+ throw Exception(
68
+ 'Failed to get sign message. Status: ${response.statusCode} ${response.body}');
69
+ }
70
+ }
71
+
72
+ Future<String> signUp() async {
73
+ final signMessage = getSignature(await getSignMessage());
74
+ final walletAddress = _wallet.walletAddresses.address;
75
+
76
+ final requestBody = jsonEncode({
77
+ 'wallet': walletName,
78
+ 'address': walletAddress,
79
+ 'signature': signMessage,
80
+ });
81
+
82
+ final uri = Uri.https(_baseUrl, _signUpPath);
83
+ var response = await http.post(uri,
84
+ headers: {'Content-Type': 'application/json'}, body: requestBody);
85
+
86
+ if (response.statusCode == 201) {
87
+ final responseBody = jsonDecode(response.body);
88
+ return responseBody['accessToken'] as String;
89
+ } else {
90
+ throw Exception(
91
+ 'Failed to sign up. Status: ${response.statusCode} ${response.body}');
92
+ }
93
+ }
94
+
95
+ Future<String> signIn() async {
96
+ final signMessage = getSignature(await getSignMessage());
97
+ final walletAddress = _wallet.walletAddresses.address;
98
+
99
+ final requestBody = jsonEncode({
100
+ 'address': walletAddress,
101
+ 'signature': signMessage,
102
+ });
103
+
104
+ final uri = Uri.https(_baseUrl, _signInPath);
105
+ var response = await http.post(uri,
106
+ headers: {'Content-Type': 'application/json'}, body: requestBody);
107
+
108
+ if (response.statusCode == 201) {
109
+ final responseBody = jsonDecode(response.body);
110
+ return responseBody['accessToken'] as String;
111
+ } else {
112
+ throw Exception(
113
+ 'Failed to sign in. Status: ${response.statusCode} ${response.body}');
114
+ }
115
+ }
116
+
117
+ String getSignature(String message) {
118
+ switch (_wallet.type) {
119
+ case WalletType.ethereum:
120
+ return _wallet.signMessage(message);
121
+ case WalletType.monero:
122
+ case WalletType.litecoin:
123
+ case WalletType.bitcoin:
124
+ case WalletType.bitcoinCash:
125
+ return _wallet.signMessage(message,
126
+ address: _wallet.walletAddresses.address);
127
+ default:
128
+ throw Exception("WalletType is not available for DFX ${_wallet.type}");
129
+ }
130
+ }
131
+
132
+ Future<void> launchProvider(BuildContext context) async {
133
+ try {
134
+ final assetOut = this.assetOut;
135
+ final blockchain = this.blockchain;
136
+
137
+ String accessToken;
138
+
139
+ try {
140
+ accessToken = await signUp();
141
+ } on Exception catch (e) {
142
+ if (e.toString().contains('409')) {
143
+ accessToken = await signIn();
144
+ } else {
145
+ rethrow;
146
+ }
147
+ }
148
+
149
+ final uri = Uri.https('services.dfx.swiss', '/buy', {
150
+ 'session': accessToken,
151
+ 'lang': 'en',
152
+ 'asset-out': assetOut,
153
+ 'blockchain': blockchain,
154
+ 'asset-in': 'EUR',
155
+ });
156
+
157
+ if (await canLaunchUrl(uri)) {
158
+ if (DeviceInfo.instance.isMobile) {
159
+ Navigator.of(context).pushNamed(Routes.webViewPage,
160
+ arguments: [S.of(context).buy, uri]);
161
+ } else {
162
+ await launchUrl(uri, mode: LaunchMode.externalApplication);
163
+ }
164
+ } else {
165
+ throw Exception('Could not launch URL');
166
+ }
167
+ } catch (e) {
168
+ await showPopUp<void>(
169
+ context: context,
170
+ builder: (BuildContext context) {
171
+ return AlertWithOneAction(
172
+ alertTitle: "DFX Connect",
173
+ alertContent: S.of(context).buy_provider_unavailable + ': $e',
174
+ buttonText: S.of(context).ok,
175
+ buttonAction: () => Navigator.of(context).pop());
176
+ });
177
+ }
178
+ }
179
+}
lib/di.dart
+5
-1
@@ -227,6 +227,7 @@ import 'package:cake_wallet/core/wallet_loading_service.dart';
227
import 'package:cw_core/crypto_currency.dart';
228
import 'package:cake_wallet/entities/qr_view_data.dart';
229
230
+import 'buy/dfx/dfx_buy_provider.dart';
231
import 'core/totp_request_details.dart';
232
import 'src/screens/settings/desktop_settings/desktop_settings_page.dart';
233
@@ -795,6 +796,9 @@ Future<void> setup({
796
getIt.registerFactory<RobinhoodBuyProvider>(
797
() => RobinhoodBuyProvider(wallet: getIt.get<AppStore>().wallet!));
798
799
+ getIt.registerFactory<DFXBuyProvider>(
800
+ () => DFXBuyProvider(wallet: getIt.get<AppStore>().wallet!));
801
+
802
getIt.registerFactory<OnRamperBuyProvider>(() => OnRamperBuyProvider(
803
settingsStore: getIt.get<AppStore>().settingsStore,
804
wallet: getIt.get<AppStore>().wallet!,
@@ -940,7 +944,7 @@ Future<void> setup({
944
945
getIt.registerFactory(() => BuyAmountViewModel());
946
943
- getIt.registerFactory(() => BuyOptionsPage());
947
+ getIt.registerFactory(() => BuyOptionsPage(getIt.get<DashboardViewModel>()));
948
949
getIt.registerFactory(() {
950
final wallet = getIt.get<AppStore>().wallet;
lib/entities/buy_provider_types.dart
+39
-1
@@ -1,9 +1,11 @@
1
import 'package:cake_wallet/generated/i18n.dart';
2
+import 'package:cw_core/wallet_type.dart';
3
4
enum BuyProviderType {
5
AskEachTime,
6
Robinhood,
6
- Onramper;
7
+ Onramper,
8
+ DFX;
9
10
@override
11
String toString() {
@@ -14,6 +16,42 @@ enum BuyProviderType {
16
return "Robinhood";
17
case BuyProviderType.Onramper:
18
return "Onramper";
19
+ case BuyProviderType.DFX:
20
+ return "DFX";
21
+ }
22
+ }
23
+
24
+ static List<BuyProviderType> getAvailableProviders(WalletType walletType) {
25
+ switch (walletType) {
26
+ case WalletType.nano:
27
+ case WalletType.banano:
28
+ return [
29
+ BuyProviderType.AskEachTime,
30
+ BuyProviderType.Onramper
31
+ ];
32
+ case WalletType.monero:
33
+ return [
34
+ BuyProviderType.AskEachTime,
35
+ BuyProviderType.Onramper,
36
+ BuyProviderType.DFX
37
+ ];
38
+ case WalletType.bitcoin:
39
+ case WalletType.ethereum:
40
+ return [
41
+ BuyProviderType.AskEachTime,
42
+ BuyProviderType.Onramper,
43
+ BuyProviderType.DFX,
44
+ BuyProviderType.Robinhood
45
+ ];
46
+ case WalletType.litecoin:
47
+ case WalletType.bitcoinCash:
48
+ return [
49
+ BuyProviderType.AskEachTime,
50
+ BuyProviderType.Onramper,
51
+ BuyProviderType.Robinhood
52
+ ];
53
+ default:
54
+ return [];
55
}
56
}
57
}
lib/entities/main_actions.dart
+44
-37
@@ -1,3 +1,4 @@
1
+import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart';
2
import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart';
3
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
4
import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart';
@@ -44,48 +45,54 @@ class MainActions {
45
isEnabled: (viewModel) => viewModel.isEnabledBuyAction,
46
canShow: (viewModel) => viewModel.hasBuyAction,
47
onTap: (BuildContext context, DashboardViewModel viewModel) async {
47
- final defaultBuyProvider = viewModel.defaultBuyProvider;
48
- final walletType = viewModel.type;
49
-
50
- if (!viewModel.isEnabledBuyAction) return;
48
+ if (!viewModel.isEnabledBuyAction) {
49
+ await _showErrorDialog(context, S.of(context).unsupported_asset);
50
+ return;
51
+ }
52
52
- switch (walletType) {
53
- case WalletType.bitcoin:
54
- case WalletType.litecoin:
55
- case WalletType.ethereum:
56
- case WalletType.polygon:
57
- case WalletType.bitcoinCash:
58
- switch (defaultBuyProvider) {
59
- case BuyProviderType.AskEachTime:
60
- Navigator.pushNamed(context, Routes.buy);
61
- break;
62
- case BuyProviderType.Onramper:
63
- await getIt.get<OnRamperBuyProvider>().launchProvider(context);
64
- break;
65
- case BuyProviderType.Robinhood:
66
- await getIt.get<RobinhoodBuyProvider>().launchProvider(context);
67
- break;
68
- }
69
- break;
70
- case WalletType.nano:
71
- case WalletType.banano:
72
- case WalletType.monero:
73
- await getIt.get<OnRamperBuyProvider>().launchProvider(context);
74
- break;
75
- default:
76
- await showPopUp<void>(
77
- context: context,
78
- builder: (BuildContext context) {
79
- return AlertWithOneAction(
80
- alertTitle: S.of(context).buy,
81
- alertContent: S.of(context).unsupported_asset,
82
- buttonText: S.of(context).ok,
83
- buttonAction: () => Navigator.of(context).pop());
84
- });
53
+ final defaultBuyProvider = viewModel.defaultBuyProvider;
54
+ try {
55
+ await _launchProviderByType(context, defaultBuyProvider);
56
+ } catch (e) {
57
+ await _showErrorDialog(context, e.toString());
58
}
59
},
60
);
61
62
+ static Future<void> _launchProviderByType(BuildContext context, BuyProviderType providerType) async {
63
+ switch (providerType) {
64
+ case BuyProviderType.AskEachTime:
65
+ Navigator.pushNamed(context, Routes.buy);
66
+ break;
67
+ case BuyProviderType.Onramper:
68
+ await getIt.get<OnRamperBuyProvider>().launchProvider(context);
69
+ break;
70
+ case BuyProviderType.Robinhood:
71
+ await getIt.get<RobinhoodBuyProvider>().launchProvider(context);
72
+ break;
73
+ case BuyProviderType.DFX:
74
+ await getIt.get<DFXBuyProvider>().launchProvider(context);
75
+ break;
76
+ default:
77
+ throw UnsupportedError('Unsupported buy provider type');
78
+ }
79
+ }
80
+
81
+
82
+ static Future<void> _showErrorDialog(BuildContext context, String errorMessage) async {
83
+ await showPopUp<void>(
84
+ context: context,
85
+ builder: (BuildContext context) {
86
+ return AlertWithOneAction(
87
+ alertTitle: S.of(context).buy,
88
+ alertContent: errorMessage,
89
+ buttonText: S.of(context).ok,
90
+ buttonAction: () => Navigator.of(context).pop(),
91
+ );
92
+ },
93
+ );
94
+ }
95
+
96
static MainActions receiveAction = MainActions._(
97
name: (context) => S.of(context).receive,
98
image: 'assets/images/received.png',
lib/src/screens/buy/buy_options_page.dart
+58
-24
@@ -1,18 +1,26 @@
1
+import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart';
2
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
3
import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart';
4
import 'package:cake_wallet/di.dart';
5
+import 'package:cake_wallet/entities/buy_provider_types.dart';
6
import 'package:cake_wallet/generated/i18n.dart';
7
import 'package:cake_wallet/src/screens/base_page.dart';
8
import 'package:cake_wallet/src/widgets/option_tile.dart';
9
import 'package:cake_wallet/themes/extensions/option_tile_theme.dart';
10
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
11
+import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
12
import 'package:flutter/material.dart';
13
14
class BuyOptionsPage extends BasePage {
15
+ BuyOptionsPage(this.dashboardViewModel);
16
+
17
+ final DashboardViewModel dashboardViewModel;
18
final iconDarkRobinhood = 'assets/images/robinhood_dark.png';
19
final iconLightRobinhood = 'assets/images/robinhood_light.png';
20
final iconDarkOnramper = 'assets/images/onramper_dark.png';
21
final iconLightOnramper = 'assets/images/onramper_light.png';
22
+ final iconDarkDFX = 'assets/images/dfx_dark.png';
23
+ final iconLightDFX = 'assets/images/dfx_light.png';
24
25
@override
26
String get title => S.current.buy;
@@ -22,11 +30,19 @@ class BuyOptionsPage extends BasePage {
30
31
@override
32
Widget body(BuildContext context) {
25
- final isLightMode = Theme.of(context).extension<OptionTileTheme>()?.useDarkImage ?? false;
26
- final iconRobinhood =
27
- Image.asset(isLightMode ? iconLightRobinhood : iconDarkRobinhood, height: 40, width: 40);
28
- final iconOnramper =
29
- Image.asset(isLightMode ? iconLightOnramper : iconDarkOnramper, height: 40, width: 40);
33
+ final isLightMode =
34
+ Theme.of(context).extension<OptionTileTheme>()?.useDarkImage ?? false;
35
+ final iconRobinhood = Image.asset(
36
+ isLightMode ? iconLightRobinhood : iconDarkRobinhood,
37
+ height: 40,
38
+ width: 40);
39
+ final iconOnramper = Image.asset(
40
+ isLightMode ? iconLightOnramper : iconDarkOnramper,
41
+ height: 40,
42
+ width: 40);
43
+ final iconDFX = Image.asset(isLightMode ? iconLightDFX : iconDarkDFX,
44
+ height: 40, width: 40);
45
+ final availableProviders = dashboardViewModel.availableProviders;
46
47
return Container(
48
child: Center(
@@ -34,26 +50,42 @@ class BuyOptionsPage extends BasePage {
50
constraints: BoxConstraints(maxWidth: 330),
51
child: Column(
52
children: [
37
- Padding(
38
- padding: EdgeInsets.only(top: 24),
39
- child: OptionTile(
40
- image: iconOnramper,
41
- title: "Onramper",
42
- description: S.of(context).onramper_option_description,
43
- onPressed: () async =>
44
- await getIt.get<OnRamperBuyProvider>().launchProvider(context),
53
+ if (availableProviders.contains(BuyProviderType.Onramper))
54
+ Padding(
55
+ padding: EdgeInsets.only(top: 24),
56
+ child: OptionTile(
57
+ image: iconOnramper,
58
+ title: "Onramper",
59
+ description: S.of(context).onramper_option_description,
60
+ onPressed: () async => await getIt
61
+ .get<OnRamperBuyProvider>()
62
+ .launchProvider(context),
63
+ ),
64
),
46
- ),
47
- Padding(
48
- padding: EdgeInsets.only(top: 24),
49
- child: OptionTile(
50
- image: iconRobinhood,
51
- title: "Robinhood Connect",
52
- description: S.of(context).robinhood_option_description,
53
- onPressed: () async =>
54
- await getIt.get<RobinhoodBuyProvider>().launchProvider(context),
65
+ if (availableProviders.contains(BuyProviderType.Robinhood))
66
+ Padding(
67
+ padding: EdgeInsets.only(top: 24),
68
+ child: OptionTile(
69
+ image: iconRobinhood,
70
+ title: "Robinhood Connect",
71
+ description: S.of(context).robinhood_option_description,
72
+ onPressed: () async => await getIt
73
+ .get<RobinhoodBuyProvider>()
74
+ .launchProvider(context),
75
+ ),
76
+ ),
77
+ if (availableProviders.contains(BuyProviderType.DFX))
78
+ Padding(
79
+ padding: EdgeInsets.only(top: 24),
80
+ child: OptionTile(
81
+ image: iconDFX,
82
+ title: "DFX Connect",
83
+ description: S.of(context).dfx_option_description,
84
+ onPressed: () async => await getIt
85
+ .get<DFXBuyProvider>()
86
+ .launchProvider(context),
87
+ ),
88
),
56
- ),
89
Spacer(),
90
Padding(
91
padding: EdgeInsets.fromLTRB(24, 24, 24, 32),
@@ -63,7 +95,9 @@ class BuyOptionsPage extends BasePage {
95
style: TextStyle(
96
fontSize: 14,
97
fontWeight: FontWeight.normal,
66
- color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
98
+ color: Theme.of(context)
99
+ .extension<TransactionTradeTheme>()!
100
+ .detailsTitlesColor,
101
),
102
),
103
),
lib/src/screens/settings/other_settings_page.dart
+2
-3
@@ -1,5 +1,3 @@
1
-import 'package:cake_wallet/buy/buy_provider.dart';
2
-import 'package:cake_wallet/entities/buy_provider_types.dart';
1
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
2
import 'package:cake_wallet/generated/i18n.dart';
3
import 'package:cake_wallet/routes.dart';
@@ -42,9 +40,10 @@ class OtherSettingsPage extends BasePage {
40
handler: (BuildContext context) =>
41
Navigator.of(context).pushNamed(Routes.changeRep),
42
),
43
+ if(_otherSettingsViewModel.isEnabledBuyAction)
44
SettingsPickerCell(
45
title: S.current.default_buy_provider,
47
- items: BuyProviderType.values,
46
+ items: _otherSettingsViewModel.availableBuyProviders,
47
displayItem: _otherSettingsViewModel.getBuyProviderType,
48
selectedItem: _otherSettingsViewModel.buyProviderType,
49
onItemSelected: _otherSettingsViewModel.onBuyProviderTypeSelected,
lib/store/settings_store.dart
+18
-10
@@ -36,7 +36,6 @@ import 'package:cake_wallet/monero/monero.dart';
36
import 'package:cake_wallet/entities/action_list_display_mode.dart';
37
import 'package:cake_wallet/entities/fiat_api_mode.dart';
38
import 'package:cw_core/set_app_secure_native.dart';
39
-
39
part 'settings_store.g.dart';
40
41
class SettingsStore = SettingsStoreBase with _$SettingsStore;
@@ -123,7 +122,6 @@ abstract class SettingsStoreBase with Store {
122
isAppSecure = initialAppSecure,
123
disableBuy = initialDisableBuy,
124
disableSell = initialDisableSell,
126
- defaultBuyProvider = initialDefaultBuyProvider,
125
shouldShowMarketPlaceInDashboard = initialShouldShowMarketPlaceInDashboard,
126
exchangeStatus = initialExchangeStatus,
127
currentTheme = initialTheme,
@@ -178,6 +176,12 @@ abstract class SettingsStoreBase with Store {
176
177
initializeTrocadorProviderStates();
178
179
+ WalletType.values.forEach((walletType) {
180
+ final key = 'defaultBuyProvider_${walletType.toString()}';
181
+ final providerIndex = sharedPreferences.getInt(key);
182
+ defaultBuyProviders[walletType] = providerIndex != null ? BuyProviderType.values[providerIndex] : BuyProviderType.AskEachTime;
183
+ });
184
+
185
reaction(
186
(_) => fiatCurrency,
187
(FiatCurrency fiatCurrency) => sharedPreferences.setString(
@@ -244,9 +248,14 @@ abstract class SettingsStoreBase with Store {
248
sharedPreferences.setBool(PreferencesKey.disableSellKey, disableSell));
249
250
reaction(
247
- (_) => defaultBuyProvider,
248
- (BuyProviderType defaultBuyProvider) =>
249
- sharedPreferences.setInt(PreferencesKey.defaultBuyProvider, defaultBuyProvider.index));
251
+ (_) => defaultBuyProviders.asObservable(),
252
+ (ObservableMap<WalletType, BuyProviderType> providers) {
253
+ providers.forEach((walletType, provider) {
254
+ final key = 'defaultBuyProvider_${walletType.toString()}';
255
+ sharedPreferences.setInt(key, provider.index);
256
+ });
257
+ }
258
+ );
259
260
reaction(
261
(_) => autoGenerateSubaddressStatus,
@@ -333,6 +342,7 @@ abstract class SettingsStoreBase with Store {
342
343
reaction((_) => totpSecretKey,
344
(String totpKey) => sharedPreferences.setString(PreferencesKey.totpSecretKey, totpKey));
345
+
346
reaction(
347
(_) => numberOfFailedTokenTrials,
348
(int failedTokenTrail) =>
@@ -494,9 +504,6 @@ abstract class SettingsStoreBase with Store {
504
@observable
505
bool disableSell;
506
497
- @observable
498
- BuyProviderType defaultBuyProvider;
499
-
507
@observable
508
bool allowBiometricalAuthentication;
509
@@ -566,6 +573,9 @@ abstract class SettingsStoreBase with Store {
573
@observable
574
ObservableMap<String, bool> trocadorProviderStates = ObservableMap<String, bool>();
575
576
+ @observable
577
+ ObservableMap<WalletType, BuyProviderType> defaultBuyProviders = ObservableMap<WalletType, BuyProviderType>();
578
+
579
@observable
580
SortBalanceBy sortBalanceBy;
581
@@ -1003,8 +1013,6 @@ abstract class SettingsStoreBase with Store {
1013
isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? isAppSecure;
1014
disableBuy = sharedPreferences.getBool(PreferencesKey.disableBuyKey) ?? disableBuy;
1015
disableSell = sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? disableSell;
1006
- defaultBuyProvider =
1007
- BuyProviderType.values[sharedPreferences.getInt(PreferencesKey.defaultBuyProvider) ?? 0];
1016
allowBiometricalAuthentication =
1017
sharedPreferences.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ??
1018
allowBiometricalAuthentication;
lib/view_model/dashboard/dashboard_view_model.dart
+5
-1
@@ -282,10 +282,14 @@ abstract class DashboardViewModelBase with Store {
282
283
Map<String, List<FilterItem>> filterItems;
284
285
- BuyProviderType get defaultBuyProvider => settingsStore.defaultBuyProvider;
285
+ BuyProviderType get defaultBuyProvider =>
286
+ settingsStore.defaultBuyProviders[wallet.type] ??
287
+ BuyProviderType.AskEachTime;
288
289
bool get isBuyEnabled => settingsStore.isBitcoinBuyEnabled;
290
291
+ List<BuyProviderType> get availableProviders => BuyProviderType.getAvailableProviders(wallet.type);
292
+
293
bool get shouldShowYatPopup => settingsStore.shouldShowYatPopup;
294
295
@action
lib/view_model/settings/other_settings_view_model.dart
+20
-15
@@ -13,14 +13,15 @@ import 'package:package_info/package_info.dart';
13
14
part 'other_settings_view_model.g.dart';
15
16
-class OtherSettingsViewModel = OtherSettingsViewModelBase with _$OtherSettingsViewModel;
16
+class OtherSettingsViewModel = OtherSettingsViewModelBase
17
+ with _$OtherSettingsViewModel;
18
19
abstract class OtherSettingsViewModelBase with Store {
20
OtherSettingsViewModelBase(this._settingsStore, this._wallet)
21
: walletType = _wallet.type,
22
currentVersion = '' {
22
- PackageInfo.fromPlatform()
23
- .then((PackageInfo packageInfo) => currentVersion = packageInfo.version);
23
+ PackageInfo.fromPlatform().then(
24
+ (PackageInfo packageInfo) => currentVersion = packageInfo.version);
25
26
final priority = _settingsStore.priority[_wallet.type];
27
final priorities = priorityForWalletType(_wallet.type);
@@ -31,7 +32,8 @@ abstract class OtherSettingsViewModelBase with Store {
32
}
33
34
final WalletType walletType;
34
- final WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> _wallet;
35
+ final WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
36
+ TransactionInfo> _wallet;
37
38
@observable
39
String currentVersion;
@@ -50,15 +52,19 @@ abstract class OtherSettingsViewModelBase with Store {
52
}
53
54
@computed
53
- bool get changeRepresentativeEnabled {
54
- if (_wallet.type == WalletType.nano || _wallet.type == WalletType.banano) {
55
- return true;
56
- }
55
+ bool get changeRepresentativeEnabled =>
56
+ _wallet.type == WalletType.nano || _wallet.type == WalletType.banano;
57
58
- return false;
59
- }
60
-
61
- BuyProviderType get buyProviderType { return _settingsStore.defaultBuyProvider; }
58
+ @computed
59
+ bool get isEnabledBuyAction =>
60
+ !_settingsStore.disableBuy && _wallet.type != WalletType.haven;
61
+
62
+ List<BuyProviderType> get availableBuyProviders =>
63
+ BuyProviderType.getAvailableProviders(walletType);
64
+
65
+ BuyProviderType get buyProviderType =>
66
+ _settingsStore.defaultBuyProviders[walletType] ??
67
+ BuyProviderType.AskEachTime;
68
69
String getDisplayPriority(dynamic priority) {
70
final _priority = priority as TransactionPriority;
@@ -73,7 +79,7 @@ abstract class OtherSettingsViewModelBase with Store {
79
return priority.toString();
80
}
81
76
- String getBuyProviderType (dynamic buyProviderType) {
82
+ String getBuyProviderType(dynamic buyProviderType) {
83
final _buyProviderType = buyProviderType as BuyProviderType;
84
85
return _buyProviderType.toString();
@@ -83,6 +89,5 @@ abstract class OtherSettingsViewModelBase with Store {
89
_settingsStore.priority[_wallet.type] = priority;
90
91
void onBuyProviderTypeSelected(BuyProviderType buyProviderType) =>
86
- _settingsStore.defaultBuyProvider = buyProviderType;
87
-
92
+ _settingsStore.defaultBuyProviders[walletType] = buyProviderType;
93
}
pubspec_base.yaml
+1
-1
@@ -96,7 +96,7 @@ dependencies:
96
bitcoin_flutter:
97
git:
98
url: https://github.com/cake-tech/bitcoin_flutter.git
99
- ref: cake-update-v3
99
+ ref: cake-update-v4
100
fluttertoast: 8.1.4
101
# tor:
102
# git:
res/values/strings_ar.arb
+1
@@ -746,5 +746,6 @@
746
"seed_language_czech": "التشيكية",
747
"seed_language_korean": "الكورية",
748
"seed_language_chinese_traditional": "تقاليد صينية)",
749
+ "dfx_option_description": "ﺎﺑﻭﺭﻭﺃ ﻲﻓ ﺕﺎﻛﺮﺸﻟﺍﻭ ﺔﺋﺰﺠﺘﻟﺍ ءﻼﻤﻌﻟ .ﻲﻓﺎﺿﺇ KYC ﻥﻭﺪﺑ ﻭﺭﻮﻳ 990 ﻰﻟﺇ ﻞﺼﻳ ﺎﻣ .ﻱﺮﺴﻳﻮﺴﻟﺍ",
750
"polygonscan_history": "ﻥﺎﻜﺴﻧﻮﺠﻴﻟﻮﺑ ﺦﻳﺭﺎﺗ"
751
}
res/values/strings_bg.arb
+1
@@ -742,5 +742,6 @@
742
"seed_language_czech": "Чех",
743
"seed_language_korean": "Корейски",
744
"seed_language_chinese_traditional": "Традиционен китайски)",
745
+ "dfx_option_description": "Купете крипто с EUR и CHF. До 990 € без допълнителен KYC. За клиенти на дребно и корпоративни клиенти в Европа",
746
"polygonscan_history": "История на PolygonScan"
747
}
res/values/strings_cs.arb
+1
@@ -742,5 +742,6 @@
742
"seed_language_czech": "čeština",
743
"seed_language_korean": "korejština",
744
"seed_language_chinese_traditional": "Číňan (tradiční)",
745
+ "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ě",
746
"polygonscan_history": "Historie PolygonScan"
747
}
res/values/strings_de.arb
+1
@@ -750,5 +750,6 @@
750
"seed_language_czech": "Tschechisch",
751
"seed_language_korean": "Koreanisch",
752
"seed_language_chinese_traditional": "Chinesisch (Traditionell)",
753
+ "dfx_option_description": "Krypto mit EUR und CHF kaufen. Bis zu 990€ ohne zusätzliches KYC. Für Privat- und Firmenkunden in Europa",
754
"polygonscan_history": "PolygonScan-Verlauf"
755
}
res/values/strings_en.arb
+1
@@ -751,5 +751,6 @@
751
"seed_language_czech": "Czech",
752
"seed_language_korean": "Korean",
753
"seed_language_chinese_traditional": "Chinese (Traditional)",
754
+ "dfx_option_description": "Buy crypto with EUR & CHF. Up to 990€ without additional KYC. For retail and corporate customers in Europe",
755
"polygonscan_history": "PolygonScan history"
756
}
res/values/strings_es.arb
+2
@@ -749,6 +749,8 @@
749
"seedtype_polyseed": "Polieta (16 palabras)",
750
"seed_language_czech": "checo",
751
"seed_language_korean": "coreano",
752
+ "seed_language_chinese_traditional": "Chino tradicional)",
753
+ "dfx_option_description": "Compre criptomonedas con EUR y CHF. Hasta 990€ sin KYC adicional. Para clientes minoristas y corporativos en Europa",
754
"seed_language_chinese_traditional": "Chino (tradicional)",
755
"polygonscan_history": "Historial de PolygonScan"
756
}
res/values/strings_fr.arb
+1
@@ -750,5 +750,6 @@
750
"seed_language_czech": "tchèque",
751
"seed_language_korean": "coréen",
752
"seed_language_chinese_traditional": "Chinois (Traditionnel)",
753
+ "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",
754
"polygonscan_history": "Historique de PolygonScan"
755
}
res/values/strings_ha.arb
+1
@@ -728,5 +728,6 @@
728
"seed_language_czech": "Czech",
729
"seed_language_korean": "Yaren Koriya",
730
"seed_language_chinese_traditional": "Sinanci (na gargajiya)",
731
+ "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",
732
"polygonscan_history": "PolygonScan tarihin kowane zamani"
733
}
res/values/strings_hi.arb
+1
@@ -750,5 +750,6 @@
750
"seed_language_czech": "चेक",
751
"seed_language_korean": "कोरियाई",
752
"seed_language_chinese_traditional": "चीनी पारंपरिक)",
753
+ "dfx_option_description": "EUR और CHF के साथ क्रिप्टो खरीदें। अतिरिक्त केवाईसी के बिना 990€ तक। यूरोप में खुदरा और कॉर्पोरेट ग्राहकों के लिए",
754
"polygonscan_history": "पॉलीगॉनस्कैन इतिहास"
755
}
res/values/strings_hr.arb
+2
@@ -747,6 +747,8 @@
747
"seedtype_polyseed": "Poliseed (16 riječi)",
748
"seed_language_czech": "češki",
749
"seed_language_korean": "korejski",
750
+ "seed_language_chinese_traditional": "Kinesko tradicionalno)",
751
+ "dfx_option_description": "Kupujte kripto s EUR i CHF. Do 990 € bez dodatnog KYC-a. Za maloprodajne i poslovne korisnike u Europi",
752
"seed_language_chinese_traditional": "Kinesko (tradicionalno)",
753
"polygonscan_history": "Povijest PolygonScan"
754
}
res/values/strings_id.arb
+2
@@ -737,6 +737,8 @@
737
"seedtype_polyseed": "Polyseed (16 kata)",
738
"seed_language_czech": "Ceko",
739
"seed_language_korean": "Korea",
740
+ "seed_language_chinese_traditional": "Cina tradisional)",
741
+ "dfx_option_description": "Beli kripto dengan EUR & CHF. Hingga 990€ tanpa KYC tambahan. Untuk pelanggan ritel dan korporat di Eropa",
742
"seed_language_chinese_traditional": "Cina (tradisional)",
743
"polygonscan_history": "Sejarah PolygonScan"
744
}
res/values/strings_it.arb
+1
@@ -750,5 +750,6 @@
750
"seed_language_czech": "ceco",
751
"seed_language_korean": "coreano",
752
"seed_language_chinese_traditional": "Cinese tradizionale)",
753
+ "dfx_option_description": "Acquista criptovalute con EUR e CHF. Fino a 990€ senza KYC aggiuntivi. Per clienti al dettaglio e aziendali in Europa",
754
"polygonscan_history": "Cronologia PolygonScan"
755
}
res/values/strings_ja.arb
+1
@@ -750,5 +750,6 @@
750
"seed_language_czech": "チェコ",
751
"seed_language_korean": "韓国語",
752
"seed_language_chinese_traditional": "中国の伝統的な)",
753
+ "dfx_option_description": "EUR と CHF で暗号通貨を購入します。追加のKYCなしで最大990ユーロ。ヨーロッパの小売および法人顧客向け",
754
"polygonscan_history": "ポリゴンスキャン履歴"
755
}
res/values/strings_ko.arb
+1
@@ -748,5 +748,6 @@
748
"seed_language_czech": "체코 사람",
749
"seed_language_korean": "한국인",
750
"seed_language_chinese_traditional": "중국 전통)",
751
+ "dfx_option_description": "EUR 및 CHF로 암호화폐를 구매하세요. 추가 KYC 없이 최대 990€. 유럽의 소매 및 기업 고객용",
752
"polygonscan_history": "다각형 스캔 기록"
753
}
res/values/strings_my.arb
+1
@@ -748,5 +748,6 @@
748
"seed_language_czech": "ချက်",
749
"seed_language_korean": "ကိုးရီးယား",
750
"seed_language_chinese_traditional": "တရုတ်ရိုးရာ)",
751
+ "dfx_option_description": "EUR & CHF ဖြင့် crypto ကိုဝယ်ပါ။ အပို KYC မပါဘဲ 990€ အထိ။ ဥရောပရှိ လက်လီရောင်းချသူများနှင့် ကော်ပိုရိတ်ဖောက်သည်များအတွက်",
752
"polygonscan_history": "PolygonScan မှတ်တမ်း"
753
}
res/values/strings_nl.arb
+2
@@ -749,6 +749,8 @@
749
"seedtype_polyseed": "Polyseed (16 woorden)",
750
"seed_language_czech": "Tsjechisch",
751
"seed_language_korean": "Koreaans",
752
+ "seed_language_chinese_traditional": "Chinese traditionele)",
753
+ "dfx_option_description": "Koop crypto met EUR & CHF. Tot 990€ zonder extra KYC. Voor particuliere en zakelijke klanten in Europa",
754
"seed_language_chinese_traditional": "Chinese (traditionele)",
755
"polygonscan_history": "PolygonScan-geschiedenis"
756
}
res/values/strings_pl.arb
+1
@@ -750,5 +750,6 @@
750
"seed_language_czech": "Czech",
751
"seed_language_korean": "koreański",
752
"seed_language_chinese_traditional": "Chiński tradycyjny)",
753
+ "dfx_option_description": "Kupuj kryptowaluty za EUR i CHF. Do 990 € bez dodatkowego KYC. Dla klientów detalicznych i korporacyjnych w Europie",
754
"polygonscan_history": "Historia PolygonScan"
755
}
res/values/strings_pt.arb
+1
@@ -749,5 +749,6 @@
749
"seed_language_czech": "Tcheco",
750
"seed_language_korean": "coreano",
751
"seed_language_chinese_traditional": "Chinês tradicional)",
752
+ "dfx_option_description": "Compre criptografia com EUR e CHF. Até 990€ sem KYC adicional. Para clientes de varejo e corporativos na Europa",
753
"polygonscan_history": "História do PolygonScan"
754
}
res/values/strings_ru.arb
+1
@@ -750,5 +750,6 @@
750
"seed_language_czech": "Чешский",
751
"seed_language_korean": "Корейский",
752
"seed_language_chinese_traditional": "Китайский традиционный)",
753
+ "dfx_option_description": "Покупайте криптовалюту за EUR и CHF. До 990€ без дополнительного KYC. Для розничных и корпоративных клиентов в Европе",
754
"polygonscan_history": "История PolygonScan"
755
}
res/values/strings_th.arb
+1
@@ -748,5 +748,6 @@
748
"seed_language_czech": "ภาษาเช็ก",
749
"seed_language_korean": "เกาหลี",
750
"seed_language_chinese_traditional": "จีน (ดั้งเดิม)",
751
+ "dfx_option_description": "ซื้อ crypto ด้วย EUR และ CHF สูงถึง 990€ โดยไม่มี KYC เพิ่มเติม สำหรับลูกค้ารายย่อยและลูกค้าองค์กรในยุโรป",
752
"polygonscan_history": "ประวัติ PolygonScan"
753
}
res/values/strings_tl.arb
+1
@@ -744,5 +744,6 @@
744
"seed_language_czech": "Czech",
745
"seed_language_korean": "Korean",
746
"seed_language_chinese_traditional": "Intsik (tradisyonal)",
747
+ "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",
748
"polygonscan_history": "Kasaysayan ng PolygonScan"
749
}
res/values/strings_tr.arb
+1
@@ -748,5 +748,6 @@
748
"seed_language_czech": "Çek",
749
"seed_language_korean": "Koreli",
750
"seed_language_chinese_traditional": "Çin geleneği)",
751
+ "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",
752
"polygonscan_history": "PolygonScan geçmişi"
753
}
res/values/strings_uk.arb
+1
@@ -749,6 +749,7 @@
749
"seedtype_polyseed": "Полісей (16 слів)",
750
"seed_language_czech": "Чеський",
751
"seed_language_korean": "Корейський",
752
+ "dfx_option_description": "Купуйте криптовалюту за EUR і CHF. До 990 євро без додаткового KYC. Для роздрібних і корпоративних клієнтів у Європі",
753
"seed_language_chinese_traditional": "Китайський (традиційний)",
754
"polygonscan_history": "Історія PolygonScan"
755
}
res/values/strings_ur.arb
+1
@@ -742,5 +742,6 @@
742
"seed_language_czech": "چیک",
743
"seed_language_korean": "کورین",
744
"seed_language_chinese_traditional": "چینی (روایتی)",
745
+ "dfx_option_description": "EUR ﺭﻭﺍ CHF ﯽﻓﺎﺿﺍ ۔ﮟﯾﺪﯾﺮﺧ ﻮﭩﭘﺮﮐ ﮫﺗﺎﺳ ﮯﮐ KYC ﮯﯿﻟ ﮯﮐ ﻦﯿﻓﺭﺎﺻ ﭧﯾﺭﻮﭘﺭﺎﮐ ﺭﻭﺍ ﮦﺩﺭﻮﺧ ﮟ",
746
"polygonscan_history": "ﺦﯾﺭﺎﺗ ﯽﮐ ﻦﯿﮑﺳﺍ ﻥﻮﮔ ﯽﻟﻮﭘ"
747
}
res/values/strings_yo.arb
+1
@@ -744,5 +744,6 @@
744
"seed_language_czech": "Czech",
745
"seed_language_korean": "Ara ẹni",
746
"seed_language_chinese_traditional": "Kannada (ibile)",
747
+ "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",
748
"polygonscan_history": "PolygonScan itan"
749
}
res/values/strings_zh.arb
+1
@@ -749,5 +749,6 @@
749
"seed_language_czech": "捷克",
750
"seed_language_korean": "韩国人",
751
"seed_language_chinese_traditional": "中国传统的)",
752
+ "dfx_option_description": "用欧元和瑞士法郎购买加密货币。高达 990 欧元,无需额外 KYC。对于欧洲的零售和企业客户",
753
"polygonscan_history": "多边形扫描历史"
754
}