Btc address types (#1263)

* inital migration changes * feat: rest of changes * minor fix [skip ci] * fix: P2wshAddress & wallet address index * fix: address review comments * fix: address type restore * feat: add testnet * Fix review comments Remove bitcoin_base from cw_core * Fix address not matching selected type on start * remove un-necessary parameter [skip ci] * Remove bitcoin specific code from main lib Fix possible runtime exception from list wrong access * Minor fix * fix: fixes for Testnet * fix: bitcoin receive option dependency breaks monerocom * Fix issues when building Monero.com * feat: Transaction Builder changes * fix: discover addresses, testnet restoring, duplicate unspent coins, and taproot address vs schnorr sig tweak * fix: remove print * feat: improve error when failed broadcast response * feat: create fish shell env script * fix: unmodifiable maps * fix: build * fix: build * fix: computed observable side effect bug * feat: add nix script for android build_all * fix: wrong keypairs used for signing * fix: wrong addresses when using fromScriptPubKey scripts * fix(actual commit): testnet tx expanded + wrong addresses when using fromScriptPubKey scripts (update bitcoin_base deps) * fix: self-send [skip ci] * fix: p2wsh * fix: testnet fees * New versions * Update macos build number Minor UI fix * fix: use new bitcoin_base ref, fix tx list wrong hex value & refactor hidden vs hd use - if always use sideHd for isHidden, it is easier to simplify the functions instead of passing both which can be error prone - (ps: now this could probably be changed, for example from isHidden to isChange since with address list we now see "hidden" addresses) * Fix if condition to handle litecoin case * fix: self-send, change address was always making direction incoming * refactor: improve estimation function, add more inputs if balance missing * fix: new bitcoin_base update, fixes script issues * Update evm chain wallet service arguments * Fix translation [skip ci] * Fix translation [skip ci] * Update strings_fr.arb [skip ci] * fix: async isChange function not being awaited, refactor to reduce looping into a single place * fix: _address vs address, missing p2sh * fix: minor mistake in storing p2sh page type [skip ci] * refactor: use already matched addresses property * feat: improved perfomance for fetching transaction histories * feat: continue perfomance change, improve address discovery only to last address by type with history * fix: make sure transaction list is sorted by date * refactor: isTestnet only for bitcoin * fix: walletInfo type null case * fix: deprecated p2pk * refactor: make condition more readable * refactor: remove unnecessary Str variant * refactor: make condition more readable * fix: infinite loop possible * Revert removing isTestnet from other wallets [skip ci] * refactor: rename addresses when matched by receive type * Make the beta build [skip ci] Remove app_env.fish --------- Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>

Rafael committed Feb 23, 2024 at 13:13 UTC a3a35f05e1d7347b1937caa08895833430c57fa5
91 files changed +1848 -1330
assets/text/Release_Notes.txt
+3 -5
@@ -1,5 +1,3 @@
1 -Bitcoin transactions fixes and enhancements
2 -EVM wallets enhancements (Ethereum and Polygon)
3 -Improve wallet recovery and error tolerance
4 -Enhance Background sync for Monero wallets
5 -Bug fixes
\ No newline at end of file
1 +Support ALL Bitcoin address types (Legacy, Segwit (both variants), Taproot)
2 +Enhance Sending/Receiving flow for Bitcoin
3 +Improve fee calculations in Bitcoin
\ No newline at end of file
cw_bitcoin/lib/address_from_output.dart
+18 -18
@@ -1,23 +1,23 @@
1 -import 'dart:typed_data';
2 -import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
3 -import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
1 +import 'package:bitcoin_base/bitcoin_base.dart';
2
5 -String addressFromOutput(Uint8List script, bitcoin.NetworkType networkType) {
3 +String addressFromOutputScript(Script script, BasedUtxoNetwork network) {
4 try {
7 - return bitcoin.P2PKH(
8 - data: PaymentData(output: script),
9 - network: networkType)
10 - .data
11 - .address!;
5 + switch (script.getAddressType()) {
6 + case P2pkhAddressType.p2pkh:
7 + return P2pkhAddress.fromScriptPubkey(script: script).toAddress(network);
8 + case P2shAddressType.p2pkInP2sh:
9 + return P2shAddress.fromScriptPubkey(script: script).toAddress(network);
10 + case SegwitAddresType.p2wpkh:
11 + return P2wpkhAddress.fromScriptPubkey(script: script).toAddress(network);
12 + case P2shAddressType.p2pkhInP2sh:
13 + return P2shAddress.fromScriptPubkey(script: script).toAddress(network);
14 + case SegwitAddresType.p2wsh:
15 + return P2wshAddress.fromScriptPubkey(script: script).toAddress(network);
16 + case SegwitAddresType.p2tr:
17 + return P2trAddress.fromScriptPubkey(script: script).toAddress(network);
18 + default:
19 + }
20 } catch (_) {}
21
14 - try {
15 - return bitcoin.P2WPKH(
16 - data: PaymentData(output: script),
17 - network: networkType)
18 - .data
19 - .address!;
20 - } catch(_) {}
21 -
22 return '';
23 -}
\ No newline at end of file
23 +}
cw_bitcoin/lib/address_to_output_script.dart
+3 -21
@@ -1,27 +1,9 @@
1 import 'dart:typed_data';
2 -import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
3 -import 'package:bs58check/bs58check.dart' as bs58check;
4 -import 'package:bitcoin_flutter/src/utils/constants/op.dart';
5 -import 'package:bitcoin_flutter/src/utils/script.dart' as bscript;
6 -import 'package:bitcoin_flutter/src/address.dart';
2 +import 'package:bitcoin_base/bitcoin_base.dart' as bitcoin;
3
8 -Uint8List p2shAddressToOutputScript(String address) {
9 - final decodeBase58 = bs58check.decode(address);
10 - final hash = decodeBase58.sublist(1);
11 - return bscript.compile(<dynamic>[OPS['OP_HASH160'], hash, OPS['OP_EQUAL']]);
12 -}
13 -
14 -Uint8List addressToOutputScript(
15 - String address, bitcoin.NetworkType networkType) {
4 +List<int> addressToOutputScript(String address, bitcoin.BasedUtxoNetwork network) {
5 try {
17 - // FIXME: improve validation for p2sh addresses
18 - // 3 for bitcoin
19 - // m for litecoin
20 - if (address.startsWith('3') || address.toLowerCase().startsWith('m')) {
21 - return p2shAddressToOutputScript(address);
22 - }
23 -
24 - return Address.addressToOutputScript(address, networkType);
6 + return bitcoin.addressToOutputScript(address: address, network: network);
7 } catch (err) {
8 print(err);
9 return Uint8List(0);
cw_bitcoin/lib/bitcoin_address_record.dart
+43 -13
@@ -1,6 +1,9 @@
1 import 'dart:convert';
2 import 'package:bitbox/bitbox.dart' as bitbox;
3
4 +import 'package:bitcoin_base/bitcoin_base.dart';
5 +import 'package:cw_bitcoin/script_hash.dart' as sh;
6 +
7 class BitcoinAddressRecord {
8 BitcoinAddressRecord(
9 this.address, {
@@ -10,23 +13,41 @@ class BitcoinAddressRecord {
13 int balance = 0,
14 String name = '',
15 bool isUsed = false,
16 + required this.type,
17 + String? scriptHash,
18 + required this.network,
19 }) : _txCount = txCount,
20 _balance = balance,
21 _name = name,
16 - _isUsed = isUsed;
22 + _isUsed = isUsed,
23 + scriptHash =
24 + scriptHash ?? (network != null ? sh.scriptHash(address, network: network) : null);
25
18 - factory BitcoinAddressRecord.fromJSON(String jsonSource) {
26 + factory BitcoinAddressRecord.fromJSON(String jsonSource, BasedUtxoNetwork? network) {
27 final decoded = json.decode(jsonSource) as Map;
28
21 - return BitcoinAddressRecord(decoded['address'] as String,
22 - index: decoded['index'] as int,
23 - isHidden: decoded['isHidden'] as bool? ?? false,
24 - isUsed: decoded['isUsed'] as bool? ?? false,
25 - txCount: decoded['txCount'] as int? ?? 0,
26 - name: decoded['name'] as String? ?? '',
27 - balance: decoded['balance'] as int? ?? 0);
29 + return BitcoinAddressRecord(
30 + decoded['address'] as String,
31 + index: decoded['index'] as int,
32 + isHidden: decoded['isHidden'] as bool? ?? false,
33 + isUsed: decoded['isUsed'] as bool? ?? false,
34 + txCount: decoded['txCount'] as int? ?? 0,
35 + name: decoded['name'] as String? ?? '',
36 + balance: decoded['balance'] as int? ?? 0,
37 + type: decoded['type'] != null && decoded['type'] != ''
38 + ? BitcoinAddressType.values
39 + .firstWhere((type) => type.toString() == decoded['type'] as String)
40 + : SegwitAddresType.p2wpkh,
41 + scriptHash: decoded['scriptHash'] as String?,
42 + network: (decoded['network'] as String?) == null
43 + ? network
44 + : BasedUtxoNetwork.fromName(decoded['network'] as String),
45 + );
46 }
47
48 + @override
49 + bool operator ==(Object o) => o is BitcoinAddressRecord && address == o.address;
50 +
51 final String address;
52 bool isHidden;
53 final int index;
@@ -34,6 +55,8 @@ class BitcoinAddressRecord {
55 int _balance;
56 String _name;
57 bool _isUsed;
58 + String? scriptHash;
59 + BasedUtxoNetwork? network;
60
61 int get txCount => _txCount;
62
@@ -50,21 +73,28 @@ class BitcoinAddressRecord {
73 void setAsUsed() => _isUsed = true;
74 void setNewName(String label) => _name = label;
75
53 - @override
54 - bool operator ==(Object o) => o is BitcoinAddressRecord && address == o.address;
55 -
76 @override
77 int get hashCode => address.hashCode;
78
79 String get cashAddr => bitbox.Address.toCashAddress(address);
80
81 + BitcoinAddressType type;
82 +
83 + String updateScriptHash(BasedUtxoNetwork network) {
84 + scriptHash = sh.scriptHash(address, network: network);
85 + return scriptHash!;
86 + }
87 +
88 String toJSON() => json.encode({
89 'address': address,
90 'index': index,
91 'isHidden': isHidden,
92 + 'isUsed': isUsed,
93 'txCount': txCount,
94 'name': name,
67 - 'isUsed': isUsed,
95 'balance': balance,
96 + 'type': type.toString(),
97 + 'scriptHash': scriptHash,
98 + 'network': network?.value,
99 });
100 }
cw_bitcoin/lib/bitcoin_receive_page_option.dart new
+42
@@ -0,0 +1,42 @@
1 +import 'package:bitcoin_base/bitcoin_base.dart';
2 +import 'package:cw_core/receive_page_option.dart';
3 +
4 +class BitcoinReceivePageOption implements ReceivePageOption {
5 + static const p2wpkh = BitcoinReceivePageOption._('Segwit (P2WPKH)');
6 + static const p2sh = BitcoinReceivePageOption._('Segwit-Compatible (P2SH)');
7 + static const p2tr = BitcoinReceivePageOption._('Taproot (P2TR)');
8 + static const p2wsh = BitcoinReceivePageOption._('Segwit (P2WSH)');
9 + static const p2pkh = BitcoinReceivePageOption._('Legacy (P2PKH)');
10 +
11 + const BitcoinReceivePageOption._(this.value);
12 +
13 + final String value;
14 +
15 + String toString() {
16 + return value;
17 + }
18 +
19 + static const all = [
20 + BitcoinReceivePageOption.p2wpkh,
21 + BitcoinReceivePageOption.p2sh,
22 + BitcoinReceivePageOption.p2tr,
23 + BitcoinReceivePageOption.p2wsh,
24 + BitcoinReceivePageOption.p2pkh
25 + ];
26 +
27 + factory BitcoinReceivePageOption.fromType(BitcoinAddressType type) {
28 + switch (type) {
29 + case SegwitAddresType.p2tr:
30 + return BitcoinReceivePageOption.p2tr;
31 + case SegwitAddresType.p2wsh:
32 + return BitcoinReceivePageOption.p2wsh;
33 + case P2pkhAddressType.p2pkh:
34 + return BitcoinReceivePageOption.p2pkh;
35 + case P2shAddressType.p2wpkhInP2sh:
36 + return BitcoinReceivePageOption.p2sh;
37 + case SegwitAddresType.p2wpkh:
38 + default:
39 + return BitcoinReceivePageOption.p2wpkh;
40 + }
41 + }
42 +}
cw_bitcoin/lib/bitcoin_unspent.dart
+3 -4
@@ -6,10 +6,9 @@ class BitcoinUnspent extends Unspent {
6 : bitcoinAddressRecord = addressRecord,
7 super(addressRecord.address, hash, value, vout, null);
8
9 - factory BitcoinUnspent.fromJSON(
10 - BitcoinAddressRecord address, Map<String, dynamic> json) =>
11 - BitcoinUnspent(address, json['tx_hash'] as String, json['value'] as int,
12 - json['tx_pos'] as int);
9 + factory BitcoinUnspent.fromJSON(BitcoinAddressRecord address, Map<String, dynamic> json) =>
10 + BitcoinUnspent(
11 + address, json['tx_hash'] as String, json['value'] as int, json['tx_pos'] as int);
12
13 final BitcoinAddressRecord bitcoinAddressRecord;
14 }
cw_bitcoin/lib/bitcoin_wallet.dart
+60 -43
@@ -1,3 +1,4 @@
1 +import 'package:bitcoin_base/bitcoin_base.dart';
2 import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
3 import 'package:cw_core/crypto_currency.dart';
4 import 'package:cw_core/unspent_coins_info.dart';
@@ -17,36 +18,42 @@ part 'bitcoin_wallet.g.dart';
18 class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet;
19
20 abstract class BitcoinWalletBase extends ElectrumWallet with Store {
20 - BitcoinWalletBase(
21 - {required String mnemonic,
22 - required String password,
23 - required WalletInfo walletInfo,
24 - required Box<UnspentCoinsInfo> unspentCoinsInfo,
25 - required Uint8List seedBytes,
26 - List<BitcoinAddressRecord>? initialAddresses,
27 - ElectrumBalance? initialBalance,
28 - int initialRegularAddressIndex = 0,
29 - int initialChangeAddressIndex = 0})
30 - : super(
21 + BitcoinWalletBase({
22 + required String mnemonic,
23 + required String password,
24 + required WalletInfo walletInfo,
25 + required Box<UnspentCoinsInfo> unspentCoinsInfo,
26 + required Uint8List seedBytes,
27 + String? addressPageType,
28 + BasedUtxoNetwork? networkParam,
29 + List<BitcoinAddressRecord>? initialAddresses,
30 + ElectrumBalance? initialBalance,
31 + Map<String, int>? initialRegularAddressIndex,
32 + Map<String, int>? initialChangeAddressIndex,
33 + }) : super(
34 mnemonic: mnemonic,
35 password: password,
36 walletInfo: walletInfo,
37 unspentCoinsInfo: unspentCoinsInfo,
35 - networkType: bitcoin.bitcoin,
38 + networkType: networkParam == null
39 + ? bitcoin.bitcoin
40 + : networkParam == BitcoinNetwork.mainnet
41 + ? bitcoin.bitcoin
42 + : bitcoin.testnet,
43 initialAddresses: initialAddresses,
44 initialBalance: initialBalance,
45 seedBytes: seedBytes,
46 currency: CryptoCurrency.btc) {
47 walletAddresses = BitcoinWalletAddresses(
41 - walletInfo,
42 - electrumClient: electrumClient,
43 - initialAddresses: initialAddresses,
44 - initialRegularAddressIndex: initialRegularAddressIndex,
45 - initialChangeAddressIndex: initialChangeAddressIndex,
46 - mainHd: hd,
47 - sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType)
48 - .derivePath("m/0'/1"),
49 - networkType: networkType);
48 + walletInfo,
49 + electrumClient: electrumClient,
50 + initialAddresses: initialAddresses,
51 + initialRegularAddressIndex: initialRegularAddressIndex,
52 + initialChangeAddressIndex: initialChangeAddressIndex,
53 + mainHd: hd,
54 + sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/1"),
55 + network: networkParam ?? network,
56 + );
57 autorun((_) {
58 this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
59 });
@@ -57,21 +64,26 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
64 required String password,
65 required WalletInfo walletInfo,
66 required Box<UnspentCoinsInfo> unspentCoinsInfo,
67 + String? addressPageType,
68 + BasedUtxoNetwork? network,
69 List<BitcoinAddressRecord>? initialAddresses,
70 ElectrumBalance? initialBalance,
62 - int initialRegularAddressIndex = 0,
63 - int initialChangeAddressIndex = 0
71 + Map<String, int>? initialRegularAddressIndex,
72 + Map<String, int>? initialChangeAddressIndex,
73 }) async {
74 return BitcoinWallet(
66 - mnemonic: mnemonic,
67 - password: password,
68 - walletInfo: walletInfo,
69 - unspentCoinsInfo: unspentCoinsInfo,
70 - initialAddresses: initialAddresses,
71 - initialBalance: initialBalance,
72 - seedBytes: await mnemonicToSeedBytes(mnemonic),
73 - initialRegularAddressIndex: initialRegularAddressIndex,
74 - initialChangeAddressIndex: initialChangeAddressIndex);
75 + mnemonic: mnemonic,
76 + password: password,
77 + walletInfo: walletInfo,
78 + unspentCoinsInfo: unspentCoinsInfo,
79 + initialAddresses: initialAddresses,
80 + initialBalance: initialBalance,
81 + seedBytes: await mnemonicToSeedBytes(mnemonic),
82 + initialRegularAddressIndex: initialRegularAddressIndex,
83 + initialChangeAddressIndex: initialChangeAddressIndex,
84 + addressPageType: addressPageType,
85 + networkParam: network,
86 + );
87 }
88
89 static Future<BitcoinWallet> open({
@@ -80,16 +92,21 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
92 required Box<UnspentCoinsInfo> unspentCoinsInfo,
93 required String password,
94 }) async {
83 - final snp = await ElectrumWallletSnapshot.load(name, walletInfo.type, password);
95 + final snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password,
96 + walletInfo.network != null ? BasedUtxoNetwork.fromName(walletInfo.network!) : null);
97 +
98 return BitcoinWallet(
85 - mnemonic: snp.mnemonic,
86 - password: password,
87 - walletInfo: walletInfo,
88 - unspentCoinsInfo: unspentCoinsInfo,
89 - initialAddresses: snp.addresses,
90 - initialBalance: snp.balance,
91 - seedBytes: await mnemonicToSeedBytes(snp.mnemonic),
92 - initialRegularAddressIndex: snp.regularAddressIndex,
93 - initialChangeAddressIndex: snp.changeAddressIndex);
99 + mnemonic: snp.mnemonic,
100 + password: password,
101 + walletInfo: walletInfo,
102 + unspentCoinsInfo: unspentCoinsInfo,
103 + initialAddresses: snp.addresses,
104 + initialBalance: snp.balance,
105 + seedBytes: await mnemonicToSeedBytes(snp.mnemonic),
106 + initialRegularAddressIndex: snp.regularAddressIndex,
107 + initialChangeAddressIndex: snp.changeAddressIndex,
108 + addressPageType: snp.addressPageType,
109 + networkParam: snp.network,
110 + );
111 }
95 -}
\ No newline at end of file
112 +}
cw_bitcoin/lib/bitcoin_wallet_addresses.dart
+27 -21
@@ -1,6 +1,5 @@
1 -import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
2 -import 'package:cw_bitcoin/bitcoin_address_record.dart';
3 -import 'package:cw_bitcoin/electrum.dart';
1 +import 'package:bitcoin_base/bitcoin_base.dart';
2 +import 'package:bitcoin_flutter/bitcoin_flutter.dart';
3 import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
4 import 'package:cw_bitcoin/utils.dart';
5 import 'package:cw_core/wallet_info.dart';
@@ -11,24 +10,31 @@ part 'bitcoin_wallet_addresses.g.dart';
10 class BitcoinWalletAddresses = BitcoinWalletAddressesBase with _$BitcoinWalletAddresses;
11
12 abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
14 - BitcoinWalletAddressesBase(WalletInfo walletInfo,
15 - {required bitcoin.HDWallet mainHd,
16 - required bitcoin.HDWallet sideHd,
17 - required bitcoin.NetworkType networkType,
18 - required ElectrumClient electrumClient,
19 - List<BitcoinAddressRecord>? initialAddresses,
20 - int initialRegularAddressIndex = 0,
21 - int initialChangeAddressIndex = 0})
22 - : super(walletInfo,
23 - initialAddresses: initialAddresses,
24 - initialRegularAddressIndex: initialRegularAddressIndex,
25 - initialChangeAddressIndex: initialChangeAddressIndex,
26 - mainHd: mainHd,
27 - sideHd: sideHd,
28 - electrumClient: electrumClient,
29 - networkType: networkType);
13 + BitcoinWalletAddressesBase(
14 + WalletInfo walletInfo, {
15 + required super.mainHd,
16 + required super.sideHd,
17 + required super.network,
18 + required super.electrumClient,
19 + super.initialAddresses,
20 + super.initialRegularAddressIndex,
21 + super.initialChangeAddressIndex,
22 + }) : super(walletInfo);
23
24 @override
32 - String getAddress({required int index, required bitcoin.HDWallet hd}) =>
33 - generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
25 + String getAddress({required int index, required HDWallet hd, BitcoinAddressType? addressType}) {
26 + if (addressType == P2pkhAddressType.p2pkh)
27 + return generateP2PKHAddress(hd: hd, index: index, network: network);
28 +
29 + if (addressType == SegwitAddresType.p2tr)
30 + return generateP2TRAddress(hd: hd, index: index, network: network);
31 +
32 + if (addressType == SegwitAddresType.p2wsh)
33 + return generateP2WSHAddress(hd: hd, index: index, network: network);
34 +
35 + if (addressType == P2shAddressType.p2wpkhInP2sh)
36 + return generateP2SHAddress(hd: hd, index: index, network: network);
37 +
38 + return generateP2WPKHAddress(hd: hd, index: index, network: network);
39 + }
40 }
cw_bitcoin/lib/bitcoin_wallet_service.dart
+24 -11
@@ -1,4 +1,5 @@
1 import 'dart:io';
2 +import 'package:bitcoin_base/bitcoin_base.dart';
3 import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
4 import 'package:cw_bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart';
5 import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart';
@@ -23,12 +24,17 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
24 WalletType getType() => WalletType.bitcoin;
25
26 @override
26 - Future<BitcoinWallet> create(BitcoinNewWalletCredentials credentials) async {
27 + Future<BitcoinWallet> create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async {
28 + final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
29 + credentials.walletInfo?.network = network.value;
30 +
31 final wallet = await BitcoinWalletBase.create(
28 - mnemonic: await generateMnemonic(),
29 - password: credentials.password!,
30 - walletInfo: credentials.walletInfo!,
31 - unspentCoinsInfo: unspentCoinsInfoSource);
32 + mnemonic: await generateMnemonic(),
33 + password: credentials.password!,
34 + walletInfo: credentials.walletInfo!,
35 + unspentCoinsInfo: unspentCoinsInfoSource,
36 + network: network,
37 + );
38 await wallet.save();
39 await wallet.init();
40 return wallet;
@@ -92,20 +98,27 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
98 }
99
100 @override
95 - Future<BitcoinWallet> restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials) async =>
101 + Future<BitcoinWallet> restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials,
102 + {bool? isTestnet}) async =>
103 throw UnimplementedError();
104
105 @override
99 - Future<BitcoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials) async {
106 + Future<BitcoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials,
107 + {bool? isTestnet}) async {
108 if (!validateMnemonic(credentials.mnemonic)) {
109 throw BitcoinMnemonicIsIncorrectException();
110 }
111
112 + final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
113 + credentials.walletInfo?.network = network.value;
114 +
115 final wallet = await BitcoinWalletBase.create(
105 - password: credentials.password!,
106 - mnemonic: credentials.mnemonic,
107 - walletInfo: credentials.walletInfo!,
108 - unspentCoinsInfo: unspentCoinsInfoSource);
116 + password: credentials.password!,
117 + mnemonic: credentials.mnemonic,
118 + walletInfo: credentials.walletInfo!,
119 + unspentCoinsInfo: unspentCoinsInfoSource,
120 + network: network,
121 + );
122 await wallet.save();
123 await wallet.init();
124 return wallet;
cw_bitcoin/lib/electrum.dart
+71 -62
@@ -2,12 +2,12 @@ import 'dart:async';
2 import 'dart:convert';
3 import 'dart:io';
4 import 'dart:typed_data';
5 -import 'package:bitcoin_flutter/bitcoin_flutter.dart';
5 +import 'package:bitcoin_base/bitcoin_base.dart';
6 import 'package:cw_bitcoin/bitcoin_amount_format.dart';
7 import 'package:cw_bitcoin/script_hash.dart';
8 import 'package:flutter/foundation.dart';
9 import 'package:rxdart/rxdart.dart';
10 -import 'package:collection/collection.dart';
10 +import 'package:http/http.dart' as http;
11
12 String jsonrpcparams(List<Object> params) {
13 final _params = params?.map((val) => '"${val.toString()}"')?.join(',');
@@ -22,10 +22,7 @@ String jsonrpc(
22 '{"jsonrpc": "$version", "method": "$method", "id": "$id", "params": ${json.encode(params)}}\n';
23
24 class SocketTask {
25 - SocketTask({
26 - required this.isSubscription,
27 - this.completer,
28 - this.subject});
25 + SocketTask({required this.isSubscription, this.completer, this.subject});
26
27 final Completer<dynamic>? completer;
28 final BehaviorSubject<dynamic>? subject;
@@ -51,8 +48,7 @@ class ElectrumClient {
48 Timer? _aliveTimer;
49 String unterminatedString;
50
54 - Future<void> connectToUri(Uri uri) async =>
55 - await connect(host: uri.host, port: uri.port);
51 + Future<void> connectToUri(Uri uri) async => await connect(host: uri.host, port: uri.port);
52
53 Future<void> connect({required String host, required int port}) async {
54 try {
@@ -104,21 +100,20 @@ class ElectrumClient {
100 }
101
102 if (isJSONStringCorrect(unterminatedString)) {
107 - final response =
108 - json.decode(unterminatedString) as Map<String, dynamic>;
103 + final response = json.decode(unterminatedString) as Map<String, dynamic>;
104 _handleResponse(response);
105 unterminatedString = '';
106 }
107 } on TypeError catch (e) {
113 - if (!e.toString().contains('Map<String, Object>') && !e.toString().contains('Map<String, dynamic>')) {
108 + if (!e.toString().contains('Map<String, Object>') &&
109 + !e.toString().contains('Map<String, dynamic>')) {
110 return;
111 }
112
113 unterminatedString += message;
114
115 if (isJSONStringCorrect(unterminatedString)) {
120 - final response =
121 - json.decode(unterminatedString) as Map<String, dynamic>;
116 + final response = json.decode(unterminatedString) as Map<String, dynamic>;
117 _handleResponse(response);
118 // unterminatedString = null;
119 unterminatedString = '';
@@ -142,8 +137,7 @@ class ElectrumClient {
137 }
138 }
139
145 - Future<List<String>> version() =>
146 - call(method: 'server.version').then((dynamic result) {
140 + Future<List<String>> version() => call(method: 'server.version').then((dynamic result) {
141 if (result is List) {
142 return result.map((dynamic val) => val.toString()).toList();
143 }
@@ -178,11 +172,10 @@ class ElectrumClient {
172 });
173
174 Future<List<Map<String, dynamic>>> getListUnspentWithAddress(
181 - String address, NetworkType networkType) =>
175 + String address, BasedUtxoNetwork network) =>
176 call(
183 - method: 'blockchain.scripthash.listunspent',
184 - params: [scriptHash(address, networkType: networkType)])
185 - .then((dynamic result) {
177 + method: 'blockchain.scripthash.listunspent',
178 + params: [scriptHash(address, network: network)]).then((dynamic result) {
179 if (result is List) {
180 return result.map((dynamic val) {
181 if (val is Map<String, dynamic>) {
@@ -229,8 +222,7 @@ class ElectrumClient {
222 return [];
223 });
224
232 - Future<Map<String, dynamic>> getTransactionRaw(
233 - {required String hash}) async =>
225 + Future<Map<String, dynamic>> getTransactionRaw({required String hash}) async =>
226 callWithTimeout(method: 'blockchain.transaction.get', params: [hash, true], timeout: 10000)
227 .then((dynamic result) {
228 if (result is Map<String, dynamic>) {
@@ -240,8 +232,7 @@ class ElectrumClient {
232 return <String, dynamic>{};
233 });
234
243 - Future<String> getTransactionHex(
244 - {required String hash}) async =>
235 + Future<String> getTransactionHex({required String hash}) async =>
236 callWithTimeout(method: 'blockchain.transaction.get', params: [hash, false], timeout: 10000)
237 .then((dynamic result) {
238 if (result is String) {
@@ -252,29 +243,40 @@ class ElectrumClient {
243 });
244
245 Future<String> broadcastTransaction(
255 - {required String transactionRaw}) async =>
256 - call(method: 'blockchain.transaction.broadcast', params: [transactionRaw])
257 - .then((dynamic result) {
258 - if (result is String) {
259 - return result;
246 + {required String transactionRaw, BasedUtxoNetwork? network}) async {
247 + if (network == BitcoinNetwork.testnet) {
248 + return http
249 + .post(Uri(scheme: 'https', host: 'blockstream.info', path: '/testnet/api/tx'),
250 + headers: <String, String>{'Content-Type': 'application/json; charset=utf-8'},
251 + body: transactionRaw)
252 + .then((http.Response response) {
253 + if (response.statusCode == 200) {
254 + return response.body;
255 }
256
262 - return '';
257 + throw Exception('Failed to broadcast transaction: ${response.body}');
258 });
259 + }
260
265 - Future<Map<String, dynamic>> getMerkle(
266 - {required String hash, required int height}) async =>
267 - await call(
268 - method: 'blockchain.transaction.get_merkle',
269 - params: [hash, height]) as Map<String, dynamic>;
261 + return call(method: 'blockchain.transaction.broadcast', params: [transactionRaw])
262 + .then((dynamic result) {
263 + if (result is String) {
264 + return result;
265 + }
266
271 - Future<Map<String, dynamic>> getHeader({required int height}) async =>
272 - await call(method: 'blockchain.block.get_header', params: [height])
267 + return '';
268 + });
269 + }
270 +
271 + Future<Map<String, dynamic>> getMerkle({required String hash, required int height}) async =>
272 + await call(method: 'blockchain.transaction.get_merkle', params: [hash, height])
273 as Map<String, dynamic>;
274
275 + Future<Map<String, dynamic>> getHeader({required int height}) async =>
276 + await call(method: 'blockchain.block.get_header', params: [height]) as Map<String, dynamic>;
277 +
278 Future<double> estimatefee({required int p}) =>
276 - call(method: 'blockchain.estimatefee', params: [p])
277 - .then((dynamic result) {
279 + call(method: 'blockchain.estimatefee', params: [p]).then((dynamic result) {
280 if (result is double) {
281 return result;
282 }
@@ -314,20 +316,17 @@ class ElectrumClient {
316 return [];
317 });
318
317 - Future<List<int>> feeRates() async {
319 + Future<List<int>> feeRates({BasedUtxoNetwork? network}) async {
320 + if (network == BitcoinNetwork.testnet) {
321 + return [1, 1, 1];
322 + }
323 try {
324 final topDoubleString = await estimatefee(p: 1);
325 final middleDoubleString = await estimatefee(p: 5);
326 final bottomDoubleString = await estimatefee(p: 100);
322 - final top =
323 - (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000)
324 - .round();
325 - final middle =
326 - (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000)
327 - .round();
328 - final bottom =
329 - (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000)
330 - .round();
327 + final top = (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000).round();
328 + final middle = (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000).round();
329 + final bottom = (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000).round();
330
331 return [bottom, middle, top];
332 } catch (_) {
@@ -335,6 +334,21 @@ class ElectrumClient {
334 }
335 }
336
337 + // https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-headers-subscribe
338 + // example response:
339 + // {
340 + // "height": 520481,
341 + // "hex": "00000020890208a0ae3a3892aa047c5468725846577cfcd9b512b50000000000000000005dc2b02f2d297a9064ee103036c14d678f9afc7e3d9409cf53fd58b82e938e8ecbeca05a2d2103188ce804c4"
342 + // }
343 + Future<int?> getCurrentBlockChainTip() =>
344 + call(method: 'blockchain.headers.subscribe').then((result) {
345 + if (result is Map<String, dynamic>) {
346 + return result["height"] as int;
347 + }
348 +
349 + return null;
350 + });
351 +
352 BehaviorSubject<Object>? scripthashUpdate(String scripthash) {
353 _id += 1;
354 return subscribe<Object>(
@@ -344,16 +358,14 @@ class ElectrumClient {
358 }
359
360 BehaviorSubject<T>? subscribe<T>(
347 - {required String id,
348 - required String method,
349 - List<Object> params = const []}) {
361 + {required String id, required String method, List<Object> params = const []}) {
362 try {
363 final subscription = BehaviorSubject<T>();
364 _regisrySubscription(id, subscription);
365 socket!.write(jsonrpc(method: method, id: _id, params: params));
366
367 return subscription;
356 - } catch(e) {
368 + } catch (e) {
369 print(e.toString());
370 return null;
371 }
@@ -370,9 +382,7 @@ class ElectrumClient {
382 }
383
384 Future<dynamic> callWithTimeout(
373 - {required String method,
374 - List<Object> params = const [],
375 - int timeout = 4000}) async {
385 + {required String method, List<Object> params = const [], int timeout = 4000}) async {
386 try {
387 final completer = Completer<dynamic>();
388 _id += 1;
@@ -386,7 +396,7 @@ class ElectrumClient {
396 });
397
398 return completer.future;
389 - } catch(e) {
399 + } catch (e) {
400 print(e.toString());
401 }
402 }
@@ -397,8 +407,8 @@ class ElectrumClient {
407 onConnectionStatusChange = null;
408 }
409
400 - void _registryTask(int id, Completer<dynamic> completer) => _tasks[id.toString()] =
401 - SocketTask(completer: completer, isSubscription: false);
410 + void _registryTask(int id, Completer<dynamic> completer) =>
411 + _tasks[id.toString()] = SocketTask(completer: completer, isSubscription: false);
412
413 void _regisrySubscription(String id, BehaviorSubject<dynamic> subject) =>
414 _tasks[id] = SocketTask(subject: subject, isSubscription: true);
@@ -419,8 +429,7 @@ class ElectrumClient {
429 }
430 }
431
422 - void _methodHandler(
423 - {required String method, required Map<String, dynamic> request}) {
432 + void _methodHandler({required String method, required Map<String, dynamic> request}) {
433 switch (method) {
434 case 'blockchain.scripthash.subscribe':
435 final params = request['params'] as List<dynamic>;
@@ -451,8 +460,8 @@ class ElectrumClient {
460 _methodHandler(method: method, request: response);
461 return;
462 }
454 -
455 - if (id != null){
463 +
464 + if (id != null) {
465 _finish(id, result);
466 }
467 }
cw_bitcoin/lib/electrum_transaction_info.dart
+41 -42
@@ -1,3 +1,4 @@
1 +import 'package:bitcoin_base/bitcoin_base.dart';
2 import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
3 import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
4 import 'package:cw_bitcoin/address_from_output.dart';
@@ -10,13 +11,12 @@ import 'package:cw_core/wallet_type.dart';
11
12 class ElectrumTransactionBundle {
13 ElectrumTransactionBundle(this.originalTransaction,
13 - {required this.ins,
14 - required this.confirmations,
15 - this.time});
16 - final bitcoin.Transaction originalTransaction;
17 - final List<bitcoin.Transaction> ins;
14 + {required this.ins, required this.confirmations, this.time, required this.height});
15 + final BtcTransaction originalTransaction;
16 + final List<BtcTransaction> ins;
17 final int? time;
18 final int confirmations;
19 + final int height;
20 }
21
22 class ElectrumTransactionInfo extends TransactionInfo {
@@ -39,8 +39,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
39 this.confirmations = confirmations;
40 }
41
42 - factory ElectrumTransactionInfo.fromElectrumVerbose(
43 - Map<String, Object> obj, WalletType type,
42 + factory ElectrumTransactionInfo.fromElectrumVerbose(Map<String, Object> obj, WalletType type,
43 {required List<BitcoinAddressRecord> addresses, required int height}) {
44 final addressesSet = addresses.map((addr) => addr.address).toSet();
45 final id = obj['txid'] as String;
@@ -58,10 +57,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
57 for (dynamic vin in vins) {
58 final vout = vin['vout'] as int;
59 final out = vin['tx']['vout'][vout] as Map;
61 - final outAddresses =
62 - (out['scriptPubKey']['addresses'] as List<Object>?)?.toSet();
63 - inputsAmount +=
64 - stringDoubleToBitcoinAmount((out['value'] as double? ?? 0).toString());
60 + final outAddresses = (out['scriptPubKey']['addresses'] as List<Object>?)?.toSet();
61 + inputsAmount += stringDoubleToBitcoinAmount((out['value'] as double? ?? 0).toString());
62
63 if (outAddresses?.intersection(addressesSet).isNotEmpty ?? false) {
64 direction = TransactionDirection.outgoing;
@@ -69,11 +66,9 @@ class ElectrumTransactionInfo extends TransactionInfo {
66 }
67
68 for (dynamic out in vout) {
72 - final outAddresses =
73 - out['scriptPubKey']['addresses'] as List<Object>? ?? [];
69 + final outAddresses = out['scriptPubKey']['addresses'] as List<Object>? ?? [];
70 final ntrs = outAddresses.toSet().intersection(addressesSet);
75 - final value = stringDoubleToBitcoinAmount(
76 - (out['value'] as double? ?? 0.0).toString());
71 + final value = stringDoubleToBitcoinAmount((out['value'] as double? ?? 0.0).toString());
72 totalOutAmount += value;
73
74 if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) ||
@@ -96,44 +91,50 @@ class ElectrumTransactionInfo extends TransactionInfo {
91 }
92
93 factory ElectrumTransactionInfo.fromElectrumBundle(
99 - ElectrumTransactionBundle bundle,
100 - WalletType type,
101 - bitcoin.NetworkType networkType,
102 - {required Set<String> addresses,
103 - required int height}) {
94 + ElectrumTransactionBundle bundle, WalletType type, BasedUtxoNetwork network,
95 + {required Set<String> addresses, required int height}) {
96 final date = bundle.time != null
105 - ? DateTime.fromMillisecondsSinceEpoch(bundle.time! * 1000)
106 - : DateTime.now();
97 + ? DateTime.fromMillisecondsSinceEpoch(bundle.time! * 1000)
98 + : DateTime.now();
99 var direction = TransactionDirection.incoming;
100 var amount = 0;
101 var inputAmount = 0;
102 var totalOutAmount = 0;
103
112 - for (var i = 0; i < bundle.originalTransaction.ins.length; i++) {
113 - final input = bundle.originalTransaction.ins[i];
104 + for (var i = 0; i < bundle.originalTransaction.inputs.length; i++) {
105 + final input = bundle.originalTransaction.inputs[i];
106 final inputTransaction = bundle.ins[i];
115 - final vout = input.index;
116 - final outTransaction = inputTransaction.outs[vout!];
117 - final address = addressFromOutput(outTransaction.script!, networkType);
118 - inputAmount += outTransaction.value!;
119 - if (addresses.contains(address)) {
107 + final outTransaction = inputTransaction.outputs[input.txIndex];
108 + inputAmount += outTransaction.amount.toInt();
109 + if (addresses.contains(addressFromOutputScript(outTransaction.scriptPubKey, network))) {
110 direction = TransactionDirection.outgoing;
111 }
112 }
113
124 - for (final out in bundle.originalTransaction.outs) {
125 - totalOutAmount += out.value!;
126 - final address = addressFromOutput(out.script!, networkType);
127 - final addressExists = addresses.contains(address);
114 + final receivedAmounts = <int>[];
115 + for (final out in bundle.originalTransaction.outputs) {
116 + totalOutAmount += out.amount.toInt();
117 + final addressExists = addresses.contains(addressFromOutputScript(out.scriptPubKey, network));
118 +
119 + if (addressExists) {
120 + receivedAmounts.add(out.amount.toInt());
121 + }
122 +
123 if ((direction == TransactionDirection.incoming && addressExists) ||
124 (direction == TransactionDirection.outgoing && !addressExists)) {
130 - amount += out.value!;
125 + amount += out.amount.toInt();
126 }
127 }
128
129 + if (receivedAmounts.length == bundle.originalTransaction.outputs.length) {
130 + // Self-send
131 + direction = TransactionDirection.incoming;
132 + amount = receivedAmounts.reduce((a, b) => a + b);
133 + }
134 +
135 final fee = inputAmount - totalOutAmount;
136 return ElectrumTransactionInfo(type,
136 - id: bundle.originalTransaction.getId(),
137 + id: bundle.originalTransaction.txId(),
138 height: height,
139 isPending: bundle.confirmations == 0,
140 fee: fee,
@@ -152,8 +153,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
153 if (addresses != null) {
154 tx.outs.forEach((out) {
155 try {
155 - final p2pkh = bitcoin.P2PKH(
156 - data: PaymentData(output: out.script), network: bitcoin.bitcoin);
156 + final p2pkh =
157 + bitcoin.P2PKH(data: PaymentData(output: out.script), network: bitcoin.bitcoin);
158 exist = addresses.contains(p2pkh.data.address);
159
160 if (exist) {
@@ -163,9 +164,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
164 });
165 }
166
166 - final date = timestamp != null
167 - ? DateTime.fromMillisecondsSinceEpoch(timestamp * 1000)
168 - : DateTime.now();
167 + final date =
168 + timestamp != null ? DateTime.fromMillisecondsSinceEpoch(timestamp * 1000) : DateTime.now();
169
170 return ElectrumTransactionInfo(type,
171 id: tx.getId(),
@@ -178,8 +178,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
178 confirmations: confirmations);
179 }
180
181 - factory ElectrumTransactionInfo.fromJson(
182 - Map<String, dynamic> data, WalletType type) {
181 + factory ElectrumTransactionInfo.fromJson(Map<String, dynamic> data, WalletType type) {
182 return ElectrumTransactionInfo(type,
183 id: data['id'] as String,
184 height: data['height'] as int,
cw_bitcoin/lib/electrum_wallet.dart
+396 -241
@@ -3,9 +3,10 @@ import 'dart:convert';
3 import 'dart:io';
4 import 'dart:math';
5
6 +import 'package:bitcoin_base/bitcoin_base.dart';
7 import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
8 +import 'package:bitcoin_base/bitcoin_base.dart' as bitcoin_base;
9 import 'package:collection/collection.dart';
8 -import 'package:cw_bitcoin/address_to_output_script.dart';
10 import 'package:cw_bitcoin/bitcoin_address_record.dart';
11 import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
12 import 'package:cw_bitcoin/bitcoin_transaction_no_inputs_exception.dart';
@@ -18,6 +19,7 @@ import 'package:cw_bitcoin/electrum_balance.dart';
19 import 'package:cw_bitcoin/electrum_transaction_history.dart';
20 import 'package:cw_bitcoin/electrum_transaction_info.dart';
21 import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
22 +import 'package:cw_bitcoin/litecoin_network.dart';
23 import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
24 import 'package:cw_bitcoin/script_hash.dart';
25 import 'package:cw_bitcoin/utils.dart';
@@ -37,6 +39,7 @@ import 'package:hex/hex.dart';
39 import 'package:hive/hive.dart';
40 import 'package:mobx/mobx.dart';
41 import 'package:rxdart/subjects.dart';
42 +import 'package:http/http.dart' as http;
43
44 part 'electrum_wallet.g.dart';
45
@@ -73,6 +76,12 @@ abstract class ElectrumWalletBase
76 }
77 : {}),
78 this.unspentCoinsInfo = unspentCoinsInfo,
79 + this.network = networkType == bitcoin.bitcoin
80 + ? BitcoinNetwork.mainnet
81 + : networkType == litecoinNetwork
82 + ? LitecoinNetwork.mainnet
83 + : BitcoinNetwork.testnet,
84 + this.isTestnet = networkType == bitcoin.testnet,
85 super(walletInfo) {
86 this.electrumClient = electrumClient ?? ElectrumClient();
87 this.walletInfo = walletInfo;
@@ -106,13 +115,13 @@ abstract class ElectrumWalletBase
115 @observable
116 SyncStatus syncStatus;
117
109 - List<String> get scriptHashes => walletAddresses.addresses
110 - .map((addr) => scriptHash(addr.address, networkType: networkType))
118 + List<String> get scriptHashes => walletAddresses.addressesByReceiveType
119 + .map((addr) => scriptHash(addr.address, network: network))
120 .toList();
121
113 - List<String> get publicScriptHashes => walletAddresses.addresses
122 + List<String> get publicScriptHashes => walletAddresses.allAddresses
123 .where((addr) => !addr.isHidden)
115 - .map((addr) => scriptHash(addr.address, networkType: networkType))
124 + .map((addr) => scriptHash(addr.address, network: network))
125 .toList();
126
127 String get xpub => hd.base58!;
@@ -121,6 +130,10 @@ abstract class ElectrumWalletBase
130 String get seed => mnemonic;
131
132 bitcoin.NetworkType networkType;
133 + BasedUtxoNetwork network;
134 +
135 + @override
136 + bool? isTestnet;
137
138 @override
139 BitcoinWalletKeys get keys =>
@@ -145,12 +158,11 @@ abstract class ElectrumWalletBase
158 Future<void> startSync() async {
159 try {
160 syncStatus = AttemptingSyncStatus();
148 - await walletAddresses.discoverAddresses();
161 await updateTransactions();
162 _subscribeForUpdates();
163 await updateUnspent();
164 await updateBalance();
153 - _feeRates = await electrumClient.feeRates();
165 + _feeRates = await electrumClient.feeRates(network: network);
166
167 Timer.periodic(
168 const Duration(minutes: 1), (timer) async => _feeRates = await electrumClient.feeRates());
@@ -181,183 +193,206 @@ abstract class ElectrumWalletBase
193 }
194 }
195
184 - @override
185 - Future<PendingTransaction> createTransaction(Object credentials) async {
186 - const minAmount = 546;
187 - final transactionCredentials = credentials as BitcoinTransactionCredentials;
188 - final inputs = <BitcoinUnspent>[];
189 - final outputs = transactionCredentials.outputs;
190 - final hasMultiDestination = outputs.length > 1;
196 + Future<EstimatedTxResult> _estimateTxFeeAndInputsToUse(
197 + int credentialsAmount,
198 + bool sendAll,
199 + List<BitcoinBaseAddress> outputAddresses,
200 + List<BitcoinOutput> outputs,
201 + BitcoinTransactionCredentials transactionCredentials,
202 + {int? inputsCount}) async {
203 + final utxos = <UtxoWithAddress>[];
204 + List<ECPrivate> privateKeys = [];
205 +
206 + var leftAmount = credentialsAmount;
207 var allInputsAmount = 0;
208
193 - if (unspentCoins.isEmpty) {
194 - await updateUnspent();
195 - }
209 + for (int i = 0; i < unspentCoins.length; i++) {
210 + final utx = unspentCoins[i];
211
197 - for (final utx in unspentCoins) {
212 if (utx.isSending) {
213 allInputsAmount += utx.value;
200 - inputs.add(utx);
214 + leftAmount = leftAmount - utx.value;
215 +
216 + final address = _addressTypeFromStr(utx.address, network);
217 + final privkey = generateECPrivate(
218 + hd: utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
219 + index: utx.bitcoinAddressRecord.index,
220 + network: network);
221 +
222 + privateKeys.add(privkey);
223 +
224 + utxos.add(
225 + UtxoWithAddress(
226 + utxo: BitcoinUtxo(
227 + txHash: utx.hash,
228 + value: BigInt.from(utx.value),
229 + vout: utx.vout,
230 + scriptType: _getScriptType(address),
231 + ),
232 + ownerDetails:
233 + UtxoAddressDetails(publicKey: privkey.getPublic().toHex(), address: address),
234 + ),
235 + );
236 +
237 + bool amountIsAcquired = !sendAll && leftAmount <= 0;
238 + if ((inputsCount == null && amountIsAcquired) || inputsCount == i + 1) {
239 + break;
240 + }
241 }
242 }
243
204 - if (inputs.isEmpty) {
244 + if (utxos.isEmpty) {
245 throw BitcoinTransactionNoInputsException();
246 }
247
208 - final allAmountFee = transactionCredentials.feeRate != null
209 - ? feeAmountWithFeeRate(transactionCredentials.feeRate!, inputs.length, outputs.length)
210 - : feeAmountForPriority(transactionCredentials.priority!, inputs.length, outputs.length);
211 -
212 - final allAmount = allInputsAmount - allAmountFee;
213 -
214 - var credentialsAmount = 0;
215 - var amount = 0;
216 - var fee = 0;
217 -
218 - if (hasMultiDestination) {
219 - if (outputs.any((item) => item.sendAll || item.formattedCryptoAmount! <= 0)) {
220 - throw BitcoinTransactionWrongBalanceException(currency);
221 - }
222 -
223 - credentialsAmount = outputs.fold(0, (acc, value) => acc + value.formattedCryptoAmount!);
248 + var changeValue = allInputsAmount - credentialsAmount;
249
225 - if (allAmount - credentialsAmount < minAmount) {
226 - throw BitcoinTransactionWrongBalanceException(currency);
250 + if (!sendAll) {
251 + if (changeValue > 0) {
252 + final changeAddress = await walletAddresses.getChangeAddress();
253 + final address = _addressTypeFromStr(changeAddress, network);
254 + outputAddresses.add(address);
255 + outputs.add(BitcoinOutput(address: address, value: BigInt.from(changeValue)));
256 }
257 + }
258
229 - amount = credentialsAmount;
259 + final estimatedSize = BitcoinTransactionBuilder.estimateTransactionSize(
260 + utxos: utxos, outputs: outputs, network: network);
261
231 - if (transactionCredentials.feeRate != null) {
232 - fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount,
233 - outputsCount: outputs.length + 1);
234 - } else {
235 - fee = calculateEstimatedFee(transactionCredentials.priority, amount,
236 - outputsCount: outputs.length + 1);
237 - }
238 - } else {
239 - final output = outputs.first;
240 - credentialsAmount = !output.sendAll ? output.formattedCryptoAmount! : 0;
262 + final fee = transactionCredentials.feeRate != null
263 + ? feeAmountWithFeeRate(transactionCredentials.feeRate!, 0, 0, size: estimatedSize)
264 + : feeAmountForPriority(transactionCredentials.priority!, 0, 0, size: estimatedSize);
265
242 - if (credentialsAmount > allAmount) {
243 - throw BitcoinTransactionWrongBalanceException(currency);
244 - }
266 + if (fee == 0) {
267 + throw BitcoinTransactionWrongBalanceException(currency);
268 + }
269
246 - amount = output.sendAll || allAmount - credentialsAmount < minAmount
247 - ? allAmount
248 - : credentialsAmount;
270 + var amount = credentialsAmount;
271
250 - if (output.sendAll || amount == allAmount) {
251 - fee = allAmountFee;
252 - } else if (transactionCredentials.feeRate != null) {
253 - fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount);
254 - } else {
255 - fee = calculateEstimatedFee(transactionCredentials.priority, amount);
272 + final lastOutput = outputs.last;
273 + if (!sendAll) {
274 + if (changeValue > fee) {
275 + // Here, lastOutput is change, deduct the fee from it
276 + outputs[outputs.length - 1] =
277 + BitcoinOutput(address: lastOutput.address, value: lastOutput.value - BigInt.from(fee));
278 }
257 - }
258 -
259 - if (fee == 0) {
260 - throw BitcoinTransactionWrongBalanceException(currency);
279 + } else {
280 + // Here, if sendAll, the output amount equals to the input value - fee to fully spend every input on the transaction and have no amount for change
281 + amount = allInputsAmount - fee;
282 + outputs[outputs.length - 1] =
283 + BitcoinOutput(address: lastOutput.address, value: BigInt.from(amount));
284 }
285
286 final totalAmount = amount + fee;
287
265 - if (totalAmount > balance[currency]!.confirmed || totalAmount > allInputsAmount) {
288 + if (totalAmount > balance[currency]!.confirmed) {
289 throw BitcoinTransactionWrongBalanceException(currency);
290 }
291
269 - final txb = bitcoin.TransactionBuilder(network: networkType);
270 - final changeAddress = await walletAddresses.getChangeAddress();
271 - var leftAmount = totalAmount;
272 - var totalInputAmount = 0;
273 -
274 - inputs.clear();
275 -
276 - for (final utx in unspentCoins) {
277 - if (utx.isSending) {
278 - leftAmount = leftAmount - utx.value;
279 - totalInputAmount += utx.value;
280 - inputs.add(utx);
281 -
282 - if (leftAmount <= 0) {
283 - break;
292 + if (totalAmount > allInputsAmount) {
293 + if (unspentCoins.where((utx) => utx.isSending).length == utxos.length) {
294 + throw BitcoinTransactionWrongBalanceException(currency);
295 + } else {
296 + if (changeValue > fee) {
297 + outputAddresses.removeLast();
298 + outputs.removeLast();
299 }
300 +
301 + return _estimateTxFeeAndInputsToUse(
302 + credentialsAmount, sendAll, outputAddresses, outputs, transactionCredentials,
303 + inputsCount: utxos.length + 1);
304 }
305 }
306
288 - if (inputs.isEmpty) {
289 - throw BitcoinTransactionNoInputsException();
290 - }
307 + return EstimatedTxResult(utxos: utxos, privateKeys: privateKeys, fee: fee, amount: amount);
308 + }
309
292 - if (amount <= 0 || totalInputAmount < totalAmount) {
293 - throw BitcoinTransactionWrongBalanceException(currency);
294 - }
310 + @override
311 + Future<PendingTransaction> createTransaction(Object credentials) async {
312 + try {
313 + final outputs = <BitcoinOutput>[];
314 + final outputAddresses = <BitcoinBaseAddress>[];
315 + final transactionCredentials = credentials as BitcoinTransactionCredentials;
316 + final hasMultiDestination = transactionCredentials.outputs.length > 1;
317 + final sendAll = !hasMultiDestination && transactionCredentials.outputs.first.sendAll;
318
296 - txb.setVersion(1);
297 - inputs.forEach((input) {
298 - if (input.isP2wpkh) {
299 - final p2wpkh = bitcoin
300 - .P2WPKH(
301 - data: generatePaymentData(
302 - hd: input.bitcoinAddressRecord.isHidden
303 - ? walletAddresses.sideHd
304 - : walletAddresses.mainHd,
305 - index: input.bitcoinAddressRecord.index),
306 - network: networkType)
307 - .data;
308 -
309 - txb.addInput(input.hash, input.vout, null, p2wpkh.output);
310 - } else {
311 - txb.addInput(input.hash, input.vout);
312 - }
313 - });
319 + var credentialsAmount = 0;
320
315 - outputs.forEach((item) {
316 - final outputAmount = hasMultiDestination ? item.formattedCryptoAmount : amount;
317 - final outputAddress = item.isParsedAddress ? item.extractedAddress! : item.address;
318 - txb.addOutput(addressToOutputScript(outputAddress, networkType), outputAmount!);
319 - });
321 + for (final out in transactionCredentials.outputs) {
322 + final outputAddress = out.isParsedAddress ? out.extractedAddress! : out.address;
323 + final address = _addressTypeFromStr(outputAddress, network);
324
321 - final estimatedSize = estimatedTransactionSize(inputs.length, outputs.length + 1);
322 - var feeAmount = 0;
325 + outputAddresses.add(address);
326
324 - if (transactionCredentials.feeRate != null) {
325 - feeAmount = transactionCredentials.feeRate! * estimatedSize;
326 - } else {
327 - feeAmount = feeRate(transactionCredentials.priority!) * estimatedSize;
328 - }
327 + if (hasMultiDestination) {
328 + if (out.sendAll || out.formattedCryptoAmount! <= 0) {
329 + throw BitcoinTransactionWrongBalanceException(currency);
330 + }
331
330 - final changeValue = totalInputAmount - amount - feeAmount;
332 + final outputAmount = out.formattedCryptoAmount!;
333 + credentialsAmount += outputAmount;
334
332 - if (changeValue > minAmount) {
333 - txb.addOutput(changeAddress, changeValue);
334 - }
335 + outputs.add(BitcoinOutput(address: address, value: BigInt.from(outputAmount)));
336 + } else {
337 + if (!sendAll) {
338 + final outputAmount = out.formattedCryptoAmount!;
339 + credentialsAmount += outputAmount;
340 + outputs.add(BitcoinOutput(address: address, value: BigInt.from(outputAmount)));
341 + } else {
342 + // The value will be changed after estimating the Tx size and deducting the fee from the total
343 + outputs.add(BitcoinOutput(address: address, value: BigInt.from(0)));
344 + }
345 + }
346 + }
347
336 - for (var i = 0; i < inputs.length; i++) {
337 - final input = inputs[i];
338 - final keyPair = generateKeyPair(
339 - hd: input.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
340 - index: input.bitcoinAddressRecord.index,
341 - network: networkType);
342 - final witnessValue = input.isP2wpkh ? input.value : null;
348 + final estimatedTx = await _estimateTxFeeAndInputsToUse(
349 + credentialsAmount, sendAll, outputAddresses, outputs, transactionCredentials);
350
344 - txb.sign(vin: i, keyPair: keyPair, witnessValue: witnessValue);
345 - }
351 + final txb = BitcoinTransactionBuilder(
352 + utxos: estimatedTx.utxos,
353 + outputs: outputs,
354 + fee: BigInt.from(estimatedTx.fee),
355 + network: network);
356 +
357 + final transaction = txb.buildTransaction((txDigest, utxo, publicKey, sighash) {
358 + final key = estimatedTx.privateKeys
359 + .firstWhereOrNull((element) => element.getPublic().toHex() == publicKey);
360
347 - return PendingBitcoinTransaction(txb.build(), type,
348 - electrumClient: electrumClient, amount: amount, fee: fee)
349 - ..addListener((transaction) async {
350 - transactionHistory.addOne(transaction);
351 - await updateBalance();
361 + if (key == null) {
362 + throw Exception("Cannot find private key");
363 + }
364 +
365 + if (utxo.utxo.isP2tr()) {
366 + return key.signTapRoot(txDigest, sighash: sighash);
367 + } else {
368 + return key.signInput(txDigest, sigHash: sighash);
369 + }
370 });
371 +
372 + return PendingBitcoinTransaction(transaction, type,
373 + electrumClient: electrumClient,
374 + amount: estimatedTx.amount,
375 + fee: estimatedTx.fee,
376 + network: network)
377 + ..addListener((transaction) async {
378 + transactionHistory.addOne(transaction);
379 + await updateBalance();
380 + });
381 + } catch (e) {
382 + throw e;
383 + }
384 }
385
386 String toJSON() => json.encode({
387 'mnemonic': mnemonic,
357 - 'account_index': walletAddresses.currentReceiveAddressIndex.toString(),
358 - 'change_address_index': walletAddresses.currentChangeAddressIndex.toString(),
359 - 'addresses': walletAddresses.addresses.map((addr) => addr.toJSON()).toList(),
360 - 'balance': balance[currency]?.toJSON()
388 + 'account_index': walletAddresses.currentReceiveAddressIndexByType,
389 + 'change_address_index': walletAddresses.currentChangeAddressIndexByType,
390 + 'addresses': walletAddresses.allAddresses.map((addr) => addr.toJSON()).toList(),
391 + 'address_page_type': walletInfo.addressPageType == null
392 + ? SegwitAddresType.p2wpkh.toString()
393 + : walletInfo.addressPageType.toString(),
394 + 'balance': balance[currency]?.toJSON(),
395 + 'network_type': network == BitcoinNetwork.testnet ? 'testnet' : 'mainnet',
396 });
397
398 int feeRate(TransactionPriority priority) {
@@ -372,24 +407,29 @@ abstract class ElectrumWalletBase
407 }
408 }
409
375 - int feeAmountForPriority(
376 - BitcoinTransactionPriority priority, int inputsCount, int outputsCount) =>
377 - feeRate(priority) * estimatedTransactionSize(inputsCount, outputsCount);
410 + int feeAmountForPriority(BitcoinTransactionPriority priority, int inputsCount, int outputsCount,
411 + {int? size}) =>
412 + feeRate(priority) * (size ?? estimatedTransactionSize(inputsCount, outputsCount));
413
379 - int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount) =>
380 - feeRate * estimatedTransactionSize(inputsCount, outputsCount);
414 + int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount, {int? size}) =>
415 + feeRate * (size ?? estimatedTransactionSize(inputsCount, outputsCount));
416
417 @override
383 - int calculateEstimatedFee(TransactionPriority? priority, int? amount, {int? outputsCount}) {
418 + int calculateEstimatedFee(TransactionPriority? priority, int? amount,
419 + {int? outputsCount, int? size}) {
420 if (priority is BitcoinTransactionPriority) {
421 return calculateEstimatedFeeWithFeeRate(feeRate(priority), amount,
386 - outputsCount: outputsCount);
422 + outputsCount: outputsCount, size: size);
423 }
424
425 return 0;
426 }
427
392 - int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount}) {
428 + int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount, int? size}) {
429 + if (size != null) {
430 + return feeAmountWithFeeRate(feeRate, 0, 0, size: size);
431 + }
432 +
433 int inputsCount = 0;
434
435 if (amount != null) {
@@ -457,9 +497,6 @@ abstract class ElectrumWalletBase
497 await transactionHistory.changePassword(password);
498 }
499
460 - bitcoin.ECPair keyPairFor({required int index}) =>
461 - generateKeyPair(hd: hd, index: index, network: networkType);
462 -
500 @override
501 Future<void> rescan({required int height}) async => throw UnimplementedError();
502
@@ -473,20 +510,23 @@ abstract class ElectrumWalletBase
510 Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
511
512 Future<void> updateUnspent() async {
476 - final unspent = await Future.wait(walletAddresses.addresses.map((address) => electrumClient
477 - .getListUnspentWithAddress(address.address, networkType)
478 - .then((unspent) => unspent.map((unspent) {
513 + List<BitcoinUnspent> updatedUnspentCoins = [];
514 +
515 + final addressesSet = walletAddresses.allAddresses.map((addr) => addr.address).toSet();
516 +
517 + await Future.wait(walletAddresses.allAddresses.map((address) => electrumClient
518 + .getListUnspentWithAddress(address.address, network)
519 + .then((unspent) => Future.forEach<Map<String, dynamic>>(unspent, (unspent) async {
520 try {
480 - return BitcoinUnspent.fromJSON(address, unspent);
481 - } catch (_) {
482 - return null;
483 - }
484 - }).whereNotNull())));
485 - unspentCoins = unspent.expand((e) => e).toList();
486 - unspentCoins.forEach((coin) async {
487 - final tx = await fetchTransactionInfo(hash: coin.hash, height: 0);
488 - coin.isChange = tx?.direction == TransactionDirection.outgoing;
489 - });
521 + final coin = BitcoinUnspent.fromJSON(address, unspent);
522 + final tx = await fetchTransactionInfo(
523 + hash: coin.hash, height: 0, myAddresses: addressesSet);
524 + coin.isChange = tx?.direction == TransactionDirection.outgoing;
525 + updatedUnspentCoins.add(coin);
526 + } catch (_) {}
527 + }))));
528 +
529 + unspentCoins = updatedUnspentCoins;
530
531 if (unspentCoinsInfo.isEmpty) {
532 unspentCoins.forEach((coin) => _addCoinInfo(coin));
@@ -495,8 +535,10 @@ abstract class ElectrumWalletBase
535
536 if (unspentCoins.isNotEmpty) {
537 unspentCoins.forEach((coin) {
498 - final coinInfoList = unspentCoinsInfo.values
499 - .where((element) => element.walletId.contains(id) && element.hash.contains(coin.hash));
538 + final coinInfoList = unspentCoinsInfo.values.where((element) =>
539 + element.walletId.contains(id) &&
540 + element.hash.contains(coin.hash) &&
541 + element.vout == coin.vout);
542
543 if (coinInfoList.isNotEmpty) {
544 final coinInfo = coinInfoList.first;
@@ -537,7 +579,8 @@ abstract class ElectrumWalletBase
579
580 if (currentWalletUnspentCoins.isNotEmpty) {
581 currentWalletUnspentCoins.forEach((element) {
540 - final existUnspentCoins = unspentCoins.where((coin) => element.hash.contains(coin.hash));
582 + final existUnspentCoins = unspentCoins
583 + .where((coin) => element.hash.contains(coin.hash) && element.vout == coin.vout);
584
585 if (existUnspentCoins.isEmpty) {
586 keys.add(element.key);
@@ -555,92 +598,152 @@ abstract class ElectrumWalletBase
598
599 Future<ElectrumTransactionBundle> getTransactionExpanded(
600 {required String hash, required int height}) async {
558 - final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash);
559 - final transactionHex = verboseTransaction['hex'] as String;
560 - final original = bitcoin.Transaction.fromHex(transactionHex);
561 - final ins = <bitcoin.Transaction>[];
562 - final time = verboseTransaction['time'] as int?;
563 - final confirmations = verboseTransaction['confirmations'] as int? ?? 0;
564 -
565 - for (final vin in original.ins) {
566 - final id = HEX.encode(vin.hash!.reversed.toList());
567 - final txHex = await electrumClient.getTransactionHex(hash: id);
568 - final tx = bitcoin.Transaction.fromHex(txHex);
569 - ins.add(tx);
601 + String transactionHex;
602 + int? time;
603 + int confirmations = 0;
604 + if (network == BitcoinNetwork.testnet) {
605 + // Testnet public electrum server does not support verbose transaction fetching
606 + transactionHex = await electrumClient.getTransactionHex(hash: hash);
607 +
608 + final status = json.decode(
609 + (await http.get(Uri.parse("https://blockstream.info/testnet/api/tx/$hash/status"))).body);
610 +
611 + time = status["block_time"] as int?;
612 + final tip = await electrumClient.getCurrentBlockChainTip() ?? 0;
613 + confirmations = tip - (status["block_height"] as int? ?? 0);
614 + } else {
615 + final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash);
616 +
617 + transactionHex = verboseTransaction['hex'] as String;
618 + time = verboseTransaction['time'] as int?;
619 + confirmations = verboseTransaction['confirmations'] as int? ?? 0;
620 + }
621 +
622 + final original = bitcoin_base.BtcTransaction.fromRaw(transactionHex);
623 + final ins = <bitcoin_base.BtcTransaction>[];
624 +
625 + for (final vin in original.inputs) {
626 + try {
627 + final id = HEX.encode(HEX.decode(vin.txId).reversed.toList());
628 + final txHex = await electrumClient.getTransactionHex(hash: id);
629 + final tx = bitcoin_base.BtcTransaction.fromRaw(txHex);
630 + ins.add(tx);
631 + } catch (_) {
632 + ins.add(bitcoin_base.BtcTransaction.fromRaw(
633 + await electrumClient.getTransactionHex(hash: vin.txId),
634 + ));
635 + }
636 }
637
572 - return ElectrumTransactionBundle(original, ins: ins, time: time, confirmations: confirmations);
638 + return ElectrumTransactionBundle(original,
639 + ins: ins, time: time, confirmations: confirmations, height: height);
640 }
641
642 Future<ElectrumTransactionInfo?> fetchTransactionInfo(
576 - {required String hash, required int height}) async {
643 + {required String hash,
644 + required int height,
645 + required Set<String> myAddresses,
646 + bool? retryOnFailure}) async {
647 try {
578 - final tx = await getTransactionExpanded(hash: hash, height: height);
579 - final addresses = walletAddresses.addresses.map((addr) => addr.address).toSet();
580 - return ElectrumTransactionInfo.fromElectrumBundle(tx, walletInfo.type, networkType,
581 - addresses: addresses, height: height);
582 - } catch (_) {
648 + return ElectrumTransactionInfo.fromElectrumBundle(
649 + await getTransactionExpanded(hash: hash, height: height), walletInfo.type, network,
650 + addresses: myAddresses, height: height);
651 + } catch (e) {
652 + if (e is FormatException && retryOnFailure == true) {
653 + await Future.delayed(const Duration(seconds: 2));
654 + return fetchTransactionInfo(hash: hash, height: height, myAddresses: myAddresses);
655 + }
656 return null;
657 }
658 }
659
660 @override
661 Future<Map<String, ElectrumTransactionInfo>> fetchTransactions() async {
589 - final addressHashes = <String, BitcoinAddressRecord>{};
590 - final normalizedHistories = <Map<String, dynamic>>[];
591 - final newTxCounts = <String, int>{};
592 -
593 - walletAddresses.addresses.forEach((addressRecord) {
594 - final sh = scriptHash(addressRecord.address, networkType: networkType);
595 - addressHashes[sh] = addressRecord;
596 - newTxCounts[sh] = 0;
597 - });
598 -
662 try {
600 - final histories = addressHashes.keys.map((scriptHash) =>
601 - electrumClient.getHistory(scriptHash).then((history) => {scriptHash: history}));
602 - final historyResults = await Future.wait(histories);
603 -
604 - historyResults.forEach((history) {
605 - history.entries.forEach((historyItem) {
606 - if (historyItem.value.isNotEmpty) {
607 - final address = addressHashes[historyItem.key];
608 - address?.setAsUsed();
609 - newTxCounts[historyItem.key] = historyItem.value.length;
610 - normalizedHistories.addAll(historyItem.value);
663 + final Map<String, ElectrumTransactionInfo> historiesWithDetails = {};
664 + final addressesSet = walletAddresses.allAddresses.map((addr) => addr.address).toSet();
665 + final currentHeight = await electrumClient.getCurrentBlockChainTip() ?? 0;
666 +
667 + await Future.wait(ADDRESS_TYPES.map((type) {
668 + final addressesByType = walletAddresses.allAddresses.where((addr) => addr.type == type);
669 +
670 + return Future.wait(addressesByType.map((addressRecord) async {
671 + final history = await _fetchAddressHistory(addressRecord, addressesSet, currentHeight);
672 +
673 + if (history.isNotEmpty) {
674 + addressRecord.txCount = history.length;
675 + historiesWithDetails.addAll(history);
676 +
677 + final matchedAddresses =
678 + addressesByType.where((addr) => addr.isHidden == addressRecord.isHidden);
679 +
680 + final isLastUsedAddress =
681 + history.isNotEmpty && addressRecord.address == matchedAddresses.last.address;
682 +
683 + if (isLastUsedAddress) {
684 + await walletAddresses.discoverAddresses(
685 + matchedAddresses.toList(),
686 + addressRecord.isHidden,
687 + (address, addressesSet) =>
688 + _fetchAddressHistory(address, addressesSet, currentHeight)
689 + .then((history) => history.isNotEmpty ? address.address : null),
690 + type: type);
691 + }
692 }
612 - });
613 - });
693 + }));
694 + }));
695
615 - for (var sh in addressHashes.keys) {
616 - var balanceData = await electrumClient.getBalance(sh);
617 - var addressRecord = addressHashes[sh];
618 - if (addressRecord != null) {
619 - addressRecord.balance = balanceData['confirmed'] as int? ?? 0;
620 - }
621 - }
696 + return historiesWithDetails;
697 + } catch (e) {
698 + print(e.toString());
699 + return {};
700 + }
701 + }
702
623 - addressHashes.forEach((sh, addressRecord) {
624 - addressRecord.txCount = newTxCounts[sh] ?? 0;
625 - });
703 + Future<Map<String, ElectrumTransactionInfo>> _fetchAddressHistory(
704 + BitcoinAddressRecord addressRecord, Set<String> addressesSet, int currentHeight) async {
705 + try {
706 + final Map<String, ElectrumTransactionInfo> historiesWithDetails = {};
707 +
708 + final history = await electrumClient
709 + .getHistory(addressRecord.scriptHash ?? addressRecord.updateScriptHash(network));
710 +
711 + if (history.isNotEmpty) {
712 + addressRecord.setAsUsed();
713 +
714 + await Future.wait(history.map((transaction) async {
715 + final txid = transaction['tx_hash'] as String;
716 + final height = transaction['height'] as int;
717 + final storedTx = transactionHistory.transactions[txid];
718 +
719 + if (storedTx != null) {
720 + if (height > 0) {
721 + storedTx.height = height;
722 + // the tx's block itself is the first confirmation so add 1
723 + storedTx.confirmations = currentHeight - height + 1;
724 + storedTx.isPending = storedTx.confirmations == 0;
725 + }
726 +
727 + historiesWithDetails[txid] = storedTx;
728 + } else {
729 + final tx = await fetchTransactionInfo(
730 + hash: txid, height: height, myAddresses: addressesSet, retryOnFailure: true);
731 +
732 + if (tx != null) {
733 + historiesWithDetails[txid] = tx;
734 +
735 + // Got a new transaction fetched, add it to the transaction history
736 + // instead of waiting all to finish, and next time it will be faster
737 + transactionHistory.addOne(tx);
738 + await transactionHistory.save();
739 + }
740 + }
741
627 - final historiesWithDetails = await Future.wait(normalizedHistories.map((transaction) {
628 - try {
629 - return fetchTransactionInfo(
630 - hash: transaction['tx_hash'] as String, height: transaction['height'] as int);
631 - } catch (_) {
742 return Future.value(null);
633 - }
634 - }));
743 + }));
744 + }
745
636 - return historiesWithDetails.fold<Map<String, ElectrumTransactionInfo>>(
637 - <String, ElectrumTransactionInfo>{}, (acc, tx) {
638 - if (tx == null) {
639 - return acc;
640 - }
641 - acc[tx.id] = acc[tx.id]?.updated(tx) ?? tx;
642 - return acc;
643 - });
746 + return historiesWithDetails;
747 } catch (e) {
748 print(e.toString());
749 return {};
@@ -654,10 +757,8 @@ abstract class ElectrumWalletBase
757 }
758
759 _isTransactionUpdating = true;
657 - final transactions = await fetchTransactions();
658 - transactionHistory.addMany(transactions);
760 + await fetchTransactions();
761 walletAddresses.updateReceiveAddresses();
660 - await transactionHistory.save();
762 _isTransactionUpdating = false;
763 } catch (e, stacktrace) {
764 print(stacktrace);
@@ -688,11 +789,11 @@ abstract class ElectrumWalletBase
789 }
790
791 Future<ElectrumBalance> _fetchBalances() async {
691 - final addresses = walletAddresses.addresses.toList();
792 + final addresses = walletAddresses.allAddresses.toList();
793 final balanceFutures = <Future<Map<String, dynamic>>>[];
794 for (var i = 0; i < addresses.length; i++) {
795 final addressRecord = addresses[i];
695 - final sh = scriptHash(addressRecord.address, networkType: networkType);
796 + final sh = scriptHash(addressRecord.address, network: network);
797 final balanceFuture = electrumClient.getBalance(sh);
798 balanceFutures.add(balanceFuture);
799 }
@@ -701,6 +802,7 @@ abstract class ElectrumWalletBase
802 unspentCoinsInfo.values.forEach((info) {
803 unspentCoins.forEach((element) {
804 if (element.hash == info.hash &&
805 + element.vout == info.vout &&
806 info.isFrozen &&
807 element.bitcoinAddressRecord.address == info.address &&
808 element.value == info.value) {
@@ -738,10 +840,10 @@ abstract class ElectrumWalletBase
840 String getChangeAddress() {
841 const minCountOfHiddenAddresses = 5;
842 final random = Random();
741 - var addresses = walletAddresses.addresses.where((addr) => addr.isHidden).toList();
843 + var addresses = walletAddresses.allAddresses.where((addr) => addr.isHidden).toList();
844
845 if (addresses.length < minCountOfHiddenAddresses) {
744 - addresses = walletAddresses.addresses.toList();
846 + addresses = walletAddresses.allAddresses.toList();
847 }
848
849 return addresses[random.nextInt(addresses.length)].address;
@@ -753,9 +855,62 @@ abstract class ElectrumWalletBase
855 @override
856 String signMessage(String message, {String? address = null}) {
857 final index = address != null
756 - ? walletAddresses.addresses.firstWhere((element) => element.address == address).index
858 + ? walletAddresses.allAddresses.firstWhere((element) => element.address == address).index
859 : null;
860 final HD = index == null ? hd : hd.derive(index);
861 return base64Encode(HD.signMessage(message));
862 }
863 }
864 +
865 +class EstimateTxParams {
866 + EstimateTxParams(
867 + {required this.amount,
868 + required this.feeRate,
869 + required this.priority,
870 + required this.outputsCount,
871 + required this.size});
872 +
873 + final int amount;
874 + final int feeRate;
875 + final TransactionPriority priority;
876 + final int outputsCount;
877 + final int size;
878 +}
879 +
880 +class EstimatedTxResult {
881 + EstimatedTxResult(
882 + {required this.utxos, required this.privateKeys, required this.fee, required this.amount});
883 +
884 + final List<UtxoWithAddress> utxos;
885 + final List<ECPrivate> privateKeys;
886 + final int fee;
887 + final int amount;
888 +}
889 +
890 +BitcoinBaseAddress _addressTypeFromStr(String address, BasedUtxoNetwork network) {
891 + if (P2pkhAddress.regex.hasMatch(address)) {
892 + return P2pkhAddress.fromAddress(address: address, network: network);
893 + } else if (P2shAddress.regex.hasMatch(address)) {
894 + return P2shAddress.fromAddress(address: address, network: network);
895 + } else if (P2wshAddress.regex.hasMatch(address)) {
896 + return P2wshAddress.fromAddress(address: address, network: network);
897 + } else if (P2trAddress.regex.hasMatch(address)) {
898 + return P2trAddress.fromAddress(address: address, network: network);
899 + } else {
900 + return P2wpkhAddress.fromAddress(address: address, network: network);
901 + }
902 +}
903 +
904 +BitcoinAddressType _getScriptType(BitcoinBaseAddress type) {
905 + if (type is P2pkhAddress) {
906 + return P2pkhAddressType.p2pkh;
907 + } else if (type is P2shAddress) {
908 + return P2shAddressType.p2wpkhInP2sh;
909 + } else if (type is P2wshAddress) {
910 + return SegwitAddresType.p2wsh;
911 + } else if (type is P2trAddress) {
912 + return SegwitAddresType.p2tr;
913 + } else {
914 + return SegwitAddresType.p2wpkh;
915 + }
916 +}
cw_bitcoin/lib/electrum_wallet_addresses.dart
+180 -123
@@ -1,8 +1,8 @@
1 -import 'package:bitbox/bitbox.dart' as bitbox;
1 +import 'package:bitcoin_base/bitcoin_base.dart';
2 import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
3 +import 'package:bitbox/bitbox.dart' as bitbox;
4 import 'package:cw_bitcoin/bitcoin_address_record.dart';
5 import 'package:cw_bitcoin/electrum.dart';
5 -import 'package:cw_bitcoin/script_hash.dart';
6 import 'package:cw_core/wallet_addresses.dart';
7 import 'package:cw_core/wallet_info.dart';
8 import 'package:cw_core/wallet_type.dart';
@@ -12,25 +12,41 @@ part 'electrum_wallet_addresses.g.dart';
12
13 class ElectrumWalletAddresses = ElectrumWalletAddressesBase with _$ElectrumWalletAddresses;
14
15 +const List<BitcoinAddressType> ADDRESS_TYPES = [
16 + SegwitAddresType.p2wpkh,
17 + P2pkhAddressType.p2pkh,
18 + SegwitAddresType.p2tr,
19 + SegwitAddresType.p2wsh,
20 + P2shAddressType.p2wpkhInP2sh,
21 +];
22 +
23 abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
16 - ElectrumWalletAddressesBase(WalletInfo walletInfo,
17 - {required this.mainHd,
18 - required this.sideHd,
19 - required this.electrumClient,
20 - required this.networkType,
21 - List<BitcoinAddressRecord>? initialAddresses,
22 - int initialRegularAddressIndex = 0,
23 - int initialChangeAddressIndex = 0})
24 - : addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
24 + ElectrumWalletAddressesBase(
25 + WalletInfo walletInfo, {
26 + required this.mainHd,
27 + required this.sideHd,
28 + required this.electrumClient,
29 + required this.network,
30 + List<BitcoinAddressRecord>? initialAddresses,
31 + Map<String, int>? initialRegularAddressIndex,
32 + Map<String, int>? initialChangeAddressIndex,
33 + }) : _addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
34 + addressesByReceiveType =
35 + ObservableList<BitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()),
36 receiveAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
37 .where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed)
38 .toSet()),
39 changeAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
40 .where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed)
41 .toSet()),
31 - currentReceiveAddressIndex = initialRegularAddressIndex,
32 - currentChangeAddressIndex = initialChangeAddressIndex,
33 - super(walletInfo);
42 + currentReceiveAddressIndexByType = initialRegularAddressIndex ?? {},
43 + currentChangeAddressIndexByType = initialChangeAddressIndex ?? {},
44 + _addressPageType = walletInfo.addressPageType != null
45 + ? BitcoinAddressType.fromValue(walletInfo.addressPageType!)
46 + : SegwitAddresType.p2wpkh,
47 + super(walletInfo) {
48 + updateAddressesByMatch();
49 + }
50
51 static const defaultReceiveAddressesCount = 22;
52 static const defaultChangeAddressesCount = 17;
@@ -40,37 +56,48 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
56
57 static String toLegacy(String address) => bitbox.Address.toLegacyAddress(address);
58
43 - final ObservableList<BitcoinAddressRecord> addresses;
59 + final ObservableList<BitcoinAddressRecord> _addresses;
60 + // Matched by addressPageType
61 + late ObservableList<BitcoinAddressRecord> addressesByReceiveType;
62 final ObservableList<BitcoinAddressRecord> receiveAddresses;
63 final ObservableList<BitcoinAddressRecord> changeAddresses;
64 final ElectrumClient electrumClient;
47 - final bitcoin.NetworkType networkType;
65 + final BasedUtxoNetwork network;
66 final bitcoin.HDWallet mainHd;
67 final bitcoin.HDWallet sideHd;
68
69 + @observable
70 + BitcoinAddressType _addressPageType = SegwitAddresType.p2wpkh;
71 +
72 + @computed
73 + BitcoinAddressType get addressPageType => _addressPageType;
74 +
75 + @computed
76 + List<BitcoinAddressRecord> get allAddresses => _addresses;
77 +
78 @override
79 @computed
80 String get address {
54 - if (isEnabledAutoGenerateSubaddress) {
55 - if (receiveAddresses.isEmpty) {
56 - final newAddress = generateNewAddress(hd: mainHd).address;
57 - return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(newAddress) : newAddress;
58 - }
59 - final receiveAddress = receiveAddresses.first.address;
81 + String receiveAddress;
82 +
83 + final typeMatchingReceiveAddresses = receiveAddresses.where(_isAddressPageTypeMatch);
84
61 - return walletInfo.type == WalletType.bitcoinCash
62 - ? toCashAddr(receiveAddress)
63 - : receiveAddress;
85 + if ((isEnabledAutoGenerateSubaddress && receiveAddresses.isEmpty) ||
86 + typeMatchingReceiveAddresses.isEmpty) {
87 + receiveAddress = generateNewAddress().address;
88 } else {
65 - final receiveAddress = (receiveAddresses.first.address != addresses.first.address &&
66 - previousAddressRecord != null)
67 - ? previousAddressRecord!.address
68 - : addresses.first.address;
69 -
70 - return walletInfo.type == WalletType.bitcoinCash
71 - ? toCashAddr(receiveAddress)
72 - : receiveAddress;
89 + final previousAddressMatchesType =
90 + previousAddressRecord != null && previousAddressRecord!.type == addressPageType;
91 +
92 + if (previousAddressMatchesType &&
93 + typeMatchingReceiveAddresses.first.address != addressesByReceiveType.first.address) {
94 + receiveAddress = previousAddressRecord!.address;
95 + } else {
96 + receiveAddress = typeMatchingReceiveAddresses.first.address;
97 + }
98 }
99 +
100 + return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(receiveAddress) : receiveAddress;
101 }
102
103 @observable
@@ -81,7 +108,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
108 if (addr.startsWith('bitcoincash:')) {
109 addr = toLegacy(addr);
110 }
84 - final addressRecord = addresses.firstWhere((addressRecord) => addressRecord.address == addr);
111 + final addressRecord = _addresses.firstWhere((addressRecord) => addressRecord.address == addr);
112
113 previousAddressRecord = addressRecord;
114 receiveAddresses.remove(addressRecord);
@@ -89,16 +116,29 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
116 }
117
118 @override
92 - String get primaryAddress => getAddress(index: 0, hd: mainHd);
119 + String get primaryAddress => getAddress(index: 0, hd: mainHd, addressType: addressPageType);
120 +
121 + Map<String, int> currentReceiveAddressIndexByType;
122 +
123 + int get currentReceiveAddressIndex =>
124 + currentReceiveAddressIndexByType[_addressPageType.toString()] ?? 0;
125 +
126 + void set currentReceiveAddressIndex(int index) =>
127 + currentReceiveAddressIndexByType[_addressPageType.toString()] = index;
128 +
129 + Map<String, int> currentChangeAddressIndexByType;
130 +
131 + int get currentChangeAddressIndex =>
132 + currentChangeAddressIndexByType[_addressPageType.toString()] ?? 0;
133
94 - int currentReceiveAddressIndex;
95 - int currentChangeAddressIndex;
134 + void set currentChangeAddressIndex(int index) =>
135 + currentChangeAddressIndexByType[_addressPageType.toString()] = index;
136
137 @observable
138 BitcoinAddressRecord? previousAddressRecord;
139
140 @computed
101 - int get totalCountOfReceiveAddresses => addresses.fold(0, (acc, addressRecord) {
141 + int get totalCountOfReceiveAddresses => addressesByReceiveType.fold(0, (acc, addressRecord) {
142 if (!addressRecord.isHidden) {
143 return acc + 1;
144 }
@@ -106,22 +146,21 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
146 });
147
148 @computed
109 - int get totalCountOfChangeAddresses => addresses.fold(0, (acc, addressRecord) {
149 + int get totalCountOfChangeAddresses => addressesByReceiveType.fold(0, (acc, addressRecord) {
150 if (addressRecord.isHidden) {
151 return acc + 1;
152 }
153 return acc;
154 });
155
116 - Future<void> discoverAddresses() async {
117 - await _discoverAddresses(mainHd, false);
118 - await _discoverAddresses(sideHd, true);
119 - await updateAddressesInBox();
120 - }
121 -
156 @override
157 Future<void> init() async {
158 await _generateInitialAddresses();
159 + await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
160 + await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh);
161 + await _generateInitialAddresses(type: SegwitAddresType.p2tr);
162 + await _generateInitialAddresses(type: SegwitAddresType.p2wsh);
163 + updateAddressesByMatch();
164 updateReceiveAddresses();
165 updateChangeAddresses();
166 await updateAddressesInBox();
@@ -141,10 +180,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
180
181 if (changeAddresses.isEmpty) {
182 final newAddresses = await _createNewAddresses(gap,
144 - hd: sideHd,
183 startIndex: totalCountOfChangeAddresses > 0 ? totalCountOfChangeAddresses - 1 : 0,
184 isHidden: true);
147 - _addAddresses(newAddresses);
185 + addAddresses(newAddresses);
186 }
187
188 if (currentChangeAddressIndex >= changeAddresses.length) {
@@ -157,19 +195,26 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
195 return address;
196 }
197
160 - BitcoinAddressRecord generateNewAddress({bitcoin.HDWallet? hd, String? label}) {
161 - final isHidden = hd == sideHd;
162 -
163 - final newAddressIndex = addresses.fold(
164 - 0, (int acc, addressRecord) => isHidden == addressRecord.isHidden ? acc + 1 : acc);
165 -
166 - final address = BitcoinAddressRecord(getAddress(index: newAddressIndex, hd: hd ?? sideHd),
167 - index: newAddressIndex, isHidden: isHidden, name: label ?? '');
168 - addresses.add(address);
198 + BitcoinAddressRecord generateNewAddress({String label = ''}) {
199 + final newAddressIndex = addressesByReceiveType.fold(
200 + 0, (int acc, addressRecord) => addressRecord.isHidden == false ? acc + 1 : acc);
201 +
202 + final address = BitcoinAddressRecord(
203 + getAddress(index: newAddressIndex, hd: mainHd, addressType: addressPageType),
204 + index: newAddressIndex,
205 + isHidden: false,
206 + name: label,
207 + type: addressPageType,
208 + network: network,
209 + );
210 + _addresses.add(address);
211 + updateAddressesByMatch();
212 return address;
213 }
214
172 - String getAddress({required int index, required bitcoin.HDWallet hd}) => '';
215 + String getAddress(
216 + {required int index, required bitcoin.HDWallet hd, BitcoinAddressType? addressType}) =>
217 + '';
218
219 @override
220 Future<void> updateAddressesInBox() async {
@@ -187,126 +232,138 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
232 if (address.startsWith('bitcoincash:')) {
233 address = toLegacy(address);
234 }
190 - final addressRecord = addresses.firstWhere((addressRecord) => addressRecord.address == address);
235 + final addressRecord =
236 + _addresses.firstWhere((addressRecord) => addressRecord.address == address);
237 addressRecord.setNewName(label);
192 - final index = addresses.indexOf(addressRecord);
193 - addresses.remove(addressRecord);
194 - addresses.insert(index, addressRecord);
238 + final index = _addresses.indexOf(addressRecord);
239 + _addresses.remove(addressRecord);
240 + _addresses.insert(index, addressRecord);
241 + }
242 +
243 + @action
244 + void updateAddressesByMatch() {
245 + addressesByReceiveType.clear();
246 + addressesByReceiveType.addAll(_addresses.where(_isAddressPageTypeMatch).toList());
247 }
248
249 @action
250 void updateReceiveAddresses() {
251 receiveAddresses.removeRange(0, receiveAddresses.length);
252 final newAddresses =
201 - addresses.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed);
253 + _addresses.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed);
254 receiveAddresses.addAll(newAddresses);
255 }
256
257 @action
258 void updateChangeAddresses() {
259 changeAddresses.removeRange(0, changeAddresses.length);
208 - final newAddresses =
209 - addresses.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed);
260 + final newAddresses = _addresses.where((addressRecord) =>
261 + addressRecord.isHidden &&
262 + !addressRecord.isUsed &&
263 + // TODO: feature to change change address type. For now fixed to p2wpkh, the cheapest type
264 + addressRecord.type == SegwitAddresType.p2wpkh);
265 changeAddresses.addAll(newAddresses);
266 }
267
213 - Future<void> _discoverAddresses(bitcoin.HDWallet hd, bool isHidden) async {
214 - var hasAddrUse = true;
215 - List<BitcoinAddressRecord> addrs;
216 -
217 - if (addresses.isNotEmpty) {
218 -
219 -
220 - if(!isHidden) {
221 - final receiveAddressesList = addresses.where((addr) => !addr.isHidden).toList();
222 - validateSideHdAddresses(receiveAddressesList);
223 - }
224 -
225 - addrs = addresses.where((addr) => addr.isHidden == isHidden).toList();
226 - } else {
227 - addrs = await _createNewAddresses(
228 - isHidden ? defaultChangeAddressesCount : defaultReceiveAddressesCount,
229 - startIndex: 0,
230 - hd: hd,
231 - isHidden: isHidden);
268 + @action
269 + Future<void> discoverAddresses(List<BitcoinAddressRecord> addressList, bool isHidden,
270 + Future<String?> Function(BitcoinAddressRecord, Set<String>) getAddressHistory,
271 + {BitcoinAddressType type = SegwitAddresType.p2wpkh}) async {
272 + if (!isHidden) {
273 + _validateSideHdAddresses(addressList.toList());
274 }
275
234 - while (hasAddrUse) {
235 - final addr = addrs.last.address;
236 - hasAddrUse = await _hasAddressUsed(addr);
276 + final newAddresses = await _createNewAddresses(gap,
277 + startIndex: addressList.length, isHidden: isHidden, type: type);
278 + addAddresses(newAddresses);
279
238 - if (!hasAddrUse) {
239 - break;
240 - }
241 -
242 - final start = addrs.length;
243 - final count = start + gap;
244 - final batch = await _createNewAddresses(count, startIndex: start, hd: hd, isHidden: isHidden);
245 - addrs.addAll(batch);
246 - }
280 + final addressesWithHistory = await Future.wait(newAddresses
281 + .map((addr) => getAddressHistory(addr, _addresses.map((e) => e.address).toSet())));
282 + final isLastAddressUsed = addressesWithHistory.last == addressList.last.address;
283
248 - if (addresses.length < addrs.length) {
249 - _addAddresses(addrs);
284 + if (isLastAddressUsed) {
285 + discoverAddresses(addressList, isHidden, getAddressHistory, type: type);
286 }
287 }
288
253 - Future<void> _generateInitialAddresses() async {
289 + Future<void> _generateInitialAddresses(
290 + {BitcoinAddressType type = SegwitAddresType.p2wpkh}) async {
291 var countOfReceiveAddresses = 0;
292 var countOfHiddenAddresses = 0;
293
257 - addresses.forEach((addr) {
258 - if (addr.isHidden) {
259 - countOfHiddenAddresses += 1;
260 - return;
261 - }
294 + _addresses.forEach((addr) {
295 + if (addr.type == type) {
296 + if (addr.isHidden) {
297 + countOfHiddenAddresses += 1;
298 + return;
299 + }
300
263 - countOfReceiveAddresses += 1;
301 + countOfReceiveAddresses += 1;
302 + }
303 });
304
305 if (countOfReceiveAddresses < defaultReceiveAddressesCount) {
306 final addressesCount = defaultReceiveAddressesCount - countOfReceiveAddresses;
307 final newAddresses = await _createNewAddresses(addressesCount,
269 - startIndex: countOfReceiveAddresses, hd: mainHd, isHidden: false);
270 - addresses.addAll(newAddresses);
308 + startIndex: countOfReceiveAddresses, isHidden: false, type: type);
309 + addAddresses(newAddresses);
310 }
311
312 if (countOfHiddenAddresses < defaultChangeAddressesCount) {
313 final addressesCount = defaultChangeAddressesCount - countOfHiddenAddresses;
314 final newAddresses = await _createNewAddresses(addressesCount,
276 - startIndex: countOfHiddenAddresses, hd: sideHd, isHidden: true);
277 - addresses.addAll(newAddresses);
315 + startIndex: countOfHiddenAddresses, isHidden: true, type: type);
316 + addAddresses(newAddresses);
317 }
318 }
319
320 Future<List<BitcoinAddressRecord>> _createNewAddresses(int count,
282 - {required bitcoin.HDWallet hd, int startIndex = 0, bool isHidden = false}) async {
321 + {int startIndex = 0, bool isHidden = false, BitcoinAddressType? type}) async {
322 final list = <BitcoinAddressRecord>[];
323
324 for (var i = startIndex; i < count + startIndex; i++) {
286 - final address =
287 - BitcoinAddressRecord(getAddress(index: i, hd: hd), index: i, isHidden: isHidden);
325 + final address = BitcoinAddressRecord(
326 + getAddress(index: i, hd: _getHd(isHidden), addressType: type ?? addressPageType),
327 + index: i,
328 + isHidden: isHidden,
329 + type: type ?? addressPageType,
330 + network: network,
331 + );
332 list.add(address);
333 }
334
335 return list;
336 }
337
294 - void _addAddresses(Iterable<BitcoinAddressRecord> addresses) {
295 - final addressesSet = this.addresses.toSet();
338 + @action
339 + void addAddresses(Iterable<BitcoinAddressRecord> addresses) {
340 + final addressesSet = this._addresses.toSet();
341 addressesSet.addAll(addresses);
297 - this.addresses.removeRange(0, this.addresses.length);
298 - this.addresses.addAll(addressesSet);
299 - }
300 -
301 - Future<bool> _hasAddressUsed(String address) async {
302 - final sh = scriptHash(address, networkType: networkType);
303 - final transactionHistory = await electrumClient.getHistory(sh);
304 - return transactionHistory.isNotEmpty;
342 + this._addresses.clear();
343 + this._addresses.addAll(addressesSet);
344 + updateAddressesByMatch();
345 }
346
307 - void validateSideHdAddresses(List<BitcoinAddressRecord> addrWithTransactions) {
347 + void _validateSideHdAddresses(List<BitcoinAddressRecord> addrWithTransactions) {
348 addrWithTransactions.forEach((element) {
309 - if (element.address != getAddress(index: element.index, hd: mainHd)) element.isHidden = true;
349 + if (element.address !=
350 + getAddress(index: element.index, hd: mainHd, addressType: element.type))
351 + element.isHidden = true;
352 });
353 }
354 +
355 + @action
356 + Future<void> setAddressType(BitcoinAddressType type) async {
357 + _addressPageType = type;
358 + updateAddressesByMatch();
359 + walletInfo.addressPageType = addressPageType.toString();
360 + await walletInfo.save();
361 + }
362 +
363 + bool _isAddressPageTypeMatch(BitcoinAddressRecord addressRecord) {
364 + return _isAddressByType(addressRecord, addressPageType);
365 + }
366 +
367 + bitcoin.HDWallet _getHd(bool isHidden) => isHidden ? sideHd : mainHd;
368 + bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type;
369 }
cw_bitcoin/lib/electrum_wallet_snapshot.dart
+34 -15
@@ -1,12 +1,13 @@
1 import 'dart:convert';
2 +import 'package:bitcoin_base/bitcoin_base.dart';
3 import 'package:cw_bitcoin/bitcoin_address_record.dart';
4 import 'package:cw_bitcoin/electrum_balance.dart';
5 import 'package:cw_core/pathForWallet.dart';
6 import 'package:cw_core/utils/file.dart';
7 import 'package:cw_core/wallet_type.dart';
8
8 -class ElectrumWallletSnapshot {
9 - ElectrumWallletSnapshot({
9 +class ElectrumWalletSnapshot {
10 + ElectrumWalletSnapshot({
11 required this.name,
12 required this.type,
13 required this.password,
@@ -14,19 +15,24 @@ class ElectrumWallletSnapshot {
15 required this.addresses,
16 required this.balance,
17 required this.regularAddressIndex,
17 - required this.changeAddressIndex});
18 + required this.changeAddressIndex,
19 + required this.addressPageType,
20 + required this.network,
21 + });
22
23 final String name;
24 final String password;
25 final WalletType type;
26 + final String addressPageType;
27 + final BasedUtxoNetwork network;
28
29 String mnemonic;
30 List<BitcoinAddressRecord> addresses;
31 ElectrumBalance balance;
26 - int regularAddressIndex;
27 - int changeAddressIndex;
32 + Map<String, int> regularAddressIndex;
33 + Map<String, int> changeAddressIndex;
34
29 - static Future<ElectrumWallletSnapshot> load(String name, WalletType type, String password) async {
35 + static Future<ElectrumWalletSnapshot> load(String name, WalletType type, String password, BasedUtxoNetwork? network) async {
36 final path = await pathForWallet(name: name, type: type);
37 final jsonSource = await read(path: path, password: password);
38 final data = json.decode(jsonSource) as Map;
@@ -34,26 +40,39 @@ class ElectrumWallletSnapshot {
40 final mnemonic = data['mnemonic'] as String;
41 final addresses = addressesTmp
42 .whereType<String>()
37 - .map((addr) => BitcoinAddressRecord.fromJSON(addr))
43 + .map((addr) => BitcoinAddressRecord.fromJSON(addr, network))
44 .toList();
45 final balance = ElectrumBalance.fromJSON(data['balance'] as String) ??
46 ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
41 - var regularAddressIndex = 0;
42 - var changeAddressIndex = 0;
47 + var regularAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
48 + var changeAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
49
50 try {
45 - regularAddressIndex = int.parse(data['account_index'] as String? ?? '0');
46 - changeAddressIndex = int.parse(data['change_address_index'] as String? ?? '0');
47 - } catch (_) {}
51 + regularAddressIndexByType = {
52 + SegwitAddresType.p2wpkh.toString(): int.parse(data['account_index'] as String? ?? '0')
53 + };
54 + changeAddressIndexByType = {
55 + SegwitAddresType.p2wpkh.toString():
56 + int.parse(data['change_address_index'] as String? ?? '0')
57 + };
58 + } catch (_) {
59 + try {
60 + regularAddressIndexByType = data["account_index"] as Map<String, int>? ?? {};
61 + changeAddressIndexByType = data["change_address_index"] as Map<String, int>? ?? {};
62 + } catch (_) {}
63 + }
64
49 - return ElectrumWallletSnapshot(
65 + return ElectrumWalletSnapshot(
66 name: name,
67 type: type,
68 password: password,
69 mnemonic: mnemonic,
70 addresses: addresses,
71 balance: balance,
56 - regularAddressIndex: regularAddressIndex,
57 - changeAddressIndex: changeAddressIndex);
72 + regularAddressIndex: regularAddressIndexByType,
73 + changeAddressIndex: changeAddressIndexByType,
74 + addressPageType: data['address_page_type'] as String? ?? SegwitAddresType.p2wpkh.toString(),
75 + network: data['network_type'] == 'testnet' ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet,
76 + );
77 }
78 }
cw_bitcoin/lib/litecoin_wallet.dart
+56 -50
@@ -1,3 +1,4 @@
1 +import 'package:bitcoin_base/bitcoin_base.dart';
2 import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
3 import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
4 import 'package:cw_core/crypto_currency.dart';
@@ -20,17 +21,18 @@ part 'litecoin_wallet.g.dart';
21 class LitecoinWallet = LitecoinWalletBase with _$LitecoinWallet;
22
23 abstract class LitecoinWalletBase extends ElectrumWallet with Store {
23 - LitecoinWalletBase(
24 - {required String mnemonic,
25 - required String password,
26 - required WalletInfo walletInfo,
27 - required Box<UnspentCoinsInfo> unspentCoinsInfo,
28 - required Uint8List seedBytes,
29 - List<BitcoinAddressRecord>? initialAddresses,
30 - ElectrumBalance? initialBalance,
31 - int initialRegularAddressIndex = 0,
32 - int initialChangeAddressIndex = 0})
33 - : super(
24 + LitecoinWalletBase({
25 + required String mnemonic,
26 + required String password,
27 + required WalletInfo walletInfo,
28 + required Box<UnspentCoinsInfo> unspentCoinsInfo,
29 + required Uint8List seedBytes,
30 + String? addressPageType,
31 + List<BitcoinAddressRecord>? initialAddresses,
32 + ElectrumBalance? initialBalance,
33 + Map<String, int>? initialRegularAddressIndex,
34 + Map<String, int>? initialChangeAddressIndex,
35 + }) : super(
36 mnemonic: mnemonic,
37 password: password,
38 walletInfo: walletInfo,
@@ -41,41 +43,42 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
43 seedBytes: seedBytes,
44 currency: CryptoCurrency.ltc) {
45 walletAddresses = LitecoinWalletAddresses(
44 - walletInfo,
45 - electrumClient: electrumClient,
46 - initialAddresses: initialAddresses,
47 - initialRegularAddressIndex: initialRegularAddressIndex,
48 - initialChangeAddressIndex: initialChangeAddressIndex,
49 - mainHd: hd,
50 - sideHd: bitcoin.HDWallet
51 - .fromSeed(seedBytes, network: networkType)
52 - .derivePath("m/0'/1"),
53 - networkType: networkType,);
46 + walletInfo,
47 + electrumClient: electrumClient,
48 + initialAddresses: initialAddresses,
49 + initialRegularAddressIndex: initialRegularAddressIndex,
50 + initialChangeAddressIndex: initialChangeAddressIndex,
51 + mainHd: hd,
52 + sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/1"),
53 + network: network,
54 + );
55 autorun((_) {
56 this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
57 });
58 }
59
59 - static Future<LitecoinWallet> create({
60 - required String mnemonic,
61 - required String password,
62 - required WalletInfo walletInfo,
63 - required Box<UnspentCoinsInfo> unspentCoinsInfo,
64 - List<BitcoinAddressRecord>? initialAddresses,
65 - ElectrumBalance? initialBalance,
66 - int initialRegularAddressIndex = 0,
67 - int initialChangeAddressIndex = 0
68 - }) async {
60 + static Future<LitecoinWallet> create(
61 + {required String mnemonic,
62 + required String password,
63 + required WalletInfo walletInfo,
64 + required Box<UnspentCoinsInfo> unspentCoinsInfo,
65 + String? addressPageType,
66 + List<BitcoinAddressRecord>? initialAddresses,
67 + ElectrumBalance? initialBalance,
68 + Map<String, int>? initialRegularAddressIndex,
69 + Map<String, int>? initialChangeAddressIndex}) async {
70 return LitecoinWallet(
70 - mnemonic: mnemonic,
71 - password: password,
72 - walletInfo: walletInfo,
73 - unspentCoinsInfo: unspentCoinsInfo,
74 - initialAddresses: initialAddresses,
75 - initialBalance: initialBalance,
76 - seedBytes: await mnemonicToSeedBytes(mnemonic),
77 - initialRegularAddressIndex: initialRegularAddressIndex,
78 - initialChangeAddressIndex: initialChangeAddressIndex);
71 + mnemonic: mnemonic,
72 + password: password,
73 + walletInfo: walletInfo,
74 + unspentCoinsInfo: unspentCoinsInfo,
75 + initialAddresses: initialAddresses,
76 + initialBalance: initialBalance,
77 + seedBytes: await mnemonicToSeedBytes(mnemonic),
78 + initialRegularAddressIndex: initialRegularAddressIndex,
79 + initialChangeAddressIndex: initialChangeAddressIndex,
80 + addressPageType: addressPageType,
81 + );
82 }
83
84 static Future<LitecoinWallet> open({
@@ -84,17 +87,20 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
87 required Box<UnspentCoinsInfo> unspentCoinsInfo,
88 required String password,
89 }) async {
87 - final snp = await ElectrumWallletSnapshot.load (name, walletInfo.type, password);
90 + final snp =
91 + await ElectrumWalletSnapshot.load(name, walletInfo.type, password, LitecoinNetwork.mainnet);
92 return LitecoinWallet(
89 - mnemonic: snp.mnemonic,
90 - password: password,
91 - walletInfo: walletInfo,
92 - unspentCoinsInfo: unspentCoinsInfo,
93 - initialAddresses: snp.addresses,
94 - initialBalance: snp.balance,
95 - seedBytes: await mnemonicToSeedBytes(snp.mnemonic),
96 - initialRegularAddressIndex: snp.regularAddressIndex,
97 - initialChangeAddressIndex: snp.changeAddressIndex);
93 + mnemonic: snp.mnemonic,
94 + password: password,
95 + walletInfo: walletInfo,
96 + unspentCoinsInfo: unspentCoinsInfo,
97 + initialAddresses: snp.addresses,
98 + initialBalance: snp.balance,
99 + seedBytes: await mnemonicToSeedBytes(snp.mnemonic),
100 + initialRegularAddressIndex: snp.regularAddressIndex,
101 + initialChangeAddressIndex: snp.changeAddressIndex,
102 + addressPageType: snp.addressPageType,
103 + );
104 }
105
106 @override
cw_bitcoin/lib/litecoin_wallet_addresses.dart
+16 -27
@@ -1,39 +1,28 @@
1 +import 'package:bitcoin_base/bitcoin_base.dart';
2 import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
2 -import 'package:cw_bitcoin/electrum.dart';
3 import 'package:cw_bitcoin/utils.dart';
4 -import 'package:cw_bitcoin/bitcoin_address_record.dart';
4 import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
5 import 'package:cw_core/wallet_info.dart';
7 -import 'package:flutter/foundation.dart';
6 import 'package:mobx/mobx.dart';
7
8 part 'litecoin_wallet_addresses.g.dart';
9
12 -class LitecoinWalletAddresses = LitecoinWalletAddressesBase
13 - with _$LitecoinWalletAddresses;
10 +class LitecoinWalletAddresses = LitecoinWalletAddressesBase with _$LitecoinWalletAddresses;
11
15 -abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses
16 - with Store {
12 +abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
13 LitecoinWalletAddressesBase(
18 - WalletInfo walletInfo,
19 - {required bitcoin.HDWallet mainHd,
20 - required bitcoin.HDWallet sideHd,
21 - required bitcoin.NetworkType networkType,
22 - required ElectrumClient electrumClient,
23 - List<BitcoinAddressRecord>? initialAddresses,
24 - int initialRegularAddressIndex = 0,
25 - int initialChangeAddressIndex = 0})
26 - : super(
27 - walletInfo,
28 - initialAddresses: initialAddresses,
29 - initialRegularAddressIndex: initialRegularAddressIndex,
30 - initialChangeAddressIndex: initialChangeAddressIndex,
31 - mainHd: mainHd,
32 - sideHd: sideHd,
33 - electrumClient: electrumClient,
34 - networkType: networkType);
14 + WalletInfo walletInfo, {
15 + required super.mainHd,
16 + required super.sideHd,
17 + required super.network,
18 + required super.electrumClient,
19 + super.initialAddresses,
20 + super.initialRegularAddressIndex,
21 + super.initialChangeAddressIndex,
22 + }) : super(walletInfo);
23
24 @override
37 - String getAddress({required int index, required bitcoin.HDWallet hd}) =>
38 - generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
39 -}
\ No newline at end of file
25 + String getAddress(
26 + {required int index, required bitcoin.HDWallet hd, BitcoinAddressType? addressType}) =>
27 + generateP2WPKHAddress(hd: hd, index: index, network: network);
28 +}
cw_bitcoin/lib/litecoin_wallet_service.dart
+3 -3
@@ -25,7 +25,7 @@ class LitecoinWalletService extends WalletService<
25 WalletType getType() => WalletType.litecoin;
26
27 @override
28 - Future<LitecoinWallet> create(BitcoinNewWalletCredentials credentials) async {
28 + Future<LitecoinWallet> create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async {
29 final wallet = await LitecoinWalletBase.create(
30 mnemonic: await generateMnemonic(),
31 password: credentials.password!,
@@ -94,12 +94,12 @@ class LitecoinWalletService extends WalletService<
94
95 @override
96 Future<LitecoinWallet> restoreFromKeys(
97 - BitcoinRestoreWalletFromWIFCredentials credentials) async =>
97 + BitcoinRestoreWalletFromWIFCredentials credentials, {bool? isTestnet}) async =>
98 throw UnimplementedError();
99
100 @override
101 Future<LitecoinWallet> restoreFromSeed(
102 - BitcoinRestoreWalletFromSeedCredentials credentials) async {
102 + BitcoinRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async {
103 if (!validateMnemonic(credentials.mnemonic)) {
104 throw BitcoinMnemonicIsIncorrectException();
105 }
cw_bitcoin/lib/pending_bitcoin_transaction.dart
+9 -12
@@ -1,5 +1,5 @@
1 import 'package:cw_bitcoin/bitcoin_commit_transaction_exception.dart';
2 -import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
2 +import 'package:bitcoin_base/bitcoin_base.dart';
3 import 'package:cw_core/pending_transaction.dart';
4 import 'package:cw_bitcoin/electrum.dart';
5 import 'package:cw_bitcoin/bitcoin_amount_format.dart';
@@ -9,22 +9,21 @@ import 'package:cw_core/wallet_type.dart';
9
10 class PendingBitcoinTransaction with PendingTransaction {
11 PendingBitcoinTransaction(this._tx, this.type,
12 - {required this.electrumClient,
13 - required this.amount,
14 - required this.fee})
12 + {required this.electrumClient, required this.amount, required this.fee, this.network})
13 : _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
14
15 final WalletType type;
18 - final bitcoin.Transaction _tx;
16 + final BtcTransaction _tx;
17 final ElectrumClient electrumClient;
18 final int amount;
19 final int fee;
20 + final BasedUtxoNetwork? network;
21
22 @override
24 - String get id => _tx.getId();
23 + String get id => _tx.txId();
24
25 @override
27 - String get hex => _tx.toHex();
26 + String get hex => _tx.serialize();
27
28 @override
29 String get amountFormatted => bitcoinAmountToString(amount: amount);
@@ -36,18 +35,16 @@ class PendingBitcoinTransaction with PendingTransaction {
35
36 @override
37 Future<void> commit() async {
39 - final result =
40 - await electrumClient.broadcastTransaction(transactionRaw: _tx.toHex());
38 + final result = await electrumClient.broadcastTransaction(transactionRaw: hex, network: network);
39
40 if (result.isEmpty) {
41 throw BitcoinCommitTransactionException();
42 }
43
46 - _listeners?.forEach((listener) => listener(transactionInfo()));
44 + _listeners.forEach((listener) => listener(transactionInfo()));
45 }
46
49 - void addListener(
50 - void Function(ElectrumTransactionInfo transaction) listener) =>
47 + void addListener(void Function(ElectrumTransactionInfo transaction) listener) =>
48 _listeners.add(listener);
49
50 ElectrumTransactionInfo transactionInfo() => ElectrumTransactionInfo(type,
cw_bitcoin/lib/script_hash.dart
+3 -4
@@ -1,9 +1,8 @@
1 -import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
1 +import 'package:bitcoin_base/bitcoin_base.dart';
2 import 'package:crypto/crypto.dart';
3
4 -String scriptHash(String address, {required bitcoin.NetworkType networkType}) {
5 - final outputScript =
6 - bitcoin.Address.addressToOutputScript(address, networkType);
4 +String scriptHash(String address, {required BasedUtxoNetwork network}) {
5 + final outputScript = addressToOutputScript(address: address, network: network);
6 final parts = sha256.convert(outputScript).toString().split('');
7 var res = '';
8
cw_bitcoin/lib/utils.dart
+21 -43
@@ -1,55 +1,33 @@
1 import 'dart:typed_data';
2 +import 'package:bitcoin_base/bitcoin_base.dart';
3 import 'package:flutter/foundation.dart';
4 import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
5 import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
6 import 'package:hex/hex.dart';
7
7 -bitcoin.PaymentData generatePaymentData(
8 - {required bitcoin.HDWallet hd, required int index}) =>
9 - PaymentData(
10 - pubkey: Uint8List.fromList(HEX.decode(hd.derive(index).pubKey!)));
8 +bitcoin.PaymentData generatePaymentData({required bitcoin.HDWallet hd, required int index}) =>
9 + PaymentData(pubkey: Uint8List.fromList(HEX.decode(hd.derive(index).pubKey!)));
10
12 -bitcoin.ECPair generateKeyPair(
13 - {required bitcoin.HDWallet hd,
14 - required int index,
15 - required bitcoin.NetworkType network}) =>
16 - bitcoin.ECPair.fromWIF(hd.derive(index).wif!, network: network);
11 +ECPrivate generateECPrivate(
12 + {required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
13 + ECPrivate.fromWif(hd.derive(index).wif!, netVersion: network.wifNetVer);
14
15 String generateP2WPKHAddress(
19 - {required bitcoin.HDWallet hd,
20 - required int index,
21 - required bitcoin.NetworkType networkType}) =>
22 - bitcoin
23 - .P2WPKH(
24 - data: PaymentData(
25 - pubkey:
26 - Uint8List.fromList(HEX.decode(hd.derive(index).pubKey!))),
27 - network: networkType)
28 - .data
29 - .address!;
16 + {required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
17 + ECPublic.fromHex(hd.derive(index).pubKey!).toP2wpkhAddress().toAddress(network);
18
31 -String generateP2WPKHAddressByPath(
32 - {required bitcoin.HDWallet hd,
33 - required String path,
34 - required bitcoin.NetworkType networkType}) =>
35 - bitcoin
36 - .P2WPKH(
37 - data: PaymentData(
38 - pubkey:
39 - Uint8List.fromList(HEX.decode(hd.derivePath(path).pubKey!))),
40 - network: networkType)
41 - .data
42 - .address!;
19 +String generateP2SHAddress(
20 + {required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
21 + ECPublic.fromHex(hd.derive(index).pubKey!).toP2wpkhInP2sh().toAddress(network);
22 +
23 +String generateP2WSHAddress(
24 + {required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
25 + ECPublic.fromHex(hd.derive(index).pubKey!).toP2wshAddress().toAddress(network);
26
27 String generateP2PKHAddress(
45 - {required bitcoin.HDWallet hd,
46 - required int index,
47 - required bitcoin.NetworkType networkType}) =>
48 - bitcoin
49 - .P2PKH(
50 - data: PaymentData(
51 - pubkey:
52 - Uint8List.fromList(HEX.decode(hd.derive(index).pubKey!))),
53 - network: networkType)
54 - .data
55 - .address!;
28 + {required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
29 + ECPublic.fromHex(hd.derive(index).pubKey!).toP2pkhAddress().toAddress(network);
30 +
31 +String generateP2TRAddress(
32 + {required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
33 + ECPublic.fromHex(hd.derive(index).pubKey!).toTaprootAddress().toAddress(network);
cw_bitcoin/pubspec.lock
+104 -79
@@ -21,18 +21,18 @@ packages:
21 dependency: transitive
22 description:
23 name: args
24 - sha256: "139d809800a412ebb26a3892da228b2d0ba36f0ef5d9a82166e5e52ec8d61611"
24 + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
25 url: "https://pub.dev"
26 source: hosted
27 - version: "2.3.2"
27 + version: "2.4.2"
28 asn1lib:
29 dependency: transitive
30 description:
31 name: asn1lib
32 - sha256: ab96a1cb3beeccf8145c52e449233fe68364c9641623acd3adad66f8184f1039
32 + sha256: c9c85fedbe2188b95133cbe960e16f5f448860f7133330e272edbbca5893ddc6
33 url: "https://pub.dev"
34 source: hosted
35 - version: "1.4.0"
35 + version: "1.5.2"
36 async:
37 dependency: transitive
38 description:
@@ -75,6 +75,15 @@ packages:
75 url: "https://github.com/cake-tech/bitbox-flutter.git"
76 source: git
77 version: "1.0.1"
78 + bitcoin_base:
79 + dependency: "direct main"
80 + description:
81 + path: "."
82 + ref: cake-update-v1
83 + resolved-ref: "9611e9db77e92a8434e918cdfb620068f6fcb1aa"
84 + url: "https://github.com/cake-tech/bitcoin_base.git"
85 + source: git
86 + version: "4.0.0"
87 bitcoin_flutter:
88 dependency: "direct main"
89 description:
@@ -84,6 +93,14 @@ packages:
93 url: "https://github.com/cake-tech/bitcoin_flutter.git"
94 source: git
95 version: "2.1.0"
96 + blockchain_utils:
97 + dependency: "direct main"
98 + description:
99 + name: blockchain_utils
100 + sha256: "9701dfaa74caad4daae1785f1ec4445cf7fb94e45620bc3a4aca1b9b281dc6c9"
101 + url: "https://pub.dev"
102 + source: hosted
103 + version: "1.6.0"
104 boolean_selector:
105 dependency: transitive
106 description:
@@ -104,10 +121,10 @@ packages:
121 dependency: transitive
122 description:
123 name: build
107 - sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777"
124 + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
125 url: "https://pub.dev"
126 source: hosted
110 - version: "2.3.1"
127 + version: "2.4.1"
128 build_config:
129 dependency: transitive
130 description:
@@ -120,10 +137,10 @@ packages:
137 dependency: transitive
138 description:
139 name: build_daemon
123 - sha256: "6bc5544ea6ce4428266e7ea680e945c68806c4aae2da0eb5e9ccf38df8d6acbf"
140 + sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1"
141 url: "https://pub.dev"
142 source: hosted
126 - version: "3.1.0"
143 + version: "4.0.1"
144 build_resolvers:
145 dependency: "direct dev"
146 description:
@@ -136,18 +153,18 @@ packages:
153 dependency: "direct dev"
154 description:
155 name: build_runner
139 - sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727
156 + sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21"
157 url: "https://pub.dev"
158 source: hosted
142 - version: "2.3.3"
159 + version: "2.4.8"
160 build_runner_core:
161 dependency: transitive
162 description:
163 name: build_runner_core
147 - sha256: "14febe0f5bac5ae474117a36099b4de6f1dbc52df6c5e55534b3da9591bf4292"
164 + sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41"
165 url: "https://pub.dev"
166 source: hosted
150 - version: "7.2.7"
167 + version: "7.2.10"
168 built_collection:
169 dependency: transitive
170 description:
@@ -160,10 +177,10 @@ packages:
177 dependency: transitive
178 description:
179 name: built_value
163 - sha256: "169565c8ad06adb760c3645bf71f00bff161b00002cace266cad42c5d22a7725"
180 + sha256: a3ec2e0f967bc47f69f95009bb93db936288d61d5343b9436e378b28a2f830c6
181 url: "https://pub.dev"
182 source: hosted
166 - version: "8.4.3"
183 + version: "8.9.0"
184 characters:
185 dependency: transitive
186 description:
@@ -176,10 +193,10 @@ packages:
193 dependency: transitive
194 description:
195 name: checked_yaml
179 - sha256: "3d1505d91afa809d177efd4eed5bb0eb65805097a1463abdd2add076effae311"
196 + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
197 url: "https://pub.dev"
198 source: hosted
182 - version: "2.0.2"
199 + version: "2.0.3"
200 clock:
201 dependency: transitive
202 description:
@@ -192,10 +209,10 @@ packages:
209 dependency: transitive
210 description:
211 name: code_builder
195 - sha256: "0d43dd1288fd145de1ecc9a3948ad4a6d5a82f0a14c4fdd0892260787d975cbe"
212 + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37
213 url: "https://pub.dev"
214 source: hosted
198 - version: "4.4.0"
215 + version: "4.10.0"
216 collection:
217 dependency: transitive
218 description:
@@ -216,18 +233,18 @@ packages:
233 dependency: transitive
234 description:
235 name: crypto
219 - sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67
236 + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
237 url: "https://pub.dev"
238 source: hosted
222 - version: "3.0.2"
239 + version: "3.0.3"
240 cryptography:
241 dependency: "direct main"
242 description:
243 name: cryptography
227 - sha256: e0e37f79665cd5c86e8897f9abe1accfe813c0cc5299dab22256e22fddc1fef8
244 + sha256: df156c5109286340817d21fa7b62f9140f17915077127dd70f8bd7a2a0997a35
245 url: "https://pub.dev"
246 source: hosted
230 - version: "2.0.5"
247 + version: "2.5.0"
248 cw_core:
249 dependency: "direct main"
250 description:
@@ -247,10 +264,10 @@ packages:
264 dependency: transitive
265 description:
266 name: encrypt
250 - sha256: "4fd4e4fdc21b9d7d4141823e1e6515cd94e7b8d84749504c232999fba25d9bbb"
267 + sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2"
268 url: "https://pub.dev"
269 source: hosted
253 - version: "5.0.1"
270 + version: "5.0.3"
271 fake_async:
272 dependency: transitive
273 description:
@@ -263,10 +280,10 @@ packages:
280 dependency: transitive
281 description:
282 name: ffi
266 - sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978
283 + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
284 url: "https://pub.dev"
285 source: hosted
269 - version: "2.0.1"
286 + version: "2.1.0"
287 file:
288 dependency: transitive
289 description:
@@ -292,10 +309,10 @@ packages:
309 dependency: "direct main"
310 description:
311 name: flutter_mobx
295 - sha256: "0da4add0016387a7bf309a0d0c41d36c6b3ae25ed7a176409267f166509e723e"
312 + sha256: "4a5d062ff85ed3759f4aac6410ff0ffae32e324b2e71ca722ae1b37b32e865f4"
313 url: "https://pub.dev"
314 source: hosted
298 - version: "2.0.6+5"
315 + version: "2.2.0+2"
316 flutter_test:
317 dependency: "direct dev"
318 description: flutter
@@ -313,18 +330,18 @@ packages:
330 dependency: transitive
331 description:
332 name: glob
316 - sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c"
333 + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
334 url: "https://pub.dev"
335 source: hosted
319 - version: "2.1.1"
336 + version: "2.1.2"
337 graphs:
338 dependency: transitive
339 description:
340 name: graphs
324 - sha256: f9e130f3259f52d26f0cfc0e964513796dafed572fa52e45d2f8d6ca14db39b2
341 + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19
342 url: "https://pub.dev"
343 source: hosted
327 - version: "2.2.0"
344 + version: "2.3.1"
345 hex:
346 dependency: transitive
347 description:
@@ -401,18 +418,18 @@ packages:
418 dependency: transitive
419 description:
420 name: json_annotation
404 - sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317
421 + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
422 url: "https://pub.dev"
423 source: hosted
407 - version: "4.8.0"
424 + version: "4.8.1"
425 logging:
426 dependency: transitive
427 description:
428 name: logging
412 - sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d"
429 + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
430 url: "https://pub.dev"
431 source: hosted
415 - version: "1.1.1"
432 + version: "1.2.0"
433 matcher:
434 dependency: transitive
435 description:
@@ -449,18 +466,26 @@ packages:
466 dependency: "direct main"
467 description:
468 name: mobx
452 - sha256: f1862bd92c6a903fab67338f27e2f731117c3cb9ea37cee1a487f9e4e0de314a
469 + sha256: "74ee54012dc7c1b3276eaa960a600a7418ef5f9997565deb8fca1fd88fb36b78"
470 url: "https://pub.dev"
471 source: hosted
455 - version: "2.1.3+1"
472 + version: "2.3.0+1"
473 mobx_codegen:
474 dependency: "direct dev"
475 description:
476 name: mobx_codegen
460 - sha256: "86122e410d8ea24dda0c69adb5c2a6ccadd5ce02ad46e144764e0d0184a06181"
477 + sha256: d4beb9cea4b7b014321235f8fdc7c2193ee0fe1d1198e9da7403f8bc85c4407c
478 url: "https://pub.dev"
479 source: hosted
463 - version: "2.1.1"
480 + version: "2.3.0"
481 + nested:
482 + dependency: transitive
483 + description:
484 + name: nested
485 + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
486 + url: "https://pub.dev"
487 + source: hosted
488 + version: "1.0.0"
489 package_config:
490 dependency: transitive
491 description:
@@ -481,26 +506,26 @@ packages:
506 dependency: "direct main"
507 description:
508 name: path_provider
484 - sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa
509 + sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
510 url: "https://pub.dev"
511 source: hosted
487 - version: "2.1.1"
512 + version: "2.1.2"
513 path_provider_android:
514 dependency: transitive
515 description:
516 name: path_provider_android
492 - sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72
517 + sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
518 url: "https://pub.dev"
519 source: hosted
495 - version: "2.2.1"
520 + version: "2.2.2"
521 path_provider_foundation:
522 dependency: transitive
523 description:
524 name: path_provider_foundation
500 - sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d"
525 + sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
526 url: "https://pub.dev"
527 source: hosted
503 - version: "2.3.1"
528 + version: "2.3.2"
529 path_provider_linux:
530 dependency: transitive
531 description:
@@ -513,10 +538,10 @@ packages:
538 dependency: transitive
539 description:
540 name: path_provider_platform_interface
516 - sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c"
541 + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
542 url: "https://pub.dev"
543 source: hosted
519 - version: "2.1.1"
544 + version: "2.1.2"
545 path_provider_windows:
546 dependency: transitive
547 description:
@@ -529,26 +554,26 @@ packages:
554 dependency: transitive
555 description:
556 name: platform
532 - sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
557 + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
558 url: "https://pub.dev"
559 source: hosted
535 - version: "3.1.0"
560 + version: "3.1.4"
561 plugin_platform_interface:
562 dependency: transitive
563 description:
564 name: plugin_platform_interface
540 - sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a
565 + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
566 url: "https://pub.dev"
567 source: hosted
543 - version: "2.1.3"
568 + version: "2.1.8"
569 pointycastle:
570 dependency: transitive
571 description:
572 name: pointycastle
548 - sha256: db7306cf0249f838d1a24af52b5a5887c5bf7f31d8bb4e827d071dc0939ad346
573 + sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29"
574 url: "https://pub.dev"
575 source: hosted
551 - version: "3.6.2"
576 + version: "3.7.4"
577 pool:
578 dependency: transitive
579 description:
@@ -557,30 +582,30 @@ packages:
582 url: "https://pub.dev"
583 source: hosted
584 version: "1.5.1"
560 - process:
585 + provider:
586 dependency: transitive
587 description:
563 - name: process
564 - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
588 + name: provider
589 + sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096"
590 url: "https://pub.dev"
591 source: hosted
567 - version: "4.2.4"
592 + version: "6.1.1"
593 pub_semver:
594 dependency: transitive
595 description:
596 name: pub_semver
572 - sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17"
597 + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
598 url: "https://pub.dev"
599 source: hosted
575 - version: "2.1.3"
600 + version: "2.1.4"
601 pubspec_parse:
602 dependency: transitive
603 description:
604 name: pubspec_parse
580 - sha256: "75f6614d6dde2dc68948dffbaa4fe5dae32cd700eb9fb763fe11dfb45a3c4d0a"
605 + sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367
606 url: "https://pub.dev"
607 source: hosted
583 - version: "1.2.1"
608 + version: "1.2.3"
609 rxdart:
610 dependency: "direct main"
611 description:
@@ -593,18 +618,18 @@ packages:
618 dependency: transitive
619 description:
620 name: shelf
596 - sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c
621 + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
622 url: "https://pub.dev"
623 source: hosted
599 - version: "1.4.0"
624 + version: "1.4.1"
625 shelf_web_socket:
626 dependency: transitive
627 description:
628 name: shelf_web_socket
604 - sha256: a988c0e8d8ffbdb8a28aa7ec8e449c260f3deb808781fe1284d22c5bba7156e8
629 + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
630 url: "https://pub.dev"
631 source: hosted
607 - version: "1.0.3"
632 + version: "1.0.4"
633 sky_engine:
634 dependency: transitive
635 description: flutter
@@ -702,10 +727,10 @@ packages:
727 dependency: transitive
728 description:
729 name: typed_data
705 - sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5"
730 + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
731 url: "https://pub.dev"
732 source: hosted
708 - version: "1.3.1"
733 + version: "1.3.2"
734 unorm_dart:
735 dependency: "direct main"
736 description:
@@ -726,42 +751,42 @@ packages:
751 dependency: transitive
752 description:
753 name: watcher
729 - sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0"
754 + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
755 url: "https://pub.dev"
756 source: hosted
732 - version: "1.0.2"
757 + version: "1.1.0"
758 web_socket_channel:
759 dependency: transitive
760 description:
761 name: web_socket_channel
737 - sha256: ca49c0bc209c687b887f30527fb6a9d80040b072cc2990f34b9bec3e7663101b
762 + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
763 url: "https://pub.dev"
764 source: hosted
740 - version: "2.3.0"
765 + version: "2.4.0"
766 win32:
767 dependency: transitive
768 description:
769 name: win32
745 - sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46
770 + sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3"
771 url: "https://pub.dev"
772 source: hosted
748 - version: "3.1.3"
773 + version: "5.0.9"
774 xdg_directories:
775 dependency: transitive
776 description:
777 name: xdg_directories
753 - sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86
778 + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
779 url: "https://pub.dev"
780 source: hosted
756 - version: "0.2.0+3"
781 + version: "1.0.4"
782 yaml:
783 dependency: transitive
784 description:
785 name: yaml
761 - sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370"
786 + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
787 url: "https://pub.dev"
788 source: hosted
764 - version: "3.1.1"
789 + version: "3.1.2"
790 sdks:
791 dart: ">=3.0.0 <4.0.0"
767 - flutter: ">=3.7.0"
792 + flutter: ">=3.10.0"
cw_bitcoin/pubspec.yaml
+5
@@ -30,6 +30,11 @@ dependencies:
30 rxdart: ^0.27.5
31 unorm_dart: ^0.2.0
32 cryptography: ^2.0.5
33 + bitcoin_base:
34 + git:
35 + url: https://github.com/cake-tech/bitcoin_base.git
36 + ref: cake-update-v1
37 + blockchain_utils: ^1.6.0
38
39 dev_dependencies:
40 flutter_test:
cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart
+57 -51
@@ -28,17 +28,18 @@ part 'bitcoin_cash_wallet.g.dart';
28 class BitcoinCashWallet = BitcoinCashWalletBase with _$BitcoinCashWallet;
29
30 abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
31 - BitcoinCashWalletBase(
32 - {required String mnemonic,
33 - required String password,
34 - required WalletInfo walletInfo,
35 - required Box<UnspentCoinsInfo> unspentCoinsInfo,
36 - required Uint8List seedBytes,
37 - List<BitcoinAddressRecord>? initialAddresses,
38 - ElectrumBalance? initialBalance,
39 - int initialRegularAddressIndex = 0,
40 - int initialChangeAddressIndex = 0})
41 - : super(
31 + BitcoinCashWalletBase({
32 + required String mnemonic,
33 + required String password,
34 + required WalletInfo walletInfo,
35 + required Box<UnspentCoinsInfo> unspentCoinsInfo,
36 + required Uint8List seedBytes,
37 + String? addressPageType,
38 + List<BitcoinAddressRecord>? initialAddresses,
39 + ElectrumBalance? initialBalance,
40 + Map<String, int>? initialRegularAddressIndex,
41 + Map<String, int>? initialChangeAddressIndex,
42 + }) : super(
43 mnemonic: mnemonic,
44 password: password,
45 walletInfo: walletInfo,
@@ -48,40 +49,43 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
49 initialBalance: initialBalance,
50 seedBytes: seedBytes,
51 currency: CryptoCurrency.bch) {
51 - walletAddresses = BitcoinCashWalletAddresses(walletInfo,
52 - electrumClient: electrumClient,
53 - initialAddresses: initialAddresses,
54 - initialRegularAddressIndex: initialRegularAddressIndex,
55 - initialChangeAddressIndex: initialChangeAddressIndex,
56 - mainHd: hd,
57 - sideHd: bitcoin.HDWallet.fromSeed(seedBytes)
58 - .derivePath("m/44'/145'/0'/1"),
59 - networkType: networkType);
52 + walletAddresses = BitcoinCashWalletAddresses(
53 + walletInfo,
54 + electrumClient: electrumClient,
55 + initialAddresses: initialAddresses,
56 + initialRegularAddressIndex: initialRegularAddressIndex,
57 + initialChangeAddressIndex: initialChangeAddressIndex,
58 + mainHd: hd,
59 + sideHd: bitcoin.HDWallet.fromSeed(seedBytes).derivePath("m/44'/145'/0'/1"),
60 + network: network,
61 + );
62 autorun((_) {
63 this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
64 });
65 }
66
65 -
67 static Future<BitcoinCashWallet> create(
68 {required String mnemonic,
69 required String password,
70 required WalletInfo walletInfo,
71 required Box<UnspentCoinsInfo> unspentCoinsInfo,
72 + String? addressPageType,
73 List<BitcoinAddressRecord>? initialAddresses,
74 ElectrumBalance? initialBalance,
73 - int initialRegularAddressIndex = 0,
74 - int initialChangeAddressIndex = 0}) async {
75 + Map<String, int>? initialRegularAddressIndex,
76 + Map<String, int>? initialChangeAddressIndex}) async {
77 return BitcoinCashWallet(
76 - mnemonic: mnemonic,
77 - password: password,
78 - walletInfo: walletInfo,
79 - unspentCoinsInfo: unspentCoinsInfo,
80 - initialAddresses: initialAddresses,
81 - initialBalance: initialBalance,
82 - seedBytes: await Mnemonic.toSeed(mnemonic),
83 - initialRegularAddressIndex: initialRegularAddressIndex,
84 - initialChangeAddressIndex: initialChangeAddressIndex);
78 + mnemonic: mnemonic,
79 + password: password,
80 + walletInfo: walletInfo,
81 + unspentCoinsInfo: unspentCoinsInfo,
82 + initialAddresses: initialAddresses,
83 + initialBalance: initialBalance,
84 + seedBytes: await Mnemonic.toSeed(mnemonic),
85 + initialRegularAddressIndex: initialRegularAddressIndex,
86 + initialChangeAddressIndex: initialChangeAddressIndex,
87 + addressPageType: addressPageType,
88 + );
89 }
90
91 static Future<BitcoinCashWallet> open({
@@ -90,17 +94,20 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
94 required Box<UnspentCoinsInfo> unspentCoinsInfo,
95 required String password,
96 }) async {
93 - final snp = await ElectrumWallletSnapshot.load(name, walletInfo.type, password);
97 + final snp = await ElectrumWalletSnapshot.load(
98 + name, walletInfo.type, password, BitcoinCashNetwork.mainnet);
99 return BitcoinCashWallet(
95 - mnemonic: snp.mnemonic,
96 - password: password,
97 - walletInfo: walletInfo,
98 - unspentCoinsInfo: unspentCoinsInfo,
99 - initialAddresses: snp.addresses,
100 - initialBalance: snp.balance,
101 - seedBytes: await Mnemonic.toSeed(snp.mnemonic),
102 - initialRegularAddressIndex: snp.regularAddressIndex,
103 - initialChangeAddressIndex: snp.changeAddressIndex);
100 + mnemonic: snp.mnemonic,
101 + password: password,
102 + walletInfo: walletInfo,
103 + unspentCoinsInfo: unspentCoinsInfo,
104 + initialAddresses: snp.addresses,
105 + initialBalance: snp.balance,
106 + seedBytes: await Mnemonic.toSeed(snp.mnemonic),
107 + initialRegularAddressIndex: snp.regularAddressIndex,
108 + initialChangeAddressIndex: snp.changeAddressIndex,
109 + addressPageType: snp.addressPageType,
110 + );
111 }
112
113 @override
@@ -270,20 +277,18 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
277 electrumClient: electrumClient, amount: amount, fee: fee);
278 }
279
273 - bitbox.ECPair generateKeyPair(
274 - {required bitcoin.HDWallet hd,
275 - required int index}) =>
280 + bitbox.ECPair generateKeyPair({required bitcoin.HDWallet hd, required int index}) =>
281 bitbox.ECPair.fromWIF(hd.derive(index).wif!);
282
283 @override
279 - int feeAmountForPriority(
280 - BitcoinTransactionPriority priority, int inputsCount, int outputsCount) =>
284 + int feeAmountForPriority(BitcoinTransactionPriority priority, int inputsCount, int outputsCount,
285 + {int? size}) =>
286 feeRate(priority) * bitbox.BitcoinCash.getByteCount(inputsCount, outputsCount);
287
283 - int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount) =>
288 + int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount, {int? size}) =>
289 feeRate * bitbox.BitcoinCash.getByteCount(inputsCount, outputsCount);
290
286 - int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount}) {
291 + int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount, int? size}) {
292 int inputsCount = 0;
293 int totalValue = 0;
294
@@ -323,9 +328,10 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
328 @override
329 String signMessage(String message, {String? address = null}) {
330 final index = address != null
326 - ? walletAddresses.addresses
331 + ? walletAddresses.allAddresses
332 .firstWhere((element) => element.address == AddressUtils.toLegacyAddress(address))
328 - .index : null;
333 + .index
334 + : null;
335 final HD = index == null ? hd : hd.derive(index);
336 return base64Encode(HD.signMessage(message));
337 }
cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart
+14 -20
@@ -1,6 +1,5 @@
1 +import 'package:bitcoin_base/bitcoin_base.dart';
2 import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
2 -import 'package:cw_bitcoin/bitcoin_address_record.dart';
3 -import 'package:cw_bitcoin/electrum.dart';
3 import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
4 import 'package:cw_bitcoin/utils.dart';
5 import 'package:cw_core/wallet_info.dart';
@@ -11,24 +10,19 @@ part 'bitcoin_cash_wallet_addresses.g.dart';
10 class BitcoinCashWalletAddresses = BitcoinCashWalletAddressesBase with _$BitcoinCashWalletAddresses;
11
12 abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses with Store {
14 - BitcoinCashWalletAddressesBase(WalletInfo walletInfo,
15 - {required bitcoin.HDWallet mainHd,
16 - required bitcoin.HDWallet sideHd,
17 - required bitcoin.NetworkType networkType,
18 - required ElectrumClient electrumClient,
19 - List<BitcoinAddressRecord>? initialAddresses,
20 - int initialRegularAddressIndex = 0,
21 - int initialChangeAddressIndex = 0})
22 - : super(walletInfo,
23 - initialAddresses: initialAddresses,
24 - initialRegularAddressIndex: initialRegularAddressIndex,
25 - initialChangeAddressIndex: initialChangeAddressIndex,
26 - mainHd: mainHd,
27 - sideHd: sideHd,
28 - electrumClient: electrumClient,
29 - networkType: networkType);
13 + BitcoinCashWalletAddressesBase(
14 + WalletInfo walletInfo, {
15 + required super.mainHd,
16 + required super.sideHd,
17 + required super.network,
18 + required super.electrumClient,
19 + super.initialAddresses,
20 + super.initialRegularAddressIndex,
21 + super.initialChangeAddressIndex,
22 + }) : super(walletInfo);
23
24 @override
32 - String getAddress({required int index, required bitcoin.HDWallet hd}) =>
33 - generateP2PKHAddress(hd: hd, index: index, networkType: networkType);
25 + String getAddress(
26 + {required int index, required bitcoin.HDWallet hd, BitcoinAddressType? addressType}) =>
27 + generateP2PKHAddress(hd: hd, index: index, network: network);
28 }
cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_service.dart
+22 -28
@@ -2,10 +2,7 @@ import 'dart:io';
2
3 import 'package:bip39/bip39.dart';
4 import 'package:cw_bitcoin_cash/cw_bitcoin_cash.dart';
5 -import 'package:cw_core/balance.dart';
5 import 'package:cw_core/pathForWallet.dart';
7 -import 'package:cw_core/transaction_history.dart';
8 -import 'package:cw_core/transaction_info.dart';
6 import 'package:cw_core/unspent_coins_info.dart';
7 import 'package:cw_core/wallet_base.dart';
8 import 'package:cw_core/wallet_info.dart';
@@ -15,8 +12,7 @@ import 'package:collection/collection.dart';
12 import 'package:hive/hive.dart';
13
14 class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredentials,
18 - BitcoinCashRestoreWalletFromSeedCredentials,
19 - BitcoinCashRestoreWalletFromWIFCredentials> {
15 + BitcoinCashRestoreWalletFromSeedCredentials, BitcoinCashRestoreWalletFromWIFCredentials> {
16 BitcoinCashWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
17
18 final Box<WalletInfo> walletInfoSource;
@@ -30,13 +26,9 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
26 File(await pathForWallet(name: name, type: getType())).existsSync();
27
28 @override
33 - Future<BitcoinCashWallet> create(
34 - credentials) async {
35 - final strength = (credentials.seedPhraseLength == 12)
36 - ? 128
37 - : (credentials.seedPhraseLength == 24)
38 - ? 256
39 - : 128;
29 + Future<BitcoinCashWallet> create(credentials, {bool? isTestnet}) async {
30 + final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
31 +
32 final wallet = await BitcoinCashWalletBase.create(
33 mnemonic: await Mnemonic.generate(strength: strength),
34 password: credentials.password!,
@@ -49,21 +41,25 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
41
42 @override
43 Future<BitcoinCashWallet> openWallet(String name, String password) async {
52 - final walletInfo = walletInfoSource.values.firstWhereOrNull(
53 - (info) => info.id == WalletBase.idFor(name, getType()))!;
44 + final walletInfo = walletInfoSource.values
45 + .firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
46
47 try {
48 final wallet = await BitcoinCashWalletBase.open(
57 - password: password, name: name, walletInfo: walletInfo,
49 + password: password,
50 + name: name,
51 + walletInfo: walletInfo,
52 unspentCoinsInfo: unspentCoinsInfoSource);
53 await wallet.init();
54 saveBackup(name);
55 return wallet;
62 - } catch(_) {
56 + } catch (_) {
57 await restoreWalletFilesFromBackup(name);
58 final wallet = await BitcoinCashWalletBase.open(
65 - password: password, name: name, walletInfo: walletInfo,
66 - unspentCoinsInfo: unspentCoinsInfoSource);
59 + password: password,
60 + name: name,
61 + walletInfo: walletInfo,
62 + unspentCoinsInfo: unspentCoinsInfoSource);
63 await wallet.init();
64 return wallet;
65 }
@@ -71,17 +67,16 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
67
68 @override
69 Future<void> remove(String wallet) async {
74 - File(await pathForWalletDir(name: wallet, type: getType()))
75 - .delete(recursive: true);
76 - final walletInfo = walletInfoSource.values.firstWhereOrNull(
77 - (info) => info.id == WalletBase.idFor(wallet, getType()))!;
70 + File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
71 + final walletInfo = walletInfoSource.values
72 + .firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!;
73 await walletInfoSource.delete(walletInfo.key);
74 }
75
76 @override
77 Future<void> rename(String currentName, String password, String newName) async {
83 - final currentWalletInfo = walletInfoSource.values.firstWhereOrNull(
84 - (info) => info.id == WalletBase.idFor(currentName, getType()))!;
78 + final currentWalletInfo = walletInfoSource.values
79 + .firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!;
80 final currentWallet = await BitcoinCashWalletBase.open(
81 password: password,
82 name: currentName,
@@ -99,15 +94,14 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
94 }
95
96 @override
102 - Future<BitcoinCashWallet>
103 - restoreFromKeys(credentials) {
97 + Future<BitcoinCashWallet> restoreFromKeys(credentials, {bool? isTestnet}) {
98 // TODO: implement restoreFromKeys
99 throw UnimplementedError('restoreFromKeys() is not implemented');
100 }
101
102 @override
109 - Future<BitcoinCashWallet> restoreFromSeed(
110 - BitcoinCashRestoreWalletFromSeedCredentials credentials) async {
103 + Future<BitcoinCashWallet> restoreFromSeed(BitcoinCashRestoreWalletFromSeedCredentials credentials,
104 + {bool? isTestnet}) async {
105 if (!validateMnemonic(credentials.mnemonic)) {
106 throw BitcoinCashMnemonicIsIncorrectException();
107 }
cw_bitcoin_cash/pubspec.yaml
+4 -1
@@ -29,7 +29,10 @@ dependencies:
29 git:
30 url: https://github.com/cake-tech/bitbox-flutter.git
31 ref: master
32 - bitcoin_base: ^3.0.1
32 + bitcoin_base:
33 + git:
34 + url: https://github.com/cake-tech/bitcoin_base.git
35 + ref: cake-update-v1
36
37
38
cw_core/lib/enumerate.dart new
+13
@@ -0,0 +1,13 @@
1 +abstract class Enumerate {
2 + String get value;
3 +
4 + @override
5 + operator ==(other) {
6 + if (identical(other, this)) return true;
7 + if (other is! Enumerate) return false;
8 + return other.runtimeType == runtimeType && value == other.value;
9 + }
10 +
11 + @override
12 + int get hashCode => value.hashCode;
13 +}
cw_core/lib/receive_page_option.dart new
+21
@@ -0,0 +1,21 @@
1 +import 'package:cw_core/enumerate.dart';
2 +
3 +class ReceivePageOption implements Enumerate {
4 + static const mainnet = ReceivePageOption._('mainnet');
5 + static const anonPayInvoice = ReceivePageOption._('anonPayInvoice');
6 + static const anonPayDonationLink = ReceivePageOption._('anonPayDonationLink');
7 +
8 + const ReceivePageOption._(this.value);
9 +
10 + final String value;
11 +
12 + String toString() {
13 + return value;
14 + }
15 +}
16 +
17 +const ReceivePageOptions = [
18 + ReceivePageOption.mainnet,
19 + ReceivePageOption.anonPayInvoice,
20 + ReceivePageOption.anonPayDonationLink
21 +];
cw_core/lib/wallet_base.dart
+2
@@ -88,4 +88,6 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
88 Future<void> renameWalletFiles(String newWalletName);
89
90 String signMessage(String message, {String? address = null}) => throw UnimplementedError();
91 +
92 + bool? isTestnet;
93 }
cw_core/lib/wallet_info.dart
+6
@@ -148,6 +148,12 @@ class WalletInfo extends HiveObject {
148 @HiveField(17)
149 String? derivationPath;
150
151 + @HiveField(18)
152 + String? addressPageType;
153 +
154 + @HiveField(19)
155 + String? network;
156 +
157 String get yatLastUsedAddress => yatLastUsedAddressRaw ?? '';
158
159 set yatLastUsedAddress(String address) {
cw_core/lib/wallet_service.dart
+3 -3
@@ -9,11 +9,11 @@ abstract class WalletService<N extends WalletCredentials, RFS extends WalletCred
9 RFK extends WalletCredentials> {
10 WalletType getType();
11
12 - Future<WalletBase> create(N credentials);
12 + Future<WalletBase> create(N credentials, {bool? isTestnet});
13
14 - Future<WalletBase> restoreFromSeed(RFS credentials);
14 + Future<WalletBase> restoreFromSeed(RFS credentials, {bool? isTestnet});
15
16 - Future<WalletBase> restoreFromKeys(RFK credentials);
16 + Future<WalletBase> restoreFromKeys(RFK credentials, {bool? isTestnet});
17
18 Future<WalletBase> openWallet(String name, String password);
19
cw_core/pubspec.lock
+97 -89
@@ -5,34 +5,34 @@ packages:
5 dependency: transitive
6 description:
7 name: _fe_analyzer_shared
8 - sha256: "4897882604d919befd350648c7f91926a9d5de99e67b455bf0917cc2362f4bb8"
8 + sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051
9 url: "https://pub.dev"
10 source: hosted
11 - version: "47.0.0"
11 + version: "64.0.0"
12 analyzer:
13 dependency: transitive
14 description:
15 name: analyzer
16 - sha256: "690e335554a8385bc9d787117d9eb52c0c03ee207a607e593de3c9d71b1cfe80"
16 + sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893"
17 url: "https://pub.dev"
18 source: hosted
19 - version: "4.7.0"
19 + version: "6.2.0"
20 args:
21 dependency: transitive
22 description:
23 name: args
24 - sha256: "139d809800a412ebb26a3892da228b2d0ba36f0ef5d9a82166e5e52ec8d61611"
24 + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
25 url: "https://pub.dev"
26 source: hosted
27 - version: "2.3.2"
27 + version: "2.4.2"
28 asn1lib:
29 dependency: transitive
30 description:
31 name: asn1lib
32 - sha256: ab96a1cb3beeccf8145c52e449233fe68364c9641623acd3adad66f8184f1039
32 + sha256: "21afe4333076c02877d14f4a89df111e658a6d466cbfc802eb705eb91bd5adfd"
33 url: "https://pub.dev"
34 source: hosted
35 - version: "1.4.0"
35 + version: "1.5.0"
36 async:
37 dependency: transitive
38 description:
@@ -53,10 +53,10 @@ packages:
53 dependency: transitive
54 description:
55 name: build
56 - sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777"
56 + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
57 url: "https://pub.dev"
58 source: hosted
59 - version: "2.3.1"
59 + version: "2.4.1"
60 build_config:
61 dependency: transitive
62 description:
@@ -69,34 +69,34 @@ packages:
69 dependency: transitive
70 description:
71 name: build_daemon
72 - sha256: "6bc5544ea6ce4428266e7ea680e945c68806c4aae2da0eb5e9ccf38df8d6acbf"
72 + sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1"
73 url: "https://pub.dev"
74 source: hosted
75 - version: "3.1.0"
75 + version: "4.0.1"
76 build_resolvers:
77 dependency: "direct dev"
78 description:
79 name: build_resolvers
80 - sha256: "687cf90a3951affac1bd5f9ecb5e3e90b60487f3d9cdc359bb310f8876bb02a6"
80 + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a"
81 url: "https://pub.dev"
82 source: hosted
83 - version: "2.0.10"
83 + version: "2.4.2"
84 build_runner:
85 dependency: "direct dev"
86 description:
87 name: build_runner
88 - sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727
88 + sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21"
89 url: "https://pub.dev"
90 source: hosted
91 - version: "2.3.3"
91 + version: "2.4.8"
92 build_runner_core:
93 dependency: transitive
94 description:
95 name: build_runner_core
96 - sha256: "14febe0f5bac5ae474117a36099b4de6f1dbc52df6c5e55534b3da9591bf4292"
96 + sha256: c9e32d21dd6626b5c163d48b037ce906bbe428bc23ab77bcd77bb21e593b6185
97 url: "https://pub.dev"
98 source: hosted
99 - version: "7.2.7"
99 + version: "7.2.11"
100 built_collection:
101 dependency: transitive
102 description:
@@ -109,10 +109,10 @@ packages:
109 dependency: transitive
110 description:
111 name: built_value
112 - sha256: "169565c8ad06adb760c3645bf71f00bff161b00002cace266cad42c5d22a7725"
112 + sha256: c9aabae0718ec394e5bc3c7272e6bb0dc0b32201a08fe185ec1d8401d3e39309
113 url: "https://pub.dev"
114 source: hosted
115 - version: "8.4.3"
115 + version: "8.8.1"
116 characters:
117 dependency: transitive
118 description:
@@ -125,10 +125,10 @@ packages:
125 dependency: transitive
126 description:
127 name: checked_yaml
128 - sha256: "3d1505d91afa809d177efd4eed5bb0eb65805097a1463abdd2add076effae311"
128 + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
129 url: "https://pub.dev"
130 source: hosted
131 - version: "2.0.2"
131 + version: "2.0.3"
132 clock:
133 dependency: transitive
134 description:
@@ -141,10 +141,10 @@ packages:
141 dependency: transitive
142 description:
143 name: code_builder
144 - sha256: "0d43dd1288fd145de1ecc9a3948ad4a6d5a82f0a14c4fdd0892260787d975cbe"
144 + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37
145 url: "https://pub.dev"
146 source: hosted
147 - version: "4.4.0"
147 + version: "4.10.0"
148 collection:
149 dependency: transitive
150 description:
@@ -165,26 +165,26 @@ packages:
165 dependency: transitive
166 description:
167 name: crypto
168 - sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67
168 + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
169 url: "https://pub.dev"
170 source: hosted
171 - version: "3.0.2"
171 + version: "3.0.3"
172 dart_style:
173 dependency: transitive
174 description:
175 name: dart_style
176 - sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4"
176 + sha256: "40ae61a5d43feea6d24bd22c0537a6629db858963b99b4bc1c3db80676f32368"
177 url: "https://pub.dev"
178 source: hosted
179 - version: "2.2.4"
179 + version: "2.3.4"
180 encrypt:
181 dependency: "direct main"
182 description:
183 name: encrypt
184 - sha256: "4fd4e4fdc21b9d7d4141823e1e6515cd94e7b8d84749504c232999fba25d9bbb"
184 + sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2"
185 url: "https://pub.dev"
186 source: hosted
187 - version: "5.0.1"
187 + version: "5.0.3"
188 fake_async:
189 dependency: transitive
190 description:
@@ -197,10 +197,10 @@ packages:
197 dependency: transitive
198 description:
199 name: ffi
200 - sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978
200 + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
201 url: "https://pub.dev"
202 source: hosted
203 - version: "2.0.1"
203 + version: "2.1.0"
204 file:
205 dependency: "direct main"
206 description:
@@ -226,10 +226,10 @@ packages:
226 dependency: "direct main"
227 description:
228 name: flutter_mobx
229 - sha256: "0da4add0016387a7bf309a0d0c41d36c6b3ae25ed7a176409267f166509e723e"
229 + sha256: "4a5d062ff85ed3759f4aac6410ff0ffae32e324b2e71ca722ae1b37b32e865f4"
230 url: "https://pub.dev"
231 source: hosted
232 - version: "2.0.6+5"
232 + version: "2.2.0+2"
233 flutter_test:
234 dependency: "direct dev"
235 description: flutter
@@ -247,18 +247,18 @@ packages:
247 dependency: transitive
248 description:
249 name: glob
250 - sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c"
250 + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
251 url: "https://pub.dev"
252 source: hosted
253 - version: "2.1.1"
253 + version: "2.1.2"
254 graphs:
255 dependency: transitive
256 description:
257 name: graphs
258 - sha256: f9e130f3259f52d26f0cfc0e964513796dafed572fa52e45d2f8d6ca14db39b2
258 + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19
259 url: "https://pub.dev"
260 source: hosted
261 - version: "2.2.0"
261 + version: "2.3.1"
262 hive:
263 dependency: transitive
264 description:
@@ -327,18 +327,18 @@ packages:
327 dependency: transitive
328 description:
329 name: json_annotation
330 - sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317
330 + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
331 url: "https://pub.dev"
332 source: hosted
333 - version: "4.8.0"
333 + version: "4.8.1"
334 logging:
335 dependency: transitive
336 description:
337 name: logging
338 - sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d"
338 + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
339 url: "https://pub.dev"
340 source: hosted
341 - version: "1.1.1"
341 + version: "1.2.0"
342 matcher:
343 dependency: transitive
344 description:
@@ -375,18 +375,26 @@ packages:
375 dependency: "direct main"
376 description:
377 name: mobx
378 - sha256: f1862bd92c6a903fab67338f27e2f731117c3cb9ea37cee1a487f9e4e0de314a
378 + sha256: "74ee54012dc7c1b3276eaa960a600a7418ef5f9997565deb8fca1fd88fb36b78"
379 url: "https://pub.dev"
380 source: hosted
381 - version: "2.1.3+1"
381 + version: "2.3.0+1"
382 mobx_codegen:
383 dependency: "direct dev"
384 description:
385 name: mobx_codegen
386 - sha256: "86122e410d8ea24dda0c69adb5c2a6ccadd5ce02ad46e144764e0d0184a06181"
386 + sha256: b26c7f9c20b38f0ea572c1ed3f29d8e027cb265538bbd1aed3ec198642cfca42
387 url: "https://pub.dev"
388 source: hosted
389 - version: "2.1.1"
389 + version: "2.6.0+1"
390 + nested:
391 + dependency: transitive
392 + description:
393 + name: nested
394 + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
395 + url: "https://pub.dev"
396 + source: hosted
397 + version: "1.0.0"
398 package_config:
399 dependency: transitive
400 description:
@@ -407,26 +415,26 @@ packages:
415 dependency: "direct main"
416 description:
417 name: path_provider
410 - sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa
418 + sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
419 url: "https://pub.dev"
420 source: hosted
413 - version: "2.1.1"
421 + version: "2.1.2"
422 path_provider_android:
423 dependency: transitive
424 description:
425 name: path_provider_android
418 - sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72
426 + sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
427 url: "https://pub.dev"
428 source: hosted
421 - version: "2.2.1"
429 + version: "2.2.2"
430 path_provider_foundation:
431 dependency: transitive
432 description:
433 name: path_provider_foundation
426 - sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d"
434 + sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
435 url: "https://pub.dev"
436 source: hosted
429 - version: "2.3.1"
437 + version: "2.3.2"
438 path_provider_linux:
439 dependency: transitive
440 description:
@@ -439,10 +447,10 @@ packages:
447 dependency: transitive
448 description:
449 name: path_provider_platform_interface
442 - sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c"
450 + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
451 url: "https://pub.dev"
452 source: hosted
445 - version: "2.1.1"
453 + version: "2.1.2"
454 path_provider_windows:
455 dependency: transitive
456 description:
@@ -455,26 +463,26 @@ packages:
463 dependency: transitive
464 description:
465 name: platform
458 - sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
466 + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
467 url: "https://pub.dev"
468 source: hosted
461 - version: "3.1.0"
469 + version: "3.1.4"
470 plugin_platform_interface:
471 dependency: transitive
472 description:
473 name: plugin_platform_interface
466 - sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a
474 + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
475 url: "https://pub.dev"
476 source: hosted
469 - version: "2.1.3"
477 + version: "2.1.8"
478 pointycastle:
479 dependency: transitive
480 description:
481 name: pointycastle
474 - sha256: db7306cf0249f838d1a24af52b5a5887c5bf7f31d8bb4e827d071dc0939ad346
482 + sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29"
483 url: "https://pub.dev"
484 source: hosted
477 - version: "3.6.2"
485 + version: "3.7.4"
486 pool:
487 dependency: transitive
488 description:
@@ -483,46 +491,46 @@ packages:
491 url: "https://pub.dev"
492 source: hosted
493 version: "1.5.1"
486 - process:
494 + provider:
495 dependency: transitive
496 description:
489 - name: process
490 - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
497 + name: provider
498 + sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096"
499 url: "https://pub.dev"
500 source: hosted
493 - version: "4.2.4"
501 + version: "6.1.1"
502 pub_semver:
503 dependency: transitive
504 description:
505 name: pub_semver
498 - sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17"
506 + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
507 url: "https://pub.dev"
508 source: hosted
501 - version: "2.1.3"
509 + version: "2.1.4"
510 pubspec_parse:
511 dependency: transitive
512 description:
513 name: pubspec_parse
506 - sha256: "75f6614d6dde2dc68948dffbaa4fe5dae32cd700eb9fb763fe11dfb45a3c4d0a"
514 + sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367
515 url: "https://pub.dev"
516 source: hosted
509 - version: "1.2.1"
517 + version: "1.2.3"
518 shelf:
519 dependency: transitive
520 description:
521 name: shelf
514 - sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c
522 + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
523 url: "https://pub.dev"
524 source: hosted
517 - version: "1.4.0"
525 + version: "1.4.1"
526 shelf_web_socket:
527 dependency: transitive
528 description:
529 name: shelf_web_socket
522 - sha256: a988c0e8d8ffbdb8a28aa7ec8e449c260f3deb808781fe1284d22c5bba7156e8
530 + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
531 url: "https://pub.dev"
532 source: hosted
525 - version: "1.0.3"
533 + version: "1.0.4"
534 sky_engine:
535 dependency: transitive
536 description: flutter
@@ -540,18 +548,18 @@ packages:
548 dependency: transitive
549 description:
550 name: source_gen
543 - sha256: "2d79738b6bbf38a43920e2b8d189e9a3ce6cc201f4b8fc76be5e4fe377b1c38d"
551 + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
552 url: "https://pub.dev"
553 source: hosted
546 - version: "1.2.6"
554 + version: "1.5.0"
555 source_helper:
556 dependency: transitive
557 description:
558 name: source_helper
551 - sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f"
559 + sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd"
560 url: "https://pub.dev"
561 source: hosted
554 - version: "1.3.3"
562 + version: "1.3.4"
563 source_span:
564 dependency: transitive
565 description:
@@ -620,10 +628,10 @@ packages:
628 dependency: transitive
629 description:
630 name: typed_data
623 - sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5"
631 + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
632 url: "https://pub.dev"
633 source: hosted
626 - version: "1.3.1"
634 + version: "1.3.2"
635 vector_math:
636 dependency: transitive
637 description:
@@ -636,42 +644,42 @@ packages:
644 dependency: transitive
645 description:
646 name: watcher
639 - sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0"
647 + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
648 url: "https://pub.dev"
649 source: hosted
642 - version: "1.0.2"
650 + version: "1.1.0"
651 web_socket_channel:
652 dependency: transitive
653 description:
654 name: web_socket_channel
647 - sha256: ca49c0bc209c687b887f30527fb6a9d80040b072cc2990f34b9bec3e7663101b
655 + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
656 url: "https://pub.dev"
657 source: hosted
650 - version: "2.3.0"
658 + version: "2.4.0"
659 win32:
660 dependency: transitive
661 description:
662 name: win32
655 - sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46
663 + sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3"
664 url: "https://pub.dev"
665 source: hosted
658 - version: "3.1.3"
666 + version: "5.0.9"
667 xdg_directories:
668 dependency: transitive
669 description:
670 name: xdg_directories
663 - sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86
671 + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
672 url: "https://pub.dev"
673 source: hosted
666 - version: "0.2.0+3"
674 + version: "1.0.4"
675 yaml:
676 dependency: transitive
677 description:
678 name: yaml
671 - sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370"
679 + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
680 url: "https://pub.dev"
681 source: hosted
674 - version: "3.1.1"
682 + version: "3.1.2"
683 sdks:
684 dart: ">=3.0.0 <4.0.0"
677 - flutter: ">=3.7.0"
685 + flutter: ">=3.10.0"
cw_ethereum/lib/ethereum_wallet_service.dart
+5 -5
@@ -16,7 +16,7 @@ class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
16 WalletType getType() => WalletType.ethereum;
17
18 @override
19 - Future<EthereumWallet> create(EVMChainNewWalletCredentials credentials) async {
19 + Future<EthereumWallet> create(EVMChainNewWalletCredentials credentials, {bool? isTestnet}) async {
20 final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
21
22 final mnemonic = bip39.generateMnemonic(strength: strength);
@@ -52,7 +52,6 @@ class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
52 saveBackup(name);
53 return wallet;
54 } catch (_) {
55 -
55 await restoreWalletFilesFromBackup(name);
56
57 final wallet = await EthereumWallet.open(
@@ -84,7 +83,8 @@ class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
83 }
84
85 @override
87 - Future<EthereumWallet> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials) async {
86 + Future<EthereumWallet> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials,
87 + {bool? isTestnet}) async {
88 final wallet = EthereumWallet(
89 password: credentials.password!,
90 privateKey: credentials.privateKey,
@@ -100,8 +100,8 @@ class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
100 }
101
102 @override
103 - Future<EthereumWallet> restoreFromSeed(
104 - EVMChainRestoreWalletFromSeedCredentials credentials) async {
103 + Future<EthereumWallet> restoreFromSeed(EVMChainRestoreWalletFromSeedCredentials credentials,
104 + {bool? isTestnet}) async {
105 if (!bip39.validateMnemonic(credentials.mnemonic)) {
106 throw EthereumMnemonicIsIncorrectException();
107 }
cw_evm/lib/evm_chain_wallet_service.dart
+3 -3
@@ -22,7 +22,7 @@ abstract class EVMChainWalletService<T extends EVMChainWallet> extends WalletSer
22 WalletType getType();
23
24 @override
25 - Future<T> create(EVMChainNewWalletCredentials credentials);
25 + Future<T> create(EVMChainNewWalletCredentials credentials, {bool? isTestnet});
26
27 @override
28 Future<T> openWallet(String name, String password);
@@ -31,10 +31,10 @@ abstract class EVMChainWalletService<T extends EVMChainWallet> extends WalletSer
31 Future<void> rename(String currentName, String password, String newName);
32
33 @override
34 - Future<T> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials);
34 + Future<T> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials, {bool? isTestnet});
35
36 @override
37 - Future<T> restoreFromSeed(EVMChainRestoreWalletFromSeedCredentials credentials);
37 + Future<T> restoreFromSeed(EVMChainRestoreWalletFromSeedCredentials credentials, {bool? isTestnet});
38
39 @override
40 Future<bool> isWalletExit(String name) async =>
cw_haven/lib/haven_wallet_service.dart
+3 -3
@@ -68,7 +68,7 @@ class HavenWalletService extends WalletService<
68 WalletType getType() => WalletType.haven;
69
70 @override
71 - Future<HavenWallet> create(HavenNewWalletCredentials credentials) async {
71 + Future<HavenWallet> create(HavenNewWalletCredentials credentials, {bool? isTestnet}) async {
72 try {
73 final path = await pathForWallet(name: credentials.name, type: getType());
74 await haven_wallet_manager.createWallet(
@@ -174,7 +174,7 @@ class HavenWalletService extends WalletService<
174
175 @override
176 Future<HavenWallet> restoreFromKeys(
177 - HavenRestoreWalletFromKeysCredentials credentials) async {
177 + HavenRestoreWalletFromKeysCredentials credentials, {bool? isTestnet}) async {
178 try {
179 final path = await pathForWallet(name: credentials.name, type: getType());
180 await haven_wallet_manager.restoreFromKeys(
@@ -198,7 +198,7 @@ class HavenWalletService extends WalletService<
198
199 @override
200 Future<HavenWallet> restoreFromSeed(
201 - HavenRestoreWalletFromSeedCredentials credentials) async {
201 + HavenRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async {
202 try {
203 final path = await pathForWallet(name: credentials.name, type: getType());
204 await haven_wallet_manager.restoreFromSeed(
cw_monero/lib/monero_wallet_service.dart
+5 -3
@@ -68,7 +68,7 @@ class MoneroWalletService extends WalletService<MoneroNewWalletCredentials,
68 WalletType getType() => WalletType.monero;
69
70 @override
71 - Future<MoneroWallet> create(MoneroNewWalletCredentials credentials) async {
71 + Future<MoneroWallet> create(MoneroNewWalletCredentials credentials, {bool? isTestnet}) async {
72 try {
73 final path = await pathForWallet(name: credentials.name, type: getType());
74
@@ -203,7 +203,8 @@ class MoneroWalletService extends WalletService<MoneroNewWalletCredentials,
203 }
204
205 @override
206 - Future<MoneroWallet> restoreFromKeys(MoneroRestoreWalletFromKeysCredentials credentials) async {
206 + Future<MoneroWallet> restoreFromKeys(MoneroRestoreWalletFromKeysCredentials credentials,
207 + {bool? isTestnet}) async {
208 try {
209 final path = await pathForWallet(name: credentials.name, type: getType());
210 await monero_wallet_manager.restoreFromKeys(
@@ -227,7 +228,8 @@ class MoneroWalletService extends WalletService<MoneroNewWalletCredentials,
228 }
229
230 @override
230 - Future<MoneroWallet> restoreFromSeed(MoneroRestoreWalletFromSeedCredentials credentials) async {
231 + Future<MoneroWallet> restoreFromSeed(MoneroRestoreWalletFromSeedCredentials credentials,
232 + {bool? isTestnet}) async {
233 // Restore from Polyseed
234 if (Polyseed.isValidSeed(credentials.mnemonic)) {
235 return restoreFromPolyseed(credentials);
cw_nano/lib/nano_wallet_service.dart
+3 -3
@@ -26,7 +26,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
26 WalletType getType() => WalletType.nano;
27
28 @override
29 - Future<WalletBase> create(NanoNewWalletCredentials credentials) async {
29 + Future<WalletBase> create(NanoNewWalletCredentials credentials, {bool? isTestnet}) async {
30 // nano standard:
31 DerivationType derivationType = DerivationType.nano;
32 String seedKey = NanoSeeds.generateSeed();
@@ -79,7 +79,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
79 }
80
81 @override
82 - Future<NanoWallet> restoreFromKeys(NanoRestoreWalletFromKeysCredentials credentials) async {
82 + Future<NanoWallet> restoreFromKeys(NanoRestoreWalletFromKeysCredentials credentials, {bool? isTestnet}) async {
83 if (credentials.seedKey.contains(' ')) {
84 throw Exception("Invalid key!");
85 } else {
@@ -113,7 +113,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
113 }
114
115 @override
116 - Future<NanoWallet> restoreFromSeed(NanoRestoreWalletFromSeedCredentials credentials) async {
116 + Future<NanoWallet> restoreFromSeed(NanoRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async {
117 if (credentials.mnemonic.contains(' ')) {
118 if (!bip39.validateMnemonic(credentials.mnemonic)) {
119 throw nm.NanoMnemonicIsIncorrectException();
cw_polygon/lib/polygon_wallet_service.dart
+6 -6
@@ -19,7 +19,7 @@ class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
19 WalletType getType() => WalletType.polygon;
20
21 @override
22 - Future<PolygonWallet> create(EVMChainNewWalletCredentials credentials) async {
22 + Future<PolygonWallet> create(EVMChainNewWalletCredentials credentials, {bool? isTestnet}) async {
23 final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
24
25 final mnemonic = bip39.generateMnemonic(strength: strength);
@@ -62,7 +62,7 @@ class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
62 password: password,
63 walletInfo: walletInfo,
64 );
65 -
65 +
66 await wallet.init();
67 await wallet.save();
68 return wallet;
@@ -70,8 +70,8 @@ class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
70 }
71
72 @override
73 - Future<PolygonWallet> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials) async {
74 -
73 + Future<PolygonWallet> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials,
74 + {bool? isTestnet}) async {
75 final wallet = PolygonWallet(
76 password: credentials.password!,
77 privateKey: credentials.privateKey,
@@ -87,8 +87,8 @@ class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
87 }
88
89 @override
90 - Future<PolygonWallet> restoreFromSeed(
91 - EVMChainRestoreWalletFromSeedCredentials credentials) async {
90 + Future<PolygonWallet> restoreFromSeed(EVMChainRestoreWalletFromSeedCredentials credentials,
91 + {bool? isTestnet}) async {
92 if (!bip39.validateMnemonic(credentials.mnemonic)) {
93 throw PolygonMnemonicIsIncorrectException();
94 }
lib/bitcoin/cw_bitcoin.dart
+188 -178
@@ -1,181 +1,191 @@
1 part of 'bitcoin.dart';
2
3 class CWBitcoin extends Bitcoin {
4 - @override
5 - TransactionPriority getMediumTransactionPriority() => BitcoinTransactionPriority.medium;
6 -
7 - @override
8 - WalletCredentials createBitcoinRestoreWalletFromSeedCredentials({
9 - required String name,
10 - required String mnemonic,
11 - required String password})
12 - => BitcoinRestoreWalletFromSeedCredentials(name: name, mnemonic: mnemonic, password: password);
13 -
14 - @override
15 - WalletCredentials createBitcoinRestoreWalletFromWIFCredentials({
16 - required String name,
17 - required String password,
18 - required String wif,
19 - WalletInfo? walletInfo})
20 - => BitcoinRestoreWalletFromWIFCredentials(name: name, password: password, wif: wif, walletInfo: walletInfo);
21 -
22 - @override
23 - WalletCredentials createBitcoinNewWalletCredentials({
24 - required String name,
25 - WalletInfo? walletInfo})
26 - => BitcoinNewWalletCredentials(name: name, walletInfo: walletInfo);
27 -
28 - @override
29 - List<String> getWordList() => wordlist;
30 -
31 - @override
32 - Map<String, String> getWalletKeys(Object wallet) {
33 - final bitcoinWallet = wallet as ElectrumWallet;
34 - final keys = bitcoinWallet.keys;
35 -
36 - return <String, String>{
37 - 'wif': keys.wif,
38 - 'privateKey': keys.privateKey,
39 - 'publicKey': keys.publicKey
40 - };
41 - }
42 -
43 - @override
44 - List<TransactionPriority> getTransactionPriorities()
45 - => BitcoinTransactionPriority.all;
46 -
47 - @override
48 - List<TransactionPriority> getLitecoinTransactionPriorities()
49 - => LitecoinTransactionPriority.all;
50 -
51 - @override
52 - TransactionPriority deserializeBitcoinTransactionPriority(int raw)
53 - => BitcoinTransactionPriority.deserialize(raw: raw);
54 -
55 - @override
56 - TransactionPriority deserializeLitecoinTransactionPriority(int raw)
57 - => LitecoinTransactionPriority.deserialize(raw: raw);
58 -
59 - @override
60 - int getFeeRate(Object wallet, TransactionPriority priority) {
61 - final bitcoinWallet = wallet as ElectrumWallet;
62 - return bitcoinWallet.feeRate(priority);
63 - }
64 -
65 - @override
66 - Future<void> generateNewAddress(Object wallet, String label) async {
67 - final bitcoinWallet = wallet as ElectrumWallet;
68 - await bitcoinWallet.walletAddresses.generateNewAddress(label: label, hd: bitcoinWallet.hd);
69 - await wallet.save();
70 - }
71 -
72 - @override
73 - Future<void> updateAddress(Object wallet,String address, String label) async {
74 - final bitcoinWallet = wallet as ElectrumWallet;
75 - bitcoinWallet.walletAddresses.updateAddress(address, label);
76 - await wallet.save();
77 - }
78 -
79 - @override
80 - Object createBitcoinTransactionCredentials(List<Output> outputs, {required TransactionPriority priority, int? feeRate})
81 - => BitcoinTransactionCredentials(
82 - outputs.map((out) => OutputInfo(
83 - fiatAmount: out.fiatAmount,
84 - cryptoAmount: out.cryptoAmount,
85 - address: out.address,
86 - note: out.note,
87 - sendAll: out.sendAll,
88 - extractedAddress: out.extractedAddress,
89 - isParsedAddress: out.isParsedAddress,
90 - formattedCryptoAmount: out.formattedCryptoAmount))
91 - .toList(),
92 - priority: priority as BitcoinTransactionPriority,
93 - feeRate: feeRate);
94 -
95 - @override
96 - Object createBitcoinTransactionCredentialsRaw(List<OutputInfo> outputs, {TransactionPriority? priority, required int feeRate})
97 - => BitcoinTransactionCredentials(
98 - outputs,
99 - priority: priority != null ? priority as BitcoinTransactionPriority : null,
100 - feeRate: feeRate);
101 -
102 - @override
103 - List<String> getAddresses(Object wallet) {
104 - final bitcoinWallet = wallet as ElectrumWallet;
105 - return bitcoinWallet.walletAddresses.addresses
106 - .map((BitcoinAddressRecord addr) => addr.address)
107 - .toList();
108 - }
109 -
110 - @override
111 - @computed
112 - List<ElectrumSubAddress> getSubAddresses(Object wallet) {
113 - final electrumWallet = wallet as ElectrumWallet;
114 - return electrumWallet.walletAddresses.addresses
115 - .map((BitcoinAddressRecord addr) => ElectrumSubAddress(
116 - id: addr.index,
117 - name: addr.name,
118 - address: electrumWallet.type == WalletType.bitcoinCash ? addr.cashAddr : addr.address,
119 - txCount: addr.txCount,
120 - balance: addr.balance,
121 - isChange: addr.isHidden))
122 - .toList();
123 - }
124 -
125 - @override
126 - String getAddress(Object wallet) {
127 - final bitcoinWallet = wallet as ElectrumWallet;
128 - return bitcoinWallet.walletAddresses.address;
129 - }
130 -
131 - @override
132 - String formatterBitcoinAmountToString({required int amount})
133 - => bitcoinAmountToString(amount: amount);
134 -
135 - @override
136 - double formatterBitcoinAmountToDouble({required int amount})
137 - => bitcoinAmountToDouble(amount: amount);
138 -
139 - @override
140 - int formatterStringDoubleToBitcoinAmount(String amount)
141 - => stringDoubleToBitcoinAmount(amount);
142 -
143 - @override
144 - String bitcoinTransactionPriorityWithLabel(TransactionPriority priority, int rate)
145 - => (priority as BitcoinTransactionPriority).labelWithRate(rate);
146 -
147 - @override
148 - List<BitcoinUnspent> getUnspents(Object wallet) {
149 - final bitcoinWallet = wallet as ElectrumWallet;
150 - return bitcoinWallet.unspentCoins;
151 - }
152 -
153 - Future<void> updateUnspents(Object wallet) async {
154 - final bitcoinWallet = wallet as ElectrumWallet;
155 - await bitcoinWallet.updateUnspent();
156 - }
157 -
158 - WalletService createBitcoinWalletService(Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource) {
159 - return BitcoinWalletService(walletInfoSource, unspentCoinSource);
160 - }
161 -
162 - WalletService createLitecoinWalletService(Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource) {
163 - return LitecoinWalletService(walletInfoSource, unspentCoinSource);
164 - }
165 -
166 - @override
167 - TransactionPriority getBitcoinTransactionPriorityMedium()
168 - => BitcoinTransactionPriority.medium;
169 -
170 - @override
171 - TransactionPriority getLitecoinTransactionPriorityMedium()
172 - => LitecoinTransactionPriority.medium;
173 -
174 - @override
175 - TransactionPriority getBitcoinTransactionPrioritySlow()
176 - => BitcoinTransactionPriority.slow;
177 -
178 - @override
179 - TransactionPriority getLitecoinTransactionPrioritySlow()
180 - => LitecoinTransactionPriority.slow;
181 -}
\ No newline at end of file
4 + @override
5 + TransactionPriority getMediumTransactionPriority() => BitcoinTransactionPriority.medium;
6 +
7 + @override
8 + WalletCredentials createBitcoinRestoreWalletFromSeedCredentials(
9 + {required String name, required String mnemonic, required String password}) =>
10 + BitcoinRestoreWalletFromSeedCredentials(name: name, mnemonic: mnemonic, password: password);
11 +
12 + @override
13 + WalletCredentials createBitcoinRestoreWalletFromWIFCredentials(
14 + {required String name,
15 + required String password,
16 + required String wif,
17 + WalletInfo? walletInfo}) =>
18 + BitcoinRestoreWalletFromWIFCredentials(
19 + name: name, password: password, wif: wif, walletInfo: walletInfo);
20 +
21 + @override
22 + WalletCredentials createBitcoinNewWalletCredentials(
23 + {required String name, WalletInfo? walletInfo}) =>
24 + BitcoinNewWalletCredentials(name: name, walletInfo: walletInfo);
25 +
26 + @override
27 + List<String> getWordList() => wordlist;
28 +
29 + @override
30 + Map<String, String> getWalletKeys(Object wallet) {
31 + final bitcoinWallet = wallet as ElectrumWallet;
32 + final keys = bitcoinWallet.keys;
33 +
34 + return <String, String>{
35 + 'wif': keys.wif,
36 + 'privateKey': keys.privateKey,
37 + 'publicKey': keys.publicKey
38 + };
39 + }
40 +
41 + @override
42 + List<TransactionPriority> getTransactionPriorities() => BitcoinTransactionPriority.all;
43 +
44 + @override
45 + List<TransactionPriority> getLitecoinTransactionPriorities() => LitecoinTransactionPriority.all;
46 +
47 + @override
48 + TransactionPriority deserializeBitcoinTransactionPriority(int raw) =>
49 + BitcoinTransactionPriority.deserialize(raw: raw);
50 +
51 + @override
52 + TransactionPriority deserializeLitecoinTransactionPriority(int raw) =>
53 + LitecoinTransactionPriority.deserialize(raw: raw);
54 +
55 + @override
56 + int getFeeRate(Object wallet, TransactionPriority priority) {
57 + final bitcoinWallet = wallet as ElectrumWallet;
58 + return bitcoinWallet.feeRate(priority);
59 + }
60 +
61 + @override
62 + Future<void> generateNewAddress(Object wallet, String label) async {
63 + final bitcoinWallet = wallet as ElectrumWallet;
64 + await bitcoinWallet.walletAddresses.generateNewAddress(label: label);
65 + await wallet.save();
66 + }
67 +
68 + @override
69 + Future<void> updateAddress(Object wallet, String address, String label) async {
70 + final bitcoinWallet = wallet as ElectrumWallet;
71 + bitcoinWallet.walletAddresses.updateAddress(address, label);
72 + await wallet.save();
73 + }
74 +
75 + @override
76 + Object createBitcoinTransactionCredentials(List<Output> outputs,
77 + {required TransactionPriority priority, int? feeRate}) =>
78 + BitcoinTransactionCredentials(
79 + outputs
80 + .map((out) => OutputInfo(
81 + fiatAmount: out.fiatAmount,
82 + cryptoAmount: out.cryptoAmount,
83 + address: out.address,
84 + note: out.note,
85 + sendAll: out.sendAll,
86 + extractedAddress: out.extractedAddress,
87 + isParsedAddress: out.isParsedAddress,
88 + formattedCryptoAmount: out.formattedCryptoAmount))
89 + .toList(),
90 + priority: priority as BitcoinTransactionPriority,
91 + feeRate: feeRate);
92 +
93 + @override
94 + Object createBitcoinTransactionCredentialsRaw(List<OutputInfo> outputs,
95 + {TransactionPriority? priority, required int feeRate}) =>
96 + BitcoinTransactionCredentials(outputs,
97 + priority: priority != null ? priority as BitcoinTransactionPriority : null,
98 + feeRate: feeRate);
99 +
100 + @override
101 + List<String> getAddresses(Object wallet) {
102 + final bitcoinWallet = wallet as ElectrumWallet;
103 + return bitcoinWallet.walletAddresses.addressesByReceiveType
104 + .map((BitcoinAddressRecord addr) => addr.address)
105 + .toList();
106 + }
107 +
108 + @override
109 + @computed
110 + List<ElectrumSubAddress> getSubAddresses(Object wallet) {
111 + final electrumWallet = wallet as ElectrumWallet;
112 + return electrumWallet.walletAddresses.addressesByReceiveType
113 + .map((BitcoinAddressRecord addr) => ElectrumSubAddress(
114 + id: addr.index,
115 + name: addr.name,
116 + address: electrumWallet.type == WalletType.bitcoinCash ? addr.cashAddr : addr.address,
117 + txCount: addr.txCount,
118 + balance: addr.balance,
119 + isChange: addr.isHidden))
120 + .toList();
121 + }
122 +
123 + @override
124 + String getAddress(Object wallet) {
125 + final bitcoinWallet = wallet as ElectrumWallet;
126 + return bitcoinWallet.walletAddresses.address;
127 + }
128 +
129 + @override
130 + String formatterBitcoinAmountToString({required int amount}) =>
131 + bitcoinAmountToString(amount: amount);
132 +
133 + @override
134 + double formatterBitcoinAmountToDouble({required int amount}) =>
135 + bitcoinAmountToDouble(amount: amount);
136 +
137 + @override
138 + int formatterStringDoubleToBitcoinAmount(String amount) => stringDoubleToBitcoinAmount(amount);
139 +
140 + @override
141 + String bitcoinTransactionPriorityWithLabel(TransactionPriority priority, int rate) =>
142 + (priority as BitcoinTransactionPriority).labelWithRate(rate);
143 +
144 + @override
145 + List<BitcoinUnspent> getUnspents(Object wallet) {
146 + final bitcoinWallet = wallet as ElectrumWallet;
147 + return bitcoinWallet.unspentCoins;
148 + }
149 +
150 + Future<void> updateUnspents(Object wallet) async {
151 + final bitcoinWallet = wallet as ElectrumWallet;
152 + await bitcoinWallet.updateUnspent();
153 + }
154 +
155 + WalletService createBitcoinWalletService(
156 + Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource) {
157 + return BitcoinWalletService(walletInfoSource, unspentCoinSource);
158 + }
159 +
160 + WalletService createLitecoinWalletService(
161 + Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource) {
162 + return LitecoinWalletService(walletInfoSource, unspentCoinSource);
163 + }
164 +
165 + @override
166 + TransactionPriority getBitcoinTransactionPriorityMedium() => BitcoinTransactionPriority.medium;
167 +
168 + @override
169 + TransactionPriority getLitecoinTransactionPriorityMedium() => LitecoinTransactionPriority.medium;
170 +
171 + @override
172 + TransactionPriority getBitcoinTransactionPrioritySlow() => BitcoinTransactionPriority.slow;
173 +
174 + @override
175 + TransactionPriority getLitecoinTransactionPrioritySlow() => LitecoinTransactionPriority.slow;
176 +
177 + @override
178 + Future<void> setAddressType(Object wallet, dynamic option) async {
179 + final bitcoinWallet = wallet as ElectrumWallet;
180 + await bitcoinWallet.walletAddresses.setAddressType(option as BitcoinAddressType);
181 + }
182 +
183 + @override
184 + BitcoinReceivePageOption getSelectedAddressType(Object wallet) {
185 + final bitcoinWallet = wallet as ElectrumWallet;
186 + return BitcoinReceivePageOption.fromType(bitcoinWallet.walletAddresses.addressPageType);
187 + }
188 +
189 + @override
190 + List<BitcoinReceivePageOption> getBitcoinReceivePageOptions() => BitcoinReceivePageOption.all;
191 +}
lib/core/address_validator.dart
+12 -11
@@ -1,4 +1,4 @@
1 -import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
1 +import 'package:bitcoin_base/bitcoin_base.dart';
2 import 'package:cake_wallet/generated/i18n.dart';
3 import 'package:cake_wallet/core/validator.dart';
4 import 'package:cake_wallet/solana/solana.dart';
@@ -9,7 +9,9 @@ class AddressValidator extends TextValidator {
9 AddressValidator({required CryptoCurrency type})
10 : super(
11 errorMessage: S.current.error_text_address,
12 - useAdditionalValidation: type == CryptoCurrency.btc ? bitcoin.Address.validateAddress : null,
12 + useAdditionalValidation: type == CryptoCurrency.btc
13 + ? (String txt) => validateAddress(address: txt, network: BitcoinNetwork.mainnet)
14 + : null,
15 pattern: getPattern(type),
16 length: getLength(type));
17
@@ -24,7 +26,7 @@ class AddressValidator extends TextValidator {
26 return '^[0-9a-zA-Z]{59}\$|^[0-9a-zA-Z]{92}\$|^[0-9a-zA-Z]{104}\$'
27 '|^[0-9a-zA-Z]{105}\$|^addr1[0-9a-zA-Z]{98}\$';
28 case CryptoCurrency.btc:
27 - return '^3[0-9a-zA-Z]{32}\$|^3[0-9a-zA-Z]{33}\$|^bc1[0-9a-zA-Z]{59}\$';
29 + return '^${P2pkhAddress.regex.pattern}\$|^${P2shAddress.regex.pattern}\$|^${P2wpkhAddress.regex.pattern}\$|${P2trAddress.regex.pattern}\$|^${P2wshAddress.regex.pattern}\$';
30 case CryptoCurrency.nano:
31 return '[0-9a-zA-Z_]';
32 case CryptoCurrency.banano:
@@ -89,7 +91,7 @@ class AddressValidator extends TextValidator {
91 case CryptoCurrency.dai:
92 case CryptoCurrency.dash:
93 case CryptoCurrency.eos:
92 - return '[0-9a-zA-Z]';
94 + return '[0-9a-zA-Z]';
95 case CryptoCurrency.bch:
96 return '^(?!bitcoincash:)[0-9a-zA-Z]*\$|^(?!bitcoincash:)q|p[0-9a-zA-Z]{41}\$|^(?!bitcoincash:)q|p[0-9a-zA-Z]{42}\$|^bitcoincash:q|p[0-9a-zA-Z]{41}\$|^bitcoincash:q|p[0-9a-zA-Z]{42}\$';
97 case CryptoCurrency.bnb:
@@ -268,12 +270,11 @@ class AddressValidator extends TextValidator {
270 '|([^0-9a-zA-Z]|^)8[0-9a-zA-Z]{94}([^0-9a-zA-Z]|\$)'
271 '|([^0-9a-zA-Z]|^)[0-9a-zA-Z]{106}([^0-9a-zA-Z]|\$)';
272 case CryptoCurrency.btc:
271 - return '([^0-9a-zA-Z]|^)1[0-9a-zA-Z]{32}([^0-9a-zA-Z]|\$)'
272 - '|([^0-9a-zA-Z]|^)1[0-9a-zA-Z]{33}([^0-9a-zA-Z]|\$)'
273 - '|([^0-9a-zA-Z]|^)3[0-9a-zA-Z]{32}([^0-9a-zA-Z]|\$)'
274 - '|([^0-9a-zA-Z]|^)3[0-9a-zA-Z]{33}([^0-9a-zA-Z]|\$)'
275 - '|([^0-9a-zA-Z]|^)bc1[0-9a-zA-Z]{39}([^0-9a-zA-Z]|\$)'
276 - '|([^0-9a-zA-Z]|^)bc1[0-9a-zA-Z]{59}([^0-9a-zA-Z]|\$)';
273 + return '([^0-9a-zA-Z]|^)${P2pkhAddress.regex.pattern}|\$)'
274 + '([^0-9a-zA-Z]|^)${P2shAddress.regex.pattern}|\$)'
275 + '([^0-9a-zA-Z]|^)${P2wpkhAddress.regex.pattern}|\$)'
276 + '([^0-9a-zA-Z]|^)${P2wshAddress.regex.pattern}|\$)'
277 + '([^0-9a-zA-Z]|^)${P2trAddress.regex.pattern}|\$)';
278 case CryptoCurrency.ltc:
279 return '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)'
280 '|([^0-9a-zA-Z]|^)[LM][a-km-zA-HJ-NP-Z1-9]{26,33}([^0-9a-zA-Z]|\$)'
@@ -297,4 +298,4 @@ class AddressValidator extends TextValidator {
298 return null;
299 }
300 }
300 -}
\ No newline at end of file
301 +}
lib/core/wallet_creation_service.dart
+6 -7
@@ -2,7 +2,6 @@ import 'package:cake_wallet/di.dart';
2 import 'package:cake_wallet/store/settings_store.dart';
3 import 'package:cw_core/wallet_info.dart';
4 import 'package:cake_wallet/entities/preferences_key.dart';
5 -import 'package:flutter/foundation.dart';
5 import 'package:flutter_secure_storage/flutter_secure_storage.dart';
6 import 'package:hive/hive.dart';
7 import 'package:shared_preferences/shared_preferences.dart';
@@ -55,7 +54,7 @@ class WalletCreationService {
54 }
55 }
56
58 - Future<WalletBase> create(WalletCredentials credentials) async {
57 + Future<WalletBase> create(WalletCredentials credentials, {bool? isTestnet}) async {
58 checkIfExists(credentials.name);
59 final password = generateWalletPassword();
60 credentials.password = password;
@@ -63,7 +62,7 @@ class WalletCreationService {
62 credentials.seedPhraseLength = settingsStore.seedPhraseLength.value;
63 }
64 await keyService.saveWalletPassword(password: password, walletName: credentials.name);
66 - final wallet = await _service!.create(credentials);
65 + final wallet = await _service!.create(credentials, isTestnet: isTestnet);
66
67 if (wallet.type == WalletType.monero) {
68 await sharedPreferences.setBool(
@@ -73,12 +72,12 @@ class WalletCreationService {
72 return wallet;
73 }
74
76 - Future<WalletBase> restoreFromKeys(WalletCredentials credentials) async {
75 + Future<WalletBase> restoreFromKeys(WalletCredentials credentials, {bool? isTestnet}) async {
76 checkIfExists(credentials.name);
77 final password = generateWalletPassword();
78 credentials.password = password;
79 await keyService.saveWalletPassword(password: password, walletName: credentials.name);
81 - final wallet = await _service!.restoreFromKeys(credentials);
80 + final wallet = await _service!.restoreFromKeys(credentials, isTestnet: isTestnet);
81
82 if (wallet.type == WalletType.monero) {
83 await sharedPreferences.setBool(
@@ -88,12 +87,12 @@ class WalletCreationService {
87 return wallet;
88 }
89
91 - Future<WalletBase> restoreFromSeed(WalletCredentials credentials) async {
90 + Future<WalletBase> restoreFromSeed(WalletCredentials credentials, {bool? isTestnet}) async {
91 checkIfExists(credentials.name);
92 final password = generateWalletPassword();
93 credentials.password = password;
94 await keyService.saveWalletPassword(password: password, walletName: credentials.name);
96 - final wallet = await _service!.restoreFromSeed(credentials);
95 + final wallet = await _service!.restoreFromSeed(credentials, isTestnet: isTestnet);
96
97 if (wallet.type == WalletType.monero) {
98 await sharedPreferences.setBool(
lib/di.dart
+1 -1
@@ -13,7 +13,7 @@ import 'package:cake_wallet/core/yat_service.dart';
13 import 'package:cake_wallet/entities/background_tasks.dart';
14 import 'package:cake_wallet/entities/exchange_api_mode.dart';
15 import 'package:cake_wallet/entities/parse_address_from_domain.dart';
16 -import 'package:cake_wallet/entities/receive_page_option.dart';
16 +import 'package:cw_core/receive_page_option.dart';
17 import 'package:cake_wallet/ethereum/ethereum.dart';
18 import 'package:cake_wallet/nano/nano.dart';
19 import 'package:cake_wallet/ionia/ionia_anypay.dart';
lib/entities/default_settings_migration.dart
+19 -2
@@ -23,6 +23,10 @@ import 'package:collection/collection.dart';
23
24 const newCakeWalletMoneroUri = 'xmr-node.cakewallet.com:18081';
25 const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002';
26 +const publicBitcoinTestnetElectrumAddress = 'electrum.blockstream.info';
27 +const publicBitcoinTestnetElectrumPort = '60002';
28 +const publicBitcoinTestnetElectrumUri =
29 + '$publicBitcoinTestnetElectrumAddress:$publicBitcoinTestnetElectrumPort';
30 const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002';
31 const havenDefaultNodeUri = 'nodes.havenprotocol.org:443';
32 const ethereumDefaultNodeUri = 'ethereum.publicnode.com';
@@ -334,6 +338,12 @@ Node? getBitcoinDefaultElectrumServer({required Box<Node> nodes}) {
338 nodes.values.firstWhereOrNull((node) => node.type == WalletType.bitcoin);
339 }
340
341 +Node? getBitcoinTestnetDefaultElectrumServer({required Box<Node> nodes}) {
342 + return nodes.values
343 + .firstWhereOrNull((Node node) => node.uriRaw == publicBitcoinTestnetElectrumUri) ??
344 + nodes.values.firstWhereOrNull((node) => node.type == WalletType.bitcoin);
345 +}
346 +
347 Node? getLitecoinDefaultElectrumServer({required Box<Node> nodes}) {
348 return nodes.values
349 .firstWhereOrNull((Node node) => node.uriRaw == cakeWalletLitecoinElectrumUri) ??
@@ -503,8 +513,15 @@ Future<void> rewriteSecureStoragePin({required FlutterSecureStorage secureStorag
513 }
514
515 Future<void> changeBitcoinCurrentElectrumServerToDefault(
506 - {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
507 - final server = getBitcoinDefaultElectrumServer(nodes: nodes);
516 + {required SharedPreferences sharedPreferences,
517 + required Box<Node> nodes,
518 + bool? isTestnet}) async {
519 + Node? server;
520 + if (isTestnet == true) {
521 + server = getBitcoinTestnetDefaultElectrumServer(nodes: nodes);
522 + } else {
523 + server = getBitcoinDefaultElectrumServer(nodes: nodes);
524 + }
525 final serverId = server?.key as int? ?? 0;
526
527 await sharedPreferences.setInt(PreferencesKey.currentBitcoinElectrumSererIdKey, serverId);
lib/entities/receive_page_option.dart deleted
-23
@@ -1,23 +0,0 @@
1 -
2 -enum ReceivePageOption {
3 - mainnet,
4 - anonPayInvoice,
5 - anonPayDonationLink;
6 -
7 - @override
8 - String toString() {
9 - String label = '';
10 - switch (this) {
11 - case ReceivePageOption.mainnet:
12 - label = 'Mainnet';
13 - break;
14 - case ReceivePageOption.anonPayInvoice:
15 - label = 'Trocador AnonPay Invoice';
16 - break;
17 - case ReceivePageOption.anonPayDonationLink:
18 - label = 'Trocador AnonPay Donation Link';
19 - break;
20 - }
21 - return label;
22 - }
23 -}
lib/router.dart
+10 -4
@@ -532,13 +532,19 @@ Route<dynamic> createRoute(RouteSettings settings) {
532 builder: (_) => getIt.get<WebViewPage>(param1: title, param2: url));
533
534 case Routes.advancedPrivacySettings:
535 - final type = settings.arguments as WalletType;
535 + final args = settings.arguments as Map<String, dynamic>;
536 + final type = args['type'] as WalletType;
537 + final useTestnet = args['useTestnet'] as bool;
538 + final toggleTestnet = args['toggleTestnet'] as Function(bool? val);
539
540 return CupertinoPageRoute<void>(
541 builder: (_) => AdvancedPrivacySettingsPage(
539 - getIt.get<AdvancedPrivacySettingsViewModel>(param1: type),
540 - getIt.get<NodeCreateOrEditViewModel>(param1: type, param2: false),
541 - getIt.get<SeedTypeViewModel>()));
542 + useTestnet,
543 + toggleTestnet,
544 + getIt.get<AdvancedPrivacySettingsViewModel>(param1: type),
545 + getIt.get<NodeCreateOrEditViewModel>(param1: type, param2: false),
546 + getIt.get<SeedTypeViewModel>(),
547 + ));
548
549 case Routes.anonPayInvoicePage:
550 final args = settings.arguments as List;
lib/src/screens/dashboard/pages/address_page.dart
+22 -6
@@ -5,7 +5,8 @@ import 'package:cake_wallet/src/screens/base_page.dart';
5 import 'package:cake_wallet/src/screens/monero_accounts/monero_account_list_page.dart';
6 import 'package:cake_wallet/anonpay/anonpay_donation_link_info.dart';
7 import 'package:cake_wallet/entities/preferences_key.dart';
8 -import 'package:cake_wallet/entities/receive_page_option.dart';
8 +import 'package:cw_core/receive_page_option.dart';
9 +import 'package:cw_bitcoin/bitcoin_receive_page_option.dart';
10 import 'package:cake_wallet/src/screens/dashboard/widgets/present_receive_option_picker.dart';
11 import 'package:cake_wallet/src/widgets/gradient_background.dart';
12 import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
@@ -27,6 +28,7 @@ import 'package:keyboard_actions/keyboard_actions.dart';
28 import 'package:mobx/mobx.dart';
29 import 'package:shared_preferences/shared_preferences.dart';
30 import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
31 +import 'package:bitcoin_base/bitcoin_base.dart';
32
33 class AddressPage extends BasePage {
34 AddressPage({
@@ -69,7 +71,7 @@ class AddressPage extends BasePage {
71 size: 16,
72 );
73 final _closeButton =
72 - currentTheme.type == ThemeType.dark ? closeButtonImageDarkTheme : closeButtonImage;
74 + currentTheme.type == ThemeType.dark ? closeButtonImageDarkTheme : closeButtonImage;
75
76 bool isMobileView = responsiveLayoutUtil.shouldRenderMobileUI;
77
@@ -163,11 +165,10 @@ class AddressPage extends BasePage {
165 return SelectButton(
166 text: addressListViewModel.buttonTitle,
167 onTap: () async => dashboardViewModel.isAutoGenerateSubaddressesEnabled &&
166 - (WalletType.monero == addressListViewModel.wallet.type ||
167 - WalletType.haven == addressListViewModel.wallet.type)
168 + (WalletType.monero == addressListViewModel.wallet.type ||
169 + WalletType.haven == addressListViewModel.wallet.type)
170 ? await showPopUp<void>(
169 - context: context,
170 - builder: (_) => getIt.get<MoneroAccountListPage>())
171 + context: context, builder: (_) => getIt.get<MoneroAccountListPage>())
172 : Navigator.of(context).pushNamed(Routes.receive),
173 textColor: Theme.of(context).extension<SyncIndicatorTheme>()!.textColor,
174 color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
@@ -229,6 +230,21 @@ class AddressPage extends BasePage {
230 );
231 }
232 break;
233 + case BitcoinReceivePageOption.p2pkh:
234 + addressListViewModel.setAddressType(P2pkhAddressType.p2pkh);
235 + break;
236 + case BitcoinReceivePageOption.p2sh:
237 + addressListViewModel.setAddressType(P2shAddressType.p2wpkhInP2sh);
238 + break;
239 + case BitcoinReceivePageOption.p2wpkh:
240 + addressListViewModel.setAddressType(SegwitAddresType.p2wpkh);
241 + break;
242 + case BitcoinReceivePageOption.p2tr:
243 + addressListViewModel.setAddressType(SegwitAddresType.p2tr);
244 + break;
245 + case BitcoinReceivePageOption.p2wsh:
246 + addressListViewModel.setAddressType(SegwitAddresType.p2wsh);
247 + break;
248 default:
249 }
250 });
lib/src/screens/new_wallet/advanced_privacy_settings_page.dart
+36 -3
@@ -1,3 +1,4 @@
1 +import 'package:cake_wallet/entities/default_settings_migration.dart';
2 import 'package:cake_wallet/entities/exchange_api_mode.dart';
3 import 'package:cake_wallet/entities/fiat_api_mode.dart';
4 import 'package:cake_wallet/entities/seed_phrase_length.dart';
@@ -11,6 +12,7 @@ import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.
12 import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
13 import 'package:cake_wallet/view_model/seed_type_view_model.dart';
14 import 'package:cake_wallet/view_model/settings/choices_list_item.dart';
15 +import 'package:cw_core/wallet_type.dart';
16 import 'package:flutter_mobx/flutter_mobx.dart';
17 import 'package:flutter/material.dart';
18 import 'package:cake_wallet/generated/i18n.dart';
@@ -19,7 +21,7 @@ import 'package:cake_wallet/src/widgets/primary_button.dart';
21 import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
22
23 class AdvancedPrivacySettingsPage extends BasePage {
22 - AdvancedPrivacySettingsPage(
24 + AdvancedPrivacySettingsPage(this.useTestnet, this.toggleUseTestnet,
25 this.advancedPrivacySettingsViewModel, this.nodeViewModel, this.seedTypeViewModel);
26
27 final AdvancedPrivacySettingsViewModel advancedPrivacySettingsViewModel;
@@ -29,13 +31,16 @@ class AdvancedPrivacySettingsPage extends BasePage {
31 @override
32 String get title => S.current.privacy_settings;
33
34 + final bool useTestnet;
35 + final Function(bool? val) toggleUseTestnet;
36 +
37 @override
33 - Widget body(BuildContext context) => AdvancedPrivacySettingsBody(
38 + Widget body(BuildContext context) => AdvancedPrivacySettingsBody(useTestnet, toggleUseTestnet,
39 advancedPrivacySettingsViewModel, nodeViewModel, seedTypeViewModel);
40 }
41
42 class AdvancedPrivacySettingsBody extends StatefulWidget {
38 - const AdvancedPrivacySettingsBody(
43 + const AdvancedPrivacySettingsBody(this.useTestnet, this.toggleUseTestnet,
44 this.privacySettingsViewModel, this.nodeViewModel, this.seedTypeViewModel,
45 {Key? key})
46 : super(key: key);
@@ -44,6 +49,9 @@ class AdvancedPrivacySettingsBody extends StatefulWidget {
49 final NodeCreateOrEditViewModel nodeViewModel;
50 final SeedTypeViewModel seedTypeViewModel;
51
52 + final bool useTestnet;
53 + final Function(bool? val) toggleUseTestnet;
54 +
55 @override
56 _AdvancedPrivacySettingsBodyState createState() => _AdvancedPrivacySettingsBodyState();
57 }
@@ -52,9 +60,14 @@ class _AdvancedPrivacySettingsBodyState extends State<AdvancedPrivacySettingsBod
60 _AdvancedPrivacySettingsBodyState();
61
62 final _formKey = GlobalKey<FormState>();
63 + bool? testnetValue;
64
65 @override
66 Widget build(BuildContext context) {
67 + if (testnetValue == null && widget.useTestnet != null) {
68 + testnetValue = widget.useTestnet;
69 + }
70 +
71 return Container(
72 padding: EdgeInsets.only(top: 24),
73 child: ScrollableWithBottomSection(
@@ -125,6 +138,19 @@ class _AdvancedPrivacySettingsBodyState extends State<AdvancedPrivacySettingsBod
138 ),
139 );
140 }),
141 + if (widget.privacySettingsViewModel.type == WalletType.bitcoin)
142 + Builder(builder: (_) {
143 + final val = testnetValue!;
144 + return SettingsSwitcherCell(
145 + title: S.current.use_testnet,
146 + value: val,
147 + onValueChange: (_, __) {
148 + setState(() {
149 + testnetValue = !val;
150 + });
151 + widget.toggleUseTestnet!.call(testnetValue);
152 + });
153 + }),
154 ],
155 ),
156 bottomSectionPadding: EdgeInsets.all(24),
@@ -137,6 +163,13 @@ class _AdvancedPrivacySettingsBodyState extends State<AdvancedPrivacySettingsBod
163 return;
164 }
165
166 + widget.nodeViewModel.save();
167 + } else if (testnetValue == true) {
168 + // TODO: add type (mainnet/testnet) to Node class so when switching wallets the node can be switched to a matching type
169 + // Currently this is so you can create a working testnet wallet but you need to keep switching back the node if you use multiple wallets at once
170 + widget.nodeViewModel.address = publicBitcoinTestnetElectrumAddress;
171 + widget.nodeViewModel.port = publicBitcoinTestnetElectrumPort;
172 +
173 widget.nodeViewModel.save();
174 }
175
lib/src/screens/new_wallet/new_wallet_page.dart
+9 -5
@@ -40,7 +40,9 @@ class NewWalletPage extends BasePage {
40
41 @override
42 Widget body(BuildContext context) => WalletNameForm(
43 - _walletNewVM, currentTheme.type == ThemeType.dark ? walletNameImage : walletNameLightImage, _seedTypeViewModel);
43 + _walletNewVM,
44 + currentTheme.type == ThemeType.dark ? walletNameImage : walletNameLightImage,
45 + _seedTypeViewModel);
46 }
47
48 class WalletNameForm extends StatefulWidget {
@@ -187,7 +189,6 @@ class _WalletNameFormState extends State<WalletNameForm> {
189 ),
190 ),
191 ),
190 -
192 if (_walletNewVM.hasLanguageSelector) ...[
193 if (_walletNewVM.hasSeedType) ...[
194 Observer(
@@ -222,7 +223,7 @@ class _WalletNameFormState extends State<WalletNameForm> {
223 ),
224 ),
225 )
225 - ]
226 + ],
227 ],
228 ),
229 ),
@@ -245,8 +246,11 @@ class _WalletNameFormState extends State<WalletNameForm> {
246 const SizedBox(height: 25),
247 GestureDetector(
248 onTap: () {
248 - Navigator.of(context)
249 - .pushNamed(Routes.advancedPrivacySettings, arguments: _walletNewVM.type);
249 + Navigator.of(context).pushNamed(Routes.advancedPrivacySettings, arguments: {
250 + "type": _walletNewVM.type,
251 + "useTestnet": _walletNewVM.useTestnet,
252 + "toggleTestnet": _walletNewVM.toggleUseTestnet
253 + });
254 },
255 child: Text(S.of(context).advanced_settings),
256 ),
lib/src/screens/receive/anonpay_invoice_page.dart
+1 -1
@@ -4,7 +4,7 @@ import 'package:cake_wallet/anonpay/anonpay_donation_link_info.dart';
4 import 'package:cake_wallet/core/execution_state.dart';
5 import 'package:cake_wallet/di.dart';
6 import 'package:cake_wallet/entities/preferences_key.dart';
7 -import 'package:cake_wallet/entities/receive_page_option.dart';
7 +import 'package:cw_core/receive_page_option.dart';
8 import 'package:cake_wallet/routes.dart';
9 import 'package:cake_wallet/src/screens/dashboard/widgets/present_receive_option_picker.dart';
10 import 'package:cake_wallet/src/screens/receive/widgets/anonpay_input_form.dart';
lib/src/screens/receive/anonpay_receive_page.dart
+1 -1
@@ -1,7 +1,7 @@
1 import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
2 import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
3 import 'package:cake_wallet/entities/qr_view_data.dart';
4 -import 'package:cake_wallet/entities/receive_page_option.dart';
4 +import 'package:cw_core/receive_page_option.dart';
5 import 'package:cake_wallet/generated/i18n.dart';
6 import 'package:cake_wallet/routes.dart';
7 import 'package:cake_wallet/src/screens/base_page.dart';
lib/src/screens/restore/wallet_restore_page.dart
+6 -2
@@ -210,8 +210,12 @@ class WalletRestorePage extends BasePage {
210 const SizedBox(height: 25),
211 GestureDetector(
212 onTap: () {
213 - Navigator.of(context).pushNamed(Routes.advancedPrivacySettings,
214 - arguments: walletRestoreViewModel.type);
213 + Navigator.of(context)
214 + .pushNamed(Routes.advancedPrivacySettings, arguments: {
215 + 'type': walletRestoreViewModel.type,
216 + 'useTestnet': walletRestoreViewModel.useTestnet,
217 + 'toggleTestnet': walletRestoreViewModel.toggleUseTestnet
218 + });
219 },
220 child: Text(S.of(context).advanced_settings),
221 ),
lib/view_model/anon_invoice_page_view_model.dart
+1 -1
@@ -4,7 +4,7 @@ import 'package:cake_wallet/anonpay/anonpay_request.dart';
4 import 'package:cake_wallet/core/execution_state.dart';
5 import 'package:cake_wallet/entities/fiat_currency.dart';
6 import 'package:cake_wallet/entities/preferences_key.dart';
7 -import 'package:cake_wallet/entities/receive_page_option.dart';
7 +import 'package:cw_core/receive_page_option.dart';
8 import 'package:cake_wallet/store/settings_store.dart';
9 import 'package:cw_core/crypto_currency.dart';
10 import 'package:cw_core/currency.dart';
lib/view_model/dashboard/dashboard_view_model.dart
+14 -10
@@ -148,17 +148,21 @@ abstract class DashboardViewModelBase with Store {
148 monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id)
149 .toList();
150
151 - transactions = ObservableList.of(_accountTransactions.map((transaction) =>
152 - TransactionListItem(
153 - transaction: transaction,
154 - balanceViewModel: balanceViewModel,
155 - settingsStore: appStore.settingsStore)));
151 + final sortedTransactions = [..._accountTransactions];
152 + sortedTransactions.sort((a, b) => a.date.compareTo(b.date));
153 +
154 + transactions = ObservableList.of(sortedTransactions.map((transaction) => TransactionListItem(
155 + transaction: transaction,
156 + balanceViewModel: balanceViewModel,
157 + settingsStore: appStore.settingsStore)));
158 } else {
157 - transactions = ObservableList.of(wallet.transactionHistory.transactions.values.map(
158 - (transaction) => TransactionListItem(
159 - transaction: transaction,
160 - balanceViewModel: balanceViewModel,
161 - settingsStore: appStore.settingsStore)));
159 + final sortedTransactions = [...wallet.transactionHistory.transactions.values];
160 + sortedTransactions.sort((a, b) => a.date.compareTo(b.date));
161 +
162 + transactions = ObservableList.of(sortedTransactions.map((transaction) => TransactionListItem(
163 + transaction: transaction,
164 + balanceViewModel: balanceViewModel,
165 + settingsStore: appStore.settingsStore)));
166 }
167
168 // TODO: nano sub-account generation is disabled:
lib/view_model/dashboard/receive_option_view_model.dart
+14 -4
@@ -1,4 +1,5 @@
1 -import 'package:cake_wallet/entities/receive_page_option.dart';
1 +import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 +import 'package:cw_core/receive_page_option.dart';
3 import 'package:cw_core/wallet_base.dart';
4 import 'package:cw_core/wallet_type.dart';
5 import 'package:mobx/mobx.dart';
@@ -9,11 +10,20 @@ class ReceiveOptionViewModel = ReceiveOptionViewModelBase with _$ReceiveOptionVi
10
11 abstract class ReceiveOptionViewModelBase with Store {
12 ReceiveOptionViewModelBase(this._wallet, this.initialPageOption)
12 - : selectedReceiveOption = initialPageOption ?? ReceivePageOption.mainnet,
13 + : selectedReceiveOption = initialPageOption ??
14 + (_wallet.type == WalletType.bitcoin
15 + ? bitcoin!.getSelectedAddressType(_wallet)
16 + : ReceivePageOption.mainnet),
17 _options = [] {
18 final walletType = _wallet.type;
15 - _options =
16 - walletType == WalletType.haven ? [ReceivePageOption.mainnet] : ReceivePageOption.values;
19 + _options = walletType == WalletType.haven
20 + ? [ReceivePageOption.mainnet]
21 + : walletType == WalletType.bitcoin
22 + ? [
23 + ...bitcoin!.getBitcoinReceivePageOptions(),
24 + ...ReceivePageOptions.where((element) => element != ReceivePageOption.mainnet)
25 + ]
26 + : ReceivePageOptions;
27 }
28
29 final WalletBase _wallet;
lib/view_model/node_list/node_create_or_edit_view_model.dart
+2
@@ -65,6 +65,8 @@ abstract class NodeCreateOrEditViewModelBase with Store {
65 bool get hasAuthCredentials =>
66 _walletType == WalletType.monero || _walletType == WalletType.haven;
67
68 + bool get hasTestnetSupport => _walletType == WalletType.bitcoin;
69 +
70 String get uri {
71 var uri = address;
72
lib/view_model/node_list/node_list_view_model.dart
+5 -1
@@ -52,7 +52,11 @@ abstract class NodeListViewModelBase with Store {
52
53 switch (_appStore.wallet!.type) {
54 case WalletType.bitcoin:
55 - node = getBitcoinDefaultElectrumServer(nodes: _nodeSource)!;
55 + if (_appStore.wallet!.isTestnet == true) {
56 + node = getBitcoinTestnetDefaultElectrumServer(nodes: _nodeSource)!;
57 + } else {
58 + node = getBitcoinDefaultElectrumServer(nodes: _nodeSource)!;
59 + }
60 break;
61 case WalletType.monero:
62 node = getMoneroDefaultNode(nodes: _nodeSource);
lib/view_model/transaction_details_view_model.dart
+1 -1
@@ -119,7 +119,7 @@ abstract class TransactionDetailsViewModelBase with Store {
119 case WalletType.monero:
120 return 'https://monero.com/tx/${txId}';
121 case WalletType.bitcoin:
122 - return 'https://mempool.space/tx/${txId}';
122 + return 'https://mempool.space/${wallet.isTestnet == true ? "testnet/" : ""}tx/${txId}';
123 case WalletType.litecoin:
124 return 'https://blockchair.com/litecoin/transaction/${txId}';
125 case WalletType.bitcoinCash:
lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
+8 -10
@@ -388,9 +388,6 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
388 wallet.type == WalletType.bitcoin ||
389 wallet.type == WalletType.litecoin;
390
391 - // wallet.type == WalletType.nano ||
392 - // wallet.type == WalletType.banano; TODO: nano accounts are disabled for now
393 -
391 @computed
392 bool get isElectrumWallet =>
393 wallet.type == WalletType.bitcoin ||
@@ -409,16 +406,17 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
406 void setAddress(WalletAddressListItem address) =>
407 wallet.walletAddresses.address = address.address;
408
409 + @action
410 + Future<void> setAddressType(dynamic option) async {
411 + if (wallet.type == WalletType.bitcoin) {
412 + await bitcoin!.setAddressType(wallet, option);
413 + }
414 + }
415 +
416 void _init() {
417 _baseItems = [];
418
415 - if (wallet.type == WalletType.monero ||
416 - wallet.type ==
417 - WalletType
418 - .haven /*||
419 - wallet.type == WalletType.nano ||
420 - wallet.type == WalletType.banano*/
421 - ) {
419 + if (wallet.type == WalletType.monero || wallet.type == WalletType.haven) {
420 _baseItems.add(WalletAccountListHeader());
421 }
422
lib/view_model/wallet_creation_vm.dart
+11
@@ -23,6 +23,12 @@ abstract class WalletCreationVMBase with Store {
23 : state = InitialExecutionState(),
24 name = '';
25
26 + @observable
27 + bool _useTestnet = false;
28 +
29 + @computed
30 + bool get useTestnet => _useTestnet;
31 +
32 @observable
33 String name;
34
@@ -94,4 +100,9 @@ abstract class WalletCreationVMBase with Store {
100 Future<WalletBase> processFromRestoredWallet(
101 WalletCredentials credentials, RestoredWallet restoreWallet) =>
102 throw UnimplementedError();
103 +
104 + @action
105 + void toggleUseTestnet(bool? value) {
106 + _useTestnet = value ?? !_useTestnet;
107 + }
108 }
lib/view_model/wallet_new_vm.dart
+1 -1
@@ -87,6 +87,6 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
87 @override
88 Future<WalletBase> process(WalletCredentials credentials) async {
89 walletCreationService.changeWalletType(type: type);
90 - return walletCreationService.create(credentials);
90 + return walletCreationService.create(credentials, isTestnet: useTestnet);
91 }
92 }
lib/view_model/wallet_restore_view_model.dart
+2 -2
@@ -207,9 +207,9 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
207 @override
208 Future<WalletBase> process(WalletCredentials credentials) async {
209 if (mode == WalletRestoreMode.keys) {
210 - return walletCreationService.restoreFromKeys(credentials);
210 + return walletCreationService.restoreFromKeys(credentials, isTestnet: useTestnet);
211 }
212
213 - return walletCreationService.restoreFromSeed(credentials);
213 + return walletCreationService.restoreFromSeed(credentials, isTestnet: useTestnet);
214 }
215 }
res/values/strings_ar.arb
+1
@@ -719,6 +719,7 @@
719 "use_card_info_two": "يتم تحويل الأموال إلى الدولار الأمريكي عند الاحتفاظ بها في الحساب المدفوع مسبقًا ، وليس بالعملات الرقمية.",
720 "use_ssl": "استخدم SSL",
721 "use_suggested": "استخدام المقترح",
722 + "use_testnet": "استخدم testnet",
723 "variable_pair_not_supported": "هذا الزوج المتغير غير مدعوم في التبادلات المحددة",
724 "verification": "تَحَقّق",
725 "verify_with_2fa": "تحقق مع Cake 2FA",
res/values/strings_bg.arb
+1
@@ -719,6 +719,7 @@
719 "use_card_info_two": "Средствата се обръщат в USD, когато биват запазени в предплатената карта, а не в дигитална валута.",
720 "use_ssl": "Използване на SSL",
721 "use_suggested": "Използване на предложеното",
722 + "use_testnet": "Използвайте TestNet",
723 "variable_pair_not_supported": "Този variable pair не се поддържа от избраната борса",
724 "verification": "Потвърждаване",
725 "verify_with_2fa": "Проверете с Cake 2FA",
res/values/strings_cs.arb
+1
@@ -719,6 +719,7 @@
719 "use_card_info_two": "Prostředky jsou převedeny na USD, když jsou drženy na předplaceném účtu, nikoliv na digitální měnu.",
720 "use_ssl": "Použít SSL",
721 "use_suggested": "Použít doporučený",
722 + "use_testnet": "Použijte testNet",
723 "variable_pair_not_supported": "Tento pár s tržním kurzem není ve zvolené směnárně podporován",
724 "verification": "Ověření",
725 "verify_with_2fa": "Ověřte pomocí Cake 2FA",
res/values/strings_de.arb
+1
@@ -721,6 +721,7 @@
721 "use_card_info_two": "Guthaben werden auf dem Prepaid-Konto in USD umgerechnet, nicht in digitale Währung.",
722 "use_ssl": "SSL verwenden",
723 "use_suggested": "Vorgeschlagen verwenden",
724 + "use_testnet": "TESTNET verwenden",
725 "variable_pair_not_supported": "Dieses Variablenpaar wird von den ausgewählten Börsen nicht unterstützt",
726 "verification": "Verifizierung",
727 "verify_with_2fa": "Verifizieren Sie mit Cake 2FA",
res/values/strings_en.arb
+1
@@ -719,6 +719,7 @@
719 "use_card_info_two": "Funds are converted to USD when they're held in the prepaid account, not in digital currencies.",
720 "use_ssl": "Use SSL",
721 "use_suggested": "Use Suggested",
722 + "use_testnet": "Use Testnet",
723 "variable_pair_not_supported": "This variable pair is not supported with the selected exchanges",
724 "verification": "Verification",
725 "verify_with_2fa": "Verify with Cake 2FA",
res/values/strings_es.arb
+1
@@ -720,6 +720,7 @@
720 "use_card_info_two": "Los fondos se convierten a USD cuando se mantienen en la cuenta prepaga, no en monedas digitales.",
721 "use_ssl": "Utilice SSL",
722 "use_suggested": "Usar sugerido",
723 + "use_testnet": "Use TestNet",
724 "variable_pair_not_supported": "Este par de variables no es compatible con los intercambios seleccionados",
725 "verification": "Verificación",
726 "verify_with_2fa": "Verificar con Cake 2FA",
res/values/strings_fr.arb
+1
@@ -719,6 +719,7 @@
719 "use_card_info_two": "Les fonds sont convertis en USD lorsqu'ils sont détenus sur le compte prépayé, et non en devises numériques.",
720 "use_ssl": "Utiliser SSL",
721 "use_suggested": "Suivre la suggestion",
722 + "use_testnet": "Utiliser TestNet",
723 "variable_pair_not_supported": "Cette paire variable n'est pas prise en charge avec les échanges sélectionnés",
724 "verification": "Vérification",
725 "verify_with_2fa": "Vérifier avec Cake 2FA",
res/values/strings_ha.arb
+1
@@ -721,6 +721,7 @@
721 "use_card_info_two": "Ana canza kuɗi zuwa dalar Amurka lokacin da ake riƙe su a cikin asusun da aka riga aka biya, ba cikin agogon dijital ba.",
722 "use_ssl": "Yi amfani da SSL",
723 "use_suggested": "Amfani da Shawarwari",
724 + "use_testnet": "Amfani da gwaji",
725 "variable_pair_not_supported": "Ba a samun goyan bayan wannan m biyu tare da zaɓaɓɓun musayar",
726 "verification": "tabbatar",
727 "verify_with_2fa": "Tabbatar da Cake 2FA",
res/values/strings_hi.arb
+1
@@ -721,6 +721,7 @@
721 "use_card_info_two": "डिजिटल मुद्राओं में नहीं, प्रीपेड खाते में रखे जाने पर निधियों को यूएसडी में बदल दिया जाता है।",
722 "use_ssl": "उपयोग SSL",
723 "use_suggested": "सुझाए गए का प्रयोग करें",
724 + "use_testnet": "टेस्टनेट का उपयोग करें",
725 "variable_pair_not_supported": "यह परिवर्तनीय जोड़ी चयनित एक्सचेंजों के साथ समर्थित नहीं है",
726 "verification": "सत्यापन",
727 "verify_with_2fa": "केक 2FA के साथ सत्यापित करें",
res/values/strings_hr.arb
+1
@@ -719,6 +719,7 @@
719 "use_card_info_two": "Sredstva se pretvaraju u USD kada se drže na prepaid računu, a ne u digitalnim valutama.",
720 "use_ssl": "Koristi SSL",
721 "use_suggested": "Koristite predloženo",
722 + "use_testnet": "Koristite TestNet",
723 "variable_pair_not_supported": "Ovaj par varijabli nije podržan s odabranim burzama",
724 "verification": "Potvrda",
725 "verify_with_2fa": "Provjerite s Cake 2FA",
res/values/strings_id.arb
+1
@@ -722,6 +722,7 @@
722 "use_card_info_two": "Dana dikonversi ke USD ketika disimpan dalam akun pra-bayar, bukan dalam mata uang digital.",
723 "use_ssl": "Gunakan SSL",
724 "use_suggested": "Gunakan yang Disarankan",
725 + "use_testnet": "Gunakan TestNet",
726 "variable_pair_not_supported": "Pasangan variabel ini tidak didukung dengan bursa yang dipilih",
727 "verification": "Verifikasi",
728 "verify_with_2fa": "Verifikasi dengan Cake 2FA",
res/values/strings_it.arb
+1
@@ -721,6 +721,7 @@
721 "use_card_info_two": "I fondi vengono convertiti in USD quando sono detenuti nel conto prepagato, non in valute digitali.",
722 "use_ssl": "Usa SSL",
723 "use_suggested": "Usa suggerito",
724 + "use_testnet": "Usa TestNet",
725 "variable_pair_not_supported": "Questa coppia di variabili non è supportata con gli scambi selezionati",
726 "verification": "Verifica",
727 "verify_with_2fa": "Verifica con Cake 2FA",
res/values/strings_ja.arb
+1
@@ -720,6 +720,7 @@
720 "use_card_info_two": "デジタル通貨ではなく、プリペイドアカウントで保持されている場合、資金は米ドルに変換されます。",
721 "use_ssl": "SSLを使用する",
722 "use_suggested": "推奨を使用",
723 + "use_testnet": "テストネットを使用します",
724 "variable_pair_not_supported": "この変数ペアは、選択した取引所ではサポートされていません",
725 "verification": "検証",
726 "verify_with_2fa": "Cake 2FA で検証する",
res/values/strings_ko.arb
+1
@@ -720,6 +720,7 @@
720 "use_card_info_two": "디지털 화폐가 아닌 선불 계정에 보유하면 자금이 USD로 변환됩니다.",
721 "use_ssl": "SSL 사용",
722 "use_suggested": "추천 사용",
723 + "use_testnet": "TestNet을 사용하십시오",
724 "variable_pair_not_supported": "이 변수 쌍은 선택한 교환에서 지원되지 않습니다.",
725 "verification": "검증",
726 "verify_with_2fa": "케이크 2FA로 확인",
res/values/strings_my.arb
+1
@@ -719,6 +719,7 @@
719 "use_card_info_two": "ဒစ်ဂျစ်တယ်ငွေကြေးများဖြင့်မဟုတ်ဘဲ ကြိုတင်ငွေပေးချေသည့်အကောင့်တွင် သိမ်းထားသည့်အခါ ရန်ပုံငွေများကို USD သို့ ပြောင်းလဲပါသည်။",
720 "use_ssl": "SSL ကိုသုံးပါ။",
721 "use_suggested": "အကြံပြုထားသည်ကို အသုံးပြုပါ။",
722 + "use_testnet": "testnet ကိုသုံးပါ",
723 "variable_pair_not_supported": "ရွေးချယ်ထားသော ဖလှယ်မှုများဖြင့် ဤပြောင်းလဲနိုင်သောအတွဲကို ပံ့ပိုးမထားပါ။",
724 "verification": "စိစစ်ခြင်း။",
725 "verify_with_2fa": "Cake 2FA ဖြင့် စစ်ဆေးပါ။",
res/values/strings_nl.arb
+1
@@ -719,6 +719,7 @@
719 "use_card_info_two": "Tegoeden worden omgezet naar USD wanneer ze op de prepaid-rekening staan, niet in digitale valuta.",
720 "use_ssl": "Gebruik SSL",
721 "use_suggested": "Gebruik aanbevolen",
722 + "use_testnet": "Gebruik testnet",
723 "variable_pair_not_supported": "Dit variabelenpaar wordt niet ondersteund met de geselecteerde uitwisselingen",
724 "verification": "Verificatie",
725 "verify_with_2fa": "Controleer met Cake 2FA",
res/values/strings_pl.arb
+1
@@ -719,6 +719,7 @@
719 "use_card_info_two": "Środki są przeliczane na USD, gdy są przechowywane na koncie przedpłaconym, a nie w walutach cyfrowych.",
720 "use_ssl": "Użyj SSL",
721 "use_suggested": "Użyj sugerowane",
722 + "use_testnet": "Użyj testne",
723 "variable_pair_not_supported": "Ta para zmiennych nie jest obsługiwana na wybranych giełdach",
724 "verification": "Weryfikacja",
725 "verify_with_2fa": "Sprawdź za pomocą Cake 2FA",
res/values/strings_pt.arb
+1
@@ -721,6 +721,7 @@
721 "use_card_info_two": "Os fundos são convertidos para USD quando mantidos na conta pré-paga, não em moedas digitais.",
722 "use_ssl": "Use SSL",
723 "use_suggested": "Uso sugerido",
724 + "use_testnet": "Use testNet",
725 "variable_pair_not_supported": "Este par de variáveis não é compatível com as trocas selecionadas",
726 "verification": "Verificação",
727 "verify_with_2fa": "Verificar com Cake 2FA",
res/values/strings_ru.arb
+1
@@ -720,6 +720,7 @@
720 "use_card_info_two": "Средства конвертируются в доллары США, когда они хранятся на предоплаченном счете, а не в цифровых валютах.",
721 "use_ssl": "Использовать SSL",
722 "use_suggested": "Использовать предложенный",
723 + "use_testnet": "Используйте Testnet",
724 "variable_pair_not_supported": "Эта пара переменных не поддерживается выбранными биржами.",
725 "verification": "Проверка",
726 "verify_with_2fa": "Подтвердить с помощью Cake 2FA",
res/values/strings_th.arb
+1
@@ -719,6 +719,7 @@
719 "use_card_info_two": "เงินจะถูกแปลงค่าเป็นดอลลาร์สหรัฐเมื่อถือไว้ในบัญชีสำรองเงิน ไม่ใช่สกุลเงินดิจิตอล",
720 "use_ssl": "ใช้ SSL",
721 "use_suggested": "ใช้ที่แนะนำ",
722 + "use_testnet": "ใช้ testnet",
723 "variable_pair_not_supported": "คู่ความสัมพันธ์ที่เปลี่ยนแปลงได้นี้ไม่สนับสนุนกับหุ้นที่เลือก",
724 "verification": "การตรวจสอบ",
725 "verify_with_2fa": "ตรวจสอบกับ Cake 2FA",
res/values/strings_tl.arb
+1
@@ -719,6 +719,7 @@
719 "use_card_info_two": "Ang mga pondo ay na -convert sa USD kapag gaganapin sila sa prepaid account, hindi sa mga digital na pera.",
720 "use_ssl": "Gumamit ng SSL",
721 "use_suggested": "Gumamit ng iminungkahing",
722 + "use_testnet": "Gumamit ng testnet",
723 "variable_pair_not_supported": "Ang variable na pares na ito ay hindi suportado sa mga napiling palitan",
724 "verification": "Pag -verify",
725 "verify_with_2fa": "Mag -verify sa cake 2FA",
res/values/strings_tr.arb
+1
@@ -719,6 +719,7 @@
719 "use_card_info_two": "Paralar, dijital para birimlerinde değil, ön ödemeli hesapta tutulduğunda USD'ye dönüştürülür.",
720 "use_ssl": "SSL kullan",
721 "use_suggested": "Önerileni Kullan",
722 + "use_testnet": "TestNet kullanın",
723 "variable_pair_not_supported": "Bu değişken paritesi seçilen borsalarda desteklenmemekte",
724 "verification": "Doğrulama",
725 "verify_with_2fa": "Cake 2FA ile Doğrulayın",
res/values/strings_uk.arb
+1
@@ -720,6 +720,7 @@
720 "use_card_info_two": "Кошти конвертуються в долари США, якщо вони зберігаються на передплаченому рахунку, а не в цифрових валютах.",
721 "use_ssl": "Використати SSL",
722 "use_suggested": "Використати запропоноване",
723 + "use_testnet": "Використовуйте тестову мережу",
724 "variable_pair_not_supported": "Ця пара змінних не підтримується вибраними біржами",
725 "verification": "Перевірка",
726 "verify_with_2fa": "Перевірте за допомогою Cake 2FA",
res/values/strings_ur.arb
+1
@@ -721,6 +721,7 @@
721 "use_card_info_two": "رقوم کو امریکی ڈالر میں تبدیل کیا جاتا ہے جب پری پیڈ اکاؤنٹ میں رکھا جاتا ہے، ڈیجیٹل کرنسیوں میں نہیں۔",
722 "use_ssl": "SSL استعمال کریں۔",
723 "use_suggested": "تجویز کردہ استعمال کریں۔",
724 + "use_testnet": "ٹیسٹ نیٹ استعمال کریں",
725 "variable_pair_not_supported": "یہ متغیر جوڑا منتخب ایکسچینجز کے ساتھ تعاون یافتہ نہیں ہے۔",
726 "verification": "تصدیق",
727 "verify_with_2fa": "کیک 2FA سے تصدیق کریں۔",
res/values/strings_yo.arb
+1
@@ -720,6 +720,7 @@
720 "use_card_info_two": "A pààrọ̀ owó sí owó Amẹ́ríkà tó bá wà nínú àkanti t'á ti fikún tẹ́lẹ̀tẹ́lẹ̀. A kò kó owó náà nínú owó ayélujára.",
721 "use_ssl": "Lo SSL",
722 "use_suggested": "Lo àbá",
723 + "use_testnet": "Lo tele",
724 "variable_pair_not_supported": "A kì í ṣe k'á fi àwọn ilé pàṣípààrọ̀ yìí ṣe pàṣípààrọ̀ irú owó méji yìí",
725 "verification": "Ìjẹ́rìísí",
726 "verify_with_2fa": "Ṣeẹda pẹlu Cake 2FA",
res/values/strings_zh.arb
+1
@@ -719,6 +719,7 @@
719 "use_card_info_two": "预付账户中的资金转换为美元,不是数字货币。",
720 "use_ssl": "使用SSL",
721 "use_suggested": "使用建议",
722 + "use_testnet": "使用TestNet",
723 "variable_pair_not_supported": "所选交易所不支持此变量对",
724 "verification": "验证",
725 "verify_with_2fa": "用 Cake 2FA 验证",
scripts/android/app_env.sh
+2 -2
@@ -22,8 +22,8 @@ MONERO_COM_PACKAGE="com.monero.app"
22 MONERO_COM_SCHEME="monero.com"
23
24 CAKEWALLET_NAME="Cake Wallet"
25 -CAKEWALLET_VERSION="4.13.3"
26 -CAKEWALLET_BUILD_NUMBER=192
25 +CAKEWALLET_VERSION="4.14.0"
26 +CAKEWALLET_BUILD_NUMBER=193
27 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet"
28 CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet"
29 CAKEWALLET_SCHEME="cakewallet"
scripts/android/shell.nix new
+16
@@ -0,0 +1,16 @@
1 +{ pkgs ? import <nixpkgs> {} }:
2 +
3 +pkgs.mkShell {
4 + buildInputs = [
5 + pkgs.curl
6 + pkgs.unzip
7 + pkgs.automake
8 + pkgs.file
9 + pkgs.pkg-config
10 + pkgs.git
11 + pkgs.libtool
12 + pkgs.ncurses5
13 + pkgs.openjdk8
14 + pkgs.clang
15 + ];
16 +}
scripts/ios/app_env.sh
+2 -2
@@ -18,8 +18,8 @@ MONERO_COM_BUILD_NUMBER=73
18 MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
19
20 CAKEWALLET_NAME="Cake Wallet"
21 -CAKEWALLET_VERSION="4.13.3"
22 -CAKEWALLET_BUILD_NUMBER=212
21 +CAKEWALLET_VERSION="4.14.0"
22 +CAKEWALLET_BUILD_NUMBER=213
23 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
24
25 HAVEN_NAME="Haven"
tool/configure.dart
+6
@@ -72,6 +72,7 @@ import 'package:cake_wallet/view_model/send/output.dart';
72 import 'package:cw_core/wallet_type.dart';
73 import 'package:hive/hive.dart';""";
74 const bitcoinCWHeaders = """
75 +import 'package:cw_bitcoin/bitcoin_receive_page_option.dart';
76 import 'package:cw_bitcoin/electrum_wallet.dart';
77 import 'package:cw_bitcoin/bitcoin_unspent.dart';
78 import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
@@ -82,6 +83,7 @@ import 'package:cw_bitcoin/bitcoin_amount_format.dart';
83 import 'package:cw_bitcoin/bitcoin_address_record.dart';
84 import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
85 import 'package:cw_bitcoin/litecoin_wallet_service.dart';
86 +import 'package:bitcoin_base/bitcoin_base.dart';
87 import 'package:mobx/mobx.dart';
88 """;
89 const bitcoinCwPart = "part 'cw_bitcoin.dart';";
@@ -139,6 +141,10 @@ abstract class Bitcoin {
141 TransactionPriority getLitecoinTransactionPriorityMedium();
142 TransactionPriority getBitcoinTransactionPrioritySlow();
143 TransactionPriority getLitecoinTransactionPrioritySlow();
144 +
145 + Future<void> setAddressType(Object wallet, dynamic option);
146 + BitcoinReceivePageOption getSelectedAddressType(Object wallet);
147 + List<BitcoinReceivePageOption> getBitcoinReceivePageOptions();
148 }
149 """;
150