feat: allow scanning elect-rs using get_tweaks

This commit is contained in:
Rafael Saes 2024-02-23 19:41:26 -03:00
parent e4703a9ace
commit 71201d4dee
46 changed files with 579 additions and 609 deletions

View file

@ -23,9 +23,28 @@ class BitcoinReceivePageOption implements ReceivePageOption {
BitcoinReceivePageOption.p2sh, BitcoinReceivePageOption.p2sh,
BitcoinReceivePageOption.p2tr, BitcoinReceivePageOption.p2tr,
BitcoinReceivePageOption.p2wsh, BitcoinReceivePageOption.p2wsh,
BitcoinReceivePageOption.p2pkh BitcoinReceivePageOption.p2pkh,
BitcoinReceivePageOption.silent_payments,
]; ];
BitcoinAddressType toType() {
switch (this) {
case BitcoinReceivePageOption.p2tr:
return SegwitAddresType.p2tr;
case BitcoinReceivePageOption.p2wsh:
return SegwitAddresType.p2wsh;
case BitcoinReceivePageOption.p2pkh:
return P2pkhAddressType.p2pkh;
case BitcoinReceivePageOption.p2sh:
return P2shAddressType.p2wpkhInP2sh;
case BitcoinReceivePageOption.silent_payments:
return SilentPaymentsAddresType.p2sp;
case BitcoinReceivePageOption.p2wpkh:
default:
return SegwitAddresType.p2wpkh;
}
}
factory BitcoinReceivePageOption.fromType(BitcoinAddressType type) { factory BitcoinReceivePageOption.fromType(BitcoinAddressType type) {
switch (type) { switch (type) {
case SegwitAddresType.p2tr: case SegwitAddresType.p2tr:

View file

@ -1,3 +1,4 @@
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/unspent_coins_info.dart';
@ -37,7 +38,11 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
password: password, password: password,
walletInfo: walletInfo, walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo, unspentCoinsInfo: unspentCoinsInfo,
networkType: bitcoin.bitcoin, networkType: networkParam == null
? bitcoin.bitcoin
: networkParam == BitcoinNetwork.mainnet
? bitcoin.bitcoin
: bitcoin.testnet,
initialAddresses: initialAddresses, initialAddresses: initialAddresses,
initialBalance: initialBalance, initialBalance: initialBalance,
seedBytes: seedBytes, seedBytes: seedBytes,
@ -64,6 +69,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
required String password, required String password,
required WalletInfo walletInfo, required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo, required Box<UnspentCoinsInfo> unspentCoinsInfo,
String? addressPageType,
BasedUtxoNetwork? network,
List<BitcoinAddressRecord>? initialAddresses, List<BitcoinAddressRecord>? initialAddresses,
List<BitcoinAddressRecord>? initialSilentAddresses, List<BitcoinAddressRecord>? initialSilentAddresses,
ElectrumBalance? initialBalance, ElectrumBalance? initialBalance,
@ -71,6 +78,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
Map<String, int>? initialChangeAddressIndex, Map<String, int>? initialChangeAddressIndex,
int initialSilentAddressIndex = 0, int initialSilentAddressIndex = 0,
}) async { }) async {
final seedBytes = await mnemonicToSeedBytes(mnemonic);
return BitcoinWallet( return BitcoinWallet(
mnemonic: mnemonic, mnemonic: mnemonic,
password: password, password: password,
@ -79,10 +87,18 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
initialAddresses: initialAddresses, initialAddresses: initialAddresses,
initialSilentAddresses: initialSilentAddresses, initialSilentAddresses: initialSilentAddresses,
initialSilentAddressIndex: initialSilentAddressIndex, initialSilentAddressIndex: initialSilentAddressIndex,
silentAddress: await SilentPaymentOwner.fromMnemonic(mnemonic, silentAddress: await SilentPaymentOwner.fromPrivateKeys(
scanPrivkey: ECPrivate.fromHex(bitcoin.HDWallet.fromSeed(
seedBytes,
network: network == BitcoinNetwork.testnet ? bitcoin.testnet : bitcoin.bitcoin,
).derivePath(SCAN_PATH).privKey!),
spendPrivkey: ECPrivate.fromHex(bitcoin.HDWallet.fromSeed(
seedBytes,
network: network == BitcoinNetwork.testnet ? bitcoin.testnet : bitcoin.bitcoin,
).derivePath(SPEND_PATH).privKey!),
hrp: network == BitcoinNetwork.testnet ? 'tsp' : 'sp'), hrp: network == BitcoinNetwork.testnet ? 'tsp' : 'sp'),
initialBalance: initialBalance, initialBalance: initialBalance,
seedBytes: await mnemonicToSeedBytes(mnemonic), seedBytes: seedBytes,
initialRegularAddressIndex: initialRegularAddressIndex, initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex,
addressPageType: addressPageType, addressPageType: addressPageType,
@ -96,7 +112,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
required Box<UnspentCoinsInfo> unspentCoinsInfo, required Box<UnspentCoinsInfo> unspentCoinsInfo,
required String password, required String password,
}) async { }) async {
final snp = await ElectrumWallletSnapshot.load(name, walletInfo.type, password); final snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password,
walletInfo.network != null ? BasedUtxoNetwork.fromName(walletInfo.network!) : null);
final seedBytes = await mnemonicToSeedBytes(snp.mnemonic);
return BitcoinWallet( return BitcoinWallet(
mnemonic: snp.mnemonic, mnemonic: snp.mnemonic,
password: password, password: password,
@ -105,10 +124,18 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
initialAddresses: snp.addresses, initialAddresses: snp.addresses,
initialSilentAddresses: snp.silentAddresses, initialSilentAddresses: snp.silentAddresses,
initialSilentAddressIndex: snp.silentAddressIndex, initialSilentAddressIndex: snp.silentAddressIndex,
silentAddress: await SilentPaymentOwner.fromMnemonic(snp.mnemonic, silentAddress: await SilentPaymentOwner.fromPrivateKeys(
scanPrivkey: ECPrivate.fromHex(bitcoin.HDWallet.fromSeed(
seedBytes,
network: snp.network == BitcoinNetwork.testnet ? bitcoin.testnet : bitcoin.bitcoin,
).derivePath(SCAN_PATH).privKey!),
spendPrivkey: ECPrivate.fromHex(bitcoin.HDWallet.fromSeed(
seedBytes,
network: snp.network == BitcoinNetwork.testnet ? bitcoin.testnet : bitcoin.bitcoin,
).derivePath(SPEND_PATH).privKey!),
hrp: snp.network == BitcoinNetwork.testnet ? 'tsp' : 'sp'), hrp: snp.network == BitcoinNetwork.testnet ? 'tsp' : 'sp'),
initialBalance: snp.balance, initialBalance: snp.balance,
seedBytes: await mnemonicToSeedBytes(snp.mnemonic), seedBytes: seedBytes,
initialRegularAddressIndex: snp.regularAddressIndex, initialRegularAddressIndex: snp.regularAddressIndex,
initialChangeAddressIndex: snp.changeAddressIndex, initialChangeAddressIndex: snp.changeAddressIndex,
addressPageType: snp.addressPageType, addressPageType: snp.addressPageType,

View file

@ -2,12 +2,12 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:bitcoin_flutter/bitcoin_flutter.dart'; import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart'; import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_bitcoin/script_hash.dart'; import 'package:cw_bitcoin/script_hash.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
import 'package:collection/collection.dart'; import 'package:http/http.dart' as http;
String jsonrpcparams(List<Object> params) { String jsonrpcparams(List<Object> params) {
final _params = params?.map((val) => '"${val.toString()}"')?.join(','); final _params = params?.map((val) => '"${val.toString()}"')?.join(',');
@ -22,10 +22,7 @@ String jsonrpc(
'{"jsonrpc": "$version", "method": "$method", "id": "$id", "params": ${json.encode(params)}}\n'; '{"jsonrpc": "$version", "method": "$method", "id": "$id", "params": ${json.encode(params)}}\n';
class SocketTask { class SocketTask {
SocketTask({ SocketTask({required this.isSubscription, this.completer, this.subject});
required this.isSubscription,
this.completer,
this.subject});
final Completer<dynamic>? completer; final Completer<dynamic>? completer;
final BehaviorSubject<dynamic>? subject; final BehaviorSubject<dynamic>? subject;
@ -51,16 +48,19 @@ class ElectrumClient {
Timer? _aliveTimer; Timer? _aliveTimer;
String unterminatedString; String unterminatedString;
Future<void> connectToUri(Uri uri) async => Uri? uri;
Future<void> connectToUri(Uri uri) async {
this.uri = uri;
await connect(host: uri.host, port: uri.port); await connect(host: uri.host, port: uri.port);
}
Future<void> connect({required String host, required int port}) async { Future<void> connect({required String host, required int port}) async {
try { try {
await socket?.close(); await socket?.close();
} catch (_) {} } catch (_) {}
socket = await SecureSocket.connect(host, port, socket = await Socket.connect(host, port, timeout: connectionTimeout);
timeout: connectionTimeout, onBadCertificate: (_) => true);
_setIsConnected(true); _setIsConnected(true);
socket!.listen((Uint8List event) { socket!.listen((Uint8List event) {
@ -104,21 +104,20 @@ class ElectrumClient {
} }
if (isJSONStringCorrect(unterminatedString)) { if (isJSONStringCorrect(unterminatedString)) {
final response = final response = json.decode(unterminatedString) as Map<String, dynamic>;
json.decode(unterminatedString) as Map<String, dynamic>;
_handleResponse(response); _handleResponse(response);
unterminatedString = ''; unterminatedString = '';
} }
} on TypeError catch (e) { } on TypeError catch (e) {
if (!e.toString().contains('Map<String, Object>') && !e.toString().contains('Map<String, dynamic>')) { if (!e.toString().contains('Map<String, Object>') &&
!e.toString().contains('Map<String, dynamic>')) {
return; return;
} }
unterminatedString += message; unterminatedString += message;
if (isJSONStringCorrect(unterminatedString)) { if (isJSONStringCorrect(unterminatedString)) {
final response = final response = json.decode(unterminatedString) as Map<String, dynamic>;
json.decode(unterminatedString) as Map<String, dynamic>;
_handleResponse(response); _handleResponse(response);
// unterminatedString = null; // unterminatedString = null;
unterminatedString = ''; unterminatedString = '';
@ -142,8 +141,7 @@ class ElectrumClient {
} }
} }
Future<List<String>> version() => Future<List<String>> version() => call(method: 'server.version').then((dynamic result) {
call(method: 'server.version').then((dynamic result) {
if (result is List) { if (result is List) {
return result.map((dynamic val) => val.toString()).toList(); return result.map((dynamic val) => val.toString()).toList();
} }
@ -178,11 +176,10 @@ class ElectrumClient {
}); });
Future<List<Map<String, dynamic>>> getListUnspentWithAddress( Future<List<Map<String, dynamic>>> getListUnspentWithAddress(
String address, NetworkType networkType) => String address, BasedUtxoNetwork network) =>
call( call(
method: 'blockchain.scripthash.listunspent', method: 'blockchain.scripthash.listunspent',
params: [scriptHash(address, networkType: networkType)]) params: [scriptHash(address, network: network)]).then((dynamic result) {
.then((dynamic result) {
if (result is List) { if (result is List) {
return result.map((dynamic val) { return result.map((dynamic val) {
if (val is Map<String, dynamic>) { if (val is Map<String, dynamic>) {
@ -229,8 +226,7 @@ class ElectrumClient {
return []; return [];
}); });
Future<Map<String, dynamic>> getTransactionRaw( Future<Map<String, dynamic>> getTransactionRaw({required String hash}) async =>
{required String hash}) async =>
callWithTimeout(method: 'blockchain.transaction.get', params: [hash, true], timeout: 10000) callWithTimeout(method: 'blockchain.transaction.get', params: [hash, true], timeout: 10000)
.then((dynamic result) { .then((dynamic result) {
if (result is Map<String, dynamic>) { if (result is Map<String, dynamic>) {
@ -240,8 +236,7 @@ class ElectrumClient {
return <String, dynamic>{}; return <String, dynamic>{};
}); });
Future<String> getTransactionHex( Future<String> getTransactionHex({required String hash}) async =>
{required String hash}) async =>
callWithTimeout(method: 'blockchain.transaction.get', params: [hash, false], timeout: 10000) callWithTimeout(method: 'blockchain.transaction.get', params: [hash, false], timeout: 10000)
.then((dynamic result) { .then((dynamic result) {
if (result is String) { if (result is String) {
@ -252,29 +247,43 @@ class ElectrumClient {
}); });
Future<String> broadcastTransaction( Future<String> broadcastTransaction(
{required String transactionRaw}) async => {required String transactionRaw, BasedUtxoNetwork? network}) async {
call(method: 'blockchain.transaction.broadcast', params: [transactionRaw]) if (network == BitcoinNetwork.testnet) {
.then((dynamic result) { return http
if (result is String) { .post(Uri(scheme: 'https', host: 'blockstream.info', path: '/testnet/api/tx'),
return result; headers: <String, String>{'Content-Type': 'application/json; charset=utf-8'},
body: transactionRaw)
.then((http.Response response) {
if (response.statusCode == 200) {
return response.body;
} }
return ''; throw Exception('Failed to broadcast transaction: ${response.body}');
}); });
}
Future<Map<String, dynamic>> getMerkle( return call(method: 'blockchain.transaction.broadcast', params: [transactionRaw])
{required String hash, required int height}) async => .then((dynamic result) {
await call( if (result is String) {
method: 'blockchain.transaction.get_merkle', return result;
params: [hash, height]) as Map<String, dynamic>; }
Future<Map<String, dynamic>> getHeader({required int height}) async => return '';
await call(method: 'blockchain.block.get_header', params: [height]) });
}
Future<Map<String, dynamic>> getMerkle({required String hash, required int height}) async =>
await call(method: 'blockchain.transaction.get_merkle', params: [hash, height])
as Map<String, dynamic>; as Map<String, dynamic>;
Future<Map<String, dynamic>> getHeader({required int height}) async =>
await call(method: 'blockchain.block.get_header', params: [height]) as Map<String, dynamic>;
Future<Map<String, dynamic>> getTweaks({required int height}) async =>
await call(method: 'blockchain.block.tweaks', params: [height]) as Map<String, dynamic>;
Future<double> estimatefee({required int p}) => Future<double> estimatefee({required int p}) =>
call(method: 'blockchain.estimatefee', params: [p]) call(method: 'blockchain.estimatefee', params: [p]).then((dynamic result) {
.then((dynamic result) {
if (result is double) { if (result is double) {
return result; return result;
} }
@ -314,20 +323,17 @@ class ElectrumClient {
return []; return [];
}); });
Future<List<int>> feeRates() async { Future<List<int>> feeRates({BasedUtxoNetwork? network}) async {
if (network == BitcoinNetwork.testnet) {
return [1, 1, 1];
}
try { try {
final topDoubleString = await estimatefee(p: 1); final topDoubleString = await estimatefee(p: 1);
final middleDoubleString = await estimatefee(p: 5); final middleDoubleString = await estimatefee(p: 5);
final bottomDoubleString = await estimatefee(p: 100); final bottomDoubleString = await estimatefee(p: 100);
final top = final top = (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000).round();
(stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000) final middle = (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000).round();
.round(); final bottom = (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000).round();
final middle =
(stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000)
.round();
final bottom =
(stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000)
.round();
return [bottom, middle, top]; return [bottom, middle, top];
} catch (_) { } catch (_) {
@ -365,16 +371,14 @@ class ElectrumClient {
} }
BehaviorSubject<T>? subscribe<T>( BehaviorSubject<T>? subscribe<T>(
{required String id, {required String id, required String method, List<Object> params = const []}) {
required String method,
List<Object> params = const []}) {
try { try {
final subscription = BehaviorSubject<T>(); final subscription = BehaviorSubject<T>();
_regisrySubscription(id, subscription); _regisrySubscription(id, subscription);
socket!.write(jsonrpc(method: method, id: _id, params: params)); socket!.write(jsonrpc(method: method, id: _id, params: params));
return subscription; return subscription;
} catch(e) { } catch (e) {
print(e.toString()); print(e.toString());
return null; return null;
} }
@ -391,9 +395,7 @@ class ElectrumClient {
} }
Future<dynamic> callWithTimeout( Future<dynamic> callWithTimeout(
{required String method, {required String method, List<Object> params = const [], int timeout = 4000}) async {
List<Object> params = const [],
int timeout = 4000}) async {
try { try {
final completer = Completer<dynamic>(); final completer = Completer<dynamic>();
_id += 1; _id += 1;
@ -407,7 +409,7 @@ class ElectrumClient {
}); });
return completer.future; return completer.future;
} catch(e) { } catch (e) {
print(e.toString()); print(e.toString());
} }
} }
@ -418,8 +420,8 @@ class ElectrumClient {
onConnectionStatusChange = null; onConnectionStatusChange = null;
} }
void _registryTask(int id, Completer<dynamic> completer) => _tasks[id.toString()] = void _registryTask(int id, Completer<dynamic> completer) =>
SocketTask(completer: completer, isSubscription: false); _tasks[id.toString()] = SocketTask(completer: completer, isSubscription: false);
void _regisrySubscription(String id, BehaviorSubject<dynamic> subject) => void _regisrySubscription(String id, BehaviorSubject<dynamic> subject) =>
_tasks[id] = SocketTask(subject: subject, isSubscription: true); _tasks[id] = SocketTask(subject: subject, isSubscription: true);
@ -440,8 +442,7 @@ class ElectrumClient {
} }
} }
void _methodHandler( void _methodHandler({required String method, required Map<String, dynamic> request}) {
{required String method, required Map<String, dynamic> request}) {
switch (method) { switch (method) {
case 'blockchain.scripthash.subscribe': case 'blockchain.scripthash.subscribe':
final params = request['params'] as List<dynamic>; final params = request['params'] as List<dynamic>;
@ -472,8 +473,8 @@ class ElectrumClient {
_methodHandler(method: method, request: response); _methodHandler(method: method, request: response);
return; return;
} }
if (id != null){ if (id != null) {
_finish(id, result); _finish(id, result);
} }
} }

View file

@ -5,6 +5,7 @@ import 'dart:isolate';
import 'dart:math'; import 'dart:math';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:cw_bitcoin/address_to_output_script.dart'; import 'package:cw_bitcoin/address_to_output_script.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart';
@ -133,7 +134,7 @@ abstract class ElectrumWalletBase
Map<String, BehaviorSubject<Object>?> _scripthashesUpdateSubject; Map<String, BehaviorSubject<Object>?> _scripthashesUpdateSubject;
BehaviorSubject<Object>? _chainTipUpdateSubject; BehaviorSubject<Object>? _chainTipUpdateSubject;
bool _isTransactionUpdating; bool _isTransactionUpdating;
// Future<Isolate>? _isolate; Future<Isolate>? _isolate;
void Function(FlutterErrorDetails)? _onError; void Function(FlutterErrorDetails)? _onError;
Timer? _autoSaveTimer; Timer? _autoSaveTimer;
@ -147,66 +148,66 @@ abstract class ElectrumWalletBase
Timer.periodic(Duration(seconds: _autoSaveInterval), (_) async => await save()); Timer.periodic(Duration(seconds: _autoSaveInterval), (_) async => await save());
} }
// @action @action
// Future<void> _setListeners(int height, {int? chainTip}) async { Future<void> _setListeners(int height, {int? chainTip}) async {
// final currentChainTip = chainTip ?? await electrumClient.getCurrentBlockChainTip() ?? 0; final currentChainTip = chainTip ?? await electrumClient.getCurrentBlockChainTip() ?? 0;
// syncStatus = AttemptingSyncStatus(); syncStatus = AttemptingSyncStatus();
// if (_isolate != null) { if (_isolate != null) {
// final runningIsolate = await _isolate!; final runningIsolate = await _isolate!;
// runningIsolate.kill(priority: Isolate.immediate); runningIsolate.kill(priority: Isolate.immediate);
// } }
// final receivePort = ReceivePort(); final receivePort = ReceivePort();
// _isolate = Isolate.spawn( _isolate = Isolate.spawn(
// startRefresh, startRefresh,
// ScanData( ScanData(
// sendPort: receivePort.sendPort, sendPort: receivePort.sendPort,
// primarySilentAddress: walletAddresses.primarySilentAddress!, primarySilentAddress: walletAddresses.primarySilentAddress!,
// networkType: networkType, network: network,
// height: height, height: height,
// chainTip: currentChainTip, chainTip: currentChainTip,
// electrumClient: ElectrumClient(), electrumClient: ElectrumClient(),
// transactionHistoryIds: transactionHistory.transactions.keys.toList(), transactionHistoryIds: transactionHistory.transactions.keys.toList(),
// node: electrumClient.uri.toString(), node: electrumClient.uri.toString(),
// labels: walletAddresses.labels, labels: walletAddresses.labels,
// )); ));
// await for (var message in receivePort) { await for (var message in receivePort) {
// if (message is BitcoinUnspent) { if (message is BitcoinUnspent) {
// if (!unspentCoins.any((utx) => if (!unspentCoins.any((utx) =>
// utx.hash.contains(message.hash) && utx.hash.contains(message.hash) &&
// utx.vout == message.vout && utx.vout == message.vout &&
// utx.address.contains(message.address))) { utx.address.contains(message.address))) {
// unspentCoins.add(message); unspentCoins.add(message);
// if (unspentCoinsInfo.values.any((element) => if (unspentCoinsInfo.values.any((element) =>
// element.walletId.contains(id) && element.walletId.contains(id) &&
// element.hash.contains(message.hash) && element.hash.contains(message.hash) &&
// element.address.contains(message.address))) { element.address.contains(message.address))) {
// _addCoinInfo(message); _addCoinInfo(message);
// await walletInfo.save(); await walletInfo.save();
// await save(); await save();
// } }
// balance[currency] = await _fetchBalances(); balance[currency] = await _fetchBalances();
// } }
// } }
// if (message is Map<String, ElectrumTransactionInfo>) { if (message is Map<String, ElectrumTransactionInfo>) {
// transactionHistory.addMany(message); transactionHistory.addMany(message);
// await transactionHistory.save(); await transactionHistory.save();
// } }
// // check if is a SyncStatus type since "is SyncStatus" doesn't work here // check if is a SyncStatus type since "is SyncStatus" doesn't work here
// if (message is SyncResponse) { if (message is SyncResponse) {
// syncStatus = message.syncStatus; syncStatus = message.syncStatus;
// walletInfo.restoreHeight = message.height; walletInfo.restoreHeight = message.height;
// await walletInfo.save(); await walletInfo.save();
// } }
// } }
// } }
@action @action
@override @override
@ -248,11 +249,11 @@ abstract class ElectrumWalletBase
}; };
syncStatus = ConnectedSyncStatus(); syncStatus = ConnectedSyncStatus();
// final currentChainTip = await electrumClient.getCurrentBlockChainTip(); final currentChainTip = await electrumClient.getCurrentBlockChainTip();
// if ((currentChainTip ?? 0) > walletInfo.restoreHeight) { if ((currentChainTip ?? 0) > walletInfo.restoreHeight) {
// _setListeners(walletInfo.restoreHeight, chainTip: currentChainTip); _setListeners(walletInfo.restoreHeight, chainTip: currentChainTip);
// } }
} catch (e) { } catch (e) {
print(e.toString()); print(e.toString());
syncStatus = FailedSyncStatus(); syncStatus = FailedSyncStatus();
@ -643,7 +644,7 @@ abstract class ElectrumWalletBase
@override @override
Future<void> rescan({required int height, int? chainTip, ScanData? scanData}) async { Future<void> rescan({required int height, int? chainTip, ScanData? scanData}) async {
// _setListeners(height); _setListeners(height);
} }
@override @override
@ -908,7 +909,7 @@ abstract class ElectrumWalletBase
try { try {
final currentHeight = await electrumClient.getCurrentBlockChainTip(); final currentHeight = await electrumClient.getCurrentBlockChainTip();
if (currentHeight != null) walletInfo.restoreHeight = currentHeight; if (currentHeight != null) walletInfo.restoreHeight = currentHeight;
// _setListeners(walletInfo.restoreHeight, chainTip: currentHeight); _setListeners(walletInfo.restoreHeight, chainTip: currentHeight);
} catch (e, s) { } catch (e, s) {
print(e.toString()); print(e.toString());
_onError?.call(FlutterErrorDetails( _onError?.call(FlutterErrorDetails(
@ -1011,10 +1012,10 @@ abstract class ElectrumWalletBase
class ScanData { class ScanData {
final SendPort sendPort; final SendPort sendPort;
final SilentPaymentReceiver primarySilentAddress; final SilentPaymentOwner primarySilentAddress;
final int height; final int height;
final String node; final String node;
final bitcoin.NetworkType networkType; final BasedUtxoNetwork network;
final int chainTip; final int chainTip;
final ElectrumClient electrumClient; final ElectrumClient electrumClient;
final List<String> transactionHistoryIds; final List<String> transactionHistoryIds;
@ -1025,7 +1026,7 @@ class ScanData {
required this.primarySilentAddress, required this.primarySilentAddress,
required this.height, required this.height,
required this.node, required this.node,
required this.networkType, required this.network,
required this.chainTip, required this.chainTip,
required this.electrumClient, required this.electrumClient,
required this.transactionHistoryIds, required this.transactionHistoryIds,
@ -1038,7 +1039,7 @@ class ScanData {
primarySilentAddress: scanData.primarySilentAddress, primarySilentAddress: scanData.primarySilentAddress,
height: newHeight, height: newHeight,
node: scanData.node, node: scanData.node,
networkType: scanData.networkType, network: scanData.network,
chainTip: scanData.chainTip, chainTip: scanData.chainTip,
transactionHistoryIds: scanData.transactionHistoryIds, transactionHistoryIds: scanData.transactionHistoryIds,
electrumClient: scanData.electrumClient, electrumClient: scanData.electrumClient,
@ -1054,333 +1055,220 @@ class SyncResponse {
SyncResponse(this.height, this.syncStatus); SyncResponse(this.height, this.syncStatus);
} }
// Future<void> startRefresh(ScanData scanData) async { Future<void> startRefresh(ScanData scanData) async {
// var cachedBlockchainHeight = scanData.chainTip; var cachedBlockchainHeight = scanData.chainTip;
// Future<int> getNodeHeightOrUpdate(int baseHeight) async { Future<int> getNodeHeightOrUpdate(int baseHeight) async {
// if (cachedBlockchainHeight < baseHeight || cachedBlockchainHeight == 0) { if (cachedBlockchainHeight < baseHeight || cachedBlockchainHeight == 0) {
// final electrumClient = scanData.electrumClient; final electrumClient = scanData.electrumClient;
// if (!electrumClient.isConnected) { if (!electrumClient.isConnected) {
// final node = scanData.node; final node = scanData.node;
// await electrumClient.connectToUri(Uri.parse(node)); await electrumClient.connectToUri(Uri.parse(node));
// } }
// cachedBlockchainHeight = cachedBlockchainHeight =
// await electrumClient.getCurrentBlockChainTip() ?? cachedBlockchainHeight; await electrumClient.getCurrentBlockChainTip() ?? cachedBlockchainHeight;
// } }
// return cachedBlockchainHeight; return cachedBlockchainHeight;
// } }
// var lastKnownBlockHeight = 0; var lastKnownBlockHeight = 0;
// var initialSyncHeight = 0; var initialSyncHeight = 0;
// var syncHeight = scanData.height; var syncHeight = scanData.height;
// var currentChainTip = scanData.chainTip; var currentChainTip = scanData.chainTip;
// if (syncHeight <= 0) { if (syncHeight <= 0) {
// syncHeight = currentChainTip; syncHeight = currentChainTip;
// } }
// if (initialSyncHeight <= 0) { if (initialSyncHeight <= 0) {
// initialSyncHeight = syncHeight; initialSyncHeight = syncHeight;
// } }
// if (lastKnownBlockHeight == syncHeight) { if (lastKnownBlockHeight == syncHeight) {
// scanData.sendPort.send(SyncResponse(currentChainTip, SyncedSyncStatus())); scanData.sendPort.send(SyncResponse(currentChainTip, SyncedSyncStatus()));
// return; return;
// } }
// // Run this until no more blocks left to scan txs. At first this was recursive // Run this until no more blocks left to scan txs. At first this was recursive
// // i.e. re-calling the startRefresh function but this was easier for the above values to retain // i.e. re-calling the startRefresh function but this was easier for the above values to retain
// // their initial values // their initial values
// while (true) { while (true) {
// lastKnownBlockHeight = syncHeight; lastKnownBlockHeight = syncHeight;
// final syncingStatus = final syncingStatus =
// SyncingSyncStatus.fromHeightValues(currentChainTip, initialSyncHeight, syncHeight); SyncingSyncStatus.fromHeightValues(currentChainTip, initialSyncHeight, syncHeight);
// scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus)); scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus));
// if (syncingStatus.blocksLeft <= 0) { if (syncingStatus.blocksLeft <= 0) {
// scanData.sendPort.send(SyncResponse(currentChainTip, SyncedSyncStatus())); scanData.sendPort.send(SyncResponse(currentChainTip, SyncedSyncStatus()));
// return; return;
// } }
// // print(["Scanning from height:", syncHeight]); print(["Scanning from height:", syncHeight]);
// try { try {
// final networkPath = // Get all the tweaks from the block
// scanData.networkType.network == bitcoin.BtcNetwork.mainnet ? "" : "/testnet"; final electrumClient = scanData.electrumClient;
if (!electrumClient.isConnected) {
final node = scanData.node;
await electrumClient.connectToUri(Uri.parse(node));
}
final tweaks = await electrumClient.getTweaks(height: syncHeight);
// // This endpoint gets up to 10 latest blocks from the given height for (var i = 0; i < tweaks.length; i++) {
// final tenNewestBlocks = try {
// (await http.get(Uri.parse("https://blockstream.info$networkPath/api/blocks/$syncHeight"))) // final txid = tweaks.keys.toList()[i];
// .body; final details = tweaks.values.toList()[i];
// var decodedBlocks = json.decode(tenNewestBlocks) as List<dynamic>; print(["details", details]);
final output_pubkeys = (details["output_pubkeys"] as List<String>);
// decodedBlocks.sort((a, b) => (a["height"] as int).compareTo(b["height"] as int)); // print(["Scanning tx:", txid]);
// decodedBlocks =
// decodedBlocks.where((element) => (element["height"] as int) >= syncHeight).toList();
// // for each block, get up to 25 txs // TODO: if tx already scanned & stored skip
// for (var i = 0; i < decodedBlocks.length; i++) { // if (scanData.transactionHistoryIds.contains(txid)) {
// final blockJson = decodedBlocks[i]; // // already scanned tx, continue to next tx
// final blockHash = blockJson["id"]; // pos++;
// final txCount = blockJson["tx_count"] as int; // continue;
// }
// // print(["Scanning block index:", i, "with tx count:", txCount]); final result = SilentPayment.scanTweak(
scanData.primarySilentAddress.b_scan,
scanData.primarySilentAddress.B_spend,
details["tweak"] as String,
output_pubkeys.map((e) => BytesUtils.fromHexString(e)).toList(),
labels: scanData.labels,
);
// int startIndex = 0; if (result.isEmpty) {
// // go through each tx in block until no more txs are left // no results tx, continue to next tx
// while (startIndex < txCount) { continue;
// // This endpoint gets up to 25 txs from the given block hash and start index }
// final twentyFiveTxs = json.decode((await http.get(Uri.parse(
// "https://blockstream.info$networkPath/api/block/$blockHash/txs/$startIndex")))
// .body) as List<dynamic>;
// // print(["Scanning txs index:", startIndex]); if (result.length > 1) {
print("MULTIPLE UNSPENT COINS FOUND!");
} else {
print("UNSPENT COIN FOUND!");
}
// // For each tx, apply silent payment filtering and do shared secret calculation when applied // result.forEach((key, value) async {
// for (var i = 0; i < twentyFiveTxs.length; i++) { // final outpoint = output_pubkeys[key];
// try {
// final tx = twentyFiveTxs[i];
// final txid = tx["txid"] as String;
// // print(["Scanning tx:", txid]); // if (outpoint == null) {
// return;
// }
// // TODO: if tx already scanned & stored skip // final tweak = value[0];
// // if (scanData.transactionHistoryIds.contains(txid)) { // String? label;
// // // already scanned tx, continue to next tx // if (value.length > 1) label = value[1];
// // pos++;
// // continue;
// // }
// List<String> pubkeys = []; // final txInfo = ElectrumTransactionInfo(
// List<bitcoin.Outpoint> outpoints = []; // WalletType.bitcoin,
// id: txid,
// height: syncHeight,
// amount: outpoint.value!,
// fee: 0,
// direction: TransactionDirection.incoming,
// isPending: false,
// date: DateTime.fromMillisecondsSinceEpoch((blockJson["timestamp"] as int) * 1000),
// confirmations: currentChainTip - syncHeight,
// to: bitcoin.SilentPaymentAddress.createLabeledSilentPaymentAddress(
// scanData.primarySilentAddress.scanPubkey,
// scanData.primarySilentAddress.spendPubkey,
// label != null ? label.fromHex : "0".fromHex,
// hrp: scanData.primarySilentAddress.hrp,
// version: scanData.primarySilentAddress.version)
// .toString(),
// unspent: null,
// );
// bool skip = false; // final status = json.decode((await http
// .get(Uri.parse("https://blockstream.info/testnet/api/tx/$txid/outspends")))
// .body) as List<dynamic>;
// for (var i = 0; i < (tx["vin"] as List<dynamic>).length; i++) { // bool spent = false;
// final input = tx["vin"][i]; // for (final s in status) {
// final prevout = input["prevout"]; // if ((s["spent"] as bool) == true) {
// final scriptPubkeyType = prevout["scriptpubkey_type"]; // spent = true;
// String? pubkey;
// if (scriptPubkeyType == "v0_p2wpkh" || scriptPubkeyType == "v1_p2tr") { // scanData.sendPort.send({txid: txInfo});
// final witness = input["witness"];
// if (witness == null) {
// skip = true;
// // print("Skipping, no witness");
// break;
// }
// if (witness.length == 2) { // final sentTxId = s["txid"] as String;
// pubkey = witness[1] as String; // final sentTx = json.decode(
// } else if (witness.length == 1) { // (await http.get(Uri.parse("https://blockstream.info/testnet/api/tx/$sentTxId")))
// pubkey = "02" + (prevout["scriptpubkey"] as String).fromHex.sublist(2).hex; // .body);
// }
// }
// if (scriptPubkeyType == "p2pkh") { // int amount = 0;
// pubkey = bitcoin.P2pkhAddress( // for (final out in (sentTx["vout"] as List<dynamic>)) {
// scriptSig: bitcoin.Script.fromRaw(hexData: input["scriptsig"] as String)) // amount += out["value"] as int;
// .pubkey; // }
// }
// if (pubkey == null) { // final height = s["status"]["block_height"] as int;
// skip = true;
// // print("Skipping, invalid witness");
// break;
// }
// pubkeys.add(pubkey); // scanData.sendPort.send({
// outpoints.add( // sentTxId: ElectrumTransactionInfo(
// bitcoin.Outpoint(txid: input["txid"] as String, index: input["vout"] as int)); // WalletType.bitcoin,
// } // id: sentTxId,
// height: height,
// amount: amount,
// fee: 0,
// direction: TransactionDirection.outgoing,
// isPending: false,
// date: DateTime.fromMillisecondsSinceEpoch(
// (s["status"]["block_time"] as int) * 1000),
// confirmations: currentChainTip - height,
// )
// });
// }
// }
// if (skip) { // if (spent) {
// // skipped tx, continue to next tx // return;
// continue; // }
// }
// Map<String, bitcoin.Outpoint> outpointsByP2TRpubkey = {}; // final unspent = BitcoinUnspent(
// for (var i = 0; i < (tx["vout"] as List<dynamic>).length; i++) { // BitcoinAddressRecord(
// final output = tx["vout"][i]; // bitcoin.P2trAddress(program: key, networkType: scanData.network).address,
// if (output["scriptpubkey_type"] != "v1_p2tr") { // index: 0,
// // print("Skipping, not a v1_p2tr output"); // isHidden: true,
// continue; // isUsed: true,
// } // silentAddressLabel: null,
// silentPaymentTweak: tweak,
// type: bitcoin.AddressType.p2tr,
// ),
// txid,
// outpoint.value!,
// outpoint.index,
// silentPaymentTweak: tweak,
// type: bitcoin.AddressType.p2tr,
// );
// final script = (output["scriptpubkey"] as String).fromHex; // // found utxo for tx, send unspent coin to main isolate
// scanData.sendPort.send(unspent);
// // final alreadySpentOutput = (await electrumClient.getHistory( // // also send tx data for tx history
// // scriptHashFromScript(script, networkType: scanData.networkType))) // txInfo.unspent = unspent;
// // .length > // scanData.sendPort.send({txid: txInfo});
// // 1; // });
} catch (_) {}
}
// // if (alreadySpentOutput) { // Finished scanning block, add 1 to height and continue to next block in loop
// // print("Skipping, invalid witness"); syncHeight += 1;
// // break; currentChainTip = await getNodeHeightOrUpdate(syncHeight);
// // } scanData.sendPort.send(SyncResponse(syncHeight,
SyncingSyncStatus.fromHeightValues(currentChainTip, initialSyncHeight, syncHeight)));
} catch (e, stacktrace) {
print(stacktrace);
print(e.toString());
// final p2tr = bitcoin.P2trAddress( scanData.sendPort.send(SyncResponse(syncHeight, NotConnectedSyncStatus()));
// program: script.sublist(2).hex, networkType: scanData.networkType); break;
// final address = p2tr.address; }
}
// print(["Verifying taproot address:", address]); }
// outpointsByP2TRpubkey[script.sublist(2).hex] =
// bitcoin.Outpoint(txid: txid, index: i, value: output["value"] as int);
// }
// if (pubkeys.isEmpty || outpoints.isEmpty || outpointsByP2TRpubkey.isEmpty) {
// // skipped tx, continue to next tx
// continue;
// }
// final outpointHash = bitcoin.SilentPayment.hashOutpoints(outpoints);
// final result = bitcoin.scanOutputs(
// scanData.primarySilentAddress.scanPrivkey,
// scanData.primarySilentAddress.spendPubkey,
// bitcoin.getSumInputPubKeys(pubkeys),
// outpointHash,
// outpointsByP2TRpubkey.keys.map((e) => e.fromHex).toList(),
// labels: scanData.labels,
// );
// if (result.isEmpty) {
// // no results tx, continue to next tx
// continue;
// }
// if (result.length > 1) {
// print("MULTIPLE UNSPENT COINS FOUND!");
// } else {
// print("UNSPENT COIN FOUND!");
// }
// result.forEach((key, value) async {
// final outpoint = outpointsByP2TRpubkey[key];
// if (outpoint == null) {
// return;
// }
// final tweak = value[0];
// String? label;
// if (value.length > 1) label = value[1];
// final txInfo = ElectrumTransactionInfo(
// WalletType.bitcoin,
// id: txid,
// height: syncHeight,
// amount: outpoint.value!,
// fee: 0,
// direction: TransactionDirection.incoming,
// isPending: false,
// date: DateTime.fromMillisecondsSinceEpoch((blockJson["timestamp"] as int) * 1000),
// confirmations: currentChainTip - syncHeight,
// to: bitcoin.SilentPaymentAddress.createLabeledSilentPaymentAddress(
// scanData.primarySilentAddress.scanPubkey,
// scanData.primarySilentAddress.spendPubkey,
// label != null ? label.fromHex : "0".fromHex,
// hrp: scanData.primarySilentAddress.hrp,
// version: scanData.primarySilentAddress.version)
// .toString(),
// unspent: null,
// );
// final status = json.decode((await http
// .get(Uri.parse("https://blockstream.info/testnet/api/tx/$txid/outspends")))
// .body) as List<dynamic>;
// bool spent = false;
// for (final s in status) {
// if ((s["spent"] as bool) == true) {
// spent = true;
// scanData.sendPort.send({txid: txInfo});
// final sentTxId = s["txid"] as String;
// final sentTx = json.decode((await http
// .get(Uri.parse("https://blockstream.info/testnet/api/tx/$sentTxId")))
// .body);
// int amount = 0;
// for (final out in (sentTx["vout"] as List<dynamic>)) {
// amount += out["value"] as int;
// }
// final height = s["status"]["block_height"] as int;
// scanData.sendPort.send({
// sentTxId: ElectrumTransactionInfo(
// WalletType.bitcoin,
// id: sentTxId,
// height: height,
// amount: amount,
// fee: 0,
// direction: TransactionDirection.outgoing,
// isPending: false,
// date: DateTime.fromMillisecondsSinceEpoch(
// (s["status"]["block_time"] as int) * 1000),
// confirmations: currentChainTip - height,
// )
// });
// }
// }
// if (spent) {
// return;
// }
// final unspent = BitcoinUnspent(
// BitcoinAddressRecord(
// bitcoin.P2trAddress(program: key, networkType: scanData.networkType).address,
// index: 0,
// isHidden: true,
// isUsed: true,
// silentAddressLabel: null,
// silentPaymentTweak: tweak,
// type: bitcoin.AddressType.p2tr,
// ),
// txid,
// outpoint.value!,
// outpoint.index,
// silentPaymentTweak: tweak,
// type: bitcoin.AddressType.p2tr,
// );
// // found utxo for tx, send unspent coin to main isolate
// scanData.sendPort.send(unspent);
// // also send tx data for tx history
// txInfo.unspent = unspent;
// scanData.sendPort.send({txid: txInfo});
// });
// } catch (_) {}
// }
// // Finished scanning batch of txs in block, add 25 to start index and continue to next block in loop
// startIndex += 25;
// }
// // Finished scanning block, add 1 to height and continue to next block in loop
// syncHeight += 1;
// currentChainTip = await getNodeHeightOrUpdate(syncHeight);
// scanData.sendPort.send(SyncResponse(syncHeight,
// SyncingSyncStatus.fromHeightValues(currentChainTip, initialSyncHeight, syncHeight)));
// }
// } catch (e, stacktrace) {
// print(stacktrace);
// print(e.toString());
// scanData.sendPort.send(SyncResponse(syncHeight, NotConnectedSyncStatus()));
// break;
// }
// }
// }
class EstimatedTxResult { class EstimatedTxResult {
EstimatedTxResult( EstimatedTxResult(

View file

@ -216,7 +216,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
for (int i = 0; i < silentAddresses.length; i++) { for (int i = 0; i < silentAddresses.length; i++) {
final silentAddressRecord = silentAddresses[i]; final silentAddressRecord = silentAddresses[i];
final silentAddress = final silentAddress =
SilentPaymentDestination.fromAddress(silentAddressRecord.address, 0).spendPubkey.toHex(); SilentPaymentDestination.fromAddress(silentAddressRecord.address, 0).B_spend.toHex();
if (silentAddressRecord.silentPaymentTweak != null) if (silentAddressRecord.silentPaymentTweak != null)
labels[silentAddress] = silentAddressRecord.silentPaymentTweak!; labels[silentAddress] = silentAddressRecord.silentPaymentTweak!;
@ -232,7 +232,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
final address = BitcoinAddressRecord( final address = BitcoinAddressRecord(
SilentPaymentAddress.createLabeledSilentPaymentAddress( SilentPaymentAddress.createLabeledSilentPaymentAddress(
primarySilentAddress!.scanPubkey, primarySilentAddress!.spendPubkey, tweak, primarySilentAddress!.B_scan, primarySilentAddress!.B_spend, tweak,
hrp: primarySilentAddress!.hrp, version: primarySilentAddress!.version) hrp: primarySilentAddress!.hrp, version: primarySilentAddress!.version)
.toString(), .toString(),
index: currentSilentAddressIndex, index: currentSilentAddressIndex,

View file

@ -1,3 +1,4 @@
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
@ -20,17 +21,18 @@ part 'litecoin_wallet.g.dart';
class LitecoinWallet = LitecoinWalletBase with _$LitecoinWallet; class LitecoinWallet = LitecoinWalletBase with _$LitecoinWallet;
abstract class LitecoinWalletBase extends ElectrumWallet with Store { abstract class LitecoinWalletBase extends ElectrumWallet with Store {
LitecoinWalletBase( LitecoinWalletBase({
{required String mnemonic, required String mnemonic,
required String password, required String password,
required WalletInfo walletInfo, required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo, required Box<UnspentCoinsInfo> unspentCoinsInfo,
required Uint8List seedBytes, required Uint8List seedBytes,
List<BitcoinAddressRecord>? initialAddresses, String? addressPageType,
ElectrumBalance? initialBalance, List<BitcoinAddressRecord>? initialAddresses,
int initialRegularAddressIndex = 0, ElectrumBalance? initialBalance,
int initialChangeAddressIndex = 0}) Map<String, int>? initialRegularAddressIndex,
: super( Map<String, int>? initialChangeAddressIndex,
}) : super(
mnemonic: mnemonic, mnemonic: mnemonic,
password: password, password: password,
walletInfo: walletInfo, walletInfo: walletInfo,
@ -41,41 +43,41 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
seedBytes: seedBytes, seedBytes: seedBytes,
currency: CryptoCurrency.ltc) { currency: CryptoCurrency.ltc) {
walletAddresses = LitecoinWalletAddresses( walletAddresses = LitecoinWalletAddresses(
walletInfo, walletInfo,
electrumClient: electrumClient, initialAddresses: initialAddresses,
initialAddresses: initialAddresses, initialRegularAddressIndex: initialRegularAddressIndex,
initialRegularAddressIndex: initialRegularAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex, mainHd: hd,
mainHd: hd, sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/1"),
sideHd: bitcoin.HDWallet network: network,
.fromSeed(seedBytes, network: networkType) );
.derivePath("m/0'/1"),
networkType: networkType,);
autorun((_) { autorun((_) {
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
}); });
} }
static Future<LitecoinWallet> create({ static Future<LitecoinWallet> create(
required String mnemonic, {required String mnemonic,
required String password, required String password,
required WalletInfo walletInfo, required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo, required Box<UnspentCoinsInfo> unspentCoinsInfo,
List<BitcoinAddressRecord>? initialAddresses, String? addressPageType,
ElectrumBalance? initialBalance, List<BitcoinAddressRecord>? initialAddresses,
int initialRegularAddressIndex = 0, ElectrumBalance? initialBalance,
int initialChangeAddressIndex = 0 Map<String, int>? initialRegularAddressIndex,
}) async { Map<String, int>? initialChangeAddressIndex}) async {
return LitecoinWallet( return LitecoinWallet(
mnemonic: mnemonic, mnemonic: mnemonic,
password: password, password: password,
walletInfo: walletInfo, walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo, unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: initialAddresses, initialAddresses: initialAddresses,
initialBalance: initialBalance, initialBalance: initialBalance,
seedBytes: await mnemonicToSeedBytes(mnemonic), seedBytes: await mnemonicToSeedBytes(mnemonic),
initialRegularAddressIndex: initialRegularAddressIndex, initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex); initialChangeAddressIndex: initialChangeAddressIndex,
addressPageType: addressPageType,
);
} }
static Future<LitecoinWallet> open({ static Future<LitecoinWallet> open({
@ -84,17 +86,20 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
required Box<UnspentCoinsInfo> unspentCoinsInfo, required Box<UnspentCoinsInfo> unspentCoinsInfo,
required String password, required String password,
}) async { }) async {
final snp = await ElectrumWallletSnapshot.load (name, walletInfo.type, password); final snp =
await ElectrumWalletSnapshot.load(name, walletInfo.type, password, LitecoinNetwork.mainnet);
return LitecoinWallet( return LitecoinWallet(
mnemonic: snp.mnemonic, mnemonic: snp.mnemonic,
password: password, password: password,
walletInfo: walletInfo, walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo, unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: snp.addresses, initialAddresses: snp.addresses,
initialBalance: snp.balance, initialBalance: snp.balance,
seedBytes: await mnemonicToSeedBytes(snp.mnemonic), seedBytes: await mnemonicToSeedBytes(snp.mnemonic),
initialRegularAddressIndex: snp.regularAddressIndex, initialRegularAddressIndex: snp.regularAddressIndex,
initialChangeAddressIndex: snp.changeAddressIndex); initialChangeAddressIndex: snp.changeAddressIndex,
addressPageType: snp.addressPageType,
);
} }
@override @override

View file

@ -1,39 +1,27 @@
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:cw_bitcoin/electrum.dart';
import 'package:cw_bitcoin/utils.dart'; import 'package:cw_bitcoin/utils.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:flutter/foundation.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
part 'litecoin_wallet_addresses.g.dart'; part 'litecoin_wallet_addresses.g.dart';
class LitecoinWalletAddresses = LitecoinWalletAddressesBase class LitecoinWalletAddresses = LitecoinWalletAddressesBase with _$LitecoinWalletAddresses;
with _$LitecoinWalletAddresses;
abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
with Store {
LitecoinWalletAddressesBase( LitecoinWalletAddressesBase(
WalletInfo walletInfo, WalletInfo walletInfo, {
{required bitcoin.HDWallet mainHd, required super.mainHd,
required bitcoin.HDWallet sideHd, required super.sideHd,
required bitcoin.NetworkType networkType, required super.network,
required ElectrumClient electrumClient, super.initialAddresses,
List<BitcoinAddressRecord>? initialAddresses, super.initialRegularAddressIndex,
int initialRegularAddressIndex = 0, super.initialChangeAddressIndex,
int initialChangeAddressIndex = 0}) }) : super(walletInfo);
: super(
walletInfo,
initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: mainHd,
sideHd: sideHd,
electrumClient: electrumClient,
networkType: networkType);
@override @override
String getAddress({required int index, required bitcoin.HDWallet hd}) => String getAddress(
generateP2WPKHAddress(hd: hd, index: index, networkType: networkType); {required int index, required bitcoin.HDWallet hd, BitcoinAddressType? addressType}) =>
} generateP2WPKHAddress(hd: hd, index: index, network: network);
}

View file

@ -34,6 +34,10 @@ dependencies:
git: git:
url: https://github.com/cake-tech/bitcoin_base.git url: https://github.com/cake-tech/bitcoin_base.git
ref: cake-update-v2 ref: cake-update-v2
blockchain_utils:
git:
url: https://github.com/rafael-xmr/blockchain_utils
ref: cake-update-v1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View file

@ -28,17 +28,18 @@ part 'bitcoin_cash_wallet.g.dart';
class BitcoinCashWallet = BitcoinCashWalletBase with _$BitcoinCashWallet; class BitcoinCashWallet = BitcoinCashWalletBase with _$BitcoinCashWallet;
abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
BitcoinCashWalletBase( BitcoinCashWalletBase({
{required String mnemonic, required String mnemonic,
required String password, required String password,
required WalletInfo walletInfo, required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo, required Box<UnspentCoinsInfo> unspentCoinsInfo,
required Uint8List seedBytes, required Uint8List seedBytes,
List<BitcoinAddressRecord>? initialAddresses, String? addressPageType,
ElectrumBalance? initialBalance, List<BitcoinAddressRecord>? initialAddresses,
int initialRegularAddressIndex = 0, ElectrumBalance? initialBalance,
int initialChangeAddressIndex = 0}) Map<String, int>? initialRegularAddressIndex,
: super( Map<String, int>? initialChangeAddressIndex,
}) : super(
mnemonic: mnemonic, mnemonic: mnemonic,
password: password, password: password,
walletInfo: walletInfo, walletInfo: walletInfo,
@ -48,40 +49,42 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
initialBalance: initialBalance, initialBalance: initialBalance,
seedBytes: seedBytes, seedBytes: seedBytes,
currency: CryptoCurrency.bch) { currency: CryptoCurrency.bch) {
walletAddresses = BitcoinCashWalletAddresses(walletInfo, walletAddresses = BitcoinCashWalletAddresses(
electrumClient: electrumClient, walletInfo,
initialAddresses: initialAddresses, initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex, initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: hd, mainHd: hd,
sideHd: bitcoin.HDWallet.fromSeed(seedBytes) sideHd: bitcoin.HDWallet.fromSeed(seedBytes).derivePath("m/44'/145'/0'/1"),
.derivePath("m/44'/145'/0'/1"), network: network,
networkType: networkType); );
autorun((_) { autorun((_) {
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
}); });
} }
static Future<BitcoinCashWallet> create( static Future<BitcoinCashWallet> create(
{required String mnemonic, {required String mnemonic,
required String password, required String password,
required WalletInfo walletInfo, required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo, required Box<UnspentCoinsInfo> unspentCoinsInfo,
String? addressPageType,
List<BitcoinAddressRecord>? initialAddresses, List<BitcoinAddressRecord>? initialAddresses,
ElectrumBalance? initialBalance, ElectrumBalance? initialBalance,
int initialRegularAddressIndex = 0, Map<String, int>? initialRegularAddressIndex,
int initialChangeAddressIndex = 0}) async { Map<String, int>? initialChangeAddressIndex}) async {
return BitcoinCashWallet( return BitcoinCashWallet(
mnemonic: mnemonic, mnemonic: mnemonic,
password: password, password: password,
walletInfo: walletInfo, walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo, unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: initialAddresses, initialAddresses: initialAddresses,
initialBalance: initialBalance, initialBalance: initialBalance,
seedBytes: await Mnemonic.toSeed(mnemonic), seedBytes: await Mnemonic.toSeed(mnemonic),
initialRegularAddressIndex: initialRegularAddressIndex, initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex); initialChangeAddressIndex: initialChangeAddressIndex,
addressPageType: addressPageType,
);
} }
static Future<BitcoinCashWallet> open({ static Future<BitcoinCashWallet> open({
@ -90,17 +93,20 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
required Box<UnspentCoinsInfo> unspentCoinsInfo, required Box<UnspentCoinsInfo> unspentCoinsInfo,
required String password, required String password,
}) async { }) async {
final snp = await ElectrumWallletSnapshot.load(name, walletInfo.type, password); final snp = await ElectrumWalletSnapshot.load(
name, walletInfo.type, password, BitcoinCashNetwork.mainnet);
return BitcoinCashWallet( return BitcoinCashWallet(
mnemonic: snp.mnemonic, mnemonic: snp.mnemonic,
password: password, password: password,
walletInfo: walletInfo, walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo, unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: snp.addresses, initialAddresses: snp.addresses,
initialBalance: snp.balance, initialBalance: snp.balance,
seedBytes: await Mnemonic.toSeed(snp.mnemonic), seedBytes: await Mnemonic.toSeed(snp.mnemonic),
initialRegularAddressIndex: snp.regularAddressIndex, initialRegularAddressIndex: snp.regularAddressIndex,
initialChangeAddressIndex: snp.changeAddressIndex); initialChangeAddressIndex: snp.changeAddressIndex,
addressPageType: snp.addressPageType,
);
} }
@override @override
@ -270,20 +276,18 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
electrumClient: electrumClient, amount: amount, fee: fee); electrumClient: electrumClient, amount: amount, fee: fee);
} }
bitbox.ECPair generateKeyPair( bitbox.ECPair generateKeyPair({required bitcoin.HDWallet hd, required int index}) =>
{required bitcoin.HDWallet hd,
required int index}) =>
bitbox.ECPair.fromWIF(hd.derive(index).wif!); bitbox.ECPair.fromWIF(hd.derive(index).wif!);
@override @override
int feeAmountForPriority( int feeAmountForPriority(BitcoinTransactionPriority priority, int inputsCount, int outputsCount,
BitcoinTransactionPriority priority, int inputsCount, int outputsCount) => {int? size}) =>
feeRate(priority) * bitbox.BitcoinCash.getByteCount(inputsCount, outputsCount); feeRate(priority) * bitbox.BitcoinCash.getByteCount(inputsCount, outputsCount);
int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount) => int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount, {int? size}) =>
feeRate * bitbox.BitcoinCash.getByteCount(inputsCount, outputsCount); feeRate * bitbox.BitcoinCash.getByteCount(inputsCount, outputsCount);
int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount}) { int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount, int? size}) {
int inputsCount = 0; int inputsCount = 0;
int totalValue = 0; int totalValue = 0;
@ -323,9 +327,10 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
@override @override
String signMessage(String message, {String? address = null}) { String signMessage(String message, {String? address = null}) {
final index = address != null final index = address != null
? walletAddresses.addresses ? walletAddresses.allAddresses
.firstWhere((element) => element.address == AddressUtils.toLegacyAddress(address)) .firstWhere((element) => element.address == AddressUtils.toLegacyAddress(address))
.index : null; .index
: null;
final HD = index == null ? hd : hd.derive(index); final HD = index == null ? hd : hd.derive(index);
return base64Encode(HD.signMessage(message)); return base64Encode(HD.signMessage(message));
} }

View file

@ -1,6 +1,5 @@
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:cw_bitcoin/utils.dart'; import 'package:cw_bitcoin/utils.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
@ -11,24 +10,18 @@ part 'bitcoin_cash_wallet_addresses.g.dart';
class BitcoinCashWalletAddresses = BitcoinCashWalletAddressesBase with _$BitcoinCashWalletAddresses; class BitcoinCashWalletAddresses = BitcoinCashWalletAddressesBase with _$BitcoinCashWalletAddresses;
abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses with Store { abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses with Store {
BitcoinCashWalletAddressesBase(WalletInfo walletInfo, BitcoinCashWalletAddressesBase(
{required bitcoin.HDWallet mainHd, WalletInfo walletInfo, {
required bitcoin.HDWallet sideHd, required super.mainHd,
required bitcoin.NetworkType networkType, required super.sideHd,
required ElectrumClient electrumClient, required super.network,
List<BitcoinAddressRecord>? initialAddresses, super.initialAddresses,
int initialRegularAddressIndex = 0, super.initialRegularAddressIndex,
int initialChangeAddressIndex = 0}) super.initialChangeAddressIndex,
: super(walletInfo, }) : super(walletInfo);
initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: mainHd,
sideHd: sideHd,
electrumClient: electrumClient,
networkType: networkType);
@override @override
String getAddress({required int index, required bitcoin.HDWallet hd}) => String getAddress(
generateP2PKHAddress(hd: hd, index: index, networkType: networkType); {required int index, required bitcoin.HDWallet hd, BitcoinAddressType? addressType}) =>
generateP2PKHAddress(hd: hd, index: index, network: network);
} }

View file

@ -33,8 +33,10 @@ dependencies:
git: git:
url: https://github.com/cake-tech/bitcoin_base.git url: https://github.com/cake-tech/bitcoin_base.git
ref: cake-update-v2 ref: cake-update-v2
blockchain_utils:
git:
url: https://github.com/rafael-xmr/blockchain_utils
ref: cake-update-v1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View file

@ -338,6 +338,12 @@ class CWBitcoin extends Bitcoin {
return BitcoinReceivePageOption.fromType(bitcoinWallet.walletAddresses.addressPageType); return BitcoinReceivePageOption.fromType(bitcoinWallet.walletAddresses.addressPageType);
} }
@override
bool hasSelectedSilentPayments(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.walletAddresses.addressPageType == SilentPaymentsAddresType.p2sp;
}
@override @override
List<BitcoinReceivePageOption> getBitcoinReceivePageOptions() => BitcoinReceivePageOption.all; List<BitcoinReceivePageOption> getBitcoinReceivePageOptions() => BitcoinReceivePageOption.all;

View file

@ -25,7 +25,7 @@ class AddressValidator extends TextValidator {
return '^[0-9a-zA-Z]{59}\$|^[0-9a-zA-Z]{92}\$|^[0-9a-zA-Z]{104}\$' return '^[0-9a-zA-Z]{59}\$|^[0-9a-zA-Z]{92}\$|^[0-9a-zA-Z]{104}\$'
'|^[0-9a-zA-Z]{105}\$|^addr1[0-9a-zA-Z]{98}\$'; '|^[0-9a-zA-Z]{105}\$|^addr1[0-9a-zA-Z]{98}\$';
case CryptoCurrency.btc: case CryptoCurrency.btc:
return '^${P2pkhAddress.regex.pattern}\$|^${P2shAddress.regex.pattern}\$|^${P2wpkhAddress.regex.pattern}\$|${P2trAddress.regex.pattern}\$|^${P2wshAddress.regex.pattern}\$|^${bitcoin.SilentPaymentAddress.REGEX.pattern}\$'; return '^${P2pkhAddress.regex.pattern}\$|^${P2shAddress.regex.pattern}\$|^${P2wpkhAddress.regex.pattern}\$|${P2trAddress.regex.pattern}\$|^${P2wshAddress.regex.pattern}\$|^${SilentPaymentAddress.regex.pattern}\$';
case CryptoCurrency.nano: case CryptoCurrency.nano:
return '[0-9a-zA-Z_]'; return '[0-9a-zA-Z_]';
case CryptoCurrency.banano: case CryptoCurrency.banano:
@ -268,7 +268,7 @@ class AddressValidator extends TextValidator {
'([^0-9a-zA-Z]|^)${P2wpkhAddress.regex.pattern}|\$)' '([^0-9a-zA-Z]|^)${P2wpkhAddress.regex.pattern}|\$)'
'([^0-9a-zA-Z]|^)${P2wshAddress.regex.pattern}|\$)' '([^0-9a-zA-Z]|^)${P2wshAddress.regex.pattern}|\$)'
'([^0-9a-zA-Z]|^)${P2trAddress.regex.pattern}|\$)' '([^0-9a-zA-Z]|^)${P2trAddress.regex.pattern}|\$)'
'|${bitcoin.SilentPaymentAddress.REGEX.pattern}\$'; '|${SilentPaymentAddress.regex.pattern}\$';
case CryptoCurrency.ltc: case CryptoCurrency.ltc:
return '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)' return '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)'
'|([^0-9a-zA-Z]|^)[LM][a-km-zA-HJ-NP-Z1-9]{26,33}([^0-9a-zA-Z]|\$)' '|([^0-9a-zA-Z]|^)[LM][a-km-zA-HJ-NP-Z1-9]{26,33}([^0-9a-zA-Z]|\$)'

View file

@ -4,7 +4,7 @@ import 'package:cw_core/sync_status.dart';
String syncStatusTitle(SyncStatus syncStatus) { String syncStatusTitle(SyncStatus syncStatus) {
if (syncStatus is SyncingSyncStatus) { if (syncStatus is SyncingSyncStatus) {
return syncStatus.blocksLeft == 1 return syncStatus.blocksLeft == 1
? S.current.Block_remaining('${syncStatus.blocksLeft}') ? S.current.block_remaining
: S.current.Blocks_remaining('${syncStatus.blocksLeft}'); : S.current.Blocks_remaining('${syncStatus.blocksLeft}');
} }

View file

@ -198,6 +198,11 @@ class AddressPage extends BasePage {
} }
reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) { reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) {
if (option is BitcoinReceivePageOption) {
addressListViewModel.setAddressType(option.toType());
return;
}
switch (option) { switch (option) {
case ReceivePageOption.anonPayInvoice: case ReceivePageOption.anonPayInvoice:
Navigator.pushNamed( Navigator.pushNamed(

View file

@ -45,7 +45,7 @@ class PresentReceiveOptionPicker extends StatelessWidget {
fontSize: 18.0, fontWeight: FontWeight.bold, fontFamily: 'Lato', color: color), fontSize: 18.0, fontWeight: FontWeight.bold, fontFamily: 'Lato', color: color),
), ),
Observer( Observer(
builder: (_) => Text(describeOption(receiveOptionViewModel.selectedReceiveOption), builder: (_) => Text(receiveOptionViewModel.selectedReceiveOption.toString(),
style: TextStyle(fontSize: 10.0, fontWeight: FontWeight.w500, color: color))) style: TextStyle(fontSize: 10.0, fontWeight: FontWeight.w500, color: color)))
], ],
), ),
@ -101,7 +101,7 @@ class PresentReceiveOptionPicker extends StatelessWidget {
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text(describeOption(option), Text(option.toString(),
textAlign: TextAlign.left, textAlign: TextAlign.left,
style: textSmall( style: textSmall(
color: Theme.of(context) color: Theme.of(context)

View file

@ -13,8 +13,7 @@ class AddressEditOrCreatePage extends BasePage {
: _formKey = GlobalKey<FormState>(), : _formKey = GlobalKey<FormState>(),
_labelController = TextEditingController(), _labelController = TextEditingController(),
super() { super() {
_labelController.addListener( _labelController.addListener(() => addressEditOrCreateViewModel.label = _labelController.text);
() => addressEditOrCreateViewModel.label = _labelController.text);
_labelController.text = addressEditOrCreateViewModel.label; _labelController.text = addressEditOrCreateViewModel.label;
} }
@ -55,10 +54,8 @@ class AddressEditOrCreatePage extends BasePage {
: S.of(context).new_subaddress_create, : S.of(context).new_subaddress_create,
color: Theme.of(context).primaryColor, color: Theme.of(context).primaryColor,
textColor: Colors.white, textColor: Colors.white,
isLoading: isLoading: addressEditOrCreateViewModel.state is AddressIsSaving,
addressEditOrCreateViewModel.state is AddressIsSaving, isDisabled: addressEditOrCreateViewModel.label?.isEmpty ?? true,
isDisabled:
addressEditOrCreateViewModel.label?.isEmpty ?? true,
), ),
) )
], ],
@ -70,14 +67,13 @@ class AddressEditOrCreatePage extends BasePage {
if (_isEffectsInstalled) { if (_isEffectsInstalled) {
return; return;
} }
reaction((_) => addressEditOrCreateViewModel.state, reaction((_) => addressEditOrCreateViewModel.state, (AddressEditOrCreateState state) {
(AddressEditOrCreateState state) { if (state is AddressSavedSuccessfully) {
if (state is AddressSavedSuccessfully) { WidgetsBinding.instance.addPostFrameCallback((_) => Navigator.of(context).pop());
WidgetsBinding.instance }
.addPostFrameCallback((_) => Navigator.of(context).pop()); });
}
});
_isEffectsInstalled = true; _isEffectsInstalled = true;
} }
} }

View file

@ -42,7 +42,7 @@ import 'package:cw_core/wallet_type.dart';
import 'package:eth_sig_util/util/utils.dart'; import 'package:eth_sig_util/util/utils.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:cake_wallet/entities/provider_types.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart';
part 'dashboard_view_model.g.dart'; part 'dashboard_view_model.g.dart';
@ -273,12 +273,10 @@ abstract class DashboardViewModelBase with Store {
@observable @observable
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet; WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet;
bool get hasRescan => wallet.type == WalletType.monero || wallet.type == WalletType.haven; bool get hasRescan =>
// bool get hasRescan => (wallet.type == WalletType.bitcoin && bitcoin!.hasSelectedSilentPayments(wallet)) ||
// (wallet.type == WalletType.bitcoin && wallet.type == WalletType.monero ||
// wallet.walletAddresses.addressPageType == bitcoin.AddressType.p2sp) || wallet.type == WalletType.haven;
// wallet.type == WalletType.monero ||
// wallet.type == WalletType.haven;
final KeyService keyService; final KeyService keyService;
@ -340,15 +338,13 @@ abstract class DashboardViewModelBase with Store {
bool hasExchangeAction; bool hasExchangeAction;
@computed @computed
bool get isEnabledBuyAction => bool get isEnabledBuyAction => !settingsStore.disableBuy && availableBuyProviders.isNotEmpty;
!settingsStore.disableBuy && availableBuyProviders.isNotEmpty;
@observable @observable
bool hasBuyAction; bool hasBuyAction;
@computed @computed
bool get isEnabledSellAction => bool get isEnabledSellAction => !settingsStore.disableSell && availableSellProviders.isNotEmpty;
!settingsStore.disableSell && availableSellProviders.isNotEmpty;
@observable @observable
bool hasSellAction; bool hasSellAction;
@ -473,7 +469,8 @@ abstract class DashboardViewModelBase with Store {
Future<List<String>> checkAffectedWallets() async { Future<List<String>> checkAffectedWallets() async {
// await load file // await load file
final vulnerableSeedsString = await rootBundle.loadString('assets/text/cakewallet_weak_bitcoin_seeds_hashed_sorted_version1.txt'); final vulnerableSeedsString = await rootBundle
.loadString('assets/text/cakewallet_weak_bitcoin_seeds_hashed_sorted_version1.txt');
final vulnerableSeeds = vulnerableSeedsString.split("\n"); final vulnerableSeeds = vulnerableSeedsString.split("\n");
final walletInfoSource = await CakeHive.openBox<WalletInfo>(WalletInfo.boxName); final walletInfoSource = await CakeHive.openBox<WalletInfo>(WalletInfo.boxName);

View file

@ -387,8 +387,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
wallet.type == WalletType.bitcoinCash; wallet.type == WalletType.bitcoinCash;
@computed @computed
bool get isAutoGenerateSubaddressEnabled => bool get isAutoGenerateSubaddressEnabled => wallet.type == WalletType.bitcoin
_settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled; ? !bitcoin!.hasSelectedSilentPayments(wallet)
: _settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled;
List<ListItem> _baseItems; List<ListItem> _baseItems;

View file

@ -75,6 +75,7 @@
"bitcoin_dark_theme": "موضوع البيتكوين الظلام", "bitcoin_dark_theme": "موضوع البيتكوين الظلام",
"bitcoin_light_theme": "موضوع البيتكوين الخفيفة", "bitcoin_light_theme": "موضوع البيتكوين الخفيفة",
"bitcoin_payments_require_1_confirmation": "تتطلب مدفوعات Bitcoin تأكيدًا واحدًا ، والذي قد يستغرق 20 دقيقة أو أكثر. شكرا لصبرك! سيتم إرسال بريد إلكتروني إليك عند تأكيد الدفع.", "bitcoin_payments_require_1_confirmation": "تتطلب مدفوعات Bitcoin تأكيدًا واحدًا ، والذي قد يستغرق 20 دقيقة أو أكثر. شكرا لصبرك! سيتم إرسال بريد إلكتروني إليك عند تأكيد الدفع.",
"block_remaining": "1 كتلة متبقية",
"Blocks_remaining": "بلوك متبقي ${status}", "Blocks_remaining": "بلوك متبقي ${status}",
"bright_theme": "مشرق", "bright_theme": "مشرق",
"buy": "اشتري", "buy": "اشتري",

View file

@ -75,6 +75,7 @@
"bitcoin_dark_theme": "Тъмна тема за биткойн", "bitcoin_dark_theme": "Тъмна тема за биткойн",
"bitcoin_light_theme": "Лека биткойн тема", "bitcoin_light_theme": "Лека биткойн тема",
"bitcoin_payments_require_1_confirmation": "Плащанията с Bitcoin изискват потвърждение, което може да отнеме 20 минути или повече. Благодарим за търпението! Ще получите имейл, когато плащането е потвърдено.", "bitcoin_payments_require_1_confirmation": "Плащанията с Bitcoin изискват потвърждение, което може да отнеме 20 минути или повече. Благодарим за търпението! Ще получите имейл, когато плащането е потвърдено.",
"block_remaining": "1 блок останал",
"Blocks_remaining": "${status} оставащи блока", "Blocks_remaining": "${status} оставащи блока",
"bright_theme": "Ярко", "bright_theme": "Ярко",
"buy": "Купуване", "buy": "Купуване",

View file

@ -75,6 +75,7 @@
"bitcoin_dark_theme": "Tmavé téma bitcoinů", "bitcoin_dark_theme": "Tmavé téma bitcoinů",
"bitcoin_light_theme": "Světlé téma bitcoinů", "bitcoin_light_theme": "Světlé téma bitcoinů",
"bitcoin_payments_require_1_confirmation": "U plateb Bitcoinem je vyžadováno alespoň 1 potvrzení, což může trvat 20 minut i déle. Děkujeme za vaši trpělivost! Až bude platba potvrzena, budete informováni e-mailem.", "bitcoin_payments_require_1_confirmation": "U plateb Bitcoinem je vyžadováno alespoň 1 potvrzení, což může trvat 20 minut i déle. Děkujeme za vaši trpělivost! Až bude platba potvrzena, budete informováni e-mailem.",
"block_remaining": "1 blok zbývající",
"Blocks_remaining": "Zbývá ${status} bloků", "Blocks_remaining": "Zbývá ${status} bloků",
"bright_theme": "Jasný", "bright_theme": "Jasný",
"buy": "Koupit", "buy": "Koupit",

View file

@ -75,6 +75,7 @@
"bitcoin_dark_theme": "Dunkles Bitcoin-Thema", "bitcoin_dark_theme": "Dunkles Bitcoin-Thema",
"bitcoin_light_theme": "Bitcoin Light-Thema", "bitcoin_light_theme": "Bitcoin Light-Thema",
"bitcoin_payments_require_1_confirmation": "Bitcoin-Zahlungen erfordern 1 Bestätigung, was 20 Minuten oder länger dauern kann. Danke für Ihre Geduld! Sie erhalten eine E-Mail, wenn die Zahlung bestätigt ist.", "bitcoin_payments_require_1_confirmation": "Bitcoin-Zahlungen erfordern 1 Bestätigung, was 20 Minuten oder länger dauern kann. Danke für Ihre Geduld! Sie erhalten eine E-Mail, wenn die Zahlung bestätigt ist.",
"block_remaining": "1 Block verbleibend",
"Blocks_remaining": "${status} verbleibende Blöcke", "Blocks_remaining": "${status} verbleibende Blöcke",
"bright_theme": "Strahlend hell", "bright_theme": "Strahlend hell",
"buy": "Kaufen", "buy": "Kaufen",

View file

@ -75,6 +75,7 @@
"bitcoin_dark_theme": "Bitcoin Dark Theme", "bitcoin_dark_theme": "Bitcoin Dark Theme",
"bitcoin_light_theme": "Bitcoin Light Theme", "bitcoin_light_theme": "Bitcoin Light Theme",
"bitcoin_payments_require_1_confirmation": "Bitcoin payments require 1 confirmation, which can take 20 minutes or longer. Thanks for your patience! You will be emailed when the payment is confirmed.", "bitcoin_payments_require_1_confirmation": "Bitcoin payments require 1 confirmation, which can take 20 minutes or longer. Thanks for your patience! You will be emailed when the payment is confirmed.",
"block_remaining": "1 Block Remaining",
"Blocks_remaining": "${status} Blocks Remaining", "Blocks_remaining": "${status} Blocks Remaining",
"bright_theme": "Bright", "bright_theme": "Bright",
"buy": "Buy", "buy": "Buy",

View file

@ -75,6 +75,7 @@
"bitcoin_dark_theme": "Tema oscuro de Bitcoin", "bitcoin_dark_theme": "Tema oscuro de Bitcoin",
"bitcoin_light_theme": "Tema de la luz de Bitcoin", "bitcoin_light_theme": "Tema de la luz de Bitcoin",
"bitcoin_payments_require_1_confirmation": "Los pagos de Bitcoin requieren 1 confirmación, que puede demorar 20 minutos o más. ¡Gracias por su paciencia! Se le enviará un correo electrónico cuando se confirme el pago.", "bitcoin_payments_require_1_confirmation": "Los pagos de Bitcoin requieren 1 confirmación, que puede demorar 20 minutos o más. ¡Gracias por su paciencia! Se le enviará un correo electrónico cuando se confirme el pago.",
"block_remaining": "1 bloqueo restante",
"Blocks_remaining": "${status} Bloques restantes", "Blocks_remaining": "${status} Bloques restantes",
"bright_theme": "Brillante", "bright_theme": "Brillante",
"buy": "Comprar", "buy": "Comprar",

View file

@ -75,6 +75,7 @@
"bitcoin_dark_theme": "Thème sombre Bitcoin", "bitcoin_dark_theme": "Thème sombre Bitcoin",
"bitcoin_light_theme": "Thème léger Bitcoin", "bitcoin_light_theme": "Thème léger Bitcoin",
"bitcoin_payments_require_1_confirmation": "Les paiements Bitcoin nécessitent 1 confirmation, ce qui peut prendre 20 minutes ou plus. Merci pour votre patience ! Vous serez averti par e-mail lorsque le paiement sera confirmé.", "bitcoin_payments_require_1_confirmation": "Les paiements Bitcoin nécessitent 1 confirmation, ce qui peut prendre 20 minutes ou plus. Merci pour votre patience ! Vous serez averti par e-mail lorsque le paiement sera confirmé.",
"block_remaining": "1 bloc restant",
"Blocks_remaining": "Blocs Restants : ${status}", "Blocks_remaining": "Blocs Restants : ${status}",
"bright_theme": "Vif", "bright_theme": "Vif",
"buy": "Acheter", "buy": "Acheter",

View file

@ -75,6 +75,7 @@
"bitcoin_dark_theme": "Bitcoin Dark Jigo", "bitcoin_dark_theme": "Bitcoin Dark Jigo",
"bitcoin_light_theme": "Jigon Hasken Bitcoin", "bitcoin_light_theme": "Jigon Hasken Bitcoin",
"bitcoin_payments_require_1_confirmation": "Akwatin Bitcoin na buɗe 1 sambumbu, da yake za ta samu mintuna 20 ko yawa. Ina kira ga sabuwar lafiya! Zaka sanarwa ta email lokacin da aka samu akwatin samun lambar waya.", "bitcoin_payments_require_1_confirmation": "Akwatin Bitcoin na buɗe 1 sambumbu, da yake za ta samu mintuna 20 ko yawa. Ina kira ga sabuwar lafiya! Zaka sanarwa ta email lokacin da aka samu akwatin samun lambar waya.",
"block_remaining": "1 toshe ragowar",
"Blocks_remaining": "${status} Katanga ya rage", "Blocks_remaining": "${status} Katanga ya rage",
"bright_theme": "Mai haske", "bright_theme": "Mai haske",
"buy": "Sayi", "buy": "Sayi",

View file

@ -75,6 +75,7 @@
"bitcoin_dark_theme": "बिटकॉइन डार्क थीम", "bitcoin_dark_theme": "बिटकॉइन डार्क थीम",
"bitcoin_light_theme": "बिटकॉइन लाइट थीम", "bitcoin_light_theme": "बिटकॉइन लाइट थीम",
"bitcoin_payments_require_1_confirmation": "बिटकॉइन भुगतान के लिए 1 पुष्टिकरण की आवश्यकता होती है, जिसमें 20 मिनट या अधिक समय लग सकता है। आपके धैर्य के लिए धन्यवाद! भुगतान की पुष्टि होने पर आपको ईमेल किया जाएगा।", "bitcoin_payments_require_1_confirmation": "बिटकॉइन भुगतान के लिए 1 पुष्टिकरण की आवश्यकता होती है, जिसमें 20 मिनट या अधिक समय लग सकता है। आपके धैर्य के लिए धन्यवाद! भुगतान की पुष्टि होने पर आपको ईमेल किया जाएगा।",
"block_remaining": "1 ब्लॉक शेष",
"Blocks_remaining": "${status} शेष रहते हैं", "Blocks_remaining": "${status} शेष रहते हैं",
"bright_theme": "उज्ज्वल", "bright_theme": "उज्ज्वल",
"buy": "खरीदें", "buy": "खरीदें",

View file

@ -75,6 +75,7 @@
"bitcoin_dark_theme": "Bitcoin Tamna tema", "bitcoin_dark_theme": "Bitcoin Tamna tema",
"bitcoin_light_theme": "Bitcoin Light Theme", "bitcoin_light_theme": "Bitcoin Light Theme",
"bitcoin_payments_require_1_confirmation": "Bitcoin plaćanja zahtijevaju 1 potvrdu, što može potrajati 20 minuta ili dulje. Hvala na Vašem strpljenju! Dobit ćete e-poruku kada plaćanje bude potvrđeno.", "bitcoin_payments_require_1_confirmation": "Bitcoin plaćanja zahtijevaju 1 potvrdu, što može potrajati 20 minuta ili dulje. Hvala na Vašem strpljenju! Dobit ćete e-poruku kada plaćanje bude potvrđeno.",
"block_remaining": "Preostalo 1 blok",
"Blocks_remaining": "${status} preostalih blokova", "Blocks_remaining": "${status} preostalih blokova",
"bright_theme": "Jarka", "bright_theme": "Jarka",
"buy": "Kupi", "buy": "Kupi",

View file

@ -75,6 +75,7 @@
"bitcoin_dark_theme": "Tema Gelap Bitcoin", "bitcoin_dark_theme": "Tema Gelap Bitcoin",
"bitcoin_light_theme": "Tema Cahaya Bitcoin", "bitcoin_light_theme": "Tema Cahaya Bitcoin",
"bitcoin_payments_require_1_confirmation": "Pembayaran Bitcoin memerlukan 1 konfirmasi, yang bisa memakan waktu 20 menit atau lebih. Terima kasih atas kesabaran Anda! Anda akan diemail saat pembayaran dikonfirmasi.", "bitcoin_payments_require_1_confirmation": "Pembayaran Bitcoin memerlukan 1 konfirmasi, yang bisa memakan waktu 20 menit atau lebih. Terima kasih atas kesabaran Anda! Anda akan diemail saat pembayaran dikonfirmasi.",
"block_remaining": "1 blok tersisa",
"Blocks_remaining": "${status} Blok Tersisa", "Blocks_remaining": "${status} Blok Tersisa",
"bright_theme": "Cerah", "bright_theme": "Cerah",
"buy": "Beli", "buy": "Beli",

View file

@ -75,6 +75,7 @@
"bitcoin_dark_theme": "Tema oscuro di Bitcoin", "bitcoin_dark_theme": "Tema oscuro di Bitcoin",
"bitcoin_light_theme": "Tema luce Bitcoin", "bitcoin_light_theme": "Tema luce Bitcoin",
"bitcoin_payments_require_1_confirmation": "I pagamenti in bitcoin richiedono 1 conferma, che può richiedere 20 minuti o più. Grazie per la vostra pazienza! Riceverai un'e-mail quando il pagamento sarà confermato.", "bitcoin_payments_require_1_confirmation": "I pagamenti in bitcoin richiedono 1 conferma, che può richiedere 20 minuti o più. Grazie per la vostra pazienza! Riceverai un'e-mail quando il pagamento sarà confermato.",
"block_remaining": "1 blocco rimanente",
"Blocks_remaining": "${status} Blocchi Rimanenti", "Blocks_remaining": "${status} Blocchi Rimanenti",
"bright_theme": "Colorato", "bright_theme": "Colorato",
"buy": "Comprare", "buy": "Comprare",

View file

@ -75,6 +75,7 @@
"bitcoin_dark_theme": "ビットコインダークテーマ", "bitcoin_dark_theme": "ビットコインダークテーマ",
"bitcoin_light_theme": "ビットコインライトテーマ", "bitcoin_light_theme": "ビットコインライトテーマ",
"bitcoin_payments_require_1_confirmation": "ビットコインの支払いには 1 回の確認が必要で、これには 20 分以上かかる場合があります。お待ち頂きまして、ありがとうございます!支払いが確認されると、メールが送信されます。", "bitcoin_payments_require_1_confirmation": "ビットコインの支払いには 1 回の確認が必要で、これには 20 分以上かかる場合があります。お待ち頂きまして、ありがとうございます!支払いが確認されると、メールが送信されます。",
"block_remaining": "残り1ブロック",
"Blocks_remaining": "${status} 残りのブロック", "Blocks_remaining": "${status} 残りのブロック",
"bright_theme": "明るい", "bright_theme": "明るい",
"buy": "購入", "buy": "購入",

View file

@ -75,6 +75,7 @@
"bitcoin_dark_theme": "비트코인 다크 테마", "bitcoin_dark_theme": "비트코인 다크 테마",
"bitcoin_light_theme": "비트코인 라이트 테마", "bitcoin_light_theme": "비트코인 라이트 테마",
"bitcoin_payments_require_1_confirmation": "비트코인 결제는 1번의 확인이 필요하며 20분 이상이 소요될 수 있습니다. 기다려 주셔서 감사합니다! 결제가 확인되면 이메일이 전송됩니다.", "bitcoin_payments_require_1_confirmation": "비트코인 결제는 1번의 확인이 필요하며 20분 이상이 소요될 수 있습니다. 기다려 주셔서 감사합니다! 결제가 확인되면 이메일이 전송됩니다.",
"block_remaining": "남은 블록 1 개",
"Blocks_remaining": "${status} 남은 블록", "Blocks_remaining": "${status} 남은 블록",
"bright_theme": "선명한", "bright_theme": "선명한",
"buy": "구입", "buy": "구입",

View file

@ -75,6 +75,7 @@
"bitcoin_dark_theme": "Bitcoin Dark Theme", "bitcoin_dark_theme": "Bitcoin Dark Theme",
"bitcoin_light_theme": "Bitcoin Light အပြင်အဆင်", "bitcoin_light_theme": "Bitcoin Light အပြင်အဆင်",
"bitcoin_payments_require_1_confirmation": "Bitcoin ငွေပေးချေမှုများသည် မိနစ် 20 သို့မဟုတ် ထို့ထက်ပိုကြာနိုင်သည် 1 အတည်ပြုချက် လိုအပ်သည်။ မင်းရဲ့စိတ်ရှည်မှုအတွက် ကျေးဇူးတင်ပါတယ်။ ငွေပေးချေမှုကို အတည်ပြုပြီးသောအခါ သင့်ထံ အီးမေးလ်ပို့ပါမည်။", "bitcoin_payments_require_1_confirmation": "Bitcoin ငွေပေးချေမှုများသည် မိနစ် 20 သို့မဟုတ် ထို့ထက်ပိုကြာနိုင်သည် 1 အတည်ပြုချက် လိုအပ်သည်။ မင်းရဲ့စိတ်ရှည်မှုအတွက် ကျေးဇူးတင်ပါတယ်။ ငွေပေးချေမှုကို အတည်ပြုပြီးသောအခါ သင့်ထံ အီးမေးလ်ပို့ပါမည်။",
"block_remaining": "ကျန်ရှိနေသေးသော block",
"Blocks_remaining": "${status} ဘလောက်များ ကျန်နေပါသည်။", "Blocks_remaining": "${status} ဘလောက်များ ကျန်နေပါသည်။",
"bright_theme": "တောက်ပ", "bright_theme": "တောက်ပ",
"buy": "ဝယ်ပါ။", "buy": "ဝယ်ပါ။",

View file

@ -75,6 +75,7 @@
"bitcoin_dark_theme": "Bitcoin donker thema", "bitcoin_dark_theme": "Bitcoin donker thema",
"bitcoin_light_theme": "Bitcoin Light-thema", "bitcoin_light_theme": "Bitcoin Light-thema",
"bitcoin_payments_require_1_confirmation": "Bitcoin-betalingen vereisen 1 bevestiging, wat 20 minuten of langer kan duren. Dank voor uw geduld! U ontvangt een e-mail wanneer de betaling is bevestigd.", "bitcoin_payments_require_1_confirmation": "Bitcoin-betalingen vereisen 1 bevestiging, wat 20 minuten of langer kan duren. Dank voor uw geduld! U ontvangt een e-mail wanneer de betaling is bevestigd.",
"block_remaining": "1 blok resterend",
"Blocks_remaining": "${status} Resterende blokken", "Blocks_remaining": "${status} Resterende blokken",
"bright_theme": "Helder", "bright_theme": "Helder",
"buy": "Kopen", "buy": "Kopen",

View file

@ -75,6 +75,7 @@
"bitcoin_dark_theme": "Ciemny motyw Bitcoina", "bitcoin_dark_theme": "Ciemny motyw Bitcoina",
"bitcoin_light_theme": "Lekki motyw Bitcoin", "bitcoin_light_theme": "Lekki motyw Bitcoin",
"bitcoin_payments_require_1_confirmation": "Płatności Bitcoin wymagają 1 potwierdzenia, co może zająć 20 minut lub dłużej. Dziękuję za cierpliwość! Otrzymasz wiadomość e-mail, gdy płatność zostanie potwierdzona.", "bitcoin_payments_require_1_confirmation": "Płatności Bitcoin wymagają 1 potwierdzenia, co może zająć 20 minut lub dłużej. Dziękuję za cierpliwość! Otrzymasz wiadomość e-mail, gdy płatność zostanie potwierdzona.",
"block_remaining": "1 blok pozostałym",
"Blocks_remaining": "Pozostało ${status} bloków", "Blocks_remaining": "Pozostało ${status} bloków",
"bright_theme": "Biały", "bright_theme": "Biały",
"buy": "Kup", "buy": "Kup",

View file

@ -75,6 +75,7 @@
"bitcoin_dark_theme": "Tema escuro Bitcoin", "bitcoin_dark_theme": "Tema escuro Bitcoin",
"bitcoin_light_theme": "Tema claro de bitcoin", "bitcoin_light_theme": "Tema claro de bitcoin",
"bitcoin_payments_require_1_confirmation": "Os pagamentos em Bitcoin exigem 1 confirmação, o que pode levar 20 minutos ou mais. Obrigado pela sua paciência! Você receberá um e-mail quando o pagamento for confirmado.", "bitcoin_payments_require_1_confirmation": "Os pagamentos em Bitcoin exigem 1 confirmação, o que pode levar 20 minutos ou mais. Obrigado pela sua paciência! Você receberá um e-mail quando o pagamento for confirmado.",
"block_remaining": "1 bloco restante",
"Blocks_remaining": "${status} blocos restantes", "Blocks_remaining": "${status} blocos restantes",
"bright_theme": "Brilhante", "bright_theme": "Brilhante",
"buy": "Comprar", "buy": "Comprar",

View file

@ -75,6 +75,7 @@
"bitcoin_dark_theme": "Биткойн Темная тема", "bitcoin_dark_theme": "Биткойн Темная тема",
"bitcoin_light_theme": "Легкая биткойн-тема", "bitcoin_light_theme": "Легкая биткойн-тема",
"bitcoin_payments_require_1_confirmation": "Биткойн-платежи требуют 1 подтверждения, что может занять 20 минут или дольше. Спасибо тебе за твое терпение! Вы получите электронное письмо, когда платеж будет подтвержден.", "bitcoin_payments_require_1_confirmation": "Биткойн-платежи требуют 1 подтверждения, что может занять 20 минут или дольше. Спасибо тебе за твое терпение! Вы получите электронное письмо, когда платеж будет подтвержден.",
"block_remaining": "1 Блок остался",
"Blocks_remaining": "${status} Осталось блоков", "Blocks_remaining": "${status} Осталось блоков",
"bright_theme": "Яркая", "bright_theme": "Яркая",
"buy": "Купить", "buy": "Купить",

View file

@ -75,6 +75,7 @@
"bitcoin_dark_theme": "ธีมมืด Bitcoin", "bitcoin_dark_theme": "ธีมมืด Bitcoin",
"bitcoin_light_theme": "ธีมแสง Bitcoin", "bitcoin_light_theme": "ธีมแสง Bitcoin",
"bitcoin_payments_require_1_confirmation": "การชำระเงินด้วย Bitcoin ต้องการการยืนยัน 1 ครั้ง ซึ่งอาจใช้เวลา 20 นาทีหรือนานกว่านั้น ขอบคุณสำหรับความอดทนของคุณ! คุณจะได้รับอีเมลเมื่อการชำระเงินได้รับการยืนยัน", "bitcoin_payments_require_1_confirmation": "การชำระเงินด้วย Bitcoin ต้องการการยืนยัน 1 ครั้ง ซึ่งอาจใช้เวลา 20 นาทีหรือนานกว่านั้น ขอบคุณสำหรับความอดทนของคุณ! คุณจะได้รับอีเมลเมื่อการชำระเงินได้รับการยืนยัน",
"block_remaining": "เหลือ 1 บล็อก",
"Blocks_remaining": "${status} บล็อกที่เหลืออยู่", "Blocks_remaining": "${status} บล็อกที่เหลืออยู่",
"bright_theme": "สดใส", "bright_theme": "สดใส",
"buy": "ซื้อ", "buy": "ซื้อ",

View file

@ -75,6 +75,7 @@
"bitcoin_dark_theme": "Bitcoin Madilim na Tema", "bitcoin_dark_theme": "Bitcoin Madilim na Tema",
"bitcoin_light_theme": "Tema ng ilaw ng bitcoin", "bitcoin_light_theme": "Tema ng ilaw ng bitcoin",
"bitcoin_payments_require_1_confirmation": "Ang mga pagbabayad sa Bitcoin ay nangangailangan ng 1 kumpirmasyon, na maaaring tumagal ng 20 minuto o mas mahaba. Salamat sa iyong pasensya! Mag -email ka kapag nakumpirma ang pagbabayad.", "bitcoin_payments_require_1_confirmation": "Ang mga pagbabayad sa Bitcoin ay nangangailangan ng 1 kumpirmasyon, na maaaring tumagal ng 20 minuto o mas mahaba. Salamat sa iyong pasensya! Mag -email ka kapag nakumpirma ang pagbabayad.",
"block_remaining": "1 bloke ang natitira",
"Blocks_remaining": "Ang natitirang ${status} ay natitira", "Blocks_remaining": "Ang natitirang ${status} ay natitira",
"bright_theme": "Maliwanag", "bright_theme": "Maliwanag",
"buy": "Bilhin", "buy": "Bilhin",

View file

@ -75,6 +75,7 @@
"bitcoin_dark_theme": "Bitcoin Karanlık Teması", "bitcoin_dark_theme": "Bitcoin Karanlık Teması",
"bitcoin_light_theme": "Bitcoin Hafif Tema", "bitcoin_light_theme": "Bitcoin Hafif Tema",
"bitcoin_payments_require_1_confirmation": "Bitcoin ödemeleri, 20 dakika veya daha uzun sürebilen 1 onay gerektirir. Sabrınız için teşekkürler! Ödeme onaylandığında e-posta ile bilgilendirileceksiniz.", "bitcoin_payments_require_1_confirmation": "Bitcoin ödemeleri, 20 dakika veya daha uzun sürebilen 1 onay gerektirir. Sabrınız için teşekkürler! Ödeme onaylandığında e-posta ile bilgilendirileceksiniz.",
"block_remaining": "Kalan 1 blok",
"Blocks_remaining": "${status} Blok Kaldı", "Blocks_remaining": "${status} Blok Kaldı",
"bright_theme": "Parlak", "bright_theme": "Parlak",
"buy": "Alış", "buy": "Alış",

View file

@ -75,6 +75,7 @@
"bitcoin_dark_theme": "Темна тема Bitcoin", "bitcoin_dark_theme": "Темна тема Bitcoin",
"bitcoin_light_theme": "Світла тема Bitcoin", "bitcoin_light_theme": "Світла тема Bitcoin",
"bitcoin_payments_require_1_confirmation": "Платежі Bitcoin потребують 1 підтвердження, яке може зайняти 20 хвилин або більше. Дякую за Ваше терпіння! Ви отримаєте електронний лист, коли платіж буде підтверджено.", "bitcoin_payments_require_1_confirmation": "Платежі Bitcoin потребують 1 підтвердження, яке може зайняти 20 хвилин або більше. Дякую за Ваше терпіння! Ви отримаєте електронний лист, коли платіж буде підтверджено.",
"block_remaining": "1 блок, що залишився",
"Blocks_remaining": "${status} Залишилось блоків", "Blocks_remaining": "${status} Залишилось блоків",
"bright_theme": "Яскрава", "bright_theme": "Яскрава",
"buy": "Купити", "buy": "Купити",

View file

@ -75,6 +75,7 @@
"bitcoin_dark_theme": "بٹ کوائن ڈارک تھیم", "bitcoin_dark_theme": "بٹ کوائن ڈارک تھیم",
"bitcoin_light_theme": "بٹ کوائن لائٹ تھیم", "bitcoin_light_theme": "بٹ کوائن لائٹ تھیم",
"bitcoin_payments_require_1_confirmation": "بٹ کوائن کی ادائیگی میں 1 تصدیق کی ضرورت ہوتی ہے ، جس میں 20 منٹ یا اس سے زیادہ وقت لگ سکتا ہے۔ آپ کے صبر کا شکریہ! ادائیگی کی تصدیق ہونے پر آپ کو ای میل کیا جائے گا۔", "bitcoin_payments_require_1_confirmation": "بٹ کوائن کی ادائیگی میں 1 تصدیق کی ضرورت ہوتی ہے ، جس میں 20 منٹ یا اس سے زیادہ وقت لگ سکتا ہے۔ آپ کے صبر کا شکریہ! ادائیگی کی تصدیق ہونے پر آپ کو ای میل کیا جائے گا۔",
"block_remaining": "1 بلاک باقی",
"Blocks_remaining": "${status} بلاکس باقی ہیں۔", "Blocks_remaining": "${status} بلاکس باقی ہیں۔",
"bright_theme": "روشن", "bright_theme": "روشن",
"buy": "خریدنے", "buy": "خریدنے",

View file

@ -75,6 +75,7 @@
"bitcoin_dark_theme": "Bitcoin Dark Akori", "bitcoin_dark_theme": "Bitcoin Dark Akori",
"bitcoin_light_theme": "Bitcoin Light Akori", "bitcoin_light_theme": "Bitcoin Light Akori",
"bitcoin_payments_require_1_confirmation": "Àwọn àránṣẹ́ Bitcoin nílò ìjẹ́rìísí kan. Ó lè lo ìṣéjú ogun tàbí ìṣéjú jù. A dúpẹ́ fún sùúrù yín! Ẹ máa gba ímeèlì t'ó bá jẹ́rìísí àránṣẹ́ náà.", "bitcoin_payments_require_1_confirmation": "Àwọn àránṣẹ́ Bitcoin nílò ìjẹ́rìísí kan. Ó lè lo ìṣéjú ogun tàbí ìṣéjú jù. A dúpẹ́ fún sùúrù yín! Ẹ máa gba ímeèlì t'ó bá jẹ́rìísí àránṣẹ́ náà.",
"block_remaining": "1 bulọọki to ku",
"Blocks_remaining": "Àkójọpọ̀ ${status} kikù", "Blocks_remaining": "Àkójọpọ̀ ${status} kikù",
"bright_theme": "Funfun", "bright_theme": "Funfun",
"buy": "Rà", "buy": "Rà",

View file

@ -75,6 +75,7 @@
"bitcoin_dark_theme": "比特币黑暗主题", "bitcoin_dark_theme": "比特币黑暗主题",
"bitcoin_light_theme": "比特币浅色主题", "bitcoin_light_theme": "比特币浅色主题",
"bitcoin_payments_require_1_confirmation": "比特币支付需要 1 次确认,这可能需要 20 分钟或更长时间。谢谢你的耐心!确认付款后,您将收到电子邮件。", "bitcoin_payments_require_1_confirmation": "比特币支付需要 1 次确认,这可能需要 20 分钟或更长时间。谢谢你的耐心!确认付款后,您将收到电子邮件。",
"block_remaining": "剩下1个块",
"Blocks_remaining": "${status} 剩余的块", "Blocks_remaining": "${status} 剩余的块",
"bright_theme": "明亮", "bright_theme": "明亮",
"buy": "购买", "buy": "购买",

View file

@ -67,6 +67,7 @@ import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:hive/hive.dart';"""; import 'package:hive/hive.dart';""";
const bitcoinCWHeaders = """ const bitcoinCWHeaders = """
import 'package:cw_bitcoin/bitcoin_receive_page_option.dart';
import 'package:cw_bitcoin/electrum_wallet.dart'; import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_bitcoin/bitcoin_unspent.dart'; import 'package:cw_bitcoin/bitcoin_unspent.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
@ -77,6 +78,7 @@ import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
import 'package:cw_bitcoin/litecoin_wallet_service.dart'; import 'package:cw_bitcoin/litecoin_wallet_service.dart';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
"""; """;
const bitcoinCwPart = "part 'cw_bitcoin.dart';"; const bitcoinCwPart = "part 'cw_bitcoin.dart';";
@ -135,6 +137,11 @@ abstract class Bitcoin {
TransactionPriority getLitecoinTransactionPriorityMedium(); TransactionPriority getLitecoinTransactionPriorityMedium();
TransactionPriority getBitcoinTransactionPrioritySlow(); TransactionPriority getBitcoinTransactionPrioritySlow();
TransactionPriority getLitecoinTransactionPrioritySlow(); TransactionPriority getLitecoinTransactionPrioritySlow();
Future<void> setAddressType(Object wallet, dynamic option);
BitcoinReceivePageOption getSelectedAddressType(Object wallet);
bool hasSelectedSilentPayments(Object wallet);
List<BitcoinReceivePageOption> getBitcoinReceivePageOptions();
} }
"""; """;