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

View file

@ -276,11 +276,6 @@ abstract class ElectrumWalletBase
@override
bool isTestnet;
@observable
bool nodeSupportsSilentPayments = true;
@observable
bool silentPaymentsScanningActive = false;
bool _isTryingToConnect = false;
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/methods/methods.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:rxdart/rxdart.dart';
import 'package:sp_scanner/sp_scanner.dart';
class ElectrumWorker {
final SendPort sendPort;
ElectrumApiProvider? _electrumClient;
BasedUtxoNetwork? _network;
bool _isScanning = false;
bool _stopScanRequested = false;
BehaviorSubject<Map<String, dynamic>>? _scanningStream;
ElectrumWorker._(this.sendPort, {ElectrumApiProvider? electrumClient})
: _electrumClient = electrumClient;
@ -47,7 +45,7 @@ class ElectrumWorker {
}
void handleMessage(dynamic message) async {
print("Worker received message: $message");
print("Worker: received message: $message");
try {
Map<String, dynamic> messageJson;
@ -105,27 +103,15 @@ class ElectrumWorker {
);
break;
case ElectrumWorkerMethods.stopScanningMethod:
print("Worker: received message: $message");
await _handleStopScanning(
ElectrumWorkerStopScanningRequest.fromJson(messageJson),
);
break;
case ElectrumRequestMethods.tweaksSubscribeMethod:
if (_isScanning) {
_stopScanRequested = false;
}
if (!_stopScanRequested) {
await _handleScanSilentPayments(
ElectrumWorkerTweaksSubscribeRequest.fromJson(messageJson),
);
} else {
_stopScanRequested = false;
_sendResponse(
ElectrumWorkerTweaksSubscribeResponse(
result: TweaksSyncResponse(syncStatus: SyncedSyncStatus()),
),
);
}
await _handleScanSilentPayments(
ElectrumWorkerTweaksSubscribeRequest.fromJson(messageJson),
);
break;
case ElectrumRequestMethods.estimateFeeMethod:
@ -550,28 +536,25 @@ class ElectrumWorker {
}
Future<void> _handleStopScanning(ElectrumWorkerStopScanningRequest request) async {
_stopScanRequested = true;
_scanningStream?.close();
_scanningStream = null;
_sendResponse(
ElectrumWorkerStopScanningResponse(result: true, id: request.id),
);
}
Future<void> _handleScanSilentPayments(ElectrumWorkerTweaksSubscribeRequest request) async {
_isScanning = true;
final scanData = request.scanData;
// TODO: confirmedSwitch use new connection
// final _electrumClient = await ElectrumApiProvider.connect(
// ElectrumTCPService.connect(
// Uri.parse("tcp://electrs.cakewallet.com:50001"),
// onConnectionStatusChange: (status) {
// _sendResponse(
// ElectrumWorkerConnectionResponse(status: status, id: request.id),
// );
// },
// ),
// );
var scanningClient = _electrumClient;
if (scanData.shouldSwitchNodes) {
scanningClient = await ElectrumApiProvider.connect(
ElectrumTCPService.connect(
Uri.parse("tcp://electrs.cakewallet.com:50001"),
),
);
}
int syncHeight = scanData.height;
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
_sendResponse(ElectrumWorkerTweaksSubscribeResponse(
result: TweaksSyncResponse(
@ -597,17 +589,16 @@ class ElectrumWorker {
final req = ElectrumTweaksSubscribe(
height: syncHeight,
count: 1,
count: getCountPerRequest(syncHeight),
historicalMode: false,
);
final stream = await _electrumClient!.subscribe(req);
_scanningStream = await scanningClient!.subscribe(req);
void listenFn(Map<String, dynamic> event, ElectrumTweaksSubscribe req) {
final response = req.onResponse(event);
if (_stopScanRequested || response == null) {
_stopScanRequested = false;
_isScanning = false;
if (response == null || _scanningStream == null) {
return;
}
@ -623,10 +614,10 @@ class ElectrumWorker {
final nextHeight = syncHeight + 1;
if (nextHeight <= scanData.chainTip) {
final nextStream = _electrumClient!.subscribe(
final nextStream = scanningClient!.subscribe(
ElectrumTweaksSubscribe(
height: nextHeight,
count: 1,
count: getCountPerRequest(nextHeight),
historicalMode: false,
),
);
@ -710,6 +701,7 @@ class ElectrumWorker {
receivingOutputAddress,
labelIndex: 1, // TODO: get actual index/label
isUsed: true,
// TODO: use right wallet
spendKey: scanData.silentPaymentsWallets.first.b_spend.tweakAdd(
BigintUtils.fromBytes(BytesUtils.fromHexString(t_k)),
),
@ -753,24 +745,27 @@ class ElectrumWorker {
),
);
stream?.close();
_scanningStream?.close();
_scanningStream = null;
return;
}
}
stream?.listen((event) => listenFn(event, req));
_isScanning = false;
_scanningStream?.listen((event) => listenFn(event, req));
}
Future<void> _handleGetVersion(ElectrumWorkerGetVersionRequest request) async {
_sendResponse(ElectrumWorkerGetVersionResponse(
result: (await _electrumClient!.request(
_sendResponse(
ElectrumWorkerGetVersionResponse(
result: await _electrumClient!.request(
ElectrumVersion(
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 List<int> labelIndexes;
final bool isSingleScan;
final bool shouldSwitchNodes;
ScanData({
required this.silentPaymentsWallets,
@ -19,6 +20,7 @@ class ScanData {
required this.labels,
required this.labelIndexes,
required this.isSingleScan,
required this.shouldSwitchNodes,
});
factory ScanData.fromHeight(ScanData scanData, int newHeight) {
@ -31,6 +33,7 @@ class ScanData {
labels: scanData.labels,
labelIndexes: scanData.labelIndexes,
isSingleScan: scanData.isSingleScan,
shouldSwitchNodes: scanData.shouldSwitchNodes,
);
}
@ -44,6 +47,7 @@ class ScanData {
'labels': labels,
'labelIndexes': labelIndexes,
'isSingleScan': isSingleScan,
'shouldSwitchNodes': shouldSwitchNodes,
};
}
@ -60,6 +64,7 @@ class ScanData {
labels: json['labels'] as Map<String, String>,
labelIndexes: (json['labelIndexes'] as List).map((e) => e as int).toList(),
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);
}
@override
bool isReceiveOptionSP(ReceivePageOption option) {
return option.value == BitcoinReceivePageOption.silent_payments.value;
}
@override
bool hasSelectedSilentPayments(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
@ -610,7 +615,7 @@ class CWBitcoin extends Bitcoin {
@override
@computed
bool getScanningActive(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
final bitcoinWallet = wallet as BitcoinWallet;
return bitcoinWallet.silentPaymentsScanningActive;
}
@ -620,6 +625,12 @@ class CWBitcoin extends Bitcoin {
bitcoinWallet.setSilentPaymentsScanning(active);
}
@override
Future<void> allowToSwitchNodesForScanning(Object wallet, bool allow) async {
final bitcoinWallet = wallet as BitcoinWallet;
bitcoinWallet.allowedToSwitchNodesForScanning = allow;
}
@override
bool isTestnet(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;

View file

@ -472,10 +472,14 @@ Future<void> setup({
getIt.get<SeedSettingsViewModel>(),
type: type));
getIt.registerFactory<WalletAddressListViewModel>(() => WalletAddressListViewModel(
getIt.registerFactoryParam<WalletAddressListViewModel, ReceivePageOption?, void>(
(ReceivePageOption? addressType, _) => WalletAddressListViewModel(
appStore: getIt.get<AppStore>(),
yatStore: getIt.get<YatStore>(),
fiatConversionStore: getIt.get<FiatConversionStore>()));
fiatConversionStore: getIt.get<FiatConversionStore>(),
addressType: addressType,
),
);
getIt.registerFactory(() => BalanceViewModel(
appStore: getIt.get<AppStore>(),
@ -704,12 +708,26 @@ Future<void> setup({
getIt.get<ReceiveOptionViewModel>(param1: pageOption));
});
getIt.registerFactory<ReceivePage>(
() => ReceivePage(addressListViewModel: getIt.get<WalletAddressListViewModel>()));
getIt.registerFactory<AddressPage>(() => AddressPage(
addressListViewModel: getIt.get<WalletAddressListViewModel>(),
getIt.registerFactoryParam<ReceivePage, ReceivePageOption?, void>(
(ReceivePageOption? addressType, _) => ReceivePage(
addressListViewModel: getIt.get<WalletAddressListViewModel>(
param1: addressType,
),
),
);
getIt.registerFactoryParam<AddressPage, ReceivePageOption?, void>(
(ReceivePageOption? addressType, _) => AddressPage(
addressListViewModel: getIt.get<WalletAddressListViewModel>(
param1: addressType,
),
dashboardViewModel: getIt.get<DashboardViewModel>(),
receiveOptionViewModel: getIt.get<ReceiveOptionViewModel>()));
receiveOptionViewModel: getIt.get<ReceiveOptionViewModel>(
param1: addressType,
),
addressType: addressType,
),
);
getIt.registerFactoryParam<WalletAddressEditOrCreateViewModel, WalletAddressListItem?, void>(
(WalletAddressListItem? item, _) =>
@ -904,11 +922,11 @@ Future<void> setup({
getIt.registerFactory(() => WalletKeysViewModel(getIt.get<AppStore>()));
getIt.registerFactory(() => WalletKeysPage(getIt.get<WalletKeysViewModel>()));
getIt.registerFactory(() => AnimatedURModel(getIt.get<AppStore>()));
getIt.registerFactoryParam<AnimatedURPage, String, void>((String urQr, _) =>
AnimatedURPage(getIt.get<AnimatedURModel>(), urQr: urQr));
getIt.registerFactoryParam<AnimatedURPage, String, void>(
(String urQr, _) => AnimatedURPage(getIt.get<AnimatedURModel>(), urQr: urQr));
getIt.registerFactoryParam<ContactViewModel, ContactRecord?, void>(
(ContactRecord? contact, _) => ContactViewModel(_contactSource, contact: contact));
@ -1004,8 +1022,8 @@ Future<void> setup({
));
getIt.registerFactory<MeldBuyProvider>(() => MeldBuyProvider(
wallet: getIt.get<AppStore>().wallet!,
));
wallet: getIt.get<AppStore>().wallet!,
));
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 pickAnOption = args[1] as void Function(SelectableOption option)?;
final confirmOption = args[2] as void Function(BuildContext contex)?;
return BuyOptionsPage(
items: items, pickAnOption: pickAnOption, confirmOption: confirmOption);
return BuyOptionsPage(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 pickAnOption = args[1] as void Function(SelectableOption option)?;
return PaymentMethodOptionsPage(
items: items, pickAnOption: pickAnOption);
return PaymentMethodOptionsPage(items: items, pickAnOption: pickAnOption);
});
getIt.registerFactory(() {
@ -1300,9 +1317,8 @@ Future<void> setup({
getIt.registerFactory<CakePayService>(
() => CakePayService(getIt.get<SecureStorage>(), getIt.get<CakePayApi>()));
getIt.registerFactory(
() => CakePayCardsListViewModel(cakePayService: getIt.get<CakePayService>(),
settingsStore: getIt.get<SettingsStore>()));
getIt.registerFactory(() => CakePayCardsListViewModel(
cakePayService: getIt.get<CakePayService>(), settingsStore: getIt.get<SettingsStore>()));
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/nano_account.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/unspent_coin_type.dart';
import 'package:cw_core/wallet_info.dart';
@ -376,11 +377,21 @@ Route<dynamic> createRoute(RouteSettings settings) {
fullscreenDialog: true, builder: (_) => getIt.get<SendTemplatePage>());
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:
final args = settings.arguments as Map<String, dynamic>?;
final addressType = args?['addressType'] as ReceivePageOption?;
return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<AddressPage>());
fullscreenDialog: true,
builder: (_) => getIt.get<AddressPage>(param1: addressType),
);
case Routes.transactionDetails:
return CupertinoPageRoute<void>(
@ -588,7 +599,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.paymentMethodOptionsPage:
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:
final args = settings.arguments as List;
@ -751,7 +763,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
return MaterialPageRoute<void>(builder: (_) => getIt.get<Setup2FAInfoPage>());
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:
return CupertinoPageRoute<void>(

View file

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

View file

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:math';
import 'package:auto_size_text/auto_size_text.dart';
@ -354,12 +355,108 @@ class CryptoBalanceWidget extends StatelessWidget {
],
),
),
Observer(
builder: (_) => StandardSwitch(
value: dashboardViewModel.silentPaymentsScanningActive,
onTaped: () => _toggleSilentPaymentsScanning(context),
],
),
SizedBox(height: 8),
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 {
final isSilentPaymentsScanningActive = dashboardViewModel.silentPaymentsScanningActive;
final newValue = !isSilentPaymentsScanningActive;
final willScan = newValue == true;
dashboardViewModel.silentPaymentsScanningActive = newValue;
final needsToSwitch = !isSilentPaymentsScanningActive &&
await bitcoin!.getNodeIsElectrsSPEnabled(dashboardViewModel.wallet) == false;
if (willScan) {
late bool isElectrsSPEnabled;
try {
isElectrsSPEnabled = await bitcoin!
.getNodeIsElectrsSPEnabled(dashboardViewModel.wallet)
.timeout(const Duration(seconds: 3));
} on TimeoutException {
isElectrsSPEnabled = false;
}
if (needsToSwitch) {
return showPopUp<void>(
final needsToSwitch = isElectrsSPEnabled == false;
if (needsToSwitch) {
return showPopUp<void>(
context: context,
builder: (BuildContext context) => AlertWithTwoActions(
alertTitle: S.of(context).change_current_node_title,
alertContent: S.of(context).confirm_silent_payments_switch_node,
rightButtonText: S.of(context).confirm,
leftButtonText: S.of(context).cancel,
actionRightButton: () {
dashboardViewModel.setSilentPaymentsScanning(newValue);
Navigator.of(context).pop();
},
actionLeftButton: () {
dashboardViewModel.silentPaymentsScanningActive = isSilentPaymentsScanningActive;
Navigator.of(context).pop();
},
));
alertTitle: S.of(context).change_current_node_title,
alertContent: S.of(context).confirm_silent_payments_switch_node,
rightButtonText: S.of(context).confirm,
leftButtonText: S.of(context).cancel,
actionRightButton: () {
dashboardViewModel.allowSilentPaymentsScanning(true);
dashboardViewModel.setSilentPaymentsScanning(true);
Navigator.of(context).pop();
},
actionLeftButton: () {
dashboardViewModel.silentPaymentsScanningActive = isSilentPaymentsScanningActive;
Navigator.of(context).pop();
},
),
);
}
}
return dashboardViewModel.setSilentPaymentsScanning(newValue);
@ -1045,10 +1153,9 @@ class BalanceRowWidget extends StatelessWidget {
);
},
style: OutlinedButton.styleFrom(
backgroundColor: Colors.grey.shade400
.withAlpha(50),
side: BorderSide(color: Colors.grey.shade400
.withAlpha(50), width: 0),
backgroundColor: Colors.grey.shade400.withAlpha(50),
side:
BorderSide(color: Colors.grey.shade400.withAlpha(50), width: 0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
@ -1104,10 +1211,9 @@ class BalanceRowWidget extends StatelessWidget {
);
},
style: OutlinedButton.styleFrom(
backgroundColor: Colors.grey.shade400
.withAlpha(50),
side: BorderSide(color: Colors.grey.shade400
.withAlpha(50), width: 0),
backgroundColor: Colors.grey.shade400.withAlpha(50),
side:
BorderSide(color: Colors.grey.shade400.withAlpha(50), width: 0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),

View file

@ -1,5 +1,6 @@
import 'dart:async';
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/utils/show_pop_up.dart';
import 'package:flutter/material.dart';
@ -35,7 +36,8 @@ class RescanPage extends BasePage {
isSilentPaymentsScan: _rescanViewModel.isSilentPaymentsScan,
isMwebScan: _rescanViewModel.isMwebScan,
doSingleScan: _rescanViewModel.doSingleScan,
hasDatePicker: !_rescanViewModel.isMwebScan,// disable date picker for mweb for now
hasDatePicker:
!_rescanViewModel.isMwebScan, // disable date picker for mweb for now
toggleSingleScan: () =>
_rescanViewModel.doSingleScan = !_rescanViewModel.doSingleScan,
walletType: _rescanViewModel.wallet.type,
@ -69,24 +71,32 @@ class RescanPage extends BasePage {
Navigator.of(context).pop();
final needsToSwitch =
await bitcoin!.getNodeIsElectrsSPEnabled(_rescanViewModel.wallet) == false;
late bool isElectrsSPEnabled;
try {
isElectrsSPEnabled = await bitcoin!
.getNodeIsElectrsSPEnabled(_rescanViewModel.wallet)
.timeout(const Duration(seconds: 3));
} on TimeoutException {
isElectrsSPEnabled = false;
}
final needsToSwitch = isElectrsSPEnabled == false;
if (needsToSwitch) {
return showPopUp<void>(
context: navigatorKey.currentState!.context,
builder: (BuildContext _dialogContext) => AlertWithTwoActions(
alertTitle: S.of(_dialogContext).change_current_node_title,
alertContent: S.of(_dialogContext).confirm_silent_payments_switch_node,
rightButtonText: S.of(_dialogContext).confirm,
leftButtonText: S.of(_dialogContext).cancel,
actionRightButton: () async {
Navigator.of(_dialogContext).pop();
context: context,
builder: (BuildContext _dialogContext) => AlertWithTwoActions(
alertTitle: S.of(_dialogContext).change_current_node_title,
alertContent: S.of(_dialogContext).confirm_silent_payments_switch_node,
rightButtonText: S.of(_dialogContext).confirm,
leftButtonText: S.of(_dialogContext).cancel,
actionRightButton: () async {
Navigator.of(_dialogContext).pop();
_rescanViewModel.rescanCurrentWallet(restoreHeight: height);
},
actionLeftButton: () => Navigator.of(_dialogContext).pop(),
));
_rescanViewModel.rescanCurrentWallet(restoreHeight: height);
},
actionLeftButton: () => Navigator.of(_dialogContext).pop(),
),
);
}
_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
// about what happened - but still enough.
// 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",
// if (keys['publicSpendKey'] == List.generate(64, (index) => "0").join("")) "public spend key is 0",
if (keys['publicViewKey'] == List.generate(64, (index) => "0").join(""))
@ -454,6 +455,13 @@ abstract class DashboardViewModelBase with Store {
@observable
bool silentPaymentsScanningActive = false;
@action
void allowSilentPaymentsScanning(bool allow) {
if (hasSilentPayments) {
bitcoin!.allowToSwitchNodesForScanning(wallet, allow);
}
}
@action
void setSilentPaymentsScanning(bool 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:cw_core/amount_converter.dart';
import 'package:cw_core/currency.dart';
import 'package:cw_core/receive_page_option.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:intl/intl.dart';
import 'package:mobx/mobx.dart';
@ -209,6 +210,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
required AppStore appStore,
required this.yatStore,
required this.fiatConversionStore,
ReceivePageOption? addressType,
}) : _baseItems = <ListItem>[],
selectedCurrency = walletTypeToCryptoCurrency(appStore.wallet!.type),
_cryptoNumberFormat = NumberFormat(_cryptoNumberPattern),
@ -216,6 +218,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
.contains(appStore.wallet!.type),
amount = '',
_settingsStore = appStore.settingsStore,
_addressType = addressType,
super(appStore: appStore) {
_init();
}
@ -234,6 +237,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
final FiatConversionStore fiatConversionStore;
final SettingsStore _settingsStore;
final ReceivePageOption? _addressType;
double? _fiatRate;
String _rawAmount = '';
@ -264,8 +268,19 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
WalletType get type => wallet.type;
@computed
WalletAddressListItem get address =>
WalletAddressListItem(address: wallet.walletAddresses.address, isPrimary: false);
WalletAddressListItem get address {
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
PaymentURI get uri {
@ -354,7 +369,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
}
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 isPrimary = address.id == 0;

View file

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

View file

@ -594,6 +594,7 @@
"save_backup_password_alert": "Запазване на паролата за възстановяване",
"save_to_downloads": "Запазване в Изтегляния",
"saved_the_trade_id": "Запазих trade ID-то",
"scan": "Сканиране",
"scan_one_block": "Сканирайте един блок",
"scan_qr_code": "Сканирайте 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_to_downloads": "Uložit do Stažených souborů",
"saved_the_trade_id": "Uložil jsem si ID transakce (trade ID)",
"scan": "Skenovat",
"scan_one_block": "Prohledejte jeden blok",
"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",

View file

@ -595,6 +595,7 @@
"save_backup_password_alert": "Sicherungskennwort speichern",
"save_to_downloads": "Unter „Downloads“ speichern",
"saved_the_trade_id": "Ich habe die Handels-ID gespeichert",
"scan": "Scan",
"scan_one_block": "Einen Block scannen",
"scan_qr_code": "QR-Code scannen",
"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_to_downloads": "Save to Downloads",
"saved_the_trade_id": "I've saved the trade ID",
"scan": "Scan",
"scan_one_block": "Scan one block",
"scan_qr_code": "Scan QR code",
"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_to_downloads": "Guardar en Descargas",
"saved_the_trade_id": "He salvado comercial ID",
"scan": "Escanear",
"scan_one_block": "Escanear un bloque",
"scan_qr_code": "Escanear código QR",
"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_to_downloads": "Enregistrer dans les téléchargements",
"saved_the_trade_id": "J'ai sauvegardé l'ID d'échange",
"scan": "Balayage",
"scan_one_block": "Scanner un bloc",
"scan_qr_code": "Scannez le QR code",
"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_to_downloads": "Ajiye zuwa Zazzagewa",
"saved_the_trade_id": "Na ajiye ID na ciniki",
"scan": "Scan",
"scan_one_block": "Duba toshe daya",
"scan_qr_code": "Gani QR kodin",
"scan_qr_code_to_get_address": "Duba lambar QR don samun adireshin",

View file

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

View file

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

View file

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

View file

@ -597,6 +597,7 @@
"save_backup_password_alert": "Simpan kata sandi cadangan",
"save_to_downloads": "Simpan ke Unduhan",
"saved_the_trade_id": "Saya telah menyimpan ID perdagangan",
"scan": "Pindai",
"scan_one_block": "Pindai satu blok",
"scan_qr_code": "Scan 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_to_downloads": "Salva in Download",
"saved_the_trade_id": "Ho salvato l'ID dello scambio",
"scan": "Scansione",
"scan_one_block": "Scansionare un blocco",
"scan_qr_code": "Scansiona il codice QR",
"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_to_downloads": "ダウンロードに保存",
"saved_the_trade_id": "取引IDを保存しました",
"scan": "スキャン",
"scan_one_block": "1つのブロックをスキャンします",
"scan_qr_code": "QRコードをスキャン",
"scan_qr_code_to_get_address": "QRコードをスキャンして住所を取得します",

View file

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

View file

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

View file

@ -594,6 +594,7 @@
"save_backup_password_alert": "Bewaar back-upwachtwoord",
"save_to_downloads": "Opslaan in downloads",
"saved_the_trade_id": "Ik heb de ruil-ID opgeslagen",
"scan": "Scannen",
"scan_one_block": "Scan een blok",
"scan_qr_code": "Scan QR-code",
"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_to_downloads": "Zapisz w Pobranych",
"saved_the_trade_id": "Zapisałem ID",
"scan": "Skandować",
"scan_one_block": "Zeskanuj jeden blok",
"scan_qr_code": "Skanowania QR code",
"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_to_downloads": "Salvar em Downloads",
"saved_the_trade_id": "ID da troca salvo",
"scan": "Scan",
"scan_one_block": "Escanear um bloco",
"scan_qr_code": "Escanear código QR",
"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_send": "Converter de",
"yy": "aa"
}
}

View file

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

View file

@ -594,6 +594,7 @@
"save_backup_password_alert": "บันทึกรหัสผ่านสำรอง",
"save_to_downloads": "บันทึกลงดาวน์โหลด",
"saved_the_trade_id": "ฉันได้บันทึก ID ของการซื้อขายแล้ว",
"scan": "สแกน",
"scan_one_block": "สแกนหนึ่งบล็อก",
"scan_qr_code": "สแกนรหัส QR",
"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_to_downloads": "I-save sa mga Pag-download",
"saved_the_trade_id": "Nai-save ko na ang trade ID",
"scan": "I -scan",
"scan_one_block": "I-scan ang isang bloke",
"scan_qr_code": "I-scan ang QR code",
"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_to_downloads": "İndirilenlere Kaydet",
"saved_the_trade_id": "Takas ID'imi kaydettim",
"scan": "Taramak",
"scan_one_block": "Bir bloğu tara",
"scan_qr_code": "QR kodunu tarayın",
"scan_qr_code_to_get_address": "Adresi getirmek için QR kodunu tara",

View file

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

View file

@ -596,6 +596,7 @@
"save_backup_password_alert": "بیک اپ پاس ورڈ محفوظ کریں۔",
"save_to_downloads": "۔ﮟﯾﺮﮐ ﻅﻮﻔﺤﻣ ﮟﯿﻣ ﺯﮈﻮﻟ ﻥﺅﺍﮈ",
"saved_the_trade_id": "میں نے تجارتی ID محفوظ کر لی ہے۔",
"scan": "اسکین",
"scan_one_block": "ایک بلاک اسکین کریں",
"scan_qr_code": "پتہ حاصل کرنے کے لیے 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_to_downloads": "Lưu vào Tải xuống",
"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_qr_code": "Quét mã QR",
"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_to_downloads": "Fipamọ si Awọn igbasilẹ",
"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_qr_code": "Yan QR koodu",
"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_to_downloads": "保存到下载",
"saved_the_trade_id": "我已经保存了交易编号",
"scan": "扫描",
"scan_one_block": "扫描一个街区",
"scan_qr_code": "扫描二维码",
"scan_qr_code_to_get_address": "扫描二维码获取地址",

View file

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