From 4a3140035f1f3f17640db195380da3307382ebbb Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Fri, 6 Oct 2023 12:20:45 -0300 Subject: [PATCH] inital commit: testnet, silent payments --- cw_bitcoin/lib/bitcoin_unspent.dart | 20 +- cw_bitcoin/lib/bitcoin_wallet.dart | 63 ++-- cw_bitcoin/lib/bitcoin_wallet_addresses.dart | 44 ++- cw_bitcoin/lib/bitcoin_wallet_service.dart | 31 +- cw_bitcoin/lib/electrum.dart | 95 ++--- cw_bitcoin/lib/electrum_wallet.dart | 337 ++++++++++-------- cw_bitcoin/lib/electrum_wallet_addresses.dart | 135 ++++--- cw_bitcoin/lib/electrum_wallet_snapshot.dart | 5 + cw_bitcoin/lib/litecoin_wallet_service.dart | 2 +- cw_bitcoin/pubspec.lock | 73 +++- cw_bitcoin/pubspec.yaml | 6 +- cw_core/lib/wallet_service.dart | 2 +- cw_ethereum/lib/ethereum_wallet_service.dart | 2 +- cw_haven/lib/haven_wallet_service.dart | 2 +- cw_monero/lib/monero_wallet_service.dart | 2 +- lib/bitcoin/cw_bitcoin.dart | 10 + lib/core/address_validator.dart | 5 +- lib/core/wallet_creation_service.dart | 4 +- lib/di.dart | 2 +- lib/nano/nano.dart | 123 +++++++ lib/router.dart | 6 +- .../dashboard/widgets/address_page.dart | 47 +-- .../advanced_privacy_settings_page.dart | 12 + .../screens/new_wallet/new_wallet_page.dart | 4 +- lib/src/screens/receive/receive_page.dart | 39 +- .../validable_annotated_editable_text.dart | 2 +- .../advanced_privacy_settings_view_model.dart | 19 +- .../node_create_or_edit_view_model.dart | 2 + .../wallet_address_list_view_model.dart | 24 +- lib/view_model/wallet_creation_vm.dart | 2 +- lib/view_model/wallet_new_vm.dart | 15 +- linux/flutter/generated_plugin_registrant.cc | 4 - linux/flutter/generated_plugins.cmake | 1 - macos/Flutter/GeneratedPluginRegistrant.swift | 4 - pubspec_base.yaml | 4 +- res/values/strings_ar.arb | 6 +- res/values/strings_bg.arb | 5 +- res/values/strings_cs.arb | 5 +- res/values/strings_de.arb | 5 +- res/values/strings_en.arb | 5 +- res/values/strings_es.arb | 5 +- res/values/strings_fr.arb | 5 +- res/values/strings_ha.arb | 5 +- res/values/strings_hi.arb | 5 +- res/values/strings_hr.arb | 7 +- res/values/strings_id.arb | 7 +- res/values/strings_it.arb | 5 +- res/values/strings_ja.arb | 5 +- res/values/strings_ko.arb | 5 +- res/values/strings_my.arb | 5 +- res/values/strings_nl.arb | 5 +- res/values/strings_pl.arb | 5 +- res/values/strings_pt.arb | 5 +- res/values/strings_ru.arb | 5 +- res/values/strings_th.arb | 5 +- res/values/strings_tr.arb | 5 +- res/values/strings_uk.arb | 5 +- res/values/strings_ur.arb | 5 +- res/values/strings_yo.arb | 5 +- res/values/strings_zh.arb | 5 +- tool/configure.dart | 5 +- 61 files changed, 826 insertions(+), 457 deletions(-) create mode 100644 lib/nano/nano.dart diff --git a/cw_bitcoin/lib/bitcoin_unspent.dart b/cw_bitcoin/lib/bitcoin_unspent.dart index e5a0e8cac..4f8c74740 100644 --- a/cw_bitcoin/lib/bitcoin_unspent.dart +++ b/cw_bitcoin/lib/bitcoin_unspent.dart @@ -1,15 +1,17 @@ import 'package:cw_bitcoin/bitcoin_address_record.dart'; class BitcoinUnspent { - BitcoinUnspent(this.address, this.hash, this.value, this.vout) + BitcoinUnspent(this.address, this.hash, this.value, this.vout, {bool? isSilent}) : isSending = true, isFrozen = false, - note = ''; + note = '', + isSilent = isSilent ?? false; - factory BitcoinUnspent.fromJSON( - BitcoinAddressRecord address, Map json) => - BitcoinUnspent(address, json['tx_hash'] as String, json['value'] as int, - json['tx_pos'] as int); + factory BitcoinUnspent.fromJSON(BitcoinAddressRecord address, Map json, + {bool? isSilent}) => + BitcoinUnspent( + address, json['tx_hash'] as String, json['value'] as int, json['tx_pos'] as int, + isSilent: isSilent); final BitcoinAddressRecord address; final String hash; @@ -17,8 +19,12 @@ class BitcoinUnspent { final int vout; bool get isP2wpkh => - address.address.startsWith('bc') || address.address.startsWith('ltc'); + address.address.startsWith('bc') || + // testnet + address.address.startsWith('tb') || + address.address.startsWith('ltc'); bool isSending; bool isFrozen; + bool isSilent; String note; } diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index 3e4601eb3..9d9e52357 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -23,77 +23,84 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { required String password, required WalletInfo walletInfo, required Box unspentCoinsInfo, + bitcoin.NetworkType? networkType, required Uint8List seedBytes, required EncryptionFileUtils encryptionFileUtils, List? initialAddresses, ElectrumBalance? initialBalance, int initialRegularAddressIndex = 0, - int initialChangeAddressIndex = 0}) + int initialChangeAddressIndex = 0, + bitcoin.SilentPaymentAddress? silentAddress}) : super( mnemonic: mnemonic, password: password, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfo, - networkType: bitcoin.bitcoin, + networkType: networkType ?? bitcoin.bitcoin, initialAddresses: initialAddresses, initialBalance: initialBalance, seedBytes: seedBytes, currency: CryptoCurrency.btc, encryptionFileUtils: encryptionFileUtils) { - walletAddresses = BitcoinWalletAddresses( - walletInfo, + walletAddresses = BitcoinWalletAddresses(walletInfo, electrumClient: electrumClient, initialAddresses: initialAddresses, initialRegularAddressIndex: initialRegularAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex, mainHd: hd, - sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType) - .derivePath("m/0'/1"), - networkType: networkType); + sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/1"), + networkType: networkType ?? bitcoin.bitcoin, + silentAddress: silentAddress); } - static Future create({ - required String mnemonic, - required String password, - required WalletInfo walletInfo, - required Box unspentCoinsInfo, - required EncryptionFileUtils encryptionFileUtils, - List? initialAddresses, - ElectrumBalance? initialBalance, - int initialRegularAddressIndex = 0, - int initialChangeAddressIndex = 0 - }) async { + static Future create( + {required String mnemonic, + required String password, + required WalletInfo walletInfo, + required Box unspentCoinsInfo, + bitcoin.NetworkType? networkType, + required EncryptionFileUtils encryptionFileUtils, + List? initialAddresses, + ElectrumBalance? initialBalance, + int initialRegularAddressIndex = 0, + int initialChangeAddressIndex = 0}) async { return BitcoinWallet( mnemonic: mnemonic, password: password, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfo, + networkType: networkType, initialAddresses: initialAddresses, initialBalance: initialBalance, encryptionFileUtils: encryptionFileUtils, seedBytes: await mnemonicToSeedBytes(mnemonic), initialRegularAddressIndex: initialRegularAddressIndex, - initialChangeAddressIndex: initialChangeAddressIndex); + initialChangeAddressIndex: initialChangeAddressIndex, + silentAddress: await bitcoin.SilentPaymentAddress.fromMnemonic(mnemonic, + hrp: networkType == bitcoin.bitcoin ? 'sp' : 'tsp')); } - static Future open({ - required String name, - required WalletInfo walletInfo, - required Box unspentCoinsInfo, - required String password, - required EncryptionFileUtils encryptionFileUtils - }) async { - final snp = await ElectrumWallletSnapshot.load(encryptionFileUtils, name, walletInfo.type, password); + static Future open( + {required String name, + required WalletInfo walletInfo, + required Box unspentCoinsInfo, + required String password, + required EncryptionFileUtils encryptionFileUtils}) async { + final snp = + await ElectrumWallletSnapshot.load(encryptionFileUtils, name, walletInfo.type, password); return BitcoinWallet( mnemonic: snp.mnemonic, password: password, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfo, + networkType: snp.networkType, initialAddresses: snp.addresses, initialBalance: snp.balance, seedBytes: await mnemonicToSeedBytes(snp.mnemonic), encryptionFileUtils: encryptionFileUtils, initialRegularAddressIndex: snp.regularAddressIndex, - initialChangeAddressIndex: snp.changeAddressIndex); + initialChangeAddressIndex: snp.changeAddressIndex, + silentAddress: await bitcoin.SilentPaymentAddress.fromMnemonic(snp.mnemonic, + hrp: snp.networkType == bitcoin.bitcoin ? 'sp' : 'tsp')); } } diff --git a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart index de3fdfbca..a3eedabcf 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart @@ -4,36 +4,34 @@ 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_core/wallet_info.dart'; -import 'package:flutter/foundation.dart'; import 'package:mobx/mobx.dart'; part 'bitcoin_wallet_addresses.g.dart'; -class BitcoinWalletAddresses = BitcoinWalletAddressesBase - with _$BitcoinWalletAddresses; +class BitcoinWalletAddresses = BitcoinWalletAddressesBase with _$BitcoinWalletAddresses; -abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses - with Store { - BitcoinWalletAddressesBase( - WalletInfo walletInfo, +abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with Store { + BitcoinWalletAddressesBase(WalletInfo walletInfo, {required bitcoin.HDWallet mainHd, - required bitcoin.HDWallet sideHd, - required bitcoin.NetworkType networkType, - required ElectrumClient electrumClient, - List? initialAddresses, - int initialRegularAddressIndex = 0, - int initialChangeAddressIndex = 0}) - : super( - walletInfo, - initialAddresses: initialAddresses, - initialRegularAddressIndex: initialRegularAddressIndex, - initialChangeAddressIndex: initialChangeAddressIndex, - mainHd: mainHd, - sideHd: sideHd, - electrumClient: electrumClient, - networkType: networkType); + required bitcoin.HDWallet sideHd, + required bitcoin.NetworkType networkType, + required ElectrumClient electrumClient, + List? initialAddresses, + int initialRegularAddressIndex = 0, + int initialChangeAddressIndex = 0, + bitcoin.SilentPaymentAddress? silentAddress}) + : super(walletInfo, + initialAddresses: initialAddresses, + initialRegularAddressIndex: initialRegularAddressIndex, + initialChangeAddressIndex: initialChangeAddressIndex, + mainHd: mainHd, + sideHd: sideHd, + electrumClient: electrumClient, + networkType: networkType, + silentAddress: silentAddress); @override String getAddress({required int index, required bitcoin.HDWallet hd}) => generateP2WPKHAddress(hd: hd, index: index, networkType: networkType); -} \ No newline at end of file +} + diff --git a/cw_bitcoin/lib/bitcoin_wallet_service.dart b/cw_bitcoin/lib/bitcoin_wallet_service.dart index 743f53379..ea794e2e8 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_service.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_service.dart @@ -12,11 +12,10 @@ import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:hive/hive.dart'; import 'package:collection/collection.dart'; +import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; -class BitcoinWalletService extends WalletService< - BitcoinNewWalletCredentials, - BitcoinRestoreWalletFromSeedCredentials, - BitcoinRestoreWalletFromWIFCredentials> { +class BitcoinWalletService extends WalletService { BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.isDirect); final Box walletInfoSource; @@ -27,12 +26,13 @@ class BitcoinWalletService extends WalletService< WalletType getType() => WalletType.bitcoin; @override - Future create(BitcoinNewWalletCredentials credentials) async { + Future create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async { final wallet = await BitcoinWalletBase.create( mnemonic: await generateMnemonic(), password: credentials.password!, walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource, + networkType: isTestnet == true ? bitcoin.testnet : bitcoin.bitcoin, encryptionFileUtils: encryptionFileUtilsFor(isDirect)); await wallet.save(); await wallet.init(); @@ -45,8 +45,8 @@ class BitcoinWalletService extends WalletService< @override Future openWallet(String name, String password) async { - final walletInfo = walletInfoSource.values.firstWhereOrNull( - (info) => info.id == WalletBase.idFor(name, getType()))!; + final walletInfo = walletInfoSource.values + .firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!; final wallet = await BitcoinWalletBase.open( password: password, name: name, @@ -59,17 +59,16 @@ class BitcoinWalletService extends WalletService< @override Future remove(String wallet) async { - File(await pathForWalletDir(name: wallet, type: getType())) - .delete(recursive: true); - final walletInfo = walletInfoSource.values.firstWhereOrNull( - (info) => info.id == WalletBase.idFor(wallet, getType()))!; + File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true); + final walletInfo = walletInfoSource.values + .firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!; await walletInfoSource.delete(walletInfo.key); } @override Future rename(String currentName, String password, String newName) async { - final currentWalletInfo = walletInfoSource.values.firstWhereOrNull( - (info) => info.id == WalletBase.idFor(currentName, getType()))!; + final currentWalletInfo = walletInfoSource.values + .firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!; final currentWallet = await BitcoinWalletBase.open( password: password, name: currentName, @@ -87,13 +86,11 @@ class BitcoinWalletService extends WalletService< } @override - Future restoreFromKeys( - BitcoinRestoreWalletFromWIFCredentials credentials) async => + Future restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials) async => throw UnimplementedError(); @override - Future restoreFromSeed( - BitcoinRestoreWalletFromSeedCredentials credentials) async { + Future restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials) async { if (!validateMnemonic(credentials.mnemonic)) { throw BitcoinMnemonicIsIncorrectException(); } diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index a05c251fe..85dcff633 100644 --- a/cw_bitcoin/lib/electrum.dart +++ b/cw_bitcoin/lib/electrum.dart @@ -22,10 +22,7 @@ String jsonrpc( '{"jsonrpc": "$version", "method": "$method", "id": "$id", "params": ${json.encode(params)}}\n'; class SocketTask { - SocketTask({ - required this.isSubscription, - this.completer, - this.subject}); + SocketTask({required this.isSubscription, this.completer, this.subject}); final Completer? completer; final BehaviorSubject? subject; @@ -51,8 +48,7 @@ class ElectrumClient { Timer? _aliveTimer; String unterminatedString; - Future connectToUri(Uri uri) async => - await connect(host: uri.host, port: uri.port); + Future connectToUri(Uri uri) async => await connect(host: uri.host, port: uri.port); Future connect({required String host, required int port}) async { try { @@ -104,21 +100,20 @@ class ElectrumClient { } if (isJSONStringCorrect(unterminatedString)) { - final response = - json.decode(unterminatedString) as Map; + final response = json.decode(unterminatedString) as Map; _handleResponse(response); unterminatedString = ''; } } on TypeError catch (e) { - if (!e.toString().contains('Map') && !e.toString().contains('Map')) { + if (!e.toString().contains('Map') && + !e.toString().contains('Map')) { return; } unterminatedString += message; if (isJSONStringCorrect(unterminatedString)) { - final response = - json.decode(unterminatedString) as Map; + final response = json.decode(unterminatedString) as Map; _handleResponse(response); // unterminatedString = null; unterminatedString = ''; @@ -142,8 +137,7 @@ class ElectrumClient { } } - Future> version() => - call(method: 'server.version').then((dynamic result) { + Future> version() => call(method: 'server.version').then((dynamic result) { if (result is List) { return result.map((dynamic val) => val.toString()).toList(); } @@ -180,9 +174,8 @@ class ElectrumClient { Future>> getListUnspentWithAddress( String address, NetworkType networkType) => call( - method: 'blockchain.scripthash.listunspent', - params: [scriptHash(address, networkType: networkType)]) - .then((dynamic result) { + method: 'blockchain.scripthash.listunspent', + params: [scriptHash(address, networkType: networkType)]).then((dynamic result) { if (result is List) { return result.map((dynamic val) { if (val is Map) { @@ -229,19 +222,25 @@ class ElectrumClient { return []; }); - Future> getTransactionRaw( - {required String hash}) async => - callWithTimeout(method: 'blockchain.transaction.get', params: [hash, true], timeout: 10000) + Future getTransactionRaw( + {required String hash, required NetworkType networkType}) async => + callWithTimeout( + method: 'blockchain.transaction.get', + params: networkType == bitcoin ? [hash, true] : [hash], + timeout: 10000) .then((dynamic result) { if (result is Map) { return result; } + if (networkType == testnet && result is String) { + return result; + } + return {}; }); - Future getTransactionHex( - {required String hash}) async => + Future getTransactionHex({required String hash}) async => callWithTimeout(method: 'blockchain.transaction.get', params: [hash, false], timeout: 10000) .then((dynamic result) { if (result is String) { @@ -251,8 +250,7 @@ class ElectrumClient { return ''; }); - Future broadcastTransaction( - {required String transactionRaw}) async => + Future broadcastTransaction({required String transactionRaw}) async => call(method: 'blockchain.transaction.broadcast', params: [transactionRaw]) .then((dynamic result) { if (result is String) { @@ -262,19 +260,15 @@ class ElectrumClient { return ''; }); - Future> getMerkle( - {required String hash, required int height}) async => - await call( - method: 'blockchain.transaction.get_merkle', - params: [hash, height]) as Map; - - Future> getHeader({required int height}) async => - await call(method: 'blockchain.block.get_header', params: [height]) + Future> getMerkle({required String hash, required int height}) async => + await call(method: 'blockchain.transaction.get_merkle', params: [hash, height]) as Map; + Future> getHeader({required int height}) async => + await call(method: 'blockchain.block.get_header', params: [height]) as Map; + Future estimatefee({required int p}) => - call(method: 'blockchain.estimatefee', params: [p]) - .then((dynamic result) { + call(method: 'blockchain.estimatefee', params: [p]).then((dynamic result) { if (result is double) { return result; } @@ -319,15 +313,9 @@ class ElectrumClient { final topDoubleString = await estimatefee(p: 1); final middleDoubleString = await estimatefee(p: 5); final bottomDoubleString = await estimatefee(p: 100); - final top = - (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000) - .round(); - final middle = - (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000) - .round(); - final bottom = - (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000) - .round(); + final top = (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000).round(); + final middle = (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000).round(); + final bottom = (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000).round(); return [bottom, middle, top]; } catch (_) { @@ -344,16 +332,14 @@ class ElectrumClient { } BehaviorSubject? subscribe( - {required String id, - required String method, - List params = const []}) { + {required String id, required String method, List params = const []}) { try { final subscription = BehaviorSubject(); _regisrySubscription(id, subscription); socket!.write(jsonrpc(method: method, id: _id, params: params)); return subscription; - } catch(e) { + } catch (e) { print(e.toString()); return null; } @@ -370,9 +356,7 @@ class ElectrumClient { } Future callWithTimeout( - {required String method, - List params = const [], - int timeout = 4000}) async { + {required String method, List params = const [], int timeout = 4000}) async { try { final completer = Completer(); _id += 1; @@ -386,7 +370,7 @@ class ElectrumClient { }); return completer.future; - } catch(e) { + } catch (e) { print(e.toString()); } } @@ -397,8 +381,8 @@ class ElectrumClient { onConnectionStatusChange = null; } - void _registryTask(int id, Completer completer) => _tasks[id.toString()] = - SocketTask(completer: completer, isSubscription: false); + void _registryTask(int id, Completer completer) => + _tasks[id.toString()] = SocketTask(completer: completer, isSubscription: false); void _regisrySubscription(String id, BehaviorSubject subject) => _tasks[id] = SocketTask(subject: subject, isSubscription: true); @@ -419,8 +403,7 @@ class ElectrumClient { } } - void _methodHandler( - {required String method, required Map request}) { + void _methodHandler({required String method, required Map request}) { switch (method) { case 'blockchain.scripthash.subscribe': final params = request['params'] as List; @@ -451,8 +434,8 @@ class ElectrumClient { _methodHandler(method: method, request: response); return; } - - if (id != null){ + + if (id != null) { _finish(id, result); } } diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 710e7807f..7b9fd818d 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -35,13 +35,15 @@ import 'package:cw_bitcoin/electrum.dart'; import 'package:hex/hex.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:collection/collection.dart'; +import 'package:http/http.dart' as http; part 'electrum_wallet.g.dart'; class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet; -abstract class ElectrumWalletBase extends WalletBase with Store { +abstract class ElectrumWalletBase + extends WalletBase + with Store { ElectrumWalletBase( {required String password, required WalletInfo walletInfo, @@ -54,28 +56,25 @@ abstract class ElectrumWalletBase extends WalletBase[], _isTransactionUpdating = false, unspentCoins = [], _scripthashesUpdateSubject = {}, - balance = ObservableMap.of( - currency != null - ? {currency: initialBalance ?? const ElectrumBalance(confirmed: 0, unconfirmed: 0, - frozen: 0)} - : {}), + balance = ObservableMap.of(currency != null + ? { + currency: + initialBalance ?? const ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0) + } + : {}), this.unspentCoinsInfo = unspentCoinsInfo, super(walletInfo) { this.electrumClient = electrumClient ?? ElectrumClient(); this.walletInfo = walletInfo; - transactionHistory = - ElectrumTransactionHistory( - walletInfo: walletInfo, - password: password, - encryptionFileUtils: encryptionFileUtils); + transactionHistory = ElectrumTransactionHistory( + walletInfo: walletInfo, password: password, encryptionFileUtils: encryptionFileUtils); } static int estimatedTransactionSize(int inputsCount, int outputsCounts) => @@ -104,9 +103,9 @@ abstract class ElectrumWalletBase extends WalletBase get publicScriptHashes => walletAddresses.addresses - .where((addr) => !addr.isHidden) - .map((addr) => scriptHash(addr.address, networkType: networkType)) - .toList(); + .where((addr) => !addr.isHidden) + .map((addr) => scriptHash(addr.address, networkType: networkType)) + .toList(); String get xpub => hd.base58!; @@ -119,8 +118,8 @@ abstract class ElectrumWalletBase extends WalletBase BitcoinWalletKeys( - wif: hd.wif!, privateKey: hd.privKey!, publicKey: hd.pubKey!); + BitcoinWalletKeys get keys => + BitcoinWalletKeys(wif: hd.wif!, privateKey: hd.privKey!, publicKey: hd.pubKey!); String _password; List unspentCoins; @@ -148,8 +147,8 @@ abstract class ElectrumWalletBase extends WalletBase _feeRates = await electrumClient.feeRates()); + Timer.periodic( + const Duration(minutes: 1), (timer) async => _feeRates = await electrumClient.feeRates()); syncStatus = SyncedSyncStatus(); } catch (e, stacktrace) { @@ -178,8 +177,7 @@ abstract class ElectrumWalletBase extends WalletBase createTransaction( - Object credentials) async { + Future createTransaction(Object credentials) async { const minAmount = 546; final transactionCredentials = credentials as BitcoinTransactionCredentials; final inputs = []; @@ -202,9 +200,7 @@ abstract class ElectrumWalletBase extends WalletBase item.sendAll - || item.formattedCryptoAmount! <= 0)) { + if (outputs.any((item) => item.sendAll || item.formattedCryptoAmount! <= 0)) { throw BitcoinTransactionWrongBalanceException(currency); } - credentialsAmount = outputs.fold(0, (acc, value) => - acc + value.formattedCryptoAmount!); + credentialsAmount = outputs.fold(0, (acc, value) => acc + value.formattedCryptoAmount!); if (allAmount - credentialsAmount < minAmount) { throw BitcoinTransactionWrongBalanceException(currency); @@ -236,9 +230,7 @@ abstract class ElectrumWalletBase extends WalletBase allAmount) { throw BitcoinTransactionWrongBalanceException(currency); @@ -257,14 +249,14 @@ abstract class ElectrumWalletBase extends WalletBase balance[currency]!.confirmed || totalAmount > allInputsAmount) { - throw BitcoinTransactionWrongBalanceException(currency); + // throw BitcoinTransactionWrongBalanceException(currency); } final txb = bitcoin.TransactionBuilder(network: networkType); @@ -291,18 +283,26 @@ abstract class ElectrumWalletBase extends WalletBase privateKeys = []; + List outpoints = []; inputs.forEach((input) { + privateKeys.add(bitcoin.PrivateKeyInfo( + bitcoin.ECPrivateKey(Uint8List.fromList( + HEX.decode(walletAddresses.sideHd.derive(input.address.index).privKey!))), + false)); + outpoints.add(bitcoin.Outpoint(Uint8List.fromList(HEX.decode(input.hash)), input.vout)); + if (input.isP2wpkh) { final p2wpkh = bitcoin .P2WPKH( - data: generatePaymentData( - hd: input.address.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd, - index: input.address.index), - network: networkType) + data: generatePaymentData( + hd: input.address.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd, + index: input.address.index), + network: networkType) .data; txb.addInput(input.hash, input.vout, null, p2wpkh.output); @@ -311,28 +311,43 @@ abstract class ElectrumWalletBase extends WalletBase silentDestinations = []; outputs.forEach((item) { - final outputAmount = hasMultiDestination - ? item.formattedCryptoAmount - : amount; - final outputAddress = item.isParsedAddress - ? item.extractedAddress! - : item.address; - txb.addOutput( - addressToOutputScript(outputAddress, networkType), - outputAmount!); + final outputAmount = hasMultiDestination ? item.formattedCryptoAmount : amount; + final outputAddress = item.isParsedAddress ? item.extractedAddress! : item.address; + if (outputAddress.startsWith('tsp1')) { + silentDestinations + .add(bitcoin.SilentPaymentDestination.fromAddress(outputAddress, outputAmount!)); + } else { + txb.addOutput(addressToOutputScript(outputAddress, networkType), outputAmount!); + } }); - final estimatedSize = - estimatedTransactionSize(inputs.length, outputs.length + 1); - var feeAmount = 0; + if (silentDestinations.isNotEmpty) { + final outpointsHash = bitcoin.SilentPayment.hashOutpoints(outpoints); - if (transactionCredentials.feeRate != null) { - feeAmount = transactionCredentials.feeRate! * estimatedSize; - } else { - feeAmount = feeRate(transactionCredentials.priority!) * estimatedSize; + final sumOfInputPrivKeys = bitcoin.getSumInputPrivKeys(privateKeys); + + final generatedPubkeys = bitcoin.SilentPayment.generateMultipleRecipientPubkeys( + sumOfInputPrivKeys, outpointsHash, silentDestinations); + + generatedPubkeys.forEach((recipientSilentAddress, generatedOutputs) { + generatedOutputs.forEach((output) { + final generatedPubkey = HEX.encode(output.$1.data); + txb.addOutput(bitcoin.getTaproot(generatedPubkey).toScriptPubKey().toBytes(), output.$2); + }); + }); } + final estimatedSize = estimatedTransactionSize(inputs.length, outputs.length + 1); + var feeAmount = 188; + + // if (transactionCredentials.feeRate != null) { + // feeAmount = transactionCredentials.feeRate! * estimatedSize; + // } else { + // feeAmount = feeRate(transactionCredentials.priority!) * estimatedSize; + // } + final changeValue = totalInputAmount - amount - feeAmount; if (changeValue > minAmount) { @@ -363,7 +378,8 @@ abstract class ElectrumWalletBase extends WalletBase addr.toJSON()).toList(), - 'balance': balance[currency]?.toJSON() + 'balance': balance[currency]?.toJSON(), + 'network_type': networkType.toString() }); int feeRate(TransactionPriority priority) { @@ -373,34 +389,29 @@ abstract class ElectrumWalletBase extends WalletBase + int feeAmountForPriority( + BitcoinTransactionPriority priority, int inputsCount, int outputsCount) => feeRate(priority) * estimatedTransactionSize(inputsCount, outputsCount); - int feeAmountWithFeeRate(int feeRate, int inputsCount, - int outputsCount) => + int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount) => feeRate * estimatedTransactionSize(inputsCount, outputsCount); @override - int calculateEstimatedFee(TransactionPriority? priority, int? amount, - {int? outputsCount}) { + int calculateEstimatedFee(TransactionPriority? priority, int? amount, {int? outputsCount}) { if (priority is BitcoinTransactionPriority) { - return calculateEstimatedFeeWithFeeRate( - feeRate(priority), - amount, - outputsCount: outputsCount); + return calculateEstimatedFeeWithFeeRate(feeRate(priority), amount, + outputsCount: outputsCount); } return 0; } - int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, - {int? outputsCount}) { + int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount}) { int inputsCount = 0; if (amount != null) { @@ -429,8 +440,7 @@ abstract class ElectrumWalletBase extends WalletBase makePath() async => - pathForWallet(name: walletInfo.name, type: walletInfo.type); + Future makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); Future updateUnspent() async { - final unspent = await Future.wait(walletAddresses - .addresses.map((address) => electrumClient + final unspent = await Future.wait(walletAddresses.addresses.map((address) => electrumClient .getListUnspentWithAddress(address.address, networkType) - .then((unspent) => unspent - .map((unspent) { + .then((unspent) => unspent.map((unspent) { try { return BitcoinUnspent.fromJSON(address, unspent); - } catch(_) { + } catch (_) { return null; } }).whereNotNull()))); + final uri = Uri( + scheme: 'https', + host: 'blockstream.info', + path: '/testnet/api/tx/986547a4daec37b21d2252e39c740d77ff92d927343b0b6e017d45e857955efa'); + + await http.get(uri).then((response) { + final obj = json.decode(response.body); + final scanPrivateKey = walletAddresses.silentAddress!.scanPrivkey; + final spendPublicKey = walletAddresses.silentAddress!.spendPubkey; + Uint8List? sumOfInputPublicKeys; + List outpoints = []; + obj["vin"].forEach((input) { + sumOfInputPublicKeys = Uint8List.fromList(HEX.decode(input["witness"][1] as String)); + outpoints.add(bitcoin.Outpoint( + Uint8List.fromList(HEX.decode(input['txid'] as String)), input['vout'] as int)); + }); + final outpointHash = bitcoin.SilentPayment.hashOutpoints(outpoints); + List outputs = []; + obj['vout'].forEach((out) { + outputs.add(Uint8List.fromList( + HEX.decode(bitcoin.getScript(out["scriptpubkey"] as String)[1] as String))); + }); + final result = bitcoin.scanOutputs( + scanPrivateKey.data, spendPublicKey.data, sumOfInputPublicKeys!, outpointHash, outputs); + result.forEach((key, value) { + final tweak = value; + // TODO: store tweak for BitcoinUnspent + final spendPrivateKey = walletAddresses.silentAddress!.spendPrivkey; + final privKey = spendPrivateKey.tweak(tweak); + final pubKey = bitcoin.ECPrivateKey(privKey!.data).pubkey; + int i = 0; + final vout = obj['vout'].firstWhere((out) { + final script = bitcoin.getScript(out["scriptpubkey"] as String); + final scriptHash = script[1] as String; + i++; + return scriptHash == key; + }); + unspent.add([ + BitcoinUnspent.fromJSON( + BitcoinAddressRecord(vout["scriptpubkey_address"] as String, index: 0, isUsed: true), + {"tx_hash": obj["txid"], "value": vout["value"], "tx_pos": i}, + isSilent: true) + ]); + }); + }); unspentCoins = unspent.expand((e) => e).toList(); if (unspentCoinsInfo.isEmpty) { @@ -508,7 +559,9 @@ abstract class ElectrumWalletBase extends WalletBase - element.walletId.contains(id) && element.hash.contains(coin.hash)); + element.walletId.contains(id) && + element.hash.contains(coin.hash) && + element.address.contains(coin.address.address)); if (coinInfoList.isNotEmpty) { final coinInfo = coinInfoList.first; @@ -527,14 +580,14 @@ abstract class ElectrumWalletBase extends WalletBase _addCoinInfo(BitcoinUnspent coin) async { final newInfo = UnspentCoinsInfo( - walletId: id, - hash: coin.hash, - isFrozen: coin.isFrozen, - isSending: coin.isSending, - noteRaw: coin.note, - address: coin.address.address, - value: coin.value, - vout: coin.vout, + walletId: id, + hash: coin.hash, + isFrozen: coin.isFrozen, + isSending: coin.isSending, + noteRaw: coin.note, + address: coin.address.address, + value: coin.value, + vout: coin.vout, ); await unspentCoinsInfo.add(newInfo); @@ -543,8 +596,8 @@ abstract class ElectrumWalletBase extends WalletBase _refreshUnspentCoinsInfo() async { try { final List keys = []; - final currentWalletUnspentCoins = unspentCoinsInfo.values - .where((element) => element.walletId.contains(id)); + final currentWalletUnspentCoins = + unspentCoinsInfo.values.where((element) => element.walletId.contains(id)); if (currentWalletUnspentCoins.isNotEmpty) { currentWalletUnspentCoins.forEach((element) { @@ -566,12 +619,23 @@ abstract class ElectrumWalletBase extends WalletBase getTransactionExpanded( {required String hash, required int height}) async { - final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash); - final transactionHex = verboseTransaction['hex'] as String; + final verboseTransaction = + await electrumClient.getTransactionRaw(hash: hash, networkType: networkType); + + String transactionHex; + int? time; + int confirmations = 0; + if (networkType == bitcoin.testnet) { + transactionHex = verboseTransaction as String; + confirmations = 1; + } else { + transactionHex = verboseTransaction['hex'] as String; + time = verboseTransaction['time'] as int?; + confirmations = verboseTransaction['confirmations'] as int? ?? 0; + } + final original = bitcoin.Transaction.fromHex(transactionHex); final ins = []; - final time = verboseTransaction['time'] as int?; - final confirmations = verboseTransaction['confirmations'] as int? ?? 0; for (final vin in original.ins) { final id = HEX.encode(vin.hash!.reversed.toList()); @@ -580,27 +644,19 @@ abstract class ElectrumWalletBase extends WalletBase fetchTransactionInfo( {required String hash, required int height}) async { - try { - final tx = await getTransactionExpanded(hash: hash, height: height); - final addresses = walletAddresses.addresses.map((addr) => addr.address).toSet(); - return ElectrumTransactionInfo.fromElectrumBundle( - tx, - walletInfo.type, - networkType, - addresses: addresses, - height: height); - } catch(_) { - return null; - } + try { + final tx = await getTransactionExpanded(hash: hash, height: height); + final addresses = walletAddresses.addresses.map((addr) => addr.address).toSet(); + return ElectrumTransactionInfo.fromElectrumBundle(tx, walletInfo.type, networkType, + addresses: addresses, height: height); + } catch (_) { + return null; + } } @override @@ -611,10 +667,8 @@ abstract class ElectrumWalletBase extends WalletBase electrumClient - .getHistory(scriptHash) - .then((history) => {scriptHash: history})); + final histories = addressHashes.keys.map((scriptHash) => + electrumClient.getHistory(scriptHash).then((history) => {scriptHash: history})); final historyResults = await Future.wait(histories); historyResults.forEach((history) { history.entries.forEach((historyItem) { @@ -625,19 +679,16 @@ abstract class ElectrumWalletBase extends WalletBase>( - {}, (acc, tx) { + final historiesWithDetails = await Future.wait(normalizedHistories.map((transaction) { + try { + return fetchTransactionInfo( + hash: transaction['tx_hash'] as String, height: transaction['height'] as int); + } catch (_) { + return Future.value(null); + } + })); + return historiesWithDetails + .fold>({}, (acc, tx) { if (tx == null) { return acc; } @@ -690,6 +741,9 @@ abstract class ElectrumWalletBase extends WalletBase>>[]; + var totalConfirmed = 0; + var totalUnconfirmed = 0; + for (var i = 0; i < addresses.length; i++) { final addressRecord = addresses[i]; final sh = scriptHash(addressRecord.address, networkType: networkType); @@ -700,16 +754,21 @@ abstract class ElectrumWalletBase extends WalletBase updateBalance() async { @@ -736,9 +795,7 @@ abstract class ElectrumWalletBase extends WalletBase addr.isHidden) - .toList(); + var addresses = walletAddresses.addresses.where((addr) => addr.isHidden).toList(); if (addresses.length < minCountOfHiddenAddresses) { addresses = walletAddresses.addresses.toList(); diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index 741c2fe1c..473cd761e 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -8,8 +8,7 @@ import 'package:mobx/mobx.dart'; part 'electrum_wallet_addresses.g.dart'; -class ElectrumWalletAddresses = ElectrumWalletAddressesBase - with _$ElectrumWalletAddresses; +class ElectrumWalletAddresses = ElectrumWalletAddressesBase with _$ElectrumWalletAddresses; abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { ElectrumWalletAddressesBase(WalletInfo walletInfo, @@ -19,20 +18,19 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { required this.networkType, List? initialAddresses, int initialRegularAddressIndex = 0, - int initialChangeAddressIndex = 0}) - : addresses = ObservableList.of( - (initialAddresses ?? []).toSet()), - receiveAddresses = ObservableList.of( - (initialAddresses ?? []) + int initialChangeAddressIndex = 0, + bitcoin.SilentPaymentAddress? silentAddress}) + : addresses = ObservableList.of((initialAddresses ?? []).toSet()), + silentAddress = silentAddress, + receiveAddresses = ObservableList.of((initialAddresses ?? []) .where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed) - .toSet()), - changeAddresses = ObservableList.of( - (initialAddresses ?? []) + .toSet()), + changeAddresses = ObservableList.of((initialAddresses ?? []) .where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed) - .toSet()), + .toSet()), currentReceiveAddressIndex = initialRegularAddressIndex, currentChangeAddressIndex = initialChangeAddressIndex, - super(walletInfo); + super(walletInfo); static const defaultReceiveAddressesCount = 22; static const defaultChangeAddressesCount = 17; @@ -46,9 +44,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { final bitcoin.HDWallet mainHd; final bitcoin.HDWallet sideHd; - @override + // TODO: labels -> disable edit on receive page + final bitcoin.SilentPaymentAddress? silentAddress; + + @observable + String? activeAddress; + @computed - String get address { + String get receiveAddress { if (receiveAddresses.isEmpty) { return generateNewAddress().address; } @@ -57,28 +60,40 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { } @override - set address(String addr) => null; + @computed + String get address { + if (activeAddress != null) { + return activeAddress!; + } + + if (receiveAddresses.isEmpty) { + return generateNewAddress().address; + } + + return receiveAddresses.first.address; + } + + @override + set address(String addr) => activeAddress = addr; int currentReceiveAddressIndex; int currentChangeAddressIndex; @computed - int get totalCountOfReceiveAddresses => - addresses.fold(0, (acc, addressRecord) { - if (!addressRecord.isHidden) { - return acc + 1; - } - return acc; - }); + int get totalCountOfReceiveAddresses => addresses.fold(0, (acc, addressRecord) { + if (!addressRecord.isHidden) { + return acc + 1; + } + return acc; + }); @computed - int get totalCountOfChangeAddresses => - addresses.fold(0, (acc, addressRecord) { - if (addressRecord.isHidden) { - return acc + 1; - } - return acc; - }); + int get totalCountOfChangeAddresses => addresses.fold(0, (acc, addressRecord) { + if (addressRecord.isHidden) { + return acc + 1; + } + return acc; + }); Future discoverAddresses() async { await _discoverAddresses(mainHd, false); @@ -105,15 +120,12 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @action Future getChangeAddress() async { updateChangeAddresses(); - + if (changeAddresses.isEmpty) { - final newAddresses = await _createNewAddresses( - gap, - hd: sideHd, - startIndex: totalCountOfChangeAddresses > 0 - ? totalCountOfChangeAddresses - 1 - : 0, - isHidden: true); + final newAddresses = await _createNewAddresses(gap, + hd: sideHd, + startIndex: totalCountOfChangeAddresses > 0 ? totalCountOfChangeAddresses - 1 : 0, + isHidden: true); _addAddresses(newAddresses); } @@ -127,8 +139,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { return address; } - BitcoinAddressRecord generateNewAddress( - {bitcoin.HDWallet? hd, bool isHidden = false}) { + BitcoinAddressRecord generateNewAddress({bitcoin.HDWallet? hd, bool isHidden = false}) { currentReceiveAddressIndex += 1; // FIX-ME: Check logic for whichi HD should be used here ??? final address = BitcoinAddressRecord( @@ -155,16 +166,16 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @action void updateReceiveAddresses() { receiveAddresses.removeRange(0, receiveAddresses.length); - final newAdresses = addresses - .where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed); + final newAdresses = + addresses.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed); receiveAddresses.addAll(newAdresses); } @action void updateChangeAddresses() { changeAddresses.removeRange(0, changeAddresses.length); - final newAdresses = addresses - .where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed); + final newAdresses = + addresses.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed); changeAddresses.addAll(newAdresses); } @@ -173,20 +184,16 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { List addrs; if (addresses.isNotEmpty) { - addrs = addresses - .where((addr) => addr.isHidden == isHidden) - .toList(); + addrs = addresses.where((addr) => addr.isHidden == isHidden).toList(); } else { addrs = await _createNewAddresses( - isHidden - ? defaultChangeAddressesCount - : defaultReceiveAddressesCount, + isHidden ? defaultChangeAddressesCount : defaultReceiveAddressesCount, startIndex: 0, hd: hd, isHidden: isHidden); } - while(hasAddrUse) { + while (hasAddrUse) { final addr = addrs.last.address; hasAddrUse = await _hasAddressUsed(addr); @@ -196,11 +203,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { final start = addrs.length; final count = start + gap; - final batch = await _createNewAddresses( - count, - startIndex: start, - hd: hd, - isHidden: isHidden); + final batch = await _createNewAddresses(count, startIndex: start, hd: hd, isHidden: isHidden); addrs.addAll(batch); } @@ -224,21 +227,15 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { if (countOfReceiveAddresses < defaultReceiveAddressesCount) { final addressesCount = defaultReceiveAddressesCount - countOfReceiveAddresses; - final newAddresses = await _createNewAddresses( - addressesCount, - startIndex: countOfReceiveAddresses, - hd: mainHd, - isHidden: false); + final newAddresses = await _createNewAddresses(addressesCount, + startIndex: countOfReceiveAddresses, hd: mainHd, isHidden: false); addresses.addAll(newAddresses); } if (countOfHiddenAddresses < defaultChangeAddressesCount) { final addressesCount = defaultChangeAddressesCount - countOfHiddenAddresses; - final newAddresses = await _createNewAddresses( - addressesCount, - startIndex: countOfHiddenAddresses, - hd: sideHd, - isHidden: true); + final newAddresses = await _createNewAddresses(addressesCount, + startIndex: countOfHiddenAddresses, hd: sideHd, isHidden: true); addresses.addAll(newAddresses); } } @@ -248,10 +245,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { final list = []; for (var i = startIndex; i < count + startIndex; i++) { - final address = BitcoinAddressRecord( - getAddress(index: i, hd: hd), - index: i, - isHidden: isHidden); + final address = + BitcoinAddressRecord(getAddress(index: i, hd: hd), index: i, isHidden: isHidden); list.add(address); } @@ -270,4 +265,4 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { final transactionHistory = await electrumClient.getHistory(sh); return transactionHistory.isNotEmpty; } -} \ No newline at end of file +} diff --git a/cw_bitcoin/lib/electrum_wallet_snapshot.dart b/cw_bitcoin/lib/electrum_wallet_snapshot.dart index 61a1e898d..381661c1e 100644 --- a/cw_bitcoin/lib/electrum_wallet_snapshot.dart +++ b/cw_bitcoin/lib/electrum_wallet_snapshot.dart @@ -4,6 +4,7 @@ import 'package:cw_bitcoin/electrum_balance.dart'; import 'package:cw_bitcoin/encryption_file_utils.dart'; import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/wallet_type.dart'; +import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; class ElectrumWallletSnapshot { ElectrumWallletSnapshot({ @@ -13,6 +14,7 @@ class ElectrumWallletSnapshot { required this.mnemonic, required this.addresses, required this.balance, + required this.networkType, required this.regularAddressIndex, required this.changeAddressIndex}); @@ -23,6 +25,7 @@ class ElectrumWallletSnapshot { String mnemonic; List addresses; ElectrumBalance balance; + bitcoin.NetworkType networkType; int regularAddressIndex; int changeAddressIndex; @@ -38,6 +41,7 @@ class ElectrumWallletSnapshot { .toList(); final balance = ElectrumBalance.fromJSON(data['balance'] as String) ?? ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0); + final networkType = bitcoin.testnet; var regularAddressIndex = 0; var changeAddressIndex = 0; @@ -53,6 +57,7 @@ class ElectrumWallletSnapshot { mnemonic: mnemonic, addresses: addresses, balance: balance, + networkType: networkType, regularAddressIndex: regularAddressIndex, changeAddressIndex: changeAddressIndex); } diff --git a/cw_bitcoin/lib/litecoin_wallet_service.dart b/cw_bitcoin/lib/litecoin_wallet_service.dart index cb33df800..557254772 100644 --- a/cw_bitcoin/lib/litecoin_wallet_service.dart +++ b/cw_bitcoin/lib/litecoin_wallet_service.dart @@ -27,7 +27,7 @@ class LitecoinWalletService extends WalletService< WalletType getType() => WalletType.litecoin; @override - Future create(BitcoinNewWalletCredentials credentials) async { + Future create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async { final wallet = await LitecoinWalletBase.create( mnemonic: await generateMnemonic(), password: credentials.password!, diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index fe8b55acd..209d634a8 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -66,15 +66,27 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.6" + bitcoin_base: + dependency: "direct main" + description: + path: "/home/rafael/Storage/Repositories/btc-silent-payments/bitcoin_base" + relative: false + source: path + version: "1.1.0" bitcoin_flutter: dependency: "direct main" description: - path: "." - ref: cake-update-v3 - resolved-ref: df9204144011ed9419eff7d9ef3143102a40252d - url: "https://github.com/cake-tech/bitcoin_flutter.git" - source: git + path: "/home/rafael/Storage/Repositories/btc-silent-payments/bitcoin_flutter" + relative: false + source: path version: "2.0.2" + blockchain_utils: + dependency: transitive + description: + path: "/home/rafael/Storage/Repositories/btc-silent-payments/blockchain_utils" + relative: false + source: path + version: "0.4.0" boolean_selector: dependency: transitive description: @@ -196,6 +208,13 @@ packages: url: "https://pub.dev" source: hosted version: "4.4.0" + coinlib: + dependency: transitive + description: + path: "/home/rafael/Storage/Repositories/btc-silent-payments/coinlib/coinlib" + relative: false + source: path + version: "1.0.0" collection: dependency: transitive description: @@ -243,6 +262,22 @@ packages: relative: true source: path version: "0.0.1" + dart_base_x: + dependency: transitive + description: + name: dart_base_x + sha256: c8af4f6a6518daab4aa85bb27ee148221644e80446bb44117052b6f4674cdb23 + url: "https://pub.dev" + source: hosted + version: "1.0.0" + dart_bech32: + dependency: transitive + description: + name: dart_bech32 + sha256: "0e1dc1ff39c9669c9ffeafd5d675104918f7b50799692491badfea7e1fb40888" + url: "https://pub.dev" + source: hosted + version: "2.0.0" dart_style: dependency: transitive description: @@ -251,6 +286,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.4" + elliptic: + dependency: transitive + description: + name: elliptic + sha256: "98e2fa89a714c649174553c823db2612dc9581814477fe1264a499d448237b6b" + url: "https://pub.dev" + source: hosted + version: "0.3.10" encrypt: dependency: "direct main" description: @@ -271,10 +314,10 @@ packages: dependency: transitive description: name: ffi - sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978 + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.1.0" file: dependency: transitive description: @@ -553,10 +596,10 @@ packages: dependency: transitive description: name: pointycastle - sha256: db7306cf0249f838d1a24af52b5a5887c5bf7f31d8bb4e827d071dc0939ad346 + sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" url: "https://pub.dev" source: hosted - version: "3.6.2" + version: "3.7.3" pool: dependency: transitive description: @@ -710,10 +753,10 @@ packages: dependency: transitive description: name: typed_data - sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" unorm_dart: dependency: "direct main" description: @@ -730,6 +773,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + wasm_interop: + dependency: transitive + description: + name: wasm_interop + sha256: b1b378f07a4cf0103c25faf34d9a64d2c3312135b9efb47e0ec116ec3b14e48f + url: "https://pub.dev" + source: hosted + version: "2.0.1" watcher: dependency: transitive description: diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index e0f5dad67..5b197632d 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -20,9 +20,9 @@ dependencies: cw_core: path: ../cw_core bitcoin_flutter: - git: - url: https://github.com/cake-tech/bitcoin_flutter.git - ref: cake-update-v3 + path: /home/rafael/Storage/Repositories/btc-silent-payments/bitcoin_flutter + bitcoin_base: + path: /home/rafael/Storage/Repositories/btc-silent-payments/bitcoin_base rxdart: ^0.27.5 unorm_dart: ^0.2.0 cryptography: ^2.0.5 diff --git a/cw_core/lib/wallet_service.dart b/cw_core/lib/wallet_service.dart index f95bc1a44..a7c1d08d0 100644 --- a/cw_core/lib/wallet_service.dart +++ b/cw_core/lib/wallet_service.dart @@ -6,7 +6,7 @@ abstract class WalletService { WalletType getType(); - Future create(N credentials); + Future create(N credentials, {bool? isTestnet}); Future restoreFromSeed(RFS credentials); diff --git a/cw_ethereum/lib/ethereum_wallet_service.dart b/cw_ethereum/lib/ethereum_wallet_service.dart index 289e7d25b..ff62dd63a 100644 --- a/cw_ethereum/lib/ethereum_wallet_service.dart +++ b/cw_ethereum/lib/ethereum_wallet_service.dart @@ -21,7 +21,7 @@ class EthereumWalletService extends WalletService create(EthereumNewWalletCredentials credentials) async { + Future create(EthereumNewWalletCredentials credentials, {bool? isTestnet}) async { final mnemonic = bip39.generateMnemonic(); final wallet = EthereumWallet( walletInfo: credentials.walletInfo!, diff --git a/cw_haven/lib/haven_wallet_service.dart b/cw_haven/lib/haven_wallet_service.dart index 0bc20d2a0..eded06778 100644 --- a/cw_haven/lib/haven_wallet_service.dart +++ b/cw_haven/lib/haven_wallet_service.dart @@ -68,7 +68,7 @@ class HavenWalletService extends WalletService< WalletType getType() => WalletType.haven; @override - Future create(HavenNewWalletCredentials credentials) async { + Future create(HavenNewWalletCredentials credentials, {bool? isTestnet}) async { try { final path = await pathForWallet(name: credentials.name, type: getType()); await haven_wallet_manager.createWallet( diff --git a/cw_monero/lib/monero_wallet_service.dart b/cw_monero/lib/monero_wallet_service.dart index ef485407c..751bc6f95 100644 --- a/cw_monero/lib/monero_wallet_service.dart +++ b/cw_monero/lib/monero_wallet_service.dart @@ -65,7 +65,7 @@ class MoneroWalletService extends WalletService< WalletType getType() => WalletType.monero; @override - Future create(MoneroNewWalletCredentials credentials) async { + Future create(MoneroNewWalletCredentials credentials, {bool? isTestnet}) async { try { final path = await pathForWallet(name: credentials.name, type: getType()); await monero_wallet_manager.createWallet( diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index f03e47460..2daeac17e 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -105,6 +105,16 @@ class CWBitcoin extends Bitcoin { return bitcoinWallet.walletAddresses.address; } + String getReceiveAddress(Object wallet) { + final bitcoinWallet = wallet as ElectrumWallet; + return bitcoinWallet.walletAddresses.receiveAddress; + } + + btc.SilentPaymentAddress? getSilentAddress(Object wallet) { + final bitcoinWallet = wallet as ElectrumWallet; + return bitcoinWallet.walletAddresses.silentAddress; + } + @override String formatterBitcoinAmountToString({required int amount}) => bitcoinAmountToString(amount: amount); diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart index f2a235363..90b04efff 100644 --- a/lib/core/address_validator.dart +++ b/lib/core/address_validator.dart @@ -25,7 +25,10 @@ class AddressValidator extends TextValidator { 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}\$'; case CryptoCurrency.btc: - return '^3[0-9a-zA-Z]{32}\$|^3[0-9a-zA-Z]{33}\$|^bc1[0-9a-zA-Z]{59}\$'; + final p2sh = '^3[0-9a-zA-Z]{32}\$|^3[0-9a-zA-Z]{33}\$'; + final testnet = '^tb1[0-9a-zA-Z]{59}\$'; + final silentpayments = '^tsp1[0-9a-zA-Z]{113}\$'; + return '^bc1[0-9a-zA-Z]{59}\$|$p2sh|$testnet|$silentpayments'; case CryptoCurrency.nano: return '[0-9a-zA-Z_]'; case CryptoCurrency.usdc: diff --git a/lib/core/wallet_creation_service.dart b/lib/core/wallet_creation_service.dart index 25f2d75be..91b13dc7c 100644 --- a/lib/core/wallet_creation_service.dart +++ b/lib/core/wallet_creation_service.dart @@ -53,7 +53,7 @@ class WalletCreationService { } } - Future create(WalletCredentials credentials) async { + Future create(WalletCredentials credentials, {bool? isTestnet}) async { checkIfExists(credentials.name); if (credentials.password == null) { @@ -62,7 +62,7 @@ class WalletCreationService { password: credentials.password!, walletName: credentials.name); } - final wallet = await _service!.create(credentials); + final wallet = await _service!.create(credentials, isTestnet: isTestnet); if (wallet.type == WalletType.monero) { await sharedPreferences diff --git a/lib/di.dart b/lib/di.dart index 58b6db56d..8b4b7ae56 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -1054,7 +1054,7 @@ Future setup({ IoniaPaymentStatusPage( getIt.get(param1: paymentInfo, param2: committedInfo))); - getIt.registerFactoryParam( + getIt.registerFactoryParam( (type, _) => AdvancedPrivacySettingsViewModel(type, getIt.get())); getIt.registerFactoryParam((args, _) { diff --git a/lib/nano/nano.dart b/lib/nano/nano.dart new file mode 100644 index 000000000..7342161ff --- /dev/null +++ b/lib/nano/nano.dart @@ -0,0 +1,123 @@ +import 'package:cw_core/cake_hive.dart'; +import 'package:cw_core/nano_account.dart'; +import 'package:cw_core/account.dart'; +import 'package:cw_core/node.dart'; +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/transaction_info.dart'; +import 'package:cw_core/transaction_history.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:cw_core/output_info.dart'; +import 'package:cw_core/nano_account_info_response.dart'; +import 'package:mobx/mobx.dart'; +import 'package:hive/hive.dart'; +import 'package:cake_wallet/view_model/send/output.dart'; + +import 'package:cw_nano/nano_client.dart'; +import 'package:cw_nano/nano_mnemonic.dart'; +import 'package:cw_nano/nano_wallet.dart'; +import 'package:cw_nano/nano_wallet_service.dart'; +import 'package:cw_nano/nano_transaction_info.dart'; +import 'package:cw_nano/nano_transaction_credentials.dart'; +import 'package:cw_nano/nano_wallet_creation_credentials.dart'; +// needed for nano_util: +import 'dart:convert'; +import 'dart:typed_data'; +import 'package:convert/convert.dart'; +import "package:ed25519_hd_key/ed25519_hd_key.dart"; +import 'package:libcrypto/libcrypto.dart'; +import 'package:nanodart/nanodart.dart' as ND; +import 'package:decimal/decimal.dart'; + +part 'cw_nano.dart'; + +Nano? nano = CWNano(); +NanoUtil? nanoUtil = CWNanoUtil(); + +abstract class Nano { + NanoAccountList getAccountList(Object wallet); + + Account getCurrentAccount(Object wallet); + + void setCurrentAccount(Object wallet, int id, String label, String? balance); + + WalletService createNanoWalletService(Box walletInfoSource, bool isDirect); + + WalletCredentials createNanoNewWalletCredentials({ + required String name, + String? password, + }); + + WalletCredentials createNanoRestoreWalletFromSeedCredentials({ + required String name, + required String password, + required String mnemonic, + DerivationType? derivationType, + }); + + WalletCredentials createNanoRestoreWalletFromKeysCredentials({ + required String name, + required String password, + required String seedKey, + DerivationType? derivationType, + }); + + List getNanoWordList(String language); + Map getKeys(Object wallet); + Object createNanoTransactionCredentials(List outputs); + Future changeRep(Object wallet, String address); + Future updateTransactions(Object wallet); + BigInt getTransactionAmountRaw(TransactionInfo transactionInfo); +} + +abstract class NanoAccountList { + ObservableList get accounts; + void update(Object wallet); + void refresh(Object wallet); + Future> getAll(Object wallet); + Future addAccount(Object wallet, {required String label}); + Future setLabelAccount(Object wallet, {required int accountIndex, required String label}); +} + +abstract class NanoUtil { + String seedToPrivate(String seed, int index); + String seedToAddress(String seed, int index); + String seedToMnemonic(String seed); + Future mnemonicToSeed(String mnemonic); + String privateKeyToPublic(String privateKey); + String addressToPublicKey(String publicAddress); + String privateKeyToAddress(String privateKey); + String publicKeyToAddress(String publicKey); + bool isValidSeed(String seed); + Future hdMnemonicListToSeed(List words); + Future hdSeedToPrivate(String seed, int index); + Future hdSeedToAddress(String seed, int index); + Future uniSeedToAddress(String seed, int index, String type); + Future uniSeedToPrivate(String seed, int index, String type); + bool isValidBip39Seed(String seed); + static const int maxDecimalDigits = 6; // Max digits after decimal + BigInt rawPerNano = BigInt.parse("1000000000000000000000000000000"); + BigInt rawPerNyano = BigInt.parse("1000000000000000000000000"); + BigInt rawPerBanano = BigInt.parse("100000000000000000000000000000"); + BigInt rawPerXMR = BigInt.parse("1000000000000"); + BigInt convertXMRtoNano = BigInt.parse("1000000000000000000"); + Decimal getRawAsDecimal(String? raw, BigInt? rawPerCur); + String truncateDecimal(Decimal input, {int digits = maxDecimalDigits}); + String getRawAsUsableString(String? raw, BigInt rawPerCur); + String getRawAccuracy(String? raw, BigInt rawPerCur); + String getAmountAsRaw(String amount, BigInt rawPerCur); + + // derivationInfo: + Future getInfoFromSeedOrMnemonic( + DerivationType derivationType, { + String? seedKey, + String? mnemonic, + required Node node, + }); + Future> compareDerivationMethods({ + String? mnemonic, + String? privateKey, + required Node node, + }); +} + \ No newline at end of file diff --git a/lib/router.dart b/lib/router.dart index 3b1ac6022..79212fc1c 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -571,11 +571,13 @@ Route createRoute(RouteSettings settings) { param2: url)); case Routes.advancedPrivacySettings: - final type = settings.arguments as WalletType; + final args = settings.arguments as List; + final type = args.first as WalletType; + final func = args[1] as void Function(); return CupertinoPageRoute( builder: (_) => AdvancedPrivacySettingsPage( - getIt.get(param1: type), + getIt.get(param1: func), getIt.get(param1: type), )); diff --git a/lib/src/screens/dashboard/widgets/address_page.dart b/lib/src/screens/dashboard/widgets/address_page.dart index 2f18ef634..26887cf02 100644 --- a/lib/src/screens/dashboard/widgets/address_page.dart +++ b/lib/src/screens/dashboard/widgets/address_page.dart @@ -177,8 +177,7 @@ class AddressPage extends BasePage { return GestureDetector( onTap: () async => dashboardViewModel.isAutoGenerateSubaddressesEnabled ? await showPopUp( - context: context, - builder: (_) => getIt.get()) + context: context, builder: (_) => getIt.get()) : Navigator.of(context).pushNamed(Routes.receive), child: Container( height: 50, @@ -198,26 +197,29 @@ class AddressPage extends BasePage { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Observer( - builder: (_) { - String label = addressListViewModel.hasAccounts - ? S.of(context).accounts_subaddresses - : S.of(context).addresses; + builder: (_) { + String label = addressListViewModel.hasSilentAddresses + ? S.of(context).address_and_silent_addresses + : addressListViewModel.hasAccounts + ? S.of(context).accounts_subaddresses + : S.of(context).addresses; - if (dashboardViewModel.isAutoGenerateSubaddressesEnabled) { - label = addressListViewModel.hasAccounts - ? S.of(context).accounts - : S.of(context).account; - } - return Text( - label, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Theme.of(context) - .extension()! - .textColor), - ); - },), + if (dashboardViewModel.isAutoGenerateSubaddressesEnabled) { + label = addressListViewModel.hasAccounts + ? S.of(context).accounts + : S.of(context).account; + } + return Text( + label, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Theme.of(context) + .extension()! + .textColor), + ); + }, + ), Icon( Icons.arrow_forward_ios, size: 14, @@ -227,7 +229,8 @@ class AddressPage extends BasePage { ), ), ); - } else if (dashboardViewModel.isAutoGenerateSubaddressesEnabled || addressListViewModel.showElectrumAddressDisclaimer) { + } else if (dashboardViewModel.isAutoGenerateSubaddressesEnabled || + addressListViewModel.showElectrumAddressDisclaimer) { return Text(S.of(context).electrum_address_disclaimer, textAlign: TextAlign.center, style: TextStyle( diff --git a/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart b/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart index 386f3012b..3ab85734c 100644 --- a/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart +++ b/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart @@ -75,6 +75,18 @@ class _AdvancedPrivacySettingsBodyState extends State widget.privacySettingsViewModel.toggleUseTestnet(), + ), + ], + ); + }), Observer(builder: (_) { return Column( children: [ diff --git a/lib/src/screens/new_wallet/new_wallet_page.dart b/lib/src/screens/new_wallet/new_wallet_page.dart index d583f9933..a7947a1a2 100644 --- a/lib/src/screens/new_wallet/new_wallet_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_page.dart @@ -297,8 +297,8 @@ class _WalletNameFormState extends State { const SizedBox(height: 25), GestureDetector( onTap: () { - Navigator.of(context) - .pushNamed(Routes.advancedPrivacySettings, arguments: _walletNewVM.type); + Navigator.of(context).pushNamed(Routes.advancedPrivacySettings, + arguments: [_walletNewVM.type, _walletNewVM.toggleUseTestnet]); }, child: Text(S.of(context).advanced_privacy_settings), ), diff --git a/lib/src/screens/receive/receive_page.dart b/lib/src/screens/receive/receive_page.dart index fa150f979..093654c06 100644 --- a/lib/src/screens/receive/receive_page.dart +++ b/lib/src/screens/receive/receive_page.dart @@ -66,8 +66,7 @@ class ReceivePage extends BasePage { @override Widget Function(BuildContext, Widget) get rootWrapper => - (BuildContext context, Widget scaffold) => - GradientBackground(scaffold: scaffold); + (BuildContext context, Widget scaffold) => GradientBackground(scaffold: scaffold); @override Widget trailing(BuildContext context) { @@ -98,7 +97,8 @@ class ReceivePage extends BasePage { @override Widget body(BuildContext context) { - return (addressListViewModel.type == WalletType.monero || + return (addressListViewModel.type == WalletType.bitcoin || + addressListViewModel.type == WalletType.monero || addressListViewModel.type == WalletType.haven) ? KeyboardActions( config: KeyboardActionsConfig( @@ -144,7 +144,8 @@ class ReceivePage extends BasePage { icon: Icon( Icons.arrow_forward_ios, size: 14, - color: Theme.of(context).extension()!.iconsColor, + color: + Theme.of(context).extension()!.iconsColor, )); } @@ -152,11 +153,12 @@ class ReceivePage extends BasePage { cell = HeaderTile( onTap: () => Navigator.of(context).pushNamed(Routes.newSubaddress), - title: S.of(context).addresses, + title: S.of(context).silent_addresses, icon: Icon( Icons.add, size: 20, - color: Theme.of(context).extension()!.iconsColor, + color: + Theme.of(context).extension()!.iconsColor, )); } @@ -165,11 +167,19 @@ class ReceivePage extends BasePage { final isCurrent = item.address == addressListViewModel.address.address; final backgroundColor = isCurrent - ? Theme.of(context).extension()!.currentTileBackgroundColor - : Theme.of(context).extension()!.tilesBackgroundColor; + ? Theme.of(context) + .extension()! + .currentTileBackgroundColor + : Theme.of(context) + .extension()! + .tilesBackgroundColor; final textColor = isCurrent - ? Theme.of(context).extension()!.currentTileTextColor - : Theme.of(context).extension()!.tilesTextColor; + ? Theme.of(context) + .extension()! + .currentTileTextColor + : Theme.of(context) + .extension()! + .tilesTextColor; return AddressCell.fromItem(item, isCurrent: isCurrent, @@ -190,6 +200,15 @@ class ReceivePage extends BasePage { child: cell, ); })), + Padding( + padding: EdgeInsets.fromLTRB(24, 24, 24, 32), + child: Text(S.of(context).electrum_address_disclaimer, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 15, + color: + Theme.of(context).extension()!.labelTextColor)), + ), ], ), )) diff --git a/lib/src/widgets/validable_annotated_editable_text.dart b/lib/src/widgets/validable_annotated_editable_text.dart index 6c3fc4f16..927f74c94 100644 --- a/lib/src/widgets/validable_annotated_editable_text.dart +++ b/lib/src/widgets/validable_annotated_editable_text.dart @@ -2,7 +2,7 @@ import 'package:cake_wallet/core/seed_validator.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; -class Annotation extends Comparable { +class Annotation implements Comparable { Annotation({required this.range, required this.style}); final TextRange range; diff --git a/lib/view_model/advanced_privacy_settings_view_model.dart b/lib/view_model/advanced_privacy_settings_view_model.dart index 380937212..6ec1eeb57 100644 --- a/lib/view_model/advanced_privacy_settings_view_model.dart +++ b/lib/view_model/advanced_privacy_settings_view_model.dart @@ -1,7 +1,6 @@ import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cake_wallet/store/settings_store.dart'; -import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; part 'advanced_privacy_settings_view_model.g.dart'; @@ -10,7 +9,8 @@ class AdvancedPrivacySettingsViewModel = AdvancedPrivacySettingsViewModelBase with _$AdvancedPrivacySettingsViewModel; abstract class AdvancedPrivacySettingsViewModelBase with Store { - AdvancedPrivacySettingsViewModelBase(this.type, this._settingsStore) : _addCustomNode = false; + AdvancedPrivacySettingsViewModelBase(this.changeUseTestnet, this._settingsStore) + : _addCustomNode = false; @computed ExchangeApiMode get exchangeStatus => _settingsStore.exchangeStatus; @@ -21,13 +21,20 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store { @observable bool _addCustomNode = false; - final WalletType type; + @observable + bool _useTestnet = false; + + // TODO: electrum's node as default for testnet + final void Function() changeUseTestnet; final SettingsStore _settingsStore; @computed bool get addCustomNode => _addCustomNode; + @computed + bool get useTestnet => _useTestnet; + @action void setFiatApiMode(FiatApiMode fiatApiMode) => _settingsStore.fiatApiMode = fiatApiMode; @@ -36,4 +43,10 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store { @action void toggleAddCustomNode() => _addCustomNode = !_addCustomNode; + + @action + void toggleUseTestnet() { + _useTestnet = !_useTestnet; + changeUseTestnet(); + } } diff --git a/lib/view_model/node_list/node_create_or_edit_view_model.dart b/lib/view_model/node_list/node_create_or_edit_view_model.dart index f749ed0d5..e1ce5ef19 100644 --- a/lib/view_model/node_list/node_create_or_edit_view_model.dart +++ b/lib/view_model/node_list/node_create_or_edit_view_model.dart @@ -62,6 +62,8 @@ abstract class NodeCreateOrEditViewModelBase with Store { bool get hasAuthCredentials => _walletType == WalletType.monero || _walletType == WalletType.haven; + bool get hasTestnetSupport => _walletType == WalletType.bitcoin; + String get uri { var uri = address; diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index db45ae117..89befac7f 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -115,8 +115,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo }) : _baseItems = [], selectedCurrency = walletTypeToCryptoCurrency(appStore.wallet!.type), _cryptoNumberFormat = NumberFormat(_cryptoNumberPattern), - hasAccounts = - appStore.wallet!.type == WalletType.monero || appStore.wallet!.type == WalletType.haven, + hasAccounts = appStore.wallet!.type == WalletType.bitcoin || + appStore.wallet!.type == WalletType.monero || + appStore.wallet!.type == WalletType.haven, amount = '', super(appStore: appStore) { _init(); @@ -127,7 +128,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo _init(); selectedCurrency = walletTypeToCryptoCurrency(wallet.type); - hasAccounts = wallet.type == WalletType.monero || wallet.type == WalletType.haven; + hasAccounts = wallet.type == WalletType.bitcoin || + wallet.type == WalletType.monero || + wallet.type == WalletType.haven; } static const String _cryptoNumberPattern = '0.00000000'; @@ -217,9 +220,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo } if (wallet.type == WalletType.bitcoin) { - final primaryAddress = bitcoin!.getAddress(wallet); - final bitcoinAddresses = bitcoin!.getAddresses(wallet).map((addr) { - final isPrimary = addr == primaryAddress; + final receiveAddress = bitcoin!.getReceiveAddress(wallet); + final silentAddress = bitcoin!.getSilentAddress(wallet).toString(); + final bitcoinAddresses = [receiveAddress, silentAddress].map((addr) { + final isPrimary = addr == receiveAddress; return WalletAddressListItem(isPrimary: isPrimary, name: null, address: addr); }); @@ -252,7 +256,13 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo } @computed - bool get hasAddressList => wallet.type == WalletType.monero || wallet.type == WalletType.haven; + bool get hasSilentAddresses => wallet.type == WalletType.bitcoin; + + @computed + bool get hasAddressList => + wallet.type == WalletType.bitcoin || + wallet.type == WalletType.monero || + wallet.type == WalletType.haven; @computed bool get showElectrumAddressDisclaimer => diff --git a/lib/view_model/wallet_creation_vm.dart b/lib/view_model/wallet_creation_vm.dart index b3fc9f98f..2204d7955 100644 --- a/lib/view_model/wallet_creation_vm.dart +++ b/lib/view_model/wallet_creation_vm.dart @@ -51,7 +51,7 @@ abstract class WalletCreationVMBase with Store { bool typeExists(WalletType type) => walletCreationService.typeExists(type); - Future create({dynamic options, RestoredWallet? restoreWallet}) async { + Future create({dynamic options, RestoredWallet? restoreWallet, bool? isTestnet}) async { final type = restoreWallet?.type ?? this.type; try { state = IsExecutingState(); diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart index 42ca1d2fc..a81804227 100644 --- a/lib/view_model/wallet_new_vm.dart +++ b/lib/view_model/wallet_new_vm.dart @@ -1,6 +1,4 @@ -import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; -import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/monero/monero.dart'; @@ -25,6 +23,12 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { : selectedMnemonicLanguage = '', super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: false); + @observable + bool _useTestnet = false; + + @computed + bool get useTestnet => _useTestnet; + @observable String selectedMnemonicLanguage; @@ -46,13 +50,16 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { case WalletType.ethereum: return ethereum!.createEthereumNewWalletCredentials(name: name, password: walletPassword); default: - throw Exception('Unexpected type: ${type.toString()}');; + throw Exception('Unexpected type: ${type.toString()}'); } } @override Future process(WalletCredentials credentials) async { walletCreationService.changeWalletType(type: type); - return walletCreationService.create(credentials); + return walletCreationService.create(credentials, isTestnet: useTestnet); } + + @action + void toggleUseTestnet() => _useTestnet = !_useTestnet; } diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 77cfc4257..411aa201c 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -8,7 +8,6 @@ #include #include -#include #include void fl_register_plugins(FlPluginRegistry* registry) { @@ -18,9 +17,6 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) devicelocale_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "DevicelocalePlugin"); devicelocale_plugin_register_with_registrar(devicelocale_registrar); - g_autoptr(FlPluginRegistrar) platform_device_id_linux_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "PlatformDeviceIdLinuxPlugin"); - platform_device_id_linux_plugin_register_with_registrar(platform_device_id_linux_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index e432c44eb..1a3958652 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -5,7 +5,6 @@ list(APPEND FLUTTER_PLUGIN_LIST cw_monero devicelocale - platform_device_id_linux url_launcher_linux ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 46745f8e5..f8f725b27 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -12,8 +12,6 @@ import devicelocale import in_app_review import package_info_plus import path_provider_foundation -import platform_device_id -import platform_device_id_macos import share_plus_macos import shared_preferences_foundation import url_launcher_macos @@ -27,8 +25,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin")) FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) - PlatformDeviceIdMacosPlugin.register(with: registry.registrar(forPlugin: "PlatformDeviceIdMacosPlugin")) - PlatformDeviceIdMacosPlugin.register(with: registry.registrar(forPlugin: "PlatformDeviceIdMacosPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) diff --git a/pubspec_base.yaml b/pubspec_base.yaml index a196a8293..d535ca0ba 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -79,9 +79,7 @@ dependencies: url_launcher_android: 6.0.24 sensitive_clipboard: ^1.0.0 bitcoin_flutter: - git: - url: https://github.com/cake-tech/bitcoin_flutter.git - ref: cake-update-v3 + path: /home/rafael/Storage/Repositories/btc-silent-payments/bitcoin_flutter dev_dependencies: flutter_test: diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index b77d48034..ff8cc0435 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -697,6 +697,8 @@ "buy_provider_unavailable": "مزود حاليا غير متوفر.", "do_not_have_enough_gas_asset": "ليس لديك ما يكفي من ${currency} لإجراء معاملة وفقًا لشروط شبكة blockchain الحالية. أنت بحاجة إلى المزيد من ${currency} لدفع رسوم شبكة blockchain، حتى لو كنت ترسل أصلًا مختلفًا.", - "totp_auth_url": " TOTP ﺔﻗﺩﺎﺼﻤﻟ URL ﻥﺍﻮﻨﻋ" + "totp_auth_url": " TOTP ﺔﻗﺩﺎﺼﻤﻟ URL ﻥﺍﻮﻨﻋ", + "use_testnet": "استخدم testnet", + "address_and_silent_addresses": "العنوان والعناوين الصامتة", + "silent_addresses": "عناوين صامتة" } - diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 0186a66ed..8622c971d 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -692,5 +692,8 @@ "ask_each_time": "Питайте всеки път", "buy_provider_unavailable": "Понастоящем доставчик не е наличен.", "do_not_have_enough_gas_asset": "Нямате достатъчно ${currency}, за да извършите транзакция с текущите условия на блокчейн мрежата. Имате нужда от повече ${currency}, за да платите таксите за блокчейн мрежа, дори ако изпращате различен актив.", - "totp_auth_url": "TOTP AUTH URL" + "totp_auth_url": "TOTP AUTH URL", + "use_testnet": "Използвайте TestNet", + "address_and_silent_addresses": "Адрес и мълчаливи адреси", + "silent_addresses": "Безшумни адреси" } diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 13c16c878..7a67882bb 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -692,5 +692,8 @@ "ask_each_time": "Zeptejte se pokaždé", "buy_provider_unavailable": "Poskytovatel aktuálně nedostupný.", "do_not_have_enough_gas_asset": "Nemáte dostatek ${currency} k provedení transakce s aktuálními podmínkami blockchainové sítě. K placení poplatků za blockchainovou síť potřebujete více ${currency}, i když posíláte jiné aktivum.", - "totp_auth_url": "URL AUTH TOTP" + "totp_auth_url": "URL AUTH TOTP", + "use_testnet": "Použijte testNet", + "address_and_silent_addresses": "Adresa a tiché adresy", + "silent_addresses": "Tiché adresy" } diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 4bc3dc527..7607c1b73 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -700,5 +700,8 @@ "ask_each_time": "Jedes Mal fragen", "buy_provider_unavailable": "Anbieter derzeit nicht verfügbar.", "do_not_have_enough_gas_asset": "Sie verfügen nicht über genügend ${currency}, um eine Transaktion unter den aktuellen Bedingungen des Blockchain-Netzwerks durchzuführen. Sie benötigen mehr ${currency}, um die Gebühren für das Blockchain-Netzwerk zu bezahlen, auch wenn Sie einen anderen Vermögenswert senden.", - "totp_auth_url": "TOTP-Auth-URL" + "totp_auth_url": "TOTP-Auth-URL", + "use_testnet": "TESTNET verwenden", + "address_and_silent_addresses": "Adresse und stille Adressen", + "silent_addresses": "Stille Adressen" } diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 31b8152db..cc3c5c937 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -701,5 +701,8 @@ "robinhood_option_description": "Buy and transfer instantly using your debit card, bank account, or Robinhood balance. USA only.", "buy_provider_unavailable": "Provider currently unavailable.", "do_not_have_enough_gas_asset": "You do not have enough ${currency} to make a transaction with the current blockchain network conditions. You need more ${currency} to pay blockchain network fees, even if you are sending a different asset.", - "totp_auth_url": "TOTP AUTH URL" + "totp_auth_url": "TOTP AUTH URL", + "use_testnet": "Use testnet", + "address_and_silent_addresses": "Address and Silent Addresses", + "silent_addresses": "Silent Addresses" } diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 4d4ac2d80..fc34939bd 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -700,5 +700,8 @@ "ask_each_time": "Pregunta cada vez", "buy_provider_unavailable": "Proveedor actualmente no disponible.", "do_not_have_enough_gas_asset": "No tienes suficiente ${currency} para realizar una transacción con las condiciones actuales de la red blockchain. Necesita más ${currency} para pagar las tarifas de la red blockchain, incluso si envía un activo diferente.", - "totp_auth_url": "URL de autenticación TOTP" + "totp_auth_url": "URL de autenticación TOTP", + "use_testnet": "Use TestNet", + "address_and_silent_addresses": "Dirección y direcciones silenciosas", + "silent_addresses": "Direcciones silenciosas" } diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 32d31f706..5730956e9 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -700,5 +700,8 @@ "ask_each_time": "Demandez à chaque fois", "buy_provider_unavailable": "Fournisseur actuellement indisponible.", "do_not_have_enough_gas_asset": "Vous n'avez pas assez de ${currency} pour effectuer une transaction avec les conditions actuelles du réseau blockchain. Vous avez besoin de plus de ${currency} pour payer les frais du réseau blockchain, même si vous envoyez un actif différent.", - "totp_auth_url": "URL D'AUTORISATION TOTP" + "totp_auth_url": "URL D'AUTORISATION TOTP", + "use_testnet": "Utiliser TestNet", + "address_and_silent_addresses": "Adresse et adresses silencieuses", + "silent_addresses": "Adresses silencieuses" } diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index afdcaf66d..af55bd348 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -678,5 +678,8 @@ "ask_each_time": "Tambaya kowane lokaci", "buy_provider_unavailable": "Mai ba da kyauta a halin yanzu babu.", "do_not_have_enough_gas_asset": "Ba ku da isassun ${currency} don yin ma'amala tare da yanayin cibiyar sadarwar blockchain na yanzu. Kuna buƙatar ƙarin ${currency} don biyan kuɗaɗen cibiyar sadarwar blockchain, koda kuwa kuna aika wata kadara daban.", - "totp_auth_url": "TOTP AUTH URL" + "totp_auth_url": "TOTP AUTH URL", + "use_testnet": "Amfani da gwaji", + "address_and_silent_addresses": "Adireshin da adreshin shiru", + "silent_addresses": "Adireshin Shiru" } diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index fd0c0c1f5..6d393ff62 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -700,5 +700,8 @@ "ask_each_time": "हर बार पूछें", "buy_provider_unavailable": "वर्तमान में प्रदाता अनुपलब्ध है।", "do_not_have_enough_gas_asset": "वर्तमान ब्लॉकचेन नेटवर्क स्थितियों में लेनदेन करने के लिए आपके पास पर्याप्त ${currency} नहीं है। ब्लॉकचेन नेटवर्क शुल्क का भुगतान करने के लिए आपको अधिक ${currency} की आवश्यकता है, भले ही आप एक अलग संपत्ति भेज रहे हों।", - "totp_auth_url": "TOTP प्रामाणिक यूआरएल" + "totp_auth_url": "TOTP प्रामाणिक यूआरएल", + "use_testnet": "टेस्टनेट का उपयोग करें", + "address_and_silent_addresses": "पता और मूक पते", + "silent_addresses": "मूक पते" } diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 2d337f434..e112f480f 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -700,5 +700,8 @@ "ask_each_time": "Pitajte svaki put", "buy_provider_unavailable": "Davatelj trenutno nije dostupan.", "do_not_have_enough_gas_asset": "Nemate dovoljno ${currency} da izvršite transakciju s trenutačnim uvjetima blockchain mreže. Trebate više ${currency} da platite naknade za blockchain mrežu, čak i ako šaljete drugu imovinu.", - "totp_auth_url": "TOTP AUTH URL" -} \ No newline at end of file + "totp_auth_url": "TOTP AUTH URL", + "use_testnet": "Koristite TestNet", + "address_and_silent_addresses": "Adresa i tihe adrese", + "silent_addresses": "Tihe adrese" +} diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index cc23a004c..f39be9a1a 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -688,5 +688,8 @@ "ask_each_time": "Tanyakan setiap kali", "buy_provider_unavailable": "Penyedia saat ini tidak tersedia.", "do_not_have_enough_gas_asset": "Anda tidak memiliki cukup ${currency} untuk melakukan transaksi dengan kondisi jaringan blockchain saat ini. Anda memerlukan lebih banyak ${currency} untuk membayar biaya jaringan blockchain, meskipun Anda mengirimkan aset yang berbeda.", - "totp_auth_url": "URL Otentikasi TOTP" -} \ No newline at end of file + "totp_auth_url": "URL Otentikasi TOTP", + "use_testnet": "Gunakan TestNet", + "address_and_silent_addresses": "Alamat dan alamat diam", + "silent_addresses": "Alamat diam" +} diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 0435a6e70..c5eb4b35f 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -700,5 +700,8 @@ "ask_each_time": "Chiedi ogni volta", "buy_provider_unavailable": "Provider attualmente non disponibile.", "do_not_have_enough_gas_asset": "Non hai abbastanza ${currency} per effettuare una transazione con le attuali condizioni della rete blockchain. Hai bisogno di più ${currency} per pagare le commissioni della rete blockchain, anche se stai inviando una risorsa diversa.", - "totp_auth_url": "URL DI AUT. TOTP" + "totp_auth_url": "URL DI AUT. TOTP", + "use_testnet": "Usa TestNet", + "address_and_silent_addresses": "Indirizzo e indirizzi silenziosi", + "silent_addresses": "Indirizzi silenziosi" } diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 7ad6ddf6c..7c6c5c804 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -700,5 +700,8 @@ "ask_each_time": "毎回尋ねてください", "buy_provider_unavailable": "現在、プロバイダーは利用できません。", "do_not_have_enough_gas_asset": "現在のブロックチェーン ネットワークの状況では、トランザクションを行うのに十分な ${currency} がありません。別のアセットを送信する場合でも、ブロックチェーン ネットワーク料金を支払うにはさらに ${currency} が必要です。", - "totp_auth_url": "TOTP認証URL" + "totp_auth_url": "TOTP認証URL", + "use_testnet": "TestNetを使用します", + "address_and_silent_addresses": "住所とサイレントアドレス", + "silent_addresses": "サイレントアドレス" } diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index b3d28e578..bd5e8c1e5 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -700,5 +700,8 @@ "ask_each_time": "매번 물어보십시오", "buy_provider_unavailable": "제공자는 현재 사용할 수 없습니다.", "do_not_have_enough_gas_asset": "현재 블록체인 네트워크 조건으로 거래를 하기에는 ${currency}이(가) 충분하지 않습니다. 다른 자산을 보내더라도 블록체인 네트워크 수수료를 지불하려면 ${currency}가 더 필요합니다.", - "totp_auth_url": "TOTP 인증 URL" + "totp_auth_url": "TOTP 인증 URL", + "use_testnet": "TestNet을 사용하십시오", + "address_and_silent_addresses": "주소 및 조용한 주소", + "silent_addresses": "조용한 주소" } diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index e2cd80c04..86888c6f0 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -698,5 +698,8 @@ "ask_each_time": "တစ်ခုချင်းစီကိုအချိန်မေးပါ", "buy_provider_unavailable": "လက်ရှိတွင်လက်ရှိမရနိုင်ပါ။", "do_not_have_enough_gas_asset": "လက်ရှိ blockchain ကွန်ရက်အခြေအနေများနှင့် အရောင်းအဝယ်ပြုလုပ်ရန် သင့်တွင် ${currency} လုံလောက်မှုမရှိပါ။ သင်သည် မတူညီသော ပိုင်ဆိုင်မှုတစ်ခုကို ပေးပို့နေသော်လည်း blockchain ကွန်ရက်အခကြေးငွေကို ပေးဆောင်ရန် သင်သည် နောက်ထပ် ${currency} လိုအပ်ပါသည်။", - "totp_auth_url": "TOTP AUTH URL" + "totp_auth_url": "TOTP AUTH URL", + "use_testnet": "testnet ကိုသုံးပါ", + "address_and_silent_addresses": "လိပ်စာနှင့်အသံတိတ်လိပ်စာများ", + "silent_addresses": "အသံတိတ်လိပ်စာများ" } diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 659d2b60c..cb6fa1513 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -700,5 +700,8 @@ "ask_each_time": "Vraag het elke keer", "buy_provider_unavailable": "Provider momenteel niet beschikbaar.", "do_not_have_enough_gas_asset": "U heeft niet genoeg ${currency} om een transactie uit te voeren met de huidige blockchain-netwerkomstandigheden. U heeft meer ${currency} nodig om blockchain-netwerkkosten te betalen, zelfs als u een ander item verzendt.", - "totp_auth_url": "TOTP AUTH-URL" + "totp_auth_url": "TOTP AUTH-URL", + "use_testnet": "Gebruik testnet", + "address_and_silent_addresses": "Adres en stille adressen", + "silent_addresses": "Stille adressen" } diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 663a3784d..a00fb3965 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -700,5 +700,8 @@ "ask_each_time": "Zapytaj za każdym razem", "buy_provider_unavailable": "Dostawca obecnie niedostępny.", "do_not_have_enough_gas_asset": "Nie masz wystarczającej ilości ${currency}, aby dokonać transakcji przy bieżących warunkach sieci blockchain. Potrzebujesz więcej ${currency}, aby uiścić opłaty za sieć blockchain, nawet jeśli wysyłasz inny zasób.", - "totp_auth_url": "Adres URL TOTP AUTH" + "totp_auth_url": "Adres URL TOTP AUTH", + "use_testnet": "Użyj testne", + "address_and_silent_addresses": "Adres i ciche adresy", + "silent_addresses": "Ciche adresy" } diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index a1e663d1d..819989c4d 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -699,5 +699,8 @@ "ask_each_time": "Pergunte cada vez", "buy_provider_unavailable": "Provedor atualmente indisponível.", "do_not_have_enough_gas_asset": "Você não tem ${currency} suficiente para fazer uma transação com as condições atuais da rede blockchain. Você precisa de mais ${currency} para pagar as taxas da rede blockchain, mesmo se estiver enviando um ativo diferente.", - "totp_auth_url": "URL de autenticação TOTP" + "totp_auth_url": "URL de autenticação TOTP", + "use_testnet": "Use testNet", + "address_and_silent_addresses": "Endereço e endereços silenciosos", + "silent_addresses": "Endereços silenciosos" } diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 03f55813b..c0307f365 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -700,5 +700,8 @@ "ask_each_time": "Спросите каждый раз", "buy_provider_unavailable": "Поставщик в настоящее время недоступен.", "do_not_have_enough_gas_asset": "У вас недостаточно ${currency} для совершения транзакции при текущих условиях сети блокчейн. Вам нужно больше ${currency} для оплаты комиссий за сеть блокчейна, даже если вы отправляете другой актив.", - "totp_auth_url": "URL-адрес TOTP-АВТОРИЗАЦИИ" + "totp_auth_url": "URL-адрес TOTP-АВТОРИЗАЦИИ", + "use_testnet": "Используйте Testnet", + "address_and_silent_addresses": "Адрес и молчаливые адреса", + "silent_addresses": "Молчаливые адреса" } diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 6db49b142..bbfd1a79b 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -698,5 +698,8 @@ "ask_each_time": "ถามทุกครั้ง", "buy_provider_unavailable": "ผู้ให้บริการไม่สามารถใช้งานได้ในปัจจุบัน", "do_not_have_enough_gas_asset": "คุณมี ${currency} ไม่เพียงพอที่จะทำธุรกรรมกับเงื่อนไขเครือข่ายบล็อคเชนในปัจจุบัน คุณต้องมี ${currency} เพิ่มขึ้นเพื่อชำระค่าธรรมเนียมเครือข่ายบล็อคเชน แม้ว่าคุณจะส่งสินทรัพย์อื่นก็ตาม", - "totp_auth_url": "URL การตรวจสอบสิทธิ์ TOTP" + "totp_auth_url": "URL การตรวจสอบสิทธิ์ TOTP", + "use_testnet": "ใช้ testnet", + "address_and_silent_addresses": "ที่อยู่และที่อยู่เงียบ", + "silent_addresses": "ที่อยู่เงียบ" } diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 4b607f4e2..b249a7ccb 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -698,5 +698,8 @@ "ask_each_time": "Her seferinde sor", "buy_provider_unavailable": "Sağlayıcı şu anda kullanılamıyor.", "do_not_have_enough_gas_asset": "Mevcut blockchain ağ koşullarıyla işlem yapmak için yeterli ${currency} paranız yok. Farklı bir varlık gönderiyor olsanız bile blockchain ağ ücretlerini ödemek için daha fazla ${currency} miktarına ihtiyacınız var.", - "totp_auth_url": "TOTP YETKİ URL'si" + "totp_auth_url": "TOTP YETKİ URL'si", + "use_testnet": "TestNet kullanın", + "address_and_silent_addresses": "Adres ve sessiz adresler", + "silent_addresses": "Sessiz adresler" } diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index ee8ac0a2c..3e61da549 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -700,5 +700,8 @@ "ask_each_time": "Запитайте кожен раз", "buy_provider_unavailable": "В даний час постачальник недоступний.", "do_not_have_enough_gas_asset": "У вас недостатньо ${currency}, щоб здійснити трансакцію з поточними умовами мережі блокчейн. Вам потрібно більше ${currency}, щоб сплатити комісію мережі блокчейн, навіть якщо ви надсилаєте інший актив.", - "totp_auth_url": "TOTP AUTH URL" + "totp_auth_url": "TOTP AUTH URL", + "use_testnet": "Використовуйте тестову мережу", + "address_and_silent_addresses": "Адреса та мовчазні адреси", + "silent_addresses": "Мовчазні адреси" } diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index bf4e6c7b4..08287b3c8 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -692,5 +692,8 @@ "ask_each_time": "ہر بار پوچھیں", "buy_provider_unavailable": "فراہم کنندہ فی الحال دستیاب نہیں ہے۔", "do_not_have_enough_gas_asset": "آپ کے پاس موجودہ بلاکچین نیٹ ورک کی شرائط کے ساتھ لین دین کرنے کے لیے کافی ${currency} نہیں ہے۔ آپ کو بلاکچین نیٹ ورک کی فیس ادا کرنے کے لیے مزید ${currency} کی ضرورت ہے، چاہے آپ کوئی مختلف اثاثہ بھیج رہے ہوں۔", - "totp_auth_url": "TOTP AUTH URL" + "totp_auth_url": "TOTP AUTH URL", + "use_testnet": "ٹیسٹ نیٹ استعمال کریں", + "address_and_silent_addresses": "پتہ اور خاموش پتے", + "silent_addresses": "خاموش پتے" } diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index c1e3ddec1..e8ee5e45e 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -694,5 +694,8 @@ "ask_each_time": "Beere lọwọ kọọkan", "buy_provider_unavailable": "Olupese lọwọlọwọ ko si.", "do_not_have_enough_gas_asset": "O ko ni to ${currency} lati ṣe idunadura kan pẹlu awọn ipo nẹtiwọki blockchain lọwọlọwọ. O nilo diẹ sii ${currency} lati san awọn owo nẹtiwọọki blockchain, paapaa ti o ba nfi dukia miiran ranṣẹ.", - "totp_auth_url": "TOTP AUTH URL" + "totp_auth_url": "TOTP AUTH URL", + "use_testnet": "Lo tele", + "address_and_silent_addresses": "Adirẹsi ati awọn adirẹsi ipalọlọ", + "silent_addresses": "Awọn adirẹsi ipalọlọ" } diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 0dc833162..e45a1419b 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -699,5 +699,8 @@ "ask_each_time": "每次问", "buy_provider_unavailable": "提供者目前不可用。", "do_not_have_enough_gas_asset": "您没有足够的 ${currency} 来在当前的区块链网络条件下进行交易。即使您发送的是不同的资产,您也需要更多的 ${currency} 来支付区块链网络费用。", - "totp_auth_url": "TOTP 授权 URL" + "totp_auth_url": "TOTP 授权 URL", + "use_testnet": "使用TestNet", + "address_and_silent_addresses": "地址和无声地址", + "silent_addresses": "无声地址" } diff --git a/tool/configure.dart b/tool/configure.dart index d7730f463..ee28c42a4 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -49,6 +49,7 @@ import 'package:cw_bitcoin/bitcoin_amount_format.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; import 'package:cw_bitcoin/litecoin_wallet_service.dart'; +import 'package:bitcoin_flutter/bitcoin_flutter.dart' as btc; """; const bitcoinCwPart = "part 'cw_bitcoin.dart';"; const bitcoinContent = """ @@ -71,6 +72,8 @@ abstract class Bitcoin { List getAddresses(Object wallet); String getAddress(Object wallet); + String getReceiveAddress(Object wallet); + btc.SilentPaymentAddress? getSilentAddress(Object wallet); String formatterBitcoinAmountToString({required int amount}); double formatterBitcoinAmountToDouble({required int amount}); @@ -729,4 +732,4 @@ class FakeSecureStorage extends SecureStorage { } await outputFile.writeAsString(output); -} \ No newline at end of file +}