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>
This commit is contained in:
Serhii 2023-12-08 16:05:52 +02:00 committed by GitHub
parent d370c754c5
commit 2138c35e38
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 456 additions and 109 deletions

BIN
assets/images/dfx_dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
assets/images/dfx_light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View file

@ -725,8 +725,7 @@ abstract class ElectrumWalletBase
final index = address != null
? walletAddresses.addresses.firstWhere((element) => element.address == address).index
: null;
return index == null
? base64Encode(hd.sign(message))
: base64Encode(hd.derive(index).sign(message));
final HD = index == null ? hd : hd.derive(index);
return base64Encode(HD.signMessage(message));
}
}

View file

@ -22,7 +22,7 @@ dependencies:
bitcoin_flutter:
git:
url: https://github.com/cake-tech/bitcoin_flutter.git
ref: cake-update-v3
ref: cake-update-v4
bitbox:
git:
url: https://github.com/cake-tech/bitbox-flutter.git

View file

@ -302,10 +302,8 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
final index = address != null
? walletAddresses.addresses
.firstWhere((element) => element.address == AddressUtils.toLegacyAddress(address))
.index
: null;
return index == null
? base64Encode(hd.sign(message))
: base64Encode(hd.derive(index).sign(message));
.index : null;
final HD = index == null ? hd : hd.derive(index);
return base64Encode(HD.signMessage(message));
}
}

View file

@ -24,7 +24,7 @@ dependencies:
bitcoin_flutter:
git:
url: https://github.com/cake-tech/bitcoin_flutter.git
ref: cake-update-v3
ref: cake-update-v4
bitbox:
git:
url: https://github.com/cake-tech/bitbox-flutter.git

View file

@ -926,6 +926,8 @@ extern "C"
return m_wallet->trustedDaemon();
}
// Coin Control //
CoinsInfoRow* coin(int index)
{
if (index >= 0 && index < m_coins_info.size()) {
@ -1020,6 +1022,13 @@ extern "C"
m_coins->thaw(index);
}
// Sign Messages //
char *sign_message(char *message, char *address = "")
{
return strdup(get_current_wallet()->signMessage(std::string(message), std::string(address)).c_str());
}
#ifdef __cplusplus
}
#endif

View file

@ -32,7 +32,8 @@ void store(char *path);
void set_trusted_daemon(bool arg);
bool trusted_daemon();
char *sign_message(char *message, char *address);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -149,3 +149,5 @@ typedef coin = Pointer<CoinsInfoRow> Function(Int32 index);
typedef freeze_coin = Void Function(Int32 index);
typedef thaw_coin = Void Function(Int32 index);
typedef sign_message = Pointer<Utf8> Function(Pointer<Utf8> message, Pointer<Utf8> address);

View file

@ -149,3 +149,5 @@ typedef GetCoin = Pointer<CoinsInfoRow> Function(int);
typedef FreezeCoin = void Function(int);
typedef ThawCoin = void Function(int);
typedef SignMessage = Pointer<Utf8> Function(Pointer<Utf8>, Pointer<Utf8>);

View file

@ -8,7 +8,6 @@ import 'package:cw_monero/api/types.dart';
import 'package:cw_monero/api/monero_api.dart';
import 'package:cw_monero/api/exceptions/setup_wallet_exception.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
int _boolToInt(bool value) => value ? 1 : 0;
@ -128,6 +127,10 @@ final trustedDaemonNative = moneroApi
.lookup<NativeFunction<trusted_daemon>>('trusted_daemon')
.asFunction<TrustedDaemon>();
final signMessageNative = moneroApi
.lookup<NativeFunction<sign_message>>('sign_message')
.asFunction<SignMessage>();
int getSyncingHeight() => getSyncingHeightNative();
bool isNeededToRefresh() => isNeededToRefreshNative() != 0;
@ -296,7 +299,7 @@ class SyncListener {
final bchHeight = await getNodeHeightOrUpdate(syncHeight);
if (_lastKnownBlockHeight == syncHeight || syncHeight == null) {
if (_lastKnownBlockHeight == syncHeight) {
return;
}
@ -311,7 +314,7 @@ class SyncListener {
}
// 1. Actual new height; 2. Blocks left to finish; 3. Progress in percents;
onNewBlock?.call(syncHeight, left, ptc);
onNewBlock.call(syncHeight, left, ptc);
});
}
@ -382,4 +385,15 @@ String getSubaddressLabel(int accountIndex, int addressIndex) {
Future setTrustedDaemon(bool trusted) async => setTrustedDaemonNative(_boolToInt(trusted));
Future<bool> trustedDaemon() async => trustedDaemonNative() != 0;
Future<bool> trustedDaemon() async => trustedDaemonNative() != 0;
String signMessage(String message, {String address = ""}) {
final messagePointer = message.toNativeUtf8();
final addressPointer = address.toNativeUtf8();
final signature = convertUTF8ToString(pointer: signMessageNative(messagePointer, addressPointer));
calloc.free(messagePointer);
calloc.free(addressPointer);
return signature;
}

View file

@ -651,4 +651,10 @@ abstract class MoneroWalletBase
@override
void setExceptionHandler(void Function(FlutterErrorDetails) onError) => _onError = onError;
@override
String signMessage(String message, {String? address}) {
final useAddress = address ?? "";
return monero_wallet.signMessage(message, address: useAddress);
}
}

View file

@ -1020,6 +1020,13 @@ extern "C"
m_coins->thaw(index);
}
// Sign Messages //
char *sign_message(char *message, char *address = "")
{
return strdup(get_current_wallet()->signMessage(std::string(message), std::string(address)).c_str());
}
#ifdef __cplusplus
}
#endif

View file

@ -32,7 +32,8 @@ void store(char *path);
void set_trusted_daemon(bool arg);
bool trusted_daemon();
char *sign_message(char *message, char *address);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -0,0 +1,179 @@
import 'dart:convert';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.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:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:url_launcher/url_launcher.dart';
class DFXBuyProvider {
DFXBuyProvider({required WalletBase wallet}) : this._wallet = wallet;
final WalletBase _wallet;
static const _baseUrl = 'api.dfx.swiss';
static const _authPath = '/v1/auth/signMessage';
static const _signUpPath = '/v1/auth/signUp';
static const _signInPath = '/v1/auth/signIn';
static const walletName = 'CakeWallet';
String get assetOut {
switch (_wallet.type) {
case WalletType.bitcoin:
return 'BTC';
case WalletType.bitcoinCash:
return 'BCH';
case WalletType.litecoin:
return 'LTC';
case WalletType.monero:
return 'XMR';
case WalletType.ethereum:
return 'ETH';
default:
throw Exception("WalletType is not available for DFX ${_wallet.type}");
}
}
String get blockchain {
switch (_wallet.type) {
case WalletType.bitcoin:
case WalletType.bitcoinCash:
case WalletType.litecoin:
return 'Bitcoin';
case WalletType.monero:
return 'Monero';
case WalletType.ethereum:
return 'Ethereum';
default:
throw Exception("WalletType is not available for DFX ${_wallet.type}");
}
}
Future<String> getSignMessage() async {
final walletAddress = _wallet.walletAddresses.address;
final uri = Uri.https(_baseUrl, _authPath, {'address': walletAddress});
var response = await http.get(uri, headers: {'accept': 'application/json'});
if (response.statusCode == 200) {
final responseBody = jsonDecode(response.body);
return responseBody['message'] as String;
} else {
throw Exception(
'Failed to get sign message. Status: ${response.statusCode} ${response.body}');
}
}
Future<String> signUp() async {
final signMessage = getSignature(await getSignMessage());
final walletAddress = _wallet.walletAddresses.address;
final requestBody = jsonEncode({
'wallet': walletName,
'address': walletAddress,
'signature': signMessage,
});
final uri = Uri.https(_baseUrl, _signUpPath);
var response = await http.post(uri,
headers: {'Content-Type': 'application/json'}, body: requestBody);
if (response.statusCode == 201) {
final responseBody = jsonDecode(response.body);
return responseBody['accessToken'] as String;
} else {
throw Exception(
'Failed to sign up. Status: ${response.statusCode} ${response.body}');
}
}
Future<String> signIn() async {
final signMessage = getSignature(await getSignMessage());
final walletAddress = _wallet.walletAddresses.address;
final requestBody = jsonEncode({
'address': walletAddress,
'signature': signMessage,
});
final uri = Uri.https(_baseUrl, _signInPath);
var response = await http.post(uri,
headers: {'Content-Type': 'application/json'}, body: requestBody);
if (response.statusCode == 201) {
final responseBody = jsonDecode(response.body);
return responseBody['accessToken'] as String;
} else {
throw Exception(
'Failed to sign in. Status: ${response.statusCode} ${response.body}');
}
}
String getSignature(String message) {
switch (_wallet.type) {
case WalletType.ethereum:
return _wallet.signMessage(message);
case WalletType.monero:
case WalletType.litecoin:
case WalletType.bitcoin:
case WalletType.bitcoinCash:
return _wallet.signMessage(message,
address: _wallet.walletAddresses.address);
default:
throw Exception("WalletType is not available for DFX ${_wallet.type}");
}
}
Future<void> launchProvider(BuildContext context) async {
try {
final assetOut = this.assetOut;
final blockchain = this.blockchain;
String accessToken;
try {
accessToken = await signUp();
} on Exception catch (e) {
if (e.toString().contains('409')) {
accessToken = await signIn();
} else {
rethrow;
}
}
final uri = Uri.https('services.dfx.swiss', '/buy', {
'session': accessToken,
'lang': 'en',
'asset-out': assetOut,
'blockchain': blockchain,
'asset-in': 'EUR',
});
if (await canLaunchUrl(uri)) {
if (DeviceInfo.instance.isMobile) {
Navigator.of(context).pushNamed(Routes.webViewPage,
arguments: [S.of(context).buy, uri]);
} else {
await launchUrl(uri, mode: LaunchMode.externalApplication);
}
} else {
throw Exception('Could not launch URL');
}
} catch (e) {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: "DFX Connect",
alertContent: S.of(context).buy_provider_unavailable + ': $e',
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
}
}
}

View file

@ -227,6 +227,7 @@ import 'package:cake_wallet/core/wallet_loading_service.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/entities/qr_view_data.dart';
import 'buy/dfx/dfx_buy_provider.dart';
import 'core/totp_request_details.dart';
import 'src/screens/settings/desktop_settings/desktop_settings_page.dart';
@ -795,6 +796,9 @@ Future<void> setup({
getIt.registerFactory<RobinhoodBuyProvider>(
() => RobinhoodBuyProvider(wallet: getIt.get<AppStore>().wallet!));
getIt.registerFactory<DFXBuyProvider>(
() => DFXBuyProvider(wallet: getIt.get<AppStore>().wallet!));
getIt.registerFactory<OnRamperBuyProvider>(() => OnRamperBuyProvider(
settingsStore: getIt.get<AppStore>().settingsStore,
wallet: getIt.get<AppStore>().wallet!,
@ -940,7 +944,7 @@ Future<void> setup({
getIt.registerFactory(() => BuyAmountViewModel());
getIt.registerFactory(() => BuyOptionsPage());
getIt.registerFactory(() => BuyOptionsPage(getIt.get<DashboardViewModel>()));
getIt.registerFactory(() {
final wallet = getIt.get<AppStore>().wallet;

View file

@ -1,9 +1,11 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cw_core/wallet_type.dart';
enum BuyProviderType {
AskEachTime,
Robinhood,
Onramper;
Onramper,
DFX;
@override
String toString() {
@ -14,6 +16,42 @@ enum BuyProviderType {
return "Robinhood";
case BuyProviderType.Onramper:
return "Onramper";
case BuyProviderType.DFX:
return "DFX";
}
}
static List<BuyProviderType> getAvailableProviders(WalletType walletType) {
switch (walletType) {
case WalletType.nano:
case WalletType.banano:
return [
BuyProviderType.AskEachTime,
BuyProviderType.Onramper
];
case WalletType.monero:
return [
BuyProviderType.AskEachTime,
BuyProviderType.Onramper,
BuyProviderType.DFX
];
case WalletType.bitcoin:
case WalletType.ethereum:
return [
BuyProviderType.AskEachTime,
BuyProviderType.Onramper,
BuyProviderType.DFX,
BuyProviderType.Robinhood
];
case WalletType.litecoin:
case WalletType.bitcoinCash:
return [
BuyProviderType.AskEachTime,
BuyProviderType.Onramper,
BuyProviderType.Robinhood
];
default:
return [];
}
}
}

View file

@ -1,3 +1,4 @@
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';
@ -44,48 +45,54 @@ class MainActions {
isEnabled: (viewModel) => viewModel.isEnabledBuyAction,
canShow: (viewModel) => viewModel.hasBuyAction,
onTap: (BuildContext context, DashboardViewModel viewModel) async {
if (!viewModel.isEnabledBuyAction) {
await _showErrorDialog(context, S.of(context).unsupported_asset);
return;
}
final defaultBuyProvider = viewModel.defaultBuyProvider;
final walletType = viewModel.type;
if (!viewModel.isEnabledBuyAction) return;
switch (walletType) {
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.bitcoinCash:
switch (defaultBuyProvider) {
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;
}
break;
case WalletType.nano:
case WalletType.banano:
case WalletType.monero:
await getIt.get<OnRamperBuyProvider>().launchProvider(context);
break;
default:
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).buy,
alertContent: S.of(context).unsupported_asset,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
try {
await _launchProviderByType(context, defaultBuyProvider);
} catch (e) {
await _showErrorDialog(context, 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._(
name: (context) => S.of(context).receive,
image: 'assets/images/received.png',

View file

@ -1,18 +1,26 @@
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:cake_wallet/entities/buy_provider_types.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/option_tile.dart';
import 'package:cake_wallet/themes/extensions/option_tile_theme.dart';
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:flutter/material.dart';
class BuyOptionsPage extends BasePage {
BuyOptionsPage(this.dashboardViewModel);
final DashboardViewModel dashboardViewModel;
final iconDarkRobinhood = 'assets/images/robinhood_dark.png';
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
String get title => S.current.buy;
@ -22,11 +30,19 @@ class BuyOptionsPage extends BasePage {
@override
Widget body(BuildContext context) {
final isLightMode = Theme.of(context).extension<OptionTileTheme>()?.useDarkImage ?? false;
final iconRobinhood =
Image.asset(isLightMode ? iconLightRobinhood : iconDarkRobinhood, height: 40, width: 40);
final iconOnramper =
Image.asset(isLightMode ? iconLightOnramper : iconDarkOnramper, height: 40, width: 40);
final isLightMode =
Theme.of(context).extension<OptionTileTheme>()?.useDarkImage ?? false;
final iconRobinhood = Image.asset(
isLightMode ? iconLightRobinhood : iconDarkRobinhood,
height: 40,
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(
child: Center(
@ -34,26 +50,42 @@ class BuyOptionsPage extends BasePage {
constraints: BoxConstraints(maxWidth: 330),
child: Column(
children: [
Padding(
padding: EdgeInsets.only(top: 24),
child: OptionTile(
image: iconOnramper,
title: "Onramper",
description: S.of(context).onramper_option_description,
onPressed: () async =>
await getIt.get<OnRamperBuyProvider>().launchProvider(context),
if (availableProviders.contains(BuyProviderType.Onramper))
Padding(
padding: EdgeInsets.only(top: 24),
child: OptionTile(
image: iconOnramper,
title: "Onramper",
description: S.of(context).onramper_option_description,
onPressed: () async => await getIt
.get<OnRamperBuyProvider>()
.launchProvider(context),
),
),
),
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.Robinhood))
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(),
Padding(
padding: EdgeInsets.fromLTRB(24, 24, 24, 32),
@ -63,7 +95,9 @@ class BuyOptionsPage extends BasePage {
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
color: Theme.of(context)
.extension<TransactionTradeTheme>()!
.detailsTitlesColor,
),
),
),

View file

@ -1,5 +1,3 @@
import 'package:cake_wallet/buy/buy_provider.dart';
import 'package:cake_wallet/entities/buy_provider_types.dart';
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
@ -42,9 +40,10 @@ class OtherSettingsPage extends BasePage {
handler: (BuildContext context) =>
Navigator.of(context).pushNamed(Routes.changeRep),
),
if(_otherSettingsViewModel.isEnabledBuyAction)
SettingsPickerCell(
title: S.current.default_buy_provider,
items: BuyProviderType.values,
items: _otherSettingsViewModel.availableBuyProviders,
displayItem: _otherSettingsViewModel.getBuyProviderType,
selectedItem: _otherSettingsViewModel.buyProviderType,
onItemSelected: _otherSettingsViewModel.onBuyProviderTypeSelected,

View file

@ -36,7 +36,6 @@ import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/entities/action_list_display_mode.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cw_core/set_app_secure_native.dart';
part 'settings_store.g.dart';
class SettingsStore = SettingsStoreBase with _$SettingsStore;
@ -123,7 +122,6 @@ abstract class SettingsStoreBase with Store {
isAppSecure = initialAppSecure,
disableBuy = initialDisableBuy,
disableSell = initialDisableSell,
defaultBuyProvider = initialDefaultBuyProvider,
shouldShowMarketPlaceInDashboard = initialShouldShowMarketPlaceInDashboard,
exchangeStatus = initialExchangeStatus,
currentTheme = initialTheme,
@ -178,6 +176,12 @@ abstract class SettingsStoreBase with Store {
initializeTrocadorProviderStates();
WalletType.values.forEach((walletType) {
final key = 'defaultBuyProvider_${walletType.toString()}';
final providerIndex = sharedPreferences.getInt(key);
defaultBuyProviders[walletType] = providerIndex != null ? BuyProviderType.values[providerIndex] : BuyProviderType.AskEachTime;
});
reaction(
(_) => fiatCurrency,
(FiatCurrency fiatCurrency) => sharedPreferences.setString(
@ -244,9 +248,14 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.setBool(PreferencesKey.disableSellKey, disableSell));
reaction(
(_) => defaultBuyProvider,
(BuyProviderType defaultBuyProvider) =>
sharedPreferences.setInt(PreferencesKey.defaultBuyProvider, defaultBuyProvider.index));
(_) => defaultBuyProviders.asObservable(),
(ObservableMap<WalletType, BuyProviderType> providers) {
providers.forEach((walletType, provider) {
final key = 'defaultBuyProvider_${walletType.toString()}';
sharedPreferences.setInt(key, provider.index);
});
}
);
reaction(
(_) => autoGenerateSubaddressStatus,
@ -333,6 +342,7 @@ abstract class SettingsStoreBase with Store {
reaction((_) => totpSecretKey,
(String totpKey) => sharedPreferences.setString(PreferencesKey.totpSecretKey, totpKey));
reaction(
(_) => numberOfFailedTokenTrials,
(int failedTokenTrail) =>
@ -494,9 +504,6 @@ abstract class SettingsStoreBase with Store {
@observable
bool disableSell;
@observable
BuyProviderType defaultBuyProvider;
@observable
bool allowBiometricalAuthentication;
@ -566,6 +573,9 @@ abstract class SettingsStoreBase with Store {
@observable
ObservableMap<String, bool> trocadorProviderStates = ObservableMap<String, bool>();
@observable
ObservableMap<WalletType, BuyProviderType> defaultBuyProviders = ObservableMap<WalletType, BuyProviderType>();
@observable
SortBalanceBy sortBalanceBy;
@ -1003,8 +1013,6 @@ abstract class SettingsStoreBase with Store {
isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? isAppSecure;
disableBuy = sharedPreferences.getBool(PreferencesKey.disableBuyKey) ?? disableBuy;
disableSell = sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? disableSell;
defaultBuyProvider =
BuyProviderType.values[sharedPreferences.getInt(PreferencesKey.defaultBuyProvider) ?? 0];
allowBiometricalAuthentication =
sharedPreferences.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ??
allowBiometricalAuthentication;

View file

@ -282,10 +282,14 @@ abstract class DashboardViewModelBase with Store {
Map<String, List<FilterItem>> filterItems;
BuyProviderType get defaultBuyProvider => settingsStore.defaultBuyProvider;
BuyProviderType get defaultBuyProvider =>
settingsStore.defaultBuyProviders[wallet.type] ??
BuyProviderType.AskEachTime;
bool get isBuyEnabled => settingsStore.isBitcoinBuyEnabled;
List<BuyProviderType> get availableProviders => BuyProviderType.getAvailableProviders(wallet.type);
bool get shouldShowYatPopup => settingsStore.shouldShowYatPopup;
@action

View file

@ -13,14 +13,15 @@ import 'package:package_info/package_info.dart';
part 'other_settings_view_model.g.dart';
class OtherSettingsViewModel = OtherSettingsViewModelBase with _$OtherSettingsViewModel;
class OtherSettingsViewModel = OtherSettingsViewModelBase
with _$OtherSettingsViewModel;
abstract class OtherSettingsViewModelBase with Store {
OtherSettingsViewModelBase(this._settingsStore, this._wallet)
: walletType = _wallet.type,
currentVersion = '' {
PackageInfo.fromPlatform()
.then((PackageInfo packageInfo) => currentVersion = packageInfo.version);
PackageInfo.fromPlatform().then(
(PackageInfo packageInfo) => currentVersion = packageInfo.version);
final priority = _settingsStore.priority[_wallet.type];
final priorities = priorityForWalletType(_wallet.type);
@ -31,7 +32,8 @@ abstract class OtherSettingsViewModelBase with Store {
}
final WalletType walletType;
final WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> _wallet;
final WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
TransactionInfo> _wallet;
@observable
String currentVersion;
@ -50,15 +52,19 @@ abstract class OtherSettingsViewModelBase with Store {
}
@computed
bool get changeRepresentativeEnabled {
if (_wallet.type == WalletType.nano || _wallet.type == WalletType.banano) {
return true;
}
bool get changeRepresentativeEnabled =>
_wallet.type == WalletType.nano || _wallet.type == WalletType.banano;
return false;
}
BuyProviderType get buyProviderType { return _settingsStore.defaultBuyProvider; }
@computed
bool get isEnabledBuyAction =>
!_settingsStore.disableBuy && _wallet.type != WalletType.haven;
List<BuyProviderType> get availableBuyProviders =>
BuyProviderType.getAvailableProviders(walletType);
BuyProviderType get buyProviderType =>
_settingsStore.defaultBuyProviders[walletType] ??
BuyProviderType.AskEachTime;
String getDisplayPriority(dynamic priority) {
final _priority = priority as TransactionPriority;
@ -73,7 +79,7 @@ abstract class OtherSettingsViewModelBase with Store {
return priority.toString();
}
String getBuyProviderType (dynamic buyProviderType) {
String getBuyProviderType(dynamic buyProviderType) {
final _buyProviderType = buyProviderType as BuyProviderType;
return _buyProviderType.toString();
@ -83,6 +89,5 @@ abstract class OtherSettingsViewModelBase with Store {
_settingsStore.priority[_wallet.type] = priority;
void onBuyProviderTypeSelected(BuyProviderType buyProviderType) =>
_settingsStore.defaultBuyProvider = buyProviderType;
_settingsStore.defaultBuyProviders[walletType] = buyProviderType;
}

View file

@ -96,7 +96,7 @@ dependencies:
bitcoin_flutter:
git:
url: https://github.com/cake-tech/bitcoin_flutter.git
ref: cake-update-v3
ref: cake-update-v4
fluttertoast: 8.1.4
# tor:
# git:

View file

@ -746,5 +746,6 @@
"seed_language_czech": "التشيكية",
"seed_language_korean": "الكورية",
"seed_language_chinese_traditional": "تقاليد صينية)",
"dfx_option_description": "ﺎﺑﻭﺭﻭﺃ ﻲﻓ ﺕﺎﻛﺮﺸﻟﺍﻭ ﺔﺋﺰﺠﺘﻟﺍ ءﻼﻤﻌﻟ .ﻲﻓﺎﺿﺇ KYC ﻥﻭﺪﺑ ﻭﺭﻮﻳ 990 ﻰﻟﺇ ﻞﺼﻳ ﺎﻣ .ﻱﺮﺴﻳﻮﺴﻟﺍ",
"polygonscan_history": "ﻥﺎﻜﺴﻧﻮﺠﻴﻟﻮﺑ ﺦﻳﺭﺎﺗ"
}

View file

@ -742,5 +742,6 @@
"seed_language_czech": "Чех",
"seed_language_korean": "Корейски",
"seed_language_chinese_traditional": "Традиционен китайски)",
"dfx_option_description": "Купете крипто с EUR и CHF. До 990 € без допълнителен KYC. За клиенти на дребно и корпоративни клиенти в Европа",
"polygonscan_history": "История на PolygonScan"
}

View file

@ -742,5 +742,6 @@
"seed_language_czech": "čeština",
"seed_language_korean": "korejština",
"seed_language_chinese_traditional": "Číňan (tradiční)",
"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"
}

View file

@ -750,5 +750,6 @@
"seed_language_czech": "Tschechisch",
"seed_language_korean": "Koreanisch",
"seed_language_chinese_traditional": "Chinesisch (Traditionell)",
"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"
}

View file

@ -751,5 +751,6 @@
"seed_language_czech": "Czech",
"seed_language_korean": "Korean",
"seed_language_chinese_traditional": "Chinese (Traditional)",
"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"
}

View file

@ -749,6 +749,8 @@
"seedtype_polyseed": "Polieta (16 palabras)",
"seed_language_czech": "checo",
"seed_language_korean": "coreano",
"seed_language_chinese_traditional": "Chino tradicional)",
"dfx_option_description": "Compre criptomonedas con EUR y CHF. Hasta 990€ sin KYC adicional. Para clientes minoristas y corporativos en Europa",
"seed_language_chinese_traditional": "Chino (tradicional)",
"polygonscan_history": "Historial de PolygonScan"
}

View file

@ -750,5 +750,6 @@
"seed_language_czech": "tchèque",
"seed_language_korean": "coréen",
"seed_language_chinese_traditional": "Chinois (Traditionnel)",
"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"
}

View file

@ -728,5 +728,6 @@
"seed_language_czech": "Czech",
"seed_language_korean": "Yaren Koriya",
"seed_language_chinese_traditional": "Sinanci (na gargajiya)",
"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"
}

View file

@ -750,5 +750,6 @@
"seed_language_czech": "चेक",
"seed_language_korean": "कोरियाई",
"seed_language_chinese_traditional": "चीनी पारंपरिक)",
"dfx_option_description": "EUR और CHF के साथ क्रिप्टो खरीदें। अतिरिक्त केवाईसी के बिना 990€ तक। यूरोप में खुदरा और कॉर्पोरेट ग्राहकों के लिए",
"polygonscan_history": "पॉलीगॉनस्कैन इतिहास"
}

View file

@ -747,6 +747,8 @@
"seedtype_polyseed": "Poliseed (16 riječi)",
"seed_language_czech": "češki",
"seed_language_korean": "korejski",
"seed_language_chinese_traditional": "Kinesko tradicionalno)",
"dfx_option_description": "Kupujte kripto s EUR i CHF. Do 990 € bez dodatnog KYC-a. Za maloprodajne i poslovne korisnike u Europi",
"seed_language_chinese_traditional": "Kinesko (tradicionalno)",
"polygonscan_history": "Povijest PolygonScan"
}

View file

@ -737,6 +737,8 @@
"seedtype_polyseed": "Polyseed (16 kata)",
"seed_language_czech": "Ceko",
"seed_language_korean": "Korea",
"seed_language_chinese_traditional": "Cina tradisional)",
"dfx_option_description": "Beli kripto dengan EUR & CHF. Hingga 990€ tanpa KYC tambahan. Untuk pelanggan ritel dan korporat di Eropa",
"seed_language_chinese_traditional": "Cina (tradisional)",
"polygonscan_history": "Sejarah PolygonScan"
}

View file

@ -750,5 +750,6 @@
"seed_language_czech": "ceco",
"seed_language_korean": "coreano",
"seed_language_chinese_traditional": "Cinese tradizionale)",
"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"
}

View file

@ -750,5 +750,6 @@
"seed_language_czech": "チェコ",
"seed_language_korean": "韓国語",
"seed_language_chinese_traditional": "中国の伝統的な)",
"dfx_option_description": "EUR と CHF で暗号通貨を購入します。追加のKYCなしで最大990ユーロ。ヨーロッパの小売および法人顧客向け",
"polygonscan_history": "ポリゴンスキャン履歴"
}

View file

@ -748,5 +748,6 @@
"seed_language_czech": "체코 사람",
"seed_language_korean": "한국인",
"seed_language_chinese_traditional": "중국 전통)",
"dfx_option_description": "EUR 및 CHF로 암호화폐를 구매하세요. 추가 KYC 없이 최대 990€. 유럽의 소매 및 기업 고객용",
"polygonscan_history": "다각형 스캔 기록"
}

View file

@ -748,5 +748,6 @@
"seed_language_czech": "ချက်",
"seed_language_korean": "ကိုးရီးယား",
"seed_language_chinese_traditional": "တရုတ်ရိုးရာ)",
"dfx_option_description": "EUR & CHF ဖြင့် crypto ကိုဝယ်ပါ။ အပို KYC မပါဘဲ 990€ အထိ။ ဥရောပရှိ လက်လီရောင်းချသူများနှင့် ကော်ပိုရိတ်ဖောက်သည်များအတွက်",
"polygonscan_history": "PolygonScan မှတ်တမ်း"
}

View file

@ -749,6 +749,8 @@
"seedtype_polyseed": "Polyseed (16 woorden)",
"seed_language_czech": "Tsjechisch",
"seed_language_korean": "Koreaans",
"seed_language_chinese_traditional": "Chinese traditionele)",
"dfx_option_description": "Koop crypto met EUR & CHF. Tot 990€ zonder extra KYC. Voor particuliere en zakelijke klanten in Europa",
"seed_language_chinese_traditional": "Chinese (traditionele)",
"polygonscan_history": "PolygonScan-geschiedenis"
}

View file

@ -750,5 +750,6 @@
"seed_language_czech": "Czech",
"seed_language_korean": "koreański",
"seed_language_chinese_traditional": "Chiński tradycyjny)",
"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"
}

View file

@ -749,5 +749,6 @@
"seed_language_czech": "Tcheco",
"seed_language_korean": "coreano",
"seed_language_chinese_traditional": "Chinês tradicional)",
"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"
}

View file

@ -750,5 +750,6 @@
"seed_language_czech": "Чешский",
"seed_language_korean": "Корейский",
"seed_language_chinese_traditional": "Китайский традиционный)",
"dfx_option_description": "Покупайте криптовалюту за EUR и CHF. До 990€ без дополнительного KYC. Для розничных и корпоративных клиентов в Европе",
"polygonscan_history": "История PolygonScan"
}

View file

@ -748,5 +748,6 @@
"seed_language_czech": "ภาษาเช็ก",
"seed_language_korean": "เกาหลี",
"seed_language_chinese_traditional": "จีน (ดั้งเดิม)",
"dfx_option_description": "ซื้อ crypto ด้วย EUR และ CHF สูงถึง 990€ โดยไม่มี KYC เพิ่มเติม สำหรับลูกค้ารายย่อยและลูกค้าองค์กรในยุโรป",
"polygonscan_history": "ประวัติ PolygonScan"
}

View file

@ -744,5 +744,6 @@
"seed_language_czech": "Czech",
"seed_language_korean": "Korean",
"seed_language_chinese_traditional": "Intsik (tradisyonal)",
"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"
}

View file

@ -748,5 +748,6 @@
"seed_language_czech": "Çek",
"seed_language_korean": "Koreli",
"seed_language_chinese_traditional": "Çin geleneği)",
"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"
}

View file

@ -749,6 +749,7 @@
"seedtype_polyseed": "Полісей (16 слів)",
"seed_language_czech": "Чеський",
"seed_language_korean": "Корейський",
"dfx_option_description": "Купуйте криптовалюту за EUR і CHF. До 990 євро без додаткового KYC. Для роздрібних і корпоративних клієнтів у Європі",
"seed_language_chinese_traditional": "Китайський (традиційний)",
"polygonscan_history": "Історія PolygonScan"
}

View file

@ -742,5 +742,6 @@
"seed_language_czech": "چیک",
"seed_language_korean": "کورین",
"seed_language_chinese_traditional": "چینی (روایتی)",
"dfx_option_description": "EUR ﺭﻭﺍ CHF ﯽﻓﺎﺿﺍ ۔ﮟﯾﺪﯾﺮﺧ ﻮﭩﭘﺮﮐ ﮫﺗﺎﺳ ﮯﮐ KYC ﮯﯿﻟ ﮯﮐ ﻦﯿﻓﺭﺎﺻ ﭧﯾﺭﻮﭘﺭﺎﮐ ﺭﻭﺍ ﮦﺩﺭﻮﺧ ﮟ",
"polygonscan_history": "ﺦﯾﺭﺎﺗ ﯽﮐ ﻦﯿﮑﺳﺍ ﻥﻮﮔ ﯽﻟﻮﭘ"
}

View file

@ -744,5 +744,6 @@
"seed_language_czech": "Czech",
"seed_language_korean": "Ara ẹni",
"seed_language_chinese_traditional": "Kannada (ibile)",
"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"
}

View file

@ -749,5 +749,6 @@
"seed_language_czech": "捷克",
"seed_language_korean": "韩国人",
"seed_language_chinese_traditional": "中国传统的)",
"dfx_option_description": "用欧元和瑞士法郎购买加密货币。高达 990 欧元,无需额外 KYC。对于欧洲的零售和企业客户",
"polygonscan_history": "多边形扫描历史"
}