feat: fix rescan & stop, new card

This commit is contained in:
Rafael Saes 2024-11-24 19:05:09 -03:00
parent b7ff9ab32b
commit e16a2180c4
41 changed files with 406 additions and 178 deletions

View file

@ -35,6 +35,13 @@ part 'bitcoin_wallet.g.dart';
class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet; class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet;
abstract class BitcoinWalletBase extends ElectrumWallet with Store { abstract class BitcoinWalletBase extends ElectrumWallet with Store {
@observable
bool nodeSupportsSilentPayments = true;
@observable
bool silentPaymentsScanningActive = false;
@observable
bool allowedToSwitchNodesForScanning = false;
BitcoinWalletBase({ BitcoinWalletBase({
required String password, required String password,
required WalletInfo walletInfo, required WalletInfo walletInfo,
@ -307,63 +314,68 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
} }
Future<bool> getNodeIsElectrs() async { Future<bool> getNodeIsElectrs() async {
if (node?.uri.host.contains("electrs") ?? false) { if (node?.isElectrs != null) {
return true;
}
final version = await sendWorker(ElectrumWorkerGetVersionRequest());
if (version is List<String> && version.isNotEmpty) {
final server = version[0];
if (server.toLowerCase().contains('electrs')) {
node!.isElectrs = true;
node!.save();
return node!.isElectrs!;
}
} else if (version is String && version.toLowerCase().contains('electrs')) {
node!.isElectrs = true;
node!.save();
return node!.isElectrs!; return node!.isElectrs!;
} }
node!.isElectrs = false; final isNamedElectrs = node?.uri.host.contains("electrs") ?? false;
if (isNamedElectrs) {
node!.isElectrs = true;
}
final isNamedFulcrum = node!.uri.host.contains("fulcrum");
if (isNamedFulcrum) {
node!.isElectrs = false;
}
if (node!.isElectrs == null) {
final version = await sendWorker(ElectrumWorkerGetVersionRequest());
if (version is List<String> && version.isNotEmpty) {
final server = version[0];
if (server.toLowerCase().contains('electrs')) {
node!.isElectrs = true;
}
} else if (version is String && version.toLowerCase().contains('electrs')) {
node!.isElectrs = true;
} else {
node!.isElectrs = false;
}
}
node!.save(); node!.save();
return node!.isElectrs!; return node!.isElectrs!;
} }
Future<bool> getNodeSupportsSilentPayments() async { Future<bool> getNodeSupportsSilentPayments() async {
// TODO: handle disconnection on check if (node?.supportsSilentPayments != null) {
// TODO: use cached values return node!.supportsSilentPayments!;
if (node == null) {
return false;
}
final isFulcrum = node!.uri.host.contains("fulcrum");
if (isFulcrum) {
return false;
} }
// As of today (august 2024), only ElectrumRS supports silent payments // As of today (august 2024), only ElectrumRS supports silent payments
if (!(await getNodeIsElectrs())) { final isElectrs = await getNodeIsElectrs();
return false; if (!isElectrs) {
node!.supportsSilentPayments = false;
} }
try { if (node!.supportsSilentPayments == null) {
final workerResponse = (await sendWorker(ElectrumWorkerCheckTweaksRequest())) as String; try {
final tweaksResponse = ElectrumWorkerCheckTweaksResponse.fromJson( final workerResponse = (await sendWorker(ElectrumWorkerCheckTweaksRequest())) as String;
json.decode(workerResponse) as Map<String, dynamic>, final tweaksResponse = ElectrumWorkerCheckTweaksResponse.fromJson(
); json.decode(workerResponse) as Map<String, dynamic>,
final supportsScanning = tweaksResponse.result == true; );
final supportsScanning = tweaksResponse.result == true;
if (supportsScanning) { if (supportsScanning) {
node!.supportsSilentPayments = true; node!.supportsSilentPayments = true;
node!.save(); } else {
return node!.supportsSilentPayments!; node!.supportsSilentPayments = false;
}
} catch (_) {
node!.supportsSilentPayments = false;
} }
} catch (_) {} }
node!.supportsSilentPayments = false;
node!.save(); node!.save();
return node!.supportsSilentPayments!; return node!.supportsSilentPayments!;
} }
@ -437,8 +449,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
@action @action
Future<void> setSilentPaymentsScanning(bool active) async { Future<void> setSilentPaymentsScanning(bool active) async {
silentPaymentsScanningActive = active; silentPaymentsScanningActive = active;
final nodeSupportsSilentPayments = await getNodeSupportsSilentPayments();
final isAllowedToScan = nodeSupportsSilentPayments || allowedToSwitchNodesForScanning;
if (active) { if (active && isAllowedToScan) {
syncStatus = AttemptingScanSyncStatus(); syncStatus = AttemptingScanSyncStatus();
final tip = currentChainTip!; final tip = currentChainTip!;
@ -730,8 +744,6 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
await walletInfo.updateRestoreHeight(height); await walletInfo.updateRestoreHeight(height);
} }
} }
await save();
} }
@action @action
@ -765,6 +777,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
.map((addr) => addr.labelIndex) .map((addr) => addr.labelIndex)
.toList(), .toList(),
isSingleScan: doSingleScan ?? false, isSingleScan: doSingleScan ?? false,
shouldSwitchNodes:
!(await getNodeSupportsSilentPayments()) && allowedToSwitchNodesForScanning,
), ),
).toJson(), ).toJson(),
); );

View file

@ -276,11 +276,6 @@ abstract class ElectrumWalletBase
@override @override
bool isTestnet; bool isTestnet;
@observable
bool nodeSupportsSilentPayments = true;
@observable
bool silentPaymentsScanningActive = false;
bool _isTryingToConnect = false; bool _isTryingToConnect = false;
Completer<SharedPreferences> sharedPrefs = Completer(); Completer<SharedPreferences> sharedPrefs = Completer();

View file

@ -14,17 +14,15 @@ import 'package:cw_bitcoin/electrum_worker/electrum_worker_methods.dart';
import 'package:cw_bitcoin/electrum_worker/electrum_worker_params.dart'; import 'package:cw_bitcoin/electrum_worker/electrum_worker_params.dart';
import 'package:cw_bitcoin/electrum_worker/methods/methods.dart'; import 'package:cw_bitcoin/electrum_worker/methods/methods.dart';
import 'package:cw_core/sync_status.dart'; import 'package:cw_core/sync_status.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:rxdart/rxdart.dart';
import 'package:sp_scanner/sp_scanner.dart'; import 'package:sp_scanner/sp_scanner.dart';
class ElectrumWorker { class ElectrumWorker {
final SendPort sendPort; final SendPort sendPort;
ElectrumApiProvider? _electrumClient; ElectrumApiProvider? _electrumClient;
BasedUtxoNetwork? _network; BasedUtxoNetwork? _network;
bool _isScanning = false; BehaviorSubject<Map<String, dynamic>>? _scanningStream;
bool _stopScanRequested = false;
ElectrumWorker._(this.sendPort, {ElectrumApiProvider? electrumClient}) ElectrumWorker._(this.sendPort, {ElectrumApiProvider? electrumClient})
: _electrumClient = electrumClient; : _electrumClient = electrumClient;
@ -47,7 +45,7 @@ class ElectrumWorker {
} }
void handleMessage(dynamic message) async { void handleMessage(dynamic message) async {
print("Worker received message: $message"); print("Worker: received message: $message");
try { try {
Map<String, dynamic> messageJson; Map<String, dynamic> messageJson;
@ -105,27 +103,15 @@ class ElectrumWorker {
); );
break; break;
case ElectrumWorkerMethods.stopScanningMethod: case ElectrumWorkerMethods.stopScanningMethod:
print("Worker: received message: $message");
await _handleStopScanning( await _handleStopScanning(
ElectrumWorkerStopScanningRequest.fromJson(messageJson), ElectrumWorkerStopScanningRequest.fromJson(messageJson),
); );
break; break;
case ElectrumRequestMethods.tweaksSubscribeMethod: case ElectrumRequestMethods.tweaksSubscribeMethod:
if (_isScanning) { await _handleScanSilentPayments(
_stopScanRequested = false; ElectrumWorkerTweaksSubscribeRequest.fromJson(messageJson),
} );
if (!_stopScanRequested) {
await _handleScanSilentPayments(
ElectrumWorkerTweaksSubscribeRequest.fromJson(messageJson),
);
} else {
_stopScanRequested = false;
_sendResponse(
ElectrumWorkerTweaksSubscribeResponse(
result: TweaksSyncResponse(syncStatus: SyncedSyncStatus()),
),
);
}
break; break;
case ElectrumRequestMethods.estimateFeeMethod: case ElectrumRequestMethods.estimateFeeMethod:
@ -550,28 +536,25 @@ class ElectrumWorker {
} }
Future<void> _handleStopScanning(ElectrumWorkerStopScanningRequest request) async { Future<void> _handleStopScanning(ElectrumWorkerStopScanningRequest request) async {
_stopScanRequested = true; _scanningStream?.close();
_scanningStream = null;
_sendResponse( _sendResponse(
ElectrumWorkerStopScanningResponse(result: true, id: request.id), ElectrumWorkerStopScanningResponse(result: true, id: request.id),
); );
} }
Future<void> _handleScanSilentPayments(ElectrumWorkerTweaksSubscribeRequest request) async { Future<void> _handleScanSilentPayments(ElectrumWorkerTweaksSubscribeRequest request) async {
_isScanning = true;
final scanData = request.scanData; final scanData = request.scanData;
// TODO: confirmedSwitch use new connection var scanningClient = _electrumClient;
// final _electrumClient = await ElectrumApiProvider.connect(
// ElectrumTCPService.connect(
// Uri.parse("tcp://electrs.cakewallet.com:50001"),
// onConnectionStatusChange: (status) {
// _sendResponse(
// ElectrumWorkerConnectionResponse(status: status, id: request.id),
// );
// },
// ),
// );
if (scanData.shouldSwitchNodes) {
scanningClient = await ElectrumApiProvider.connect(
ElectrumTCPService.connect(
Uri.parse("tcp://electrs.cakewallet.com:50001"),
),
);
}
int syncHeight = scanData.height; int syncHeight = scanData.height;
int initialSyncHeight = syncHeight; int initialSyncHeight = syncHeight;
@ -587,6 +570,15 @@ class ElectrumWorker {
}, },
); );
int getCountPerRequest(int syncHeight) {
if (scanData.isSingleScan) {
return 1;
}
final amountLeft = scanData.chainTip - syncHeight + 1;
return amountLeft;
}
// Initial status UI update, send how many blocks in total to scan // Initial status UI update, send how many blocks in total to scan
_sendResponse(ElectrumWorkerTweaksSubscribeResponse( _sendResponse(ElectrumWorkerTweaksSubscribeResponse(
result: TweaksSyncResponse( result: TweaksSyncResponse(
@ -597,17 +589,16 @@ class ElectrumWorker {
final req = ElectrumTweaksSubscribe( final req = ElectrumTweaksSubscribe(
height: syncHeight, height: syncHeight,
count: 1, count: getCountPerRequest(syncHeight),
historicalMode: false, historicalMode: false,
); );
final stream = await _electrumClient!.subscribe(req); _scanningStream = await scanningClient!.subscribe(req);
void listenFn(Map<String, dynamic> event, ElectrumTweaksSubscribe req) { void listenFn(Map<String, dynamic> event, ElectrumTweaksSubscribe req) {
final response = req.onResponse(event); final response = req.onResponse(event);
if (_stopScanRequested || response == null) {
_stopScanRequested = false; if (response == null || _scanningStream == null) {
_isScanning = false;
return; return;
} }
@ -623,10 +614,10 @@ class ElectrumWorker {
final nextHeight = syncHeight + 1; final nextHeight = syncHeight + 1;
if (nextHeight <= scanData.chainTip) { if (nextHeight <= scanData.chainTip) {
final nextStream = _electrumClient!.subscribe( final nextStream = scanningClient!.subscribe(
ElectrumTweaksSubscribe( ElectrumTweaksSubscribe(
height: nextHeight, height: nextHeight,
count: 1, count: getCountPerRequest(nextHeight),
historicalMode: false, historicalMode: false,
), ),
); );
@ -710,6 +701,7 @@ class ElectrumWorker {
receivingOutputAddress, receivingOutputAddress,
labelIndex: 1, // TODO: get actual index/label labelIndex: 1, // TODO: get actual index/label
isUsed: true, isUsed: true,
// TODO: use right wallet
spendKey: scanData.silentPaymentsWallets.first.b_spend.tweakAdd( spendKey: scanData.silentPaymentsWallets.first.b_spend.tweakAdd(
BigintUtils.fromBytes(BytesUtils.fromHexString(t_k)), BigintUtils.fromBytes(BytesUtils.fromHexString(t_k)),
), ),
@ -753,24 +745,27 @@ class ElectrumWorker {
), ),
); );
stream?.close(); _scanningStream?.close();
_scanningStream = null;
return; return;
} }
} }
stream?.listen((event) => listenFn(event, req)); _scanningStream?.listen((event) => listenFn(event, req));
_isScanning = false;
} }
Future<void> _handleGetVersion(ElectrumWorkerGetVersionRequest request) async { Future<void> _handleGetVersion(ElectrumWorkerGetVersionRequest request) async {
_sendResponse(ElectrumWorkerGetVersionResponse( _sendResponse(
result: (await _electrumClient!.request( ElectrumWorkerGetVersionResponse(
result: await _electrumClient!.request(
ElectrumVersion( ElectrumVersion(
clientName: "", clientName: "",
protocolVersion: ["1.4"], protocolVersion: "1.4",
), ),
)), ),
id: request.id)); id: request.id,
),
);
} }
} }

View file

@ -9,6 +9,7 @@ class ScanData {
final Map<String, String> labels; final Map<String, String> labels;
final List<int> labelIndexes; final List<int> labelIndexes;
final bool isSingleScan; final bool isSingleScan;
final bool shouldSwitchNodes;
ScanData({ ScanData({
required this.silentPaymentsWallets, required this.silentPaymentsWallets,
@ -19,6 +20,7 @@ class ScanData {
required this.labels, required this.labels,
required this.labelIndexes, required this.labelIndexes,
required this.isSingleScan, required this.isSingleScan,
required this.shouldSwitchNodes,
}); });
factory ScanData.fromHeight(ScanData scanData, int newHeight) { factory ScanData.fromHeight(ScanData scanData, int newHeight) {
@ -31,6 +33,7 @@ class ScanData {
labels: scanData.labels, labels: scanData.labels,
labelIndexes: scanData.labelIndexes, labelIndexes: scanData.labelIndexes,
isSingleScan: scanData.isSingleScan, isSingleScan: scanData.isSingleScan,
shouldSwitchNodes: scanData.shouldSwitchNodes,
); );
} }
@ -44,6 +47,7 @@ class ScanData {
'labels': labels, 'labels': labels,
'labelIndexes': labelIndexes, 'labelIndexes': labelIndexes,
'isSingleScan': isSingleScan, 'isSingleScan': isSingleScan,
'shouldSwitchNodes': shouldSwitchNodes,
}; };
} }
@ -60,6 +64,7 @@ class ScanData {
labels: json['labels'] as Map<String, String>, labels: json['labels'] as Map<String, String>,
labelIndexes: (json['labelIndexes'] as List).map((e) => e as int).toList(), labelIndexes: (json['labelIndexes'] as List).map((e) => e as int).toList(),
isSingleScan: json['isSingleScan'] as bool, isSingleScan: json['isSingleScan'] as bool,
shouldSwitchNodes: json['shouldSwitchNodes'] as bool,
); );
} }
} }

View file

@ -288,6 +288,11 @@ class CWBitcoin extends Bitcoin {
return BitcoinReceivePageOption.fromType(bitcoinWallet.walletAddresses.addressPageType); return BitcoinReceivePageOption.fromType(bitcoinWallet.walletAddresses.addressPageType);
} }
@override
bool isReceiveOptionSP(ReceivePageOption option) {
return option.value == BitcoinReceivePageOption.silent_payments.value;
}
@override @override
bool hasSelectedSilentPayments(Object wallet) { bool hasSelectedSilentPayments(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet; final bitcoinWallet = wallet as ElectrumWallet;
@ -610,7 +615,7 @@ class CWBitcoin extends Bitcoin {
@override @override
@computed @computed
bool getScanningActive(Object wallet) { bool getScanningActive(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet; final bitcoinWallet = wallet as BitcoinWallet;
return bitcoinWallet.silentPaymentsScanningActive; return bitcoinWallet.silentPaymentsScanningActive;
} }
@ -620,6 +625,12 @@ class CWBitcoin extends Bitcoin {
bitcoinWallet.setSilentPaymentsScanning(active); bitcoinWallet.setSilentPaymentsScanning(active);
} }
@override
Future<void> allowToSwitchNodesForScanning(Object wallet, bool allow) async {
final bitcoinWallet = wallet as BitcoinWallet;
bitcoinWallet.allowedToSwitchNodesForScanning = allow;
}
@override @override
bool isTestnet(Object wallet) { bool isTestnet(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet; final bitcoinWallet = wallet as ElectrumWallet;

View file

@ -472,10 +472,14 @@ Future<void> setup({
getIt.get<SeedSettingsViewModel>(), getIt.get<SeedSettingsViewModel>(),
type: type)); type: type));
getIt.registerFactory<WalletAddressListViewModel>(() => WalletAddressListViewModel( getIt.registerFactoryParam<WalletAddressListViewModel, ReceivePageOption?, void>(
(ReceivePageOption? addressType, _) => WalletAddressListViewModel(
appStore: getIt.get<AppStore>(), appStore: getIt.get<AppStore>(),
yatStore: getIt.get<YatStore>(), yatStore: getIt.get<YatStore>(),
fiatConversionStore: getIt.get<FiatConversionStore>())); fiatConversionStore: getIt.get<FiatConversionStore>(),
addressType: addressType,
),
);
getIt.registerFactory(() => BalanceViewModel( getIt.registerFactory(() => BalanceViewModel(
appStore: getIt.get<AppStore>(), appStore: getIt.get<AppStore>(),
@ -704,12 +708,26 @@ Future<void> setup({
getIt.get<ReceiveOptionViewModel>(param1: pageOption)); getIt.get<ReceiveOptionViewModel>(param1: pageOption));
}); });
getIt.registerFactory<ReceivePage>( getIt.registerFactoryParam<ReceivePage, ReceivePageOption?, void>(
() => ReceivePage(addressListViewModel: getIt.get<WalletAddressListViewModel>())); (ReceivePageOption? addressType, _) => ReceivePage(
getIt.registerFactory<AddressPage>(() => AddressPage( addressListViewModel: getIt.get<WalletAddressListViewModel>(
addressListViewModel: getIt.get<WalletAddressListViewModel>(), param1: addressType,
),
),
);
getIt.registerFactoryParam<AddressPage, ReceivePageOption?, void>(
(ReceivePageOption? addressType, _) => AddressPage(
addressListViewModel: getIt.get<WalletAddressListViewModel>(
param1: addressType,
),
dashboardViewModel: getIt.get<DashboardViewModel>(), dashboardViewModel: getIt.get<DashboardViewModel>(),
receiveOptionViewModel: getIt.get<ReceiveOptionViewModel>())); receiveOptionViewModel: getIt.get<ReceiveOptionViewModel>(
param1: addressType,
),
addressType: addressType,
),
);
getIt.registerFactoryParam<WalletAddressEditOrCreateViewModel, WalletAddressListItem?, void>( getIt.registerFactoryParam<WalletAddressEditOrCreateViewModel, WalletAddressListItem?, void>(
(WalletAddressListItem? item, _) => (WalletAddressListItem? item, _) =>
@ -904,11 +922,11 @@ Future<void> setup({
getIt.registerFactory(() => WalletKeysViewModel(getIt.get<AppStore>())); getIt.registerFactory(() => WalletKeysViewModel(getIt.get<AppStore>()));
getIt.registerFactory(() => WalletKeysPage(getIt.get<WalletKeysViewModel>())); getIt.registerFactory(() => WalletKeysPage(getIt.get<WalletKeysViewModel>()));
getIt.registerFactory(() => AnimatedURModel(getIt.get<AppStore>())); getIt.registerFactory(() => AnimatedURModel(getIt.get<AppStore>()));
getIt.registerFactoryParam<AnimatedURPage, String, void>((String urQr, _) => getIt.registerFactoryParam<AnimatedURPage, String, void>(
AnimatedURPage(getIt.get<AnimatedURModel>(), urQr: urQr)); (String urQr, _) => AnimatedURPage(getIt.get<AnimatedURModel>(), urQr: urQr));
getIt.registerFactoryParam<ContactViewModel, ContactRecord?, void>( getIt.registerFactoryParam<ContactViewModel, ContactRecord?, void>(
(ContactRecord? contact, _) => ContactViewModel(_contactSource, contact: contact)); (ContactRecord? contact, _) => ContactViewModel(_contactSource, contact: contact));
@ -1004,8 +1022,8 @@ Future<void> setup({
)); ));
getIt.registerFactory<MeldBuyProvider>(() => MeldBuyProvider( getIt.registerFactory<MeldBuyProvider>(() => MeldBuyProvider(
wallet: getIt.get<AppStore>().wallet!, wallet: getIt.get<AppStore>().wallet!,
)); ));
getIt.registerFactoryParam<WebViewPage, String, Uri>((title, uri) => WebViewPage(title, uri)); getIt.registerFactoryParam<WebViewPage, String, Uri>((title, uri) => WebViewPage(title, uri));
@ -1207,16 +1225,15 @@ Future<void> setup({
final items = args.first as List<SelectableItem>; final items = args.first as List<SelectableItem>;
final pickAnOption = args[1] as void Function(SelectableOption option)?; final pickAnOption = args[1] as void Function(SelectableOption option)?;
final confirmOption = args[2] as void Function(BuildContext contex)?; final confirmOption = args[2] as void Function(BuildContext contex)?;
return BuyOptionsPage( return BuyOptionsPage(items: items, pickAnOption: pickAnOption, confirmOption: confirmOption);
items: items, pickAnOption: pickAnOption, confirmOption: confirmOption);
}); });
getIt.registerFactoryParam<PaymentMethodOptionsPage, List<dynamic>, void>((List<dynamic> args, _) { getIt
.registerFactoryParam<PaymentMethodOptionsPage, List<dynamic>, void>((List<dynamic> args, _) {
final items = args.first as List<SelectableOption>; final items = args.first as List<SelectableOption>;
final pickAnOption = args[1] as void Function(SelectableOption option)?; final pickAnOption = args[1] as void Function(SelectableOption option)?;
return PaymentMethodOptionsPage( return PaymentMethodOptionsPage(items: items, pickAnOption: pickAnOption);
items: items, pickAnOption: pickAnOption);
}); });
getIt.registerFactory(() { getIt.registerFactory(() {
@ -1300,9 +1317,8 @@ Future<void> setup({
getIt.registerFactory<CakePayService>( getIt.registerFactory<CakePayService>(
() => CakePayService(getIt.get<SecureStorage>(), getIt.get<CakePayApi>())); () => CakePayService(getIt.get<SecureStorage>(), getIt.get<CakePayApi>()));
getIt.registerFactory( getIt.registerFactory(() => CakePayCardsListViewModel(
() => CakePayCardsListViewModel(cakePayService: getIt.get<CakePayService>(), cakePayService: getIt.get<CakePayService>(), settingsStore: getIt.get<SettingsStore>()));
settingsStore: getIt.get<SettingsStore>()));
getIt.registerFactory(() => CakePayAuthViewModel(cakePayService: getIt.get<CakePayService>())); getIt.registerFactory(() => CakePayAuthViewModel(cakePayService: getIt.get<CakePayService>()));

View file

@ -124,6 +124,7 @@ import 'package:cake_wallet/wallet_types.g.dart';
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/nano_account.dart'; import 'package:cw_core/nano_account.dart';
import 'package:cw_core/node.dart'; import 'package:cw_core/node.dart';
import 'package:cw_core/receive_page_option.dart';
import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/unspent_coin_type.dart'; import 'package:cw_core/unspent_coin_type.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
@ -376,11 +377,21 @@ Route<dynamic> createRoute(RouteSettings settings) {
fullscreenDialog: true, builder: (_) => getIt.get<SendTemplatePage>()); fullscreenDialog: true, builder: (_) => getIt.get<SendTemplatePage>());
case Routes.receive: case Routes.receive:
return CupertinoPageRoute<void>(builder: (_) => getIt.get<ReceivePage>()); final args = settings.arguments as Map<String, dynamic>?;
final addressType = args?['addressType'] as ReceivePageOption?;
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<ReceivePage>(param1: addressType),
);
case Routes.addressPage: case Routes.addressPage:
final args = settings.arguments as Map<String, dynamic>?;
final addressType = args?['addressType'] as ReceivePageOption?;
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<AddressPage>()); fullscreenDialog: true,
builder: (_) => getIt.get<AddressPage>(param1: addressType),
);
case Routes.transactionDetails: case Routes.transactionDetails:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
@ -588,7 +599,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.paymentMethodOptionsPage: case Routes.paymentMethodOptionsPage:
final args = settings.arguments as List; final args = settings.arguments as List;
return MaterialPageRoute<void>(builder: (_) => getIt.get<PaymentMethodOptionsPage>(param1: args)); return MaterialPageRoute<void>(
builder: (_) => getIt.get<PaymentMethodOptionsPage>(param1: args));
case Routes.buyWebView: case Routes.buyWebView:
final args = settings.arguments as List; final args = settings.arguments as List;
@ -751,7 +763,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
return MaterialPageRoute<void>(builder: (_) => getIt.get<Setup2FAInfoPage>()); return MaterialPageRoute<void>(builder: (_) => getIt.get<Setup2FAInfoPage>());
case Routes.urqrAnimatedPage: case Routes.urqrAnimatedPage:
return MaterialPageRoute<void>(builder: (_) => getIt.get<AnimatedURPage>(param1: settings.arguments)); return MaterialPageRoute<void>(
builder: (_) => getIt.get<AnimatedURPage>(param1: settings.arguments));
case Routes.homeSettings: case Routes.homeSettings:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(

View file

@ -32,9 +32,11 @@ class AddressPage extends BasePage {
required this.addressListViewModel, required this.addressListViewModel,
required this.dashboardViewModel, required this.dashboardViewModel,
required this.receiveOptionViewModel, required this.receiveOptionViewModel,
ReceivePageOption? addressType,
}) : _cryptoAmountFocus = FocusNode(), }) : _cryptoAmountFocus = FocusNode(),
_formKey = GlobalKey<FormState>(), _formKey = GlobalKey<FormState>(),
_amountController = TextEditingController() { _amountController = TextEditingController(),
_addressType = addressType {
_amountController.addListener(() { _amountController.addListener(() {
if (_formKey.currentState!.validate()) { if (_formKey.currentState!.validate()) {
addressListViewModel.changeAmount( addressListViewModel.changeAmount(
@ -49,6 +51,7 @@ class AddressPage extends BasePage {
final ReceiveOptionViewModel receiveOptionViewModel; final ReceiveOptionViewModel receiveOptionViewModel;
final TextEditingController _amountController; final TextEditingController _amountController;
final GlobalKey<FormState> _formKey; final GlobalKey<FormState> _formKey;
ReceivePageOption? _addressType;
final FocusNode _cryptoAmountFocus; final FocusNode _cryptoAmountFocus;
@ -190,7 +193,11 @@ class AddressPage extends BasePage {
if (addressListViewModel.hasAddressList) { if (addressListViewModel.hasAddressList) {
return SelectButton( return SelectButton(
text: addressListViewModel.buttonTitle, text: addressListViewModel.buttonTitle,
onTap: () => Navigator.of(context).pushNamed(Routes.receive), onTap: () => Navigator.pushNamed(
context,
Routes.receive,
arguments: {'addressType': _addressType},
),
textColor: Theme.of(context).extension<SyncIndicatorTheme>()!.textColor, textColor: Theme.of(context).extension<SyncIndicatorTheme>()!.textColor,
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor, color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
borderColor: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor, borderColor: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,

View file

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:math'; import 'dart:math';
import 'package:auto_size_text/auto_size_text.dart'; import 'package:auto_size_text/auto_size_text.dart';
@ -354,12 +355,108 @@ class CryptoBalanceWidget extends StatelessWidget {
], ],
), ),
), ),
Observer( ],
builder: (_) => StandardSwitch( ),
value: dashboardViewModel.silentPaymentsScanningActive, SizedBox(height: 8),
onTaped: () => _toggleSilentPaymentsScanning(context), Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Semantics(
label: S.of(context).receive,
child: OutlinedButton(
onPressed: () {
Navigator.pushNamed(
context,
Routes.addressPage,
arguments: {
'addressType': bitcoin!
.getBitcoinReceivePageOptions()
.where(
(option) => option.value == "Silent Payments",
)
.first
},
);
},
style: OutlinedButton.styleFrom(
backgroundColor: Colors.grey.shade400.withAlpha(50),
side: BorderSide(
color: Colors.grey.shade400.withAlpha(50), width: 0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
child: Container(
padding: EdgeInsets.symmetric(vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
height: 30,
width: 30,
'assets/images/received.png',
color: Theme.of(context)
.extension<BalancePageTheme>()!
.balanceAmountColor,
),
const SizedBox(width: 8),
Text(
S.of(context).receive,
style: TextStyle(
color: Theme.of(context)
.extension<BalancePageTheme>()!
.textColor,
),
),
],
),
),
),
), ),
) ),
SizedBox(width: 24),
Expanded(
child: Semantics(
label: S.of(context).scan,
child: OutlinedButton(
onPressed: () => _toggleSilentPaymentsScanning(context),
style: OutlinedButton.styleFrom(
backgroundColor: Colors.grey.shade400.withAlpha(50),
side: BorderSide(
color: Colors.grey.shade400.withAlpha(50), width: 0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
child: Container(
padding: EdgeInsets.symmetric(vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Observer(
builder: (_) => StandardSwitch(
value:
dashboardViewModel.silentPaymentsScanningActive,
onTaped: () =>
_toggleSilentPaymentsScanning(context),
),
),
SizedBox(width: 8),
Text(
S.of(context).scan,
style: TextStyle(
color: Theme.of(context)
.extension<BalancePageTheme>()!
.textColor,
),
),
],
),
),
),
),
),
], ],
), ),
], ],
@ -466,29 +563,40 @@ class CryptoBalanceWidget extends StatelessWidget {
Future<void> _toggleSilentPaymentsScanning(BuildContext context) async { Future<void> _toggleSilentPaymentsScanning(BuildContext context) async {
final isSilentPaymentsScanningActive = dashboardViewModel.silentPaymentsScanningActive; final isSilentPaymentsScanningActive = dashboardViewModel.silentPaymentsScanningActive;
final newValue = !isSilentPaymentsScanningActive; final newValue = !isSilentPaymentsScanningActive;
final willScan = newValue == true;
dashboardViewModel.silentPaymentsScanningActive = newValue; dashboardViewModel.silentPaymentsScanningActive = newValue;
final needsToSwitch = !isSilentPaymentsScanningActive && if (willScan) {
await bitcoin!.getNodeIsElectrsSPEnabled(dashboardViewModel.wallet) == false; late bool isElectrsSPEnabled;
try {
isElectrsSPEnabled = await bitcoin!
.getNodeIsElectrsSPEnabled(dashboardViewModel.wallet)
.timeout(const Duration(seconds: 3));
} on TimeoutException {
isElectrsSPEnabled = false;
}
if (needsToSwitch) { final needsToSwitch = isElectrsSPEnabled == false;
return showPopUp<void>( if (needsToSwitch) {
return showPopUp<void>(
context: context, context: context,
builder: (BuildContext context) => AlertWithTwoActions( builder: (BuildContext context) => AlertWithTwoActions(
alertTitle: S.of(context).change_current_node_title, alertTitle: S.of(context).change_current_node_title,
alertContent: S.of(context).confirm_silent_payments_switch_node, alertContent: S.of(context).confirm_silent_payments_switch_node,
rightButtonText: S.of(context).confirm, rightButtonText: S.of(context).confirm,
leftButtonText: S.of(context).cancel, leftButtonText: S.of(context).cancel,
actionRightButton: () { actionRightButton: () {
dashboardViewModel.setSilentPaymentsScanning(newValue); dashboardViewModel.allowSilentPaymentsScanning(true);
Navigator.of(context).pop(); dashboardViewModel.setSilentPaymentsScanning(true);
}, Navigator.of(context).pop();
actionLeftButton: () { },
dashboardViewModel.silentPaymentsScanningActive = isSilentPaymentsScanningActive; actionLeftButton: () {
Navigator.of(context).pop(); dashboardViewModel.silentPaymentsScanningActive = isSilentPaymentsScanningActive;
}, Navigator.of(context).pop();
)); },
),
);
}
} }
return dashboardViewModel.setSilentPaymentsScanning(newValue); return dashboardViewModel.setSilentPaymentsScanning(newValue);
@ -1045,10 +1153,9 @@ class BalanceRowWidget extends StatelessWidget {
); );
}, },
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
backgroundColor: Colors.grey.shade400 backgroundColor: Colors.grey.shade400.withAlpha(50),
.withAlpha(50), side:
side: BorderSide(color: Colors.grey.shade400 BorderSide(color: Colors.grey.shade400.withAlpha(50), width: 0),
.withAlpha(50), width: 0),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
), ),
@ -1104,10 +1211,9 @@ class BalanceRowWidget extends StatelessWidget {
); );
}, },
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
backgroundColor: Colors.grey.shade400 backgroundColor: Colors.grey.shade400.withAlpha(50),
.withAlpha(50), side:
side: BorderSide(color: Colors.grey.shade400 BorderSide(color: Colors.grey.shade400.withAlpha(50), width: 0),
.withAlpha(50), width: 0),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
), ),

View file

@ -1,5 +1,6 @@
import 'dart:async';
import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/main.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -35,7 +36,8 @@ class RescanPage extends BasePage {
isSilentPaymentsScan: _rescanViewModel.isSilentPaymentsScan, isSilentPaymentsScan: _rescanViewModel.isSilentPaymentsScan,
isMwebScan: _rescanViewModel.isMwebScan, isMwebScan: _rescanViewModel.isMwebScan,
doSingleScan: _rescanViewModel.doSingleScan, doSingleScan: _rescanViewModel.doSingleScan,
hasDatePicker: !_rescanViewModel.isMwebScan,// disable date picker for mweb for now hasDatePicker:
!_rescanViewModel.isMwebScan, // disable date picker for mweb for now
toggleSingleScan: () => toggleSingleScan: () =>
_rescanViewModel.doSingleScan = !_rescanViewModel.doSingleScan, _rescanViewModel.doSingleScan = !_rescanViewModel.doSingleScan,
walletType: _rescanViewModel.wallet.type, walletType: _rescanViewModel.wallet.type,
@ -69,24 +71,32 @@ class RescanPage extends BasePage {
Navigator.of(context).pop(); Navigator.of(context).pop();
final needsToSwitch = late bool isElectrsSPEnabled;
await bitcoin!.getNodeIsElectrsSPEnabled(_rescanViewModel.wallet) == false; try {
isElectrsSPEnabled = await bitcoin!
.getNodeIsElectrsSPEnabled(_rescanViewModel.wallet)
.timeout(const Duration(seconds: 3));
} on TimeoutException {
isElectrsSPEnabled = false;
}
final needsToSwitch = isElectrsSPEnabled == false;
if (needsToSwitch) { if (needsToSwitch) {
return showPopUp<void>( return showPopUp<void>(
context: navigatorKey.currentState!.context, context: context,
builder: (BuildContext _dialogContext) => AlertWithTwoActions( builder: (BuildContext _dialogContext) => AlertWithTwoActions(
alertTitle: S.of(_dialogContext).change_current_node_title, alertTitle: S.of(_dialogContext).change_current_node_title,
alertContent: S.of(_dialogContext).confirm_silent_payments_switch_node, alertContent: S.of(_dialogContext).confirm_silent_payments_switch_node,
rightButtonText: S.of(_dialogContext).confirm, rightButtonText: S.of(_dialogContext).confirm,
leftButtonText: S.of(_dialogContext).cancel, leftButtonText: S.of(_dialogContext).cancel,
actionRightButton: () async { actionRightButton: () async {
Navigator.of(_dialogContext).pop(); Navigator.of(_dialogContext).pop();
_rescanViewModel.rescanCurrentWallet(restoreHeight: height); _rescanViewModel.rescanCurrentWallet(restoreHeight: height);
}, },
actionLeftButton: () => Navigator.of(_dialogContext).pop(), actionLeftButton: () => Navigator.of(_dialogContext).pop(),
)); ),
);
} }
_rescanViewModel.rescanCurrentWallet(restoreHeight: height); _rescanViewModel.rescanCurrentWallet(restoreHeight: height);

View file

@ -428,7 +428,8 @@ abstract class DashboardViewModelBase with Store {
// to not cause work duplication, this will do the job as well, it will be slightly less precise // to not cause work duplication, this will do the job as well, it will be slightly less precise
// about what happened - but still enough. // about what happened - but still enough.
// if (keys['privateSpendKey'] == List.generate(64, (index) => "0").join("")) "Private spend key is 0", // if (keys['privateSpendKey'] == List.generate(64, (index) => "0").join("")) "Private spend key is 0",
if (keys['privateViewKey'] == List.generate(64, (index) => "0").join("") && !wallet.isHardwareWallet) if (keys['privateViewKey'] == List.generate(64, (index) => "0").join("") &&
!wallet.isHardwareWallet)
"private view key is 0", "private view key is 0",
// if (keys['publicSpendKey'] == List.generate(64, (index) => "0").join("")) "public spend key is 0", // if (keys['publicSpendKey'] == List.generate(64, (index) => "0").join("")) "public spend key is 0",
if (keys['publicViewKey'] == List.generate(64, (index) => "0").join("")) if (keys['publicViewKey'] == List.generate(64, (index) => "0").join(""))
@ -454,6 +455,13 @@ abstract class DashboardViewModelBase with Store {
@observable @observable
bool silentPaymentsScanningActive = false; bool silentPaymentsScanningActive = false;
@action
void allowSilentPaymentsScanning(bool allow) {
if (hasSilentPayments) {
bitcoin!.allowToSwitchNodesForScanning(wallet, allow);
}
}
@action @action
void setSilentPaymentsScanning(bool active) { void setSilentPaymentsScanning(bool active) {
silentPaymentsScanningActive = active; silentPaymentsScanningActive = active;

View file

@ -25,6 +25,7 @@ import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_i
import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cw_core/amount_converter.dart'; import 'package:cw_core/amount_converter.dart';
import 'package:cw_core/currency.dart'; import 'package:cw_core/currency.dart';
import 'package:cw_core/receive_page_option.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
@ -209,6 +210,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
required AppStore appStore, required AppStore appStore,
required this.yatStore, required this.yatStore,
required this.fiatConversionStore, required this.fiatConversionStore,
ReceivePageOption? addressType,
}) : _baseItems = <ListItem>[], }) : _baseItems = <ListItem>[],
selectedCurrency = walletTypeToCryptoCurrency(appStore.wallet!.type), selectedCurrency = walletTypeToCryptoCurrency(appStore.wallet!.type),
_cryptoNumberFormat = NumberFormat(_cryptoNumberPattern), _cryptoNumberFormat = NumberFormat(_cryptoNumberPattern),
@ -216,6 +218,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
.contains(appStore.wallet!.type), .contains(appStore.wallet!.type),
amount = '', amount = '',
_settingsStore = appStore.settingsStore, _settingsStore = appStore.settingsStore,
_addressType = addressType,
super(appStore: appStore) { super(appStore: appStore) {
_init(); _init();
} }
@ -234,6 +237,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
final FiatConversionStore fiatConversionStore; final FiatConversionStore fiatConversionStore;
final SettingsStore _settingsStore; final SettingsStore _settingsStore;
final ReceivePageOption? _addressType;
double? _fiatRate; double? _fiatRate;
String _rawAmount = ''; String _rawAmount = '';
@ -264,8 +268,19 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
WalletType get type => wallet.type; WalletType get type => wallet.type;
@computed @computed
WalletAddressListItem get address => WalletAddressListItem get address {
WalletAddressListItem(address: wallet.walletAddresses.address, isPrimary: false); if (_addressType != null) {
final shouldForceSP = _addressType != null && bitcoin!.isReceiveOptionSP(_addressType!);
if (shouldForceSP) {
return WalletAddressListItem(
address: bitcoin!.getSilentPaymentAddresses(wallet).first.address,
isPrimary: true,
);
}
}
return WalletAddressListItem(address: wallet.walletAddresses.address, isPrimary: false);
}
@computed @computed
PaymentURI get uri { PaymentURI get uri {
@ -354,7 +369,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
} }
if (isElectrumWallet) { if (isElectrumWallet) {
if (bitcoin!.hasSelectedSilentPayments(wallet)) { final hasSelectedSP = bitcoin!.hasSelectedSilentPayments(wallet);
final shouldForceSP = _addressType != null && bitcoin!.isReceiveOptionSP(_addressType!);
if (hasSelectedSP || shouldForceSP) {
final addressItems = bitcoin!.getSilentPaymentAddresses(wallet).map((address) { final addressItems = bitcoin!.getSilentPaymentAddresses(wallet).map((address) {
final isPrimary = address.id == 0; final isPrimary = address.id == 0;

View file

@ -594,6 +594,7 @@
"save_backup_password_alert": "حفظ كلمة المرور الاحتياطية", "save_backup_password_alert": "حفظ كلمة المرور الاحتياطية",
"save_to_downloads": "ﺕﻼﻳﺰﻨﺘﻟﺍ ﻲﻓ ﻆﻔﺣ", "save_to_downloads": "ﺕﻼﻳﺰﻨﺘﻟﺍ ﻲﻓ ﻆﻔﺣ",
"saved_the_trade_id": "لقد تم حفظ معرف العملية", "saved_the_trade_id": "لقد تم حفظ معرف العملية",
"scan": "مسح",
"scan_one_block": "مسح كتلة واحدة", "scan_one_block": "مسح كتلة واحدة",
"scan_qr_code": "امسح رمز QR ضوئيًا", "scan_qr_code": "امسح رمز QR ضوئيًا",
"scan_qr_code_to_get_address": "امسح ال QR للحصول على العنوان", "scan_qr_code_to_get_address": "امسح ال QR للحصول على العنوان",

View file

@ -594,6 +594,7 @@
"save_backup_password_alert": "Запазване на паролата за възстановяване", "save_backup_password_alert": "Запазване на паролата за възстановяване",
"save_to_downloads": "Запазване в Изтегляния", "save_to_downloads": "Запазване в Изтегляния",
"saved_the_trade_id": "Запазих trade ID-то", "saved_the_trade_id": "Запазих trade ID-то",
"scan": "Сканиране",
"scan_one_block": "Сканирайте един блок", "scan_one_block": "Сканирайте един блок",
"scan_qr_code": "Сканирайте QR кода, за да получите адреса", "scan_qr_code": "Сканирайте QR кода, за да получите адреса",
"scan_qr_code_to_get_address": "Сканирайте QR кода, за да получите адреса", "scan_qr_code_to_get_address": "Сканирайте QR кода, за да получите адреса",

View file

@ -594,6 +594,7 @@
"save_backup_password_alert": "Uložit heslo pro zálohy", "save_backup_password_alert": "Uložit heslo pro zálohy",
"save_to_downloads": "Uložit do Stažených souborů", "save_to_downloads": "Uložit do Stažených souborů",
"saved_the_trade_id": "Uložil jsem si ID transakce (trade ID)", "saved_the_trade_id": "Uložil jsem si ID transakce (trade ID)",
"scan": "Skenovat",
"scan_one_block": "Prohledejte jeden blok", "scan_one_block": "Prohledejte jeden blok",
"scan_qr_code": "Naskenujte QR kód pro získání adresy", "scan_qr_code": "Naskenujte QR kód pro získání adresy",
"scan_qr_code_to_get_address": "Prohledejte QR kód a získejte adresu", "scan_qr_code_to_get_address": "Prohledejte QR kód a získejte adresu",

View file

@ -595,6 +595,7 @@
"save_backup_password_alert": "Sicherungskennwort speichern", "save_backup_password_alert": "Sicherungskennwort speichern",
"save_to_downloads": "Unter „Downloads“ speichern", "save_to_downloads": "Unter „Downloads“ speichern",
"saved_the_trade_id": "Ich habe die Handels-ID gespeichert", "saved_the_trade_id": "Ich habe die Handels-ID gespeichert",
"scan": "Scan",
"scan_one_block": "Einen Block scannen", "scan_one_block": "Einen Block scannen",
"scan_qr_code": "QR-Code scannen", "scan_qr_code": "QR-Code scannen",
"scan_qr_code_to_get_address": "Scannen Sie den QR-Code, um die Adresse zu erhalten", "scan_qr_code_to_get_address": "Scannen Sie den QR-Code, um die Adresse zu erhalten",

View file

@ -594,6 +594,7 @@
"save_backup_password_alert": "Save backup password", "save_backup_password_alert": "Save backup password",
"save_to_downloads": "Save to Downloads", "save_to_downloads": "Save to Downloads",
"saved_the_trade_id": "I've saved the trade ID", "saved_the_trade_id": "I've saved the trade ID",
"scan": "Scan",
"scan_one_block": "Scan one block", "scan_one_block": "Scan one block",
"scan_qr_code": "Scan QR code", "scan_qr_code": "Scan QR code",
"scan_qr_code_to_get_address": "Scan the QR code to get the address", "scan_qr_code_to_get_address": "Scan the QR code to get the address",

View file

@ -595,6 +595,7 @@
"save_backup_password_alert": "Guardar contraseña de respaldo", "save_backup_password_alert": "Guardar contraseña de respaldo",
"save_to_downloads": "Guardar en Descargas", "save_to_downloads": "Guardar en Descargas",
"saved_the_trade_id": "He salvado comercial ID", "saved_the_trade_id": "He salvado comercial ID",
"scan": "Escanear",
"scan_one_block": "Escanear un bloque", "scan_one_block": "Escanear un bloque",
"scan_qr_code": "Escanear código QR", "scan_qr_code": "Escanear código QR",
"scan_qr_code_to_get_address": "Escanea el código QR para obtener la dirección", "scan_qr_code_to_get_address": "Escanea el código QR para obtener la dirección",

View file

@ -594,6 +594,7 @@
"save_backup_password_alert": "Enregistrer le mot de passe de sauvegarde", "save_backup_password_alert": "Enregistrer le mot de passe de sauvegarde",
"save_to_downloads": "Enregistrer dans les téléchargements", "save_to_downloads": "Enregistrer dans les téléchargements",
"saved_the_trade_id": "J'ai sauvegardé l'ID d'échange", "saved_the_trade_id": "J'ai sauvegardé l'ID d'échange",
"scan": "Balayage",
"scan_one_block": "Scanner un bloc", "scan_one_block": "Scanner un bloc",
"scan_qr_code": "Scannez le QR code", "scan_qr_code": "Scannez le QR code",
"scan_qr_code_to_get_address": "Scannez le QR code pour obtenir l'adresse", "scan_qr_code_to_get_address": "Scannez le QR code pour obtenir l'adresse",

View file

@ -596,6 +596,7 @@
"save_backup_password_alert": "Ajiye kalmar sirri ta ajiya", "save_backup_password_alert": "Ajiye kalmar sirri ta ajiya",
"save_to_downloads": "Ajiye zuwa Zazzagewa", "save_to_downloads": "Ajiye zuwa Zazzagewa",
"saved_the_trade_id": "Na ajiye ID na ciniki", "saved_the_trade_id": "Na ajiye ID na ciniki",
"scan": "Scan",
"scan_one_block": "Duba toshe daya", "scan_one_block": "Duba toshe daya",
"scan_qr_code": "Gani QR kodin", "scan_qr_code": "Gani QR kodin",
"scan_qr_code_to_get_address": "Duba lambar QR don samun adireshin", "scan_qr_code_to_get_address": "Duba lambar QR don samun adireshin",

View file

@ -596,6 +596,7 @@
"save_backup_password_alert": "बैकअप पासवर्ड सेव करें", "save_backup_password_alert": "बैकअप पासवर्ड सेव करें",
"save_to_downloads": "डाउनलोड में सहेजें", "save_to_downloads": "डाउनलोड में सहेजें",
"saved_the_trade_id": "मैंने व्यापार बचा लिया है ID", "saved_the_trade_id": "मैंने व्यापार बचा लिया है ID",
"scan": "स्कैन",
"scan_one_block": "एक ब्लॉक को स्कैन करना", "scan_one_block": "एक ब्लॉक को स्कैन करना",
"scan_qr_code": "स्कैन क्यू आर कोड", "scan_qr_code": "स्कैन क्यू आर कोड",
"scan_qr_code_to_get_address": "पता प्राप्त करने के लिए QR कोड स्कैन करें", "scan_qr_code_to_get_address": "पता प्राप्त करने के लिए QR कोड स्कैन करें",

View file

@ -594,6 +594,7 @@
"save_backup_password_alert": "Spremi lozinku za sigurnosnu kopiju", "save_backup_password_alert": "Spremi lozinku za sigurnosnu kopiju",
"save_to_downloads": "Spremi u Preuzimanja", "save_to_downloads": "Spremi u Preuzimanja",
"saved_the_trade_id": "Spremio/la sam transakcijski ID", "saved_the_trade_id": "Spremio/la sam transakcijski ID",
"scan": "Skenirati",
"scan_one_block": "Skenirajte jedan blok", "scan_one_block": "Skenirajte jedan blok",
"scan_qr_code": "Skenirajte QR kod", "scan_qr_code": "Skenirajte QR kod",
"scan_qr_code_to_get_address": "Skeniraj QR kod za dobivanje adrese", "scan_qr_code_to_get_address": "Skeniraj QR kod za dobivanje adrese",

View file

@ -594,6 +594,7 @@
"save_backup_password_alert": "Պահպանել կրկնօրինակի գաղտնաբառը", "save_backup_password_alert": "Պահպանել կրկնօրինակի գաղտնաբառը",
"save_to_downloads": "Պահպանել ներբեռնումներում", "save_to_downloads": "Պահպանել ներբեռնումներում",
"saved_the_trade_id": "Ես պահպանել եմ առևտրի ID-ն", "saved_the_trade_id": "Ես պահպանել եմ առևտրի ID-ն",
"scan": "Սկանավորել",
"scan_one_block": "Սկանավորել մեկ բլոկ", "scan_one_block": "Սկանավորել մեկ բլոկ",
"scan_qr_code": "Սկանավորել QR կոդ", "scan_qr_code": "Սկանավորել QR կոդ",
"scan_qr_code_to_get_address": "Սկանավորել QR կոդը հասցեն ստանալու համար", "scan_qr_code_to_get_address": "Սկանավորել QR կոդը հասցեն ստանալու համար",

View file

@ -597,6 +597,7 @@
"save_backup_password_alert": "Simpan kata sandi cadangan", "save_backup_password_alert": "Simpan kata sandi cadangan",
"save_to_downloads": "Simpan ke Unduhan", "save_to_downloads": "Simpan ke Unduhan",
"saved_the_trade_id": "Saya telah menyimpan ID perdagangan", "saved_the_trade_id": "Saya telah menyimpan ID perdagangan",
"scan": "Pindai",
"scan_one_block": "Pindai satu blok", "scan_one_block": "Pindai satu blok",
"scan_qr_code": "Scan kode QR untuk mendapatkan alamat", "scan_qr_code": "Scan kode QR untuk mendapatkan alamat",
"scan_qr_code_to_get_address": "Pindai kode QR untuk mendapatkan alamat", "scan_qr_code_to_get_address": "Pindai kode QR untuk mendapatkan alamat",

View file

@ -596,6 +596,7 @@
"save_backup_password_alert": "Salva password Backup", "save_backup_password_alert": "Salva password Backup",
"save_to_downloads": "Salva in Download", "save_to_downloads": "Salva in Download",
"saved_the_trade_id": "Ho salvato l'ID dello scambio", "saved_the_trade_id": "Ho salvato l'ID dello scambio",
"scan": "Scansione",
"scan_one_block": "Scansionare un blocco", "scan_one_block": "Scansionare un blocco",
"scan_qr_code": "Scansiona il codice QR", "scan_qr_code": "Scansiona il codice QR",
"scan_qr_code_to_get_address": "Scansiona il codice QR per ottenere l'indirizzo", "scan_qr_code_to_get_address": "Scansiona il codice QR per ottenere l'indirizzo",

View file

@ -595,6 +595,7 @@
"save_backup_password_alert": "バックアップパスワードを保存する", "save_backup_password_alert": "バックアップパスワードを保存する",
"save_to_downloads": "ダウンロードに保存", "save_to_downloads": "ダウンロードに保存",
"saved_the_trade_id": "取引IDを保存しました", "saved_the_trade_id": "取引IDを保存しました",
"scan": "スキャン",
"scan_one_block": "1つのブロックをスキャンします", "scan_one_block": "1つのブロックをスキャンします",
"scan_qr_code": "QRコードをスキャン", "scan_qr_code": "QRコードをスキャン",
"scan_qr_code_to_get_address": "QRコードをスキャンして住所を取得します", "scan_qr_code_to_get_address": "QRコードをスキャンして住所を取得します",

View file

@ -595,6 +595,7 @@
"save_backup_password_alert": "백업 비밀번호 저장", "save_backup_password_alert": "백업 비밀번호 저장",
"save_to_downloads": "다운로드에 저장", "save_to_downloads": "다운로드에 저장",
"saved_the_trade_id": "거래 ID를 저장했습니다", "saved_the_trade_id": "거래 ID를 저장했습니다",
"scan": "주사",
"scan_one_block": "하나의 블록을 스캔하십시오", "scan_one_block": "하나의 블록을 스캔하십시오",
"scan_qr_code": "QR 코드 스캔", "scan_qr_code": "QR 코드 스캔",
"scan_qr_code_to_get_address": "QR 코드를 스캔하여 주소를 얻습니다.", "scan_qr_code_to_get_address": "QR 코드를 스캔하여 주소를 얻습니다.",

View file

@ -594,6 +594,7 @@
"save_backup_password_alert": "အရန်စကားဝှက်ကို သိမ်းဆည်းပါ။", "save_backup_password_alert": "အရန်စကားဝှက်ကို သိမ်းဆည်းပါ။",
"save_to_downloads": "ဒေါင်းလုဒ်များထံ သိမ်းဆည်းပါ။", "save_to_downloads": "ဒေါင်းလုဒ်များထံ သိမ်းဆည်းပါ။",
"saved_the_trade_id": "ကုန်သွယ်မှု ID ကို သိမ်းဆည်းပြီးပါပြီ။", "saved_the_trade_id": "ကုန်သွယ်မှု ID ကို သိမ်းဆည်းပြီးပါပြီ။",
"scan": "စကင်ဖတ်",
"scan_one_block": "တစ်ကွက်ကိုစကင်ဖတ်စစ်ဆေးပါ", "scan_one_block": "တစ်ကွက်ကိုစကင်ဖတ်စစ်ဆေးပါ",
"scan_qr_code": "QR ကုဒ်ကို စကင်န်ဖတ်ပါ။", "scan_qr_code": "QR ကုဒ်ကို စကင်န်ဖတ်ပါ။",
"scan_qr_code_to_get_address": "လိပ်စာရယူရန် QR ကုဒ်ကို စကင်န်ဖတ်ပါ။", "scan_qr_code_to_get_address": "လိပ်စာရယူရန် QR ကုဒ်ကို စကင်န်ဖတ်ပါ။",

View file

@ -594,6 +594,7 @@
"save_backup_password_alert": "Bewaar back-upwachtwoord", "save_backup_password_alert": "Bewaar back-upwachtwoord",
"save_to_downloads": "Opslaan in downloads", "save_to_downloads": "Opslaan in downloads",
"saved_the_trade_id": "Ik heb de ruil-ID opgeslagen", "saved_the_trade_id": "Ik heb de ruil-ID opgeslagen",
"scan": "Scannen",
"scan_one_block": "Scan een blok", "scan_one_block": "Scan een blok",
"scan_qr_code": "Scan QR-code", "scan_qr_code": "Scan QR-code",
"scan_qr_code_to_get_address": "Scan de QR-code om het adres te krijgen", "scan_qr_code_to_get_address": "Scan de QR-code om het adres te krijgen",

View file

@ -594,6 +594,7 @@
"save_backup_password_alert": "Zapisz hasło kopii zapasowej", "save_backup_password_alert": "Zapisz hasło kopii zapasowej",
"save_to_downloads": "Zapisz w Pobranych", "save_to_downloads": "Zapisz w Pobranych",
"saved_the_trade_id": "Zapisałem ID", "saved_the_trade_id": "Zapisałem ID",
"scan": "Skandować",
"scan_one_block": "Zeskanuj jeden blok", "scan_one_block": "Zeskanuj jeden blok",
"scan_qr_code": "Skanowania QR code", "scan_qr_code": "Skanowania QR code",
"scan_qr_code_to_get_address": "Zeskanuj kod QR, aby uzyskać adres", "scan_qr_code_to_get_address": "Zeskanuj kod QR, aby uzyskać adres",

View file

@ -596,6 +596,7 @@
"save_backup_password_alert": "Salvar senha de backup", "save_backup_password_alert": "Salvar senha de backup",
"save_to_downloads": "Salvar em Downloads", "save_to_downloads": "Salvar em Downloads",
"saved_the_trade_id": "ID da troca salvo", "saved_the_trade_id": "ID da troca salvo",
"scan": "Scan",
"scan_one_block": "Escanear um bloco", "scan_one_block": "Escanear um bloco",
"scan_qr_code": "Escanear código QR", "scan_qr_code": "Escanear código QR",
"scan_qr_code_to_get_address": "Digitalize o código QR para obter o endereço", "scan_qr_code_to_get_address": "Digitalize o código QR para obter o endereço",
@ -956,4 +957,4 @@
"you_will_get": "Converter para", "you_will_get": "Converter para",
"you_will_send": "Converter de", "you_will_send": "Converter de",
"yy": "aa" "yy": "aa"
} }

View file

@ -595,6 +595,7 @@
"save_backup_password_alert": "Сохранить пароль резервной копии", "save_backup_password_alert": "Сохранить пароль резервной копии",
"save_to_downloads": "Сохранить в загрузках", "save_to_downloads": "Сохранить в загрузках",
"saved_the_trade_id": "Я сохранил ID сделки", "saved_the_trade_id": "Я сохранил ID сделки",
"scan": "Сканирование",
"scan_one_block": "Сканируйте один блок", "scan_one_block": "Сканируйте один блок",
"scan_qr_code": "Сканировать QR-код", "scan_qr_code": "Сканировать QR-код",
"scan_qr_code_to_get_address": "Отсканируйте QR-код для получения адреса", "scan_qr_code_to_get_address": "Отсканируйте QR-код для получения адреса",

View file

@ -594,6 +594,7 @@
"save_backup_password_alert": "บันทึกรหัสผ่านสำรอง", "save_backup_password_alert": "บันทึกรหัสผ่านสำรอง",
"save_to_downloads": "บันทึกลงดาวน์โหลด", "save_to_downloads": "บันทึกลงดาวน์โหลด",
"saved_the_trade_id": "ฉันได้บันทึก ID ของการซื้อขายแล้ว", "saved_the_trade_id": "ฉันได้บันทึก ID ของการซื้อขายแล้ว",
"scan": "สแกน",
"scan_one_block": "สแกนหนึ่งบล็อก", "scan_one_block": "สแกนหนึ่งบล็อก",
"scan_qr_code": "สแกนรหัส QR", "scan_qr_code": "สแกนรหัส QR",
"scan_qr_code_to_get_address": "สแกน QR code เพื่อรับที่อยู่", "scan_qr_code_to_get_address": "สแกน QR code เพื่อรับที่อยู่",

View file

@ -594,6 +594,7 @@
"save_backup_password_alert": "I-save ang backup na password", "save_backup_password_alert": "I-save ang backup na password",
"save_to_downloads": "I-save sa mga Pag-download", "save_to_downloads": "I-save sa mga Pag-download",
"saved_the_trade_id": "Nai-save ko na ang trade ID", "saved_the_trade_id": "Nai-save ko na ang trade ID",
"scan": "I -scan",
"scan_one_block": "I-scan ang isang bloke", "scan_one_block": "I-scan ang isang bloke",
"scan_qr_code": "I-scan ang QR code", "scan_qr_code": "I-scan ang QR code",
"scan_qr_code_to_get_address": "I-scan ang QR code upang makuha ang address", "scan_qr_code_to_get_address": "I-scan ang QR code upang makuha ang address",

View file

@ -594,6 +594,7 @@
"save_backup_password_alert": "Yedek parolasını kaydet", "save_backup_password_alert": "Yedek parolasını kaydet",
"save_to_downloads": "İndirilenlere Kaydet", "save_to_downloads": "İndirilenlere Kaydet",
"saved_the_trade_id": "Takas ID'imi kaydettim", "saved_the_trade_id": "Takas ID'imi kaydettim",
"scan": "Taramak",
"scan_one_block": "Bir bloğu tara", "scan_one_block": "Bir bloğu tara",
"scan_qr_code": "QR kodunu tarayın", "scan_qr_code": "QR kodunu tarayın",
"scan_qr_code_to_get_address": "Adresi getirmek için QR kodunu tara", "scan_qr_code_to_get_address": "Adresi getirmek için QR kodunu tara",

View file

@ -595,6 +595,7 @@
"save_backup_password_alert": "Зберегти пароль резервної копії", "save_backup_password_alert": "Зберегти пароль резервної копії",
"save_to_downloads": "Зберегти до завантажень", "save_to_downloads": "Зберегти до завантажень",
"saved_the_trade_id": "Я зберіг ID операції", "saved_the_trade_id": "Я зберіг ID операції",
"scan": "Сканувати",
"scan_one_block": "Сканувати один блок", "scan_one_block": "Сканувати один блок",
"scan_qr_code": "Відскануйте QR-код", "scan_qr_code": "Відскануйте QR-код",
"scan_qr_code_to_get_address": "Скануйте QR-код для одержання адреси", "scan_qr_code_to_get_address": "Скануйте QR-код для одержання адреси",

View file

@ -596,6 +596,7 @@
"save_backup_password_alert": "بیک اپ پاس ورڈ محفوظ کریں۔", "save_backup_password_alert": "بیک اپ پاس ورڈ محفوظ کریں۔",
"save_to_downloads": "۔ﮟﯾﺮﮐ ﻅﻮﻔﺤﻣ ﮟﯿﻣ ﺯﮈﻮﻟ ﻥﺅﺍﮈ", "save_to_downloads": "۔ﮟﯾﺮﮐ ﻅﻮﻔﺤﻣ ﮟﯿﻣ ﺯﮈﻮﻟ ﻥﺅﺍﮈ",
"saved_the_trade_id": "میں نے تجارتی ID محفوظ کر لی ہے۔", "saved_the_trade_id": "میں نے تجارتی ID محفوظ کر لی ہے۔",
"scan": "اسکین",
"scan_one_block": "ایک بلاک اسکین کریں", "scan_one_block": "ایک بلاک اسکین کریں",
"scan_qr_code": "پتہ حاصل کرنے کے لیے QR کوڈ اسکین کریں۔", "scan_qr_code": "پتہ حاصل کرنے کے لیے QR کوڈ اسکین کریں۔",
"scan_qr_code_to_get_address": "پتہ حاصل کرنے کے لئے QR کوڈ کو اسکین کریں", "scan_qr_code_to_get_address": "پتہ حاصل کرنے کے لئے QR کوڈ کو اسکین کریں",

View file

@ -593,6 +593,7 @@
"save_backup_password_alert": "Lưu mật khẩu sao lưu", "save_backup_password_alert": "Lưu mật khẩu sao lưu",
"save_to_downloads": "Lưu vào Tải xuống", "save_to_downloads": "Lưu vào Tải xuống",
"saved_the_trade_id": "Tôi đã lưu ID giao dịch", "saved_the_trade_id": "Tôi đã lưu ID giao dịch",
"scan": "Quét",
"scan_one_block": "Quét một khối", "scan_one_block": "Quét một khối",
"scan_qr_code": "Quét mã QR", "scan_qr_code": "Quét mã QR",
"scan_qr_code_to_get_address": "Quét mã QR để nhận địa chỉ", "scan_qr_code_to_get_address": "Quét mã QR để nhận địa chỉ",

View file

@ -595,6 +595,7 @@
"save_backup_password_alert": "Pamọ́ ọ̀rọ̀ aṣínà ti ẹ̀dà", "save_backup_password_alert": "Pamọ́ ọ̀rọ̀ aṣínà ti ẹ̀dà",
"save_to_downloads": "Fipamọ si Awọn igbasilẹ", "save_to_downloads": "Fipamọ si Awọn igbasilẹ",
"saved_the_trade_id": "Mo ti pamọ́ àmì ìdánimọ̀ pàṣípààrọ̀", "saved_the_trade_id": "Mo ti pamọ́ àmì ìdánimọ̀ pàṣípààrọ̀",
"scan": "Ọlọjẹ",
"scan_one_block": "Ọlọjẹ ọkan bulọki", "scan_one_block": "Ọlọjẹ ọkan bulọki",
"scan_qr_code": "Yan QR koodu", "scan_qr_code": "Yan QR koodu",
"scan_qr_code_to_get_address": "Ṣayẹwo koodu QR naa lati gba adirẹsi naa", "scan_qr_code_to_get_address": "Ṣayẹwo koodu QR naa lati gba adirẹsi naa",

View file

@ -594,6 +594,7 @@
"save_backup_password_alert": "保存备份密码", "save_backup_password_alert": "保存备份密码",
"save_to_downloads": "保存到下载", "save_to_downloads": "保存到下载",
"saved_the_trade_id": "我已经保存了交易编号", "saved_the_trade_id": "我已经保存了交易编号",
"scan": "扫描",
"scan_one_block": "扫描一个街区", "scan_one_block": "扫描一个街区",
"scan_qr_code": "扫描二维码", "scan_qr_code": "扫描二维码",
"scan_qr_code_to_get_address": "扫描二维码获取地址", "scan_qr_code_to_get_address": "扫描二维码获取地址",

View file

@ -227,11 +227,13 @@ abstract class Bitcoin {
List<ReceivePageOption> getBitcoinReceivePageOptions(); List<ReceivePageOption> getBitcoinReceivePageOptions();
List<ReceivePageOption> getLitecoinReceivePageOptions(); List<ReceivePageOption> getLitecoinReceivePageOptions();
BitcoinAddressType getBitcoinAddressType(ReceivePageOption option); BitcoinAddressType getBitcoinAddressType(ReceivePageOption option);
bool isReceiveOptionSP(ReceivePageOption option);
bool hasSelectedSilentPayments(Object wallet); bool hasSelectedSilentPayments(Object wallet);
bool isBitcoinReceivePageOption(ReceivePageOption option); bool isBitcoinReceivePageOption(ReceivePageOption option);
BitcoinAddressType getOptionToType(ReceivePageOption option); BitcoinAddressType getOptionToType(ReceivePageOption option);
bool hasTaprootInput(PendingTransaction pendingTransaction); bool hasTaprootInput(PendingTransaction pendingTransaction);
bool getScanningActive(Object wallet); bool getScanningActive(Object wallet);
Future<void> allowToSwitchNodesForScanning(Object wallet, bool allow);
Future<void> setScanningActive(Object wallet, bool active); Future<void> setScanningActive(Object wallet, bool active);
bool isTestnet(Object wallet); bool isTestnet(Object wallet);