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>
This commit is contained in:
Rafael 2024-02-23 13:13:30 -03:00 committed by GitHub
parent 109bba4301
commit a3a35f05e1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
91 changed files with 1851 additions and 1333 deletions

View file

@ -1,5 +1,3 @@
Bitcoin transactions fixes and enhancements Support ALL Bitcoin address types (Legacy, Segwit (both variants), Taproot)
EVM wallets enhancements (Ethereum and Polygon) Enhance Sending/Receiving flow for Bitcoin
Improve wallet recovery and error tolerance Improve fee calculations in Bitcoin
Enhance Background sync for Monero wallets
Bug fixes

View file

@ -1,23 +1,23 @@
import 'dart:typed_data'; import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
String addressFromOutput(Uint8List script, bitcoin.NetworkType networkType) { String addressFromOutputScript(Script script, BasedUtxoNetwork network) {
try { try {
return bitcoin.P2PKH( switch (script.getAddressType()) {
data: PaymentData(output: script), case P2pkhAddressType.p2pkh:
network: networkType) return P2pkhAddress.fromScriptPubkey(script: script).toAddress(network);
.data case P2shAddressType.p2pkInP2sh:
.address!; return P2shAddress.fromScriptPubkey(script: script).toAddress(network);
case SegwitAddresType.p2wpkh:
return P2wpkhAddress.fromScriptPubkey(script: script).toAddress(network);
case P2shAddressType.p2pkhInP2sh:
return P2shAddress.fromScriptPubkey(script: script).toAddress(network);
case SegwitAddresType.p2wsh:
return P2wshAddress.fromScriptPubkey(script: script).toAddress(network);
case SegwitAddresType.p2tr:
return P2trAddress.fromScriptPubkey(script: script).toAddress(network);
default:
}
} catch (_) {} } catch (_) {}
try {
return bitcoin.P2WPKH(
data: PaymentData(output: script),
network: networkType)
.data
.address!;
} catch(_) {}
return ''; return '';
} }

View file

@ -1,27 +1,9 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bitcoin_base/bitcoin_base.dart' as bitcoin;
import 'package:bs58check/bs58check.dart' as bs58check;
import 'package:bitcoin_flutter/src/utils/constants/op.dart';
import 'package:bitcoin_flutter/src/utils/script.dart' as bscript;
import 'package:bitcoin_flutter/src/address.dart';
Uint8List p2shAddressToOutputScript(String address) { List<int> addressToOutputScript(String address, bitcoin.BasedUtxoNetwork network) {
final decodeBase58 = bs58check.decode(address);
final hash = decodeBase58.sublist(1);
return bscript.compile(<dynamic>[OPS['OP_HASH160'], hash, OPS['OP_EQUAL']]);
}
Uint8List addressToOutputScript(
String address, bitcoin.NetworkType networkType) {
try { try {
// FIXME: improve validation for p2sh addresses return bitcoin.addressToOutputScript(address: address, network: network);
// 3 for bitcoin
// m for litecoin
if (address.startsWith('3') || address.toLowerCase().startsWith('m')) {
return p2shAddressToOutputScript(address);
}
return Address.addressToOutputScript(address, networkType);
} catch (err) { } catch (err) {
print(err); print(err);
return Uint8List(0); return Uint8List(0);

View file

@ -1,6 +1,9 @@
import 'dart:convert'; import 'dart:convert';
import 'package:bitbox/bitbox.dart' as bitbox; import 'package:bitbox/bitbox.dart' as bitbox;
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/script_hash.dart' as sh;
class BitcoinAddressRecord { class BitcoinAddressRecord {
BitcoinAddressRecord( BitcoinAddressRecord(
this.address, { this.address, {
@ -10,23 +13,41 @@ class BitcoinAddressRecord {
int balance = 0, int balance = 0,
String name = '', String name = '',
bool isUsed = false, bool isUsed = false,
required this.type,
String? scriptHash,
required this.network,
}) : _txCount = txCount, }) : _txCount = txCount,
_balance = balance, _balance = balance,
_name = name, _name = name,
_isUsed = isUsed; _isUsed = isUsed,
scriptHash =
scriptHash ?? (network != null ? sh.scriptHash(address, network: network) : null);
factory BitcoinAddressRecord.fromJSON(String jsonSource) { factory BitcoinAddressRecord.fromJSON(String jsonSource, BasedUtxoNetwork? network) {
final decoded = json.decode(jsonSource) as Map; final decoded = json.decode(jsonSource) as Map;
return BitcoinAddressRecord(decoded['address'] as String, return BitcoinAddressRecord(
index: decoded['index'] as int, decoded['address'] as String,
isHidden: decoded['isHidden'] as bool? ?? false, index: decoded['index'] as int,
isUsed: decoded['isUsed'] as bool? ?? false, isHidden: decoded['isHidden'] as bool? ?? false,
txCount: decoded['txCount'] as int? ?? 0, isUsed: decoded['isUsed'] as bool? ?? false,
name: decoded['name'] as String? ?? '', txCount: decoded['txCount'] as int? ?? 0,
balance: decoded['balance'] as int? ?? 0); name: decoded['name'] as String? ?? '',
balance: decoded['balance'] as int? ?? 0,
type: decoded['type'] != null && decoded['type'] != ''
? BitcoinAddressType.values
.firstWhere((type) => type.toString() == decoded['type'] as String)
: SegwitAddresType.p2wpkh,
scriptHash: decoded['scriptHash'] as String?,
network: (decoded['network'] as String?) == null
? network
: BasedUtxoNetwork.fromName(decoded['network'] as String),
);
} }
@override
bool operator ==(Object o) => o is BitcoinAddressRecord && address == o.address;
final String address; final String address;
bool isHidden; bool isHidden;
final int index; final int index;
@ -34,6 +55,8 @@ class BitcoinAddressRecord {
int _balance; int _balance;
String _name; String _name;
bool _isUsed; bool _isUsed;
String? scriptHash;
BasedUtxoNetwork? network;
int get txCount => _txCount; int get txCount => _txCount;
@ -50,21 +73,28 @@ class BitcoinAddressRecord {
void setAsUsed() => _isUsed = true; void setAsUsed() => _isUsed = true;
void setNewName(String label) => _name = label; void setNewName(String label) => _name = label;
@override
bool operator ==(Object o) => o is BitcoinAddressRecord && address == o.address;
@override @override
int get hashCode => address.hashCode; int get hashCode => address.hashCode;
String get cashAddr => bitbox.Address.toCashAddress(address); String get cashAddr => bitbox.Address.toCashAddress(address);
BitcoinAddressType type;
String updateScriptHash(BasedUtxoNetwork network) {
scriptHash = sh.scriptHash(address, network: network);
return scriptHash!;
}
String toJSON() => json.encode({ String toJSON() => json.encode({
'address': address, 'address': address,
'index': index, 'index': index,
'isHidden': isHidden, 'isHidden': isHidden,
'isUsed': isUsed,
'txCount': txCount, 'txCount': txCount,
'name': name, 'name': name,
'isUsed': isUsed,
'balance': balance, 'balance': balance,
'type': type.toString(),
'scriptHash': scriptHash,
'network': network?.value,
}); });
} }

View file

@ -0,0 +1,42 @@
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_core/receive_page_option.dart';
class BitcoinReceivePageOption implements ReceivePageOption {
static const p2wpkh = BitcoinReceivePageOption._('Segwit (P2WPKH)');
static const p2sh = BitcoinReceivePageOption._('Segwit-Compatible (P2SH)');
static const p2tr = BitcoinReceivePageOption._('Taproot (P2TR)');
static const p2wsh = BitcoinReceivePageOption._('Segwit (P2WSH)');
static const p2pkh = BitcoinReceivePageOption._('Legacy (P2PKH)');
const BitcoinReceivePageOption._(this.value);
final String value;
String toString() {
return value;
}
static const all = [
BitcoinReceivePageOption.p2wpkh,
BitcoinReceivePageOption.p2sh,
BitcoinReceivePageOption.p2tr,
BitcoinReceivePageOption.p2wsh,
BitcoinReceivePageOption.p2pkh
];
factory BitcoinReceivePageOption.fromType(BitcoinAddressType type) {
switch (type) {
case SegwitAddresType.p2tr:
return BitcoinReceivePageOption.p2tr;
case SegwitAddresType.p2wsh:
return BitcoinReceivePageOption.p2wsh;
case P2pkhAddressType.p2pkh:
return BitcoinReceivePageOption.p2pkh;
case P2shAddressType.p2wpkhInP2sh:
return BitcoinReceivePageOption.p2sh;
case SegwitAddresType.p2wpkh:
default:
return BitcoinReceivePageOption.p2wpkh;
}
}
}

View file

@ -6,10 +6,9 @@ class BitcoinUnspent extends Unspent {
: bitcoinAddressRecord = addressRecord, : bitcoinAddressRecord = addressRecord,
super(addressRecord.address, hash, value, vout, null); super(addressRecord.address, hash, value, vout, null);
factory BitcoinUnspent.fromJSON( factory BitcoinUnspent.fromJSON(BitcoinAddressRecord address, Map<String, dynamic> json) =>
BitcoinAddressRecord address, Map<String, dynamic> json) => BitcoinUnspent(
BitcoinUnspent(address, json['tx_hash'] as String, json['value'] as int, address, json['tx_hash'] as String, json['value'] as int, json['tx_pos'] as int);
json['tx_pos'] as int);
final BitcoinAddressRecord bitcoinAddressRecord; final BitcoinAddressRecord bitcoinAddressRecord;
} }

View file

@ -1,3 +1,4 @@
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/unspent_coins_info.dart';
@ -17,36 +18,42 @@ part 'bitcoin_wallet.g.dart';
class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet; class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet;
abstract class BitcoinWalletBase extends ElectrumWallet with Store { abstract class BitcoinWalletBase extends ElectrumWallet with Store {
BitcoinWalletBase( BitcoinWalletBase({
{required String mnemonic, required String mnemonic,
required String password, required String password,
required WalletInfo walletInfo, required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo, required Box<UnspentCoinsInfo> unspentCoinsInfo,
required Uint8List seedBytes, required Uint8List seedBytes,
List<BitcoinAddressRecord>? initialAddresses, String? addressPageType,
ElectrumBalance? initialBalance, BasedUtxoNetwork? networkParam,
int initialRegularAddressIndex = 0, List<BitcoinAddressRecord>? initialAddresses,
int initialChangeAddressIndex = 0}) ElectrumBalance? initialBalance,
: super( Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex,
}) : super(
mnemonic: mnemonic, mnemonic: mnemonic,
password: password, password: password,
walletInfo: walletInfo, walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo, unspentCoinsInfo: unspentCoinsInfo,
networkType: bitcoin.bitcoin, networkType: networkParam == null
? bitcoin.bitcoin
: networkParam == BitcoinNetwork.mainnet
? bitcoin.bitcoin
: bitcoin.testnet,
initialAddresses: initialAddresses, initialAddresses: initialAddresses,
initialBalance: initialBalance, initialBalance: initialBalance,
seedBytes: seedBytes, seedBytes: seedBytes,
currency: CryptoCurrency.btc) { currency: CryptoCurrency.btc) {
walletAddresses = BitcoinWalletAddresses( walletAddresses = BitcoinWalletAddresses(
walletInfo, walletInfo,
electrumClient: electrumClient, electrumClient: electrumClient,
initialAddresses: initialAddresses, initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex, initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: hd, mainHd: hd,
sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType) sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/1"),
.derivePath("m/0'/1"), network: networkParam ?? network,
networkType: networkType); );
autorun((_) { autorun((_) {
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
}); });
@ -57,21 +64,26 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
required String password, required String password,
required WalletInfo walletInfo, required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo, required Box<UnspentCoinsInfo> unspentCoinsInfo,
String? addressPageType,
BasedUtxoNetwork? network,
List<BitcoinAddressRecord>? initialAddresses, List<BitcoinAddressRecord>? initialAddresses,
ElectrumBalance? initialBalance, ElectrumBalance? initialBalance,
int initialRegularAddressIndex = 0, Map<String, int>? initialRegularAddressIndex,
int initialChangeAddressIndex = 0 Map<String, int>? initialChangeAddressIndex,
}) async { }) async {
return BitcoinWallet( return BitcoinWallet(
mnemonic: mnemonic, mnemonic: mnemonic,
password: password, password: password,
walletInfo: walletInfo, walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo, unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: initialAddresses, initialAddresses: initialAddresses,
initialBalance: initialBalance, initialBalance: initialBalance,
seedBytes: await mnemonicToSeedBytes(mnemonic), seedBytes: await mnemonicToSeedBytes(mnemonic),
initialRegularAddressIndex: initialRegularAddressIndex, initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex); initialChangeAddressIndex: initialChangeAddressIndex,
addressPageType: addressPageType,
networkParam: network,
);
} }
static Future<BitcoinWallet> open({ static Future<BitcoinWallet> open({
@ -80,16 +92,21 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
required Box<UnspentCoinsInfo> unspentCoinsInfo, required Box<UnspentCoinsInfo> unspentCoinsInfo,
required String password, required String password,
}) async { }) async {
final snp = await ElectrumWallletSnapshot.load(name, walletInfo.type, password); final snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password,
walletInfo.network != null ? BasedUtxoNetwork.fromName(walletInfo.network!) : null);
return BitcoinWallet( return BitcoinWallet(
mnemonic: snp.mnemonic, mnemonic: snp.mnemonic,
password: password, password: password,
walletInfo: walletInfo, walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo, unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: snp.addresses, initialAddresses: snp.addresses,
initialBalance: snp.balance, initialBalance: snp.balance,
seedBytes: await mnemonicToSeedBytes(snp.mnemonic), seedBytes: await mnemonicToSeedBytes(snp.mnemonic),
initialRegularAddressIndex: snp.regularAddressIndex, initialRegularAddressIndex: snp.regularAddressIndex,
initialChangeAddressIndex: snp.changeAddressIndex); initialChangeAddressIndex: snp.changeAddressIndex,
addressPageType: snp.addressPageType,
networkParam: snp.network,
);
} }
} }

View file

@ -1,6 +1,5 @@
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:bitcoin_flutter/bitcoin_flutter.dart';
import 'package:cw_bitcoin/electrum.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:cw_bitcoin/utils.dart'; import 'package:cw_bitcoin/utils.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
@ -11,24 +10,31 @@ part 'bitcoin_wallet_addresses.g.dart';
class BitcoinWalletAddresses = BitcoinWalletAddressesBase with _$BitcoinWalletAddresses; class BitcoinWalletAddresses = BitcoinWalletAddressesBase with _$BitcoinWalletAddresses;
abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with Store { abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
BitcoinWalletAddressesBase(WalletInfo walletInfo, BitcoinWalletAddressesBase(
{required bitcoin.HDWallet mainHd, WalletInfo walletInfo, {
required bitcoin.HDWallet sideHd, required super.mainHd,
required bitcoin.NetworkType networkType, required super.sideHd,
required ElectrumClient electrumClient, required super.network,
List<BitcoinAddressRecord>? initialAddresses, required super.electrumClient,
int initialRegularAddressIndex = 0, super.initialAddresses,
int initialChangeAddressIndex = 0}) super.initialRegularAddressIndex,
: super(walletInfo, super.initialChangeAddressIndex,
initialAddresses: initialAddresses, }) : super(walletInfo);
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: mainHd,
sideHd: sideHd,
electrumClient: electrumClient,
networkType: networkType);
@override @override
String getAddress({required int index, required bitcoin.HDWallet hd}) => String getAddress({required int index, required HDWallet hd, BitcoinAddressType? addressType}) {
generateP2WPKHAddress(hd: hd, index: index, networkType: networkType); if (addressType == P2pkhAddressType.p2pkh)
return generateP2PKHAddress(hd: hd, index: index, network: network);
if (addressType == SegwitAddresType.p2tr)
return generateP2TRAddress(hd: hd, index: index, network: network);
if (addressType == SegwitAddresType.p2wsh)
return generateP2WSHAddress(hd: hd, index: index, network: network);
if (addressType == P2shAddressType.p2wpkhInP2sh)
return generateP2SHAddress(hd: hd, index: index, network: network);
return generateP2WPKHAddress(hd: hd, index: index, network: network);
}
} }

View file

@ -1,4 +1,5 @@
import 'dart:io'; import 'dart:io';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart';
import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart'; import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart';
@ -23,12 +24,17 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
WalletType getType() => WalletType.bitcoin; WalletType getType() => WalletType.bitcoin;
@override @override
Future<BitcoinWallet> create(BitcoinNewWalletCredentials credentials) async { Future<BitcoinWallet> create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async {
final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
credentials.walletInfo?.network = network.value;
final wallet = await BitcoinWalletBase.create( final wallet = await BitcoinWalletBase.create(
mnemonic: await generateMnemonic(), mnemonic: await generateMnemonic(),
password: credentials.password!, password: credentials.password!,
walletInfo: credentials.walletInfo!, walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource); unspentCoinsInfo: unspentCoinsInfoSource,
network: network,
);
await wallet.save(); await wallet.save();
await wallet.init(); await wallet.init();
return wallet; return wallet;
@ -92,20 +98,27 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
} }
@override @override
Future<BitcoinWallet> restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials) async => Future<BitcoinWallet> restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials,
{bool? isTestnet}) async =>
throw UnimplementedError(); throw UnimplementedError();
@override @override
Future<BitcoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials) async { Future<BitcoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials,
{bool? isTestnet}) async {
if (!validateMnemonic(credentials.mnemonic)) { if (!validateMnemonic(credentials.mnemonic)) {
throw BitcoinMnemonicIsIncorrectException(); throw BitcoinMnemonicIsIncorrectException();
} }
final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
credentials.walletInfo?.network = network.value;
final wallet = await BitcoinWalletBase.create( final wallet = await BitcoinWalletBase.create(
password: credentials.password!, password: credentials.password!,
mnemonic: credentials.mnemonic, mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo!, walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource); unspentCoinsInfo: unspentCoinsInfoSource,
network: network,
);
await wallet.save(); await wallet.save();
await wallet.init(); await wallet.init();
return wallet; return wallet;

View file

@ -2,12 +2,12 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:bitcoin_flutter/bitcoin_flutter.dart'; import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart'; import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_bitcoin/script_hash.dart'; import 'package:cw_bitcoin/script_hash.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
import 'package:collection/collection.dart'; import 'package:http/http.dart' as http;
String jsonrpcparams(List<Object> params) { String jsonrpcparams(List<Object> params) {
final _params = params?.map((val) => '"${val.toString()}"')?.join(','); final _params = params?.map((val) => '"${val.toString()}"')?.join(',');
@ -22,10 +22,7 @@ String jsonrpc(
'{"jsonrpc": "$version", "method": "$method", "id": "$id", "params": ${json.encode(params)}}\n'; '{"jsonrpc": "$version", "method": "$method", "id": "$id", "params": ${json.encode(params)}}\n';
class SocketTask { class SocketTask {
SocketTask({ SocketTask({required this.isSubscription, this.completer, this.subject});
required this.isSubscription,
this.completer,
this.subject});
final Completer<dynamic>? completer; final Completer<dynamic>? completer;
final BehaviorSubject<dynamic>? subject; final BehaviorSubject<dynamic>? subject;
@ -51,8 +48,7 @@ class ElectrumClient {
Timer? _aliveTimer; Timer? _aliveTimer;
String unterminatedString; String unterminatedString;
Future<void> connectToUri(Uri uri) async => Future<void> connectToUri(Uri uri) async => await connect(host: uri.host, port: uri.port);
await connect(host: uri.host, port: uri.port);
Future<void> connect({required String host, required int port}) async { Future<void> connect({required String host, required int port}) async {
try { try {
@ -104,21 +100,20 @@ class ElectrumClient {
} }
if (isJSONStringCorrect(unterminatedString)) { if (isJSONStringCorrect(unterminatedString)) {
final response = final response = json.decode(unterminatedString) as Map<String, dynamic>;
json.decode(unterminatedString) as Map<String, dynamic>;
_handleResponse(response); _handleResponse(response);
unterminatedString = ''; unterminatedString = '';
} }
} on TypeError catch (e) { } on TypeError catch (e) {
if (!e.toString().contains('Map<String, Object>') && !e.toString().contains('Map<String, dynamic>')) { if (!e.toString().contains('Map<String, Object>') &&
!e.toString().contains('Map<String, dynamic>')) {
return; return;
} }
unterminatedString += message; unterminatedString += message;
if (isJSONStringCorrect(unterminatedString)) { if (isJSONStringCorrect(unterminatedString)) {
final response = final response = json.decode(unterminatedString) as Map<String, dynamic>;
json.decode(unterminatedString) as Map<String, dynamic>;
_handleResponse(response); _handleResponse(response);
// unterminatedString = null; // unterminatedString = null;
unterminatedString = ''; unterminatedString = '';
@ -142,8 +137,7 @@ class ElectrumClient {
} }
} }
Future<List<String>> version() => Future<List<String>> version() => call(method: 'server.version').then((dynamic result) {
call(method: 'server.version').then((dynamic result) {
if (result is List) { if (result is List) {
return result.map((dynamic val) => val.toString()).toList(); return result.map((dynamic val) => val.toString()).toList();
} }
@ -178,11 +172,10 @@ class ElectrumClient {
}); });
Future<List<Map<String, dynamic>>> getListUnspentWithAddress( Future<List<Map<String, dynamic>>> getListUnspentWithAddress(
String address, NetworkType networkType) => String address, BasedUtxoNetwork network) =>
call( call(
method: 'blockchain.scripthash.listunspent', method: 'blockchain.scripthash.listunspent',
params: [scriptHash(address, networkType: networkType)]) params: [scriptHash(address, network: network)]).then((dynamic result) {
.then((dynamic result) {
if (result is List) { if (result is List) {
return result.map((dynamic val) { return result.map((dynamic val) {
if (val is Map<String, dynamic>) { if (val is Map<String, dynamic>) {
@ -229,8 +222,7 @@ class ElectrumClient {
return []; return [];
}); });
Future<Map<String, dynamic>> getTransactionRaw( Future<Map<String, dynamic>> getTransactionRaw({required String hash}) async =>
{required String hash}) async =>
callWithTimeout(method: 'blockchain.transaction.get', params: [hash, true], timeout: 10000) callWithTimeout(method: 'blockchain.transaction.get', params: [hash, true], timeout: 10000)
.then((dynamic result) { .then((dynamic result) {
if (result is Map<String, dynamic>) { if (result is Map<String, dynamic>) {
@ -240,8 +232,7 @@ class ElectrumClient {
return <String, dynamic>{}; return <String, dynamic>{};
}); });
Future<String> getTransactionHex( Future<String> getTransactionHex({required String hash}) async =>
{required String hash}) async =>
callWithTimeout(method: 'blockchain.transaction.get', params: [hash, false], timeout: 10000) callWithTimeout(method: 'blockchain.transaction.get', params: [hash, false], timeout: 10000)
.then((dynamic result) { .then((dynamic result) {
if (result is String) { if (result is String) {
@ -252,29 +243,40 @@ class ElectrumClient {
}); });
Future<String> broadcastTransaction( Future<String> broadcastTransaction(
{required String transactionRaw}) async => {required String transactionRaw, BasedUtxoNetwork? network}) async {
call(method: 'blockchain.transaction.broadcast', params: [transactionRaw]) if (network == BitcoinNetwork.testnet) {
.then((dynamic result) { return http
if (result is String) { .post(Uri(scheme: 'https', host: 'blockstream.info', path: '/testnet/api/tx'),
return result; headers: <String, String>{'Content-Type': 'application/json; charset=utf-8'},
body: transactionRaw)
.then((http.Response response) {
if (response.statusCode == 200) {
return response.body;
} }
return ''; throw Exception('Failed to broadcast transaction: ${response.body}');
}); });
}
Future<Map<String, dynamic>> getMerkle( return call(method: 'blockchain.transaction.broadcast', params: [transactionRaw])
{required String hash, required int height}) async => .then((dynamic result) {
await call( if (result is String) {
method: 'blockchain.transaction.get_merkle', return result;
params: [hash, height]) as Map<String, dynamic>; }
Future<Map<String, dynamic>> getHeader({required int height}) async => return '';
await call(method: 'blockchain.block.get_header', params: [height]) });
}
Future<Map<String, dynamic>> getMerkle({required String hash, required int height}) async =>
await call(method: 'blockchain.transaction.get_merkle', params: [hash, height])
as Map<String, dynamic>; as Map<String, dynamic>;
Future<Map<String, dynamic>> getHeader({required int height}) async =>
await call(method: 'blockchain.block.get_header', params: [height]) as Map<String, dynamic>;
Future<double> estimatefee({required int p}) => Future<double> estimatefee({required int p}) =>
call(method: 'blockchain.estimatefee', params: [p]) call(method: 'blockchain.estimatefee', params: [p]).then((dynamic result) {
.then((dynamic result) {
if (result is double) { if (result is double) {
return result; return result;
} }
@ -314,20 +316,17 @@ class ElectrumClient {
return []; return [];
}); });
Future<List<int>> feeRates() async { Future<List<int>> feeRates({BasedUtxoNetwork? network}) async {
if (network == BitcoinNetwork.testnet) {
return [1, 1, 1];
}
try { try {
final topDoubleString = await estimatefee(p: 1); final topDoubleString = await estimatefee(p: 1);
final middleDoubleString = await estimatefee(p: 5); final middleDoubleString = await estimatefee(p: 5);
final bottomDoubleString = await estimatefee(p: 100); final bottomDoubleString = await estimatefee(p: 100);
final top = final top = (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000).round();
(stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000) final middle = (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000).round();
.round(); final bottom = (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000).round();
final middle =
(stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000)
.round();
final bottom =
(stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000)
.round();
return [bottom, middle, top]; return [bottom, middle, top];
} catch (_) { } catch (_) {
@ -335,6 +334,21 @@ class ElectrumClient {
} }
} }
// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-headers-subscribe
// example response:
// {
// "height": 520481,
// "hex": "00000020890208a0ae3a3892aa047c5468725846577cfcd9b512b50000000000000000005dc2b02f2d297a9064ee103036c14d678f9afc7e3d9409cf53fd58b82e938e8ecbeca05a2d2103188ce804c4"
// }
Future<int?> getCurrentBlockChainTip() =>
call(method: 'blockchain.headers.subscribe').then((result) {
if (result is Map<String, dynamic>) {
return result["height"] as int;
}
return null;
});
BehaviorSubject<Object>? scripthashUpdate(String scripthash) { BehaviorSubject<Object>? scripthashUpdate(String scripthash) {
_id += 1; _id += 1;
return subscribe<Object>( return subscribe<Object>(
@ -344,16 +358,14 @@ class ElectrumClient {
} }
BehaviorSubject<T>? subscribe<T>( BehaviorSubject<T>? subscribe<T>(
{required String id, {required String id, required String method, List<Object> params = const []}) {
required String method,
List<Object> params = const []}) {
try { try {
final subscription = BehaviorSubject<T>(); final subscription = BehaviorSubject<T>();
_regisrySubscription(id, subscription); _regisrySubscription(id, subscription);
socket!.write(jsonrpc(method: method, id: _id, params: params)); socket!.write(jsonrpc(method: method, id: _id, params: params));
return subscription; return subscription;
} catch(e) { } catch (e) {
print(e.toString()); print(e.toString());
return null; return null;
} }
@ -370,9 +382,7 @@ class ElectrumClient {
} }
Future<dynamic> callWithTimeout( Future<dynamic> callWithTimeout(
{required String method, {required String method, List<Object> params = const [], int timeout = 4000}) async {
List<Object> params = const [],
int timeout = 4000}) async {
try { try {
final completer = Completer<dynamic>(); final completer = Completer<dynamic>();
_id += 1; _id += 1;
@ -386,7 +396,7 @@ class ElectrumClient {
}); });
return completer.future; return completer.future;
} catch(e) { } catch (e) {
print(e.toString()); print(e.toString());
} }
} }
@ -397,8 +407,8 @@ class ElectrumClient {
onConnectionStatusChange = null; onConnectionStatusChange = null;
} }
void _registryTask(int id, Completer<dynamic> completer) => _tasks[id.toString()] = void _registryTask(int id, Completer<dynamic> completer) =>
SocketTask(completer: completer, isSubscription: false); _tasks[id.toString()] = SocketTask(completer: completer, isSubscription: false);
void _regisrySubscription(String id, BehaviorSubject<dynamic> subject) => void _regisrySubscription(String id, BehaviorSubject<dynamic> subject) =>
_tasks[id] = SocketTask(subject: subject, isSubscription: true); _tasks[id] = SocketTask(subject: subject, isSubscription: true);
@ -419,8 +429,7 @@ class ElectrumClient {
} }
} }
void _methodHandler( void _methodHandler({required String method, required Map<String, dynamic> request}) {
{required String method, required Map<String, dynamic> request}) {
switch (method) { switch (method) {
case 'blockchain.scripthash.subscribe': case 'blockchain.scripthash.subscribe':
final params = request['params'] as List<dynamic>; final params = request['params'] as List<dynamic>;
@ -451,8 +460,8 @@ class ElectrumClient {
_methodHandler(method: method, request: response); _methodHandler(method: method, request: response);
return; return;
} }
if (id != null){ if (id != null) {
_finish(id, result); _finish(id, result);
} }
} }

View file

@ -1,3 +1,4 @@
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData; import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
import 'package:cw_bitcoin/address_from_output.dart'; import 'package:cw_bitcoin/address_from_output.dart';
@ -10,13 +11,12 @@ import 'package:cw_core/wallet_type.dart';
class ElectrumTransactionBundle { class ElectrumTransactionBundle {
ElectrumTransactionBundle(this.originalTransaction, ElectrumTransactionBundle(this.originalTransaction,
{required this.ins, {required this.ins, required this.confirmations, this.time, required this.height});
required this.confirmations, final BtcTransaction originalTransaction;
this.time}); final List<BtcTransaction> ins;
final bitcoin.Transaction originalTransaction;
final List<bitcoin.Transaction> ins;
final int? time; final int? time;
final int confirmations; final int confirmations;
final int height;
} }
class ElectrumTransactionInfo extends TransactionInfo { class ElectrumTransactionInfo extends TransactionInfo {
@ -39,8 +39,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
this.confirmations = confirmations; this.confirmations = confirmations;
} }
factory ElectrumTransactionInfo.fromElectrumVerbose( factory ElectrumTransactionInfo.fromElectrumVerbose(Map<String, Object> obj, WalletType type,
Map<String, Object> obj, WalletType type,
{required List<BitcoinAddressRecord> addresses, required int height}) { {required List<BitcoinAddressRecord> addresses, required int height}) {
final addressesSet = addresses.map((addr) => addr.address).toSet(); final addressesSet = addresses.map((addr) => addr.address).toSet();
final id = obj['txid'] as String; final id = obj['txid'] as String;
@ -58,10 +57,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
for (dynamic vin in vins) { for (dynamic vin in vins) {
final vout = vin['vout'] as int; final vout = vin['vout'] as int;
final out = vin['tx']['vout'][vout] as Map; final out = vin['tx']['vout'][vout] as Map;
final outAddresses = final outAddresses = (out['scriptPubKey']['addresses'] as List<Object>?)?.toSet();
(out['scriptPubKey']['addresses'] as List<Object>?)?.toSet(); inputsAmount += stringDoubleToBitcoinAmount((out['value'] as double? ?? 0).toString());
inputsAmount +=
stringDoubleToBitcoinAmount((out['value'] as double? ?? 0).toString());
if (outAddresses?.intersection(addressesSet).isNotEmpty ?? false) { if (outAddresses?.intersection(addressesSet).isNotEmpty ?? false) {
direction = TransactionDirection.outgoing; direction = TransactionDirection.outgoing;
@ -69,11 +66,9 @@ class ElectrumTransactionInfo extends TransactionInfo {
} }
for (dynamic out in vout) { for (dynamic out in vout) {
final outAddresses = final outAddresses = out['scriptPubKey']['addresses'] as List<Object>? ?? [];
out['scriptPubKey']['addresses'] as List<Object>? ?? [];
final ntrs = outAddresses.toSet().intersection(addressesSet); final ntrs = outAddresses.toSet().intersection(addressesSet);
final value = stringDoubleToBitcoinAmount( final value = stringDoubleToBitcoinAmount((out['value'] as double? ?? 0.0).toString());
(out['value'] as double? ?? 0.0).toString());
totalOutAmount += value; totalOutAmount += value;
if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) || if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) ||
@ -96,44 +91,50 @@ class ElectrumTransactionInfo extends TransactionInfo {
} }
factory ElectrumTransactionInfo.fromElectrumBundle( factory ElectrumTransactionInfo.fromElectrumBundle(
ElectrumTransactionBundle bundle, ElectrumTransactionBundle bundle, WalletType type, BasedUtxoNetwork network,
WalletType type, {required Set<String> addresses, required int height}) {
bitcoin.NetworkType networkType,
{required Set<String> addresses,
required int height}) {
final date = bundle.time != null final date = bundle.time != null
? DateTime.fromMillisecondsSinceEpoch(bundle.time! * 1000) ? DateTime.fromMillisecondsSinceEpoch(bundle.time! * 1000)
: DateTime.now(); : DateTime.now();
var direction = TransactionDirection.incoming; var direction = TransactionDirection.incoming;
var amount = 0; var amount = 0;
var inputAmount = 0; var inputAmount = 0;
var totalOutAmount = 0; var totalOutAmount = 0;
for (var i = 0; i < bundle.originalTransaction.ins.length; i++) { for (var i = 0; i < bundle.originalTransaction.inputs.length; i++) {
final input = bundle.originalTransaction.ins[i]; final input = bundle.originalTransaction.inputs[i];
final inputTransaction = bundle.ins[i]; final inputTransaction = bundle.ins[i];
final vout = input.index; final outTransaction = inputTransaction.outputs[input.txIndex];
final outTransaction = inputTransaction.outs[vout!]; inputAmount += outTransaction.amount.toInt();
final address = addressFromOutput(outTransaction.script!, networkType); if (addresses.contains(addressFromOutputScript(outTransaction.scriptPubKey, network))) {
inputAmount += outTransaction.value!;
if (addresses.contains(address)) {
direction = TransactionDirection.outgoing; direction = TransactionDirection.outgoing;
} }
} }
for (final out in bundle.originalTransaction.outs) { final receivedAmounts = <int>[];
totalOutAmount += out.value!; for (final out in bundle.originalTransaction.outputs) {
final address = addressFromOutput(out.script!, networkType); totalOutAmount += out.amount.toInt();
final addressExists = addresses.contains(address); final addressExists = addresses.contains(addressFromOutputScript(out.scriptPubKey, network));
if (addressExists) {
receivedAmounts.add(out.amount.toInt());
}
if ((direction == TransactionDirection.incoming && addressExists) || if ((direction == TransactionDirection.incoming && addressExists) ||
(direction == TransactionDirection.outgoing && !addressExists)) { (direction == TransactionDirection.outgoing && !addressExists)) {
amount += out.value!; amount += out.amount.toInt();
} }
} }
if (receivedAmounts.length == bundle.originalTransaction.outputs.length) {
// Self-send
direction = TransactionDirection.incoming;
amount = receivedAmounts.reduce((a, b) => a + b);
}
final fee = inputAmount - totalOutAmount; final fee = inputAmount - totalOutAmount;
return ElectrumTransactionInfo(type, return ElectrumTransactionInfo(type,
id: bundle.originalTransaction.getId(), id: bundle.originalTransaction.txId(),
height: height, height: height,
isPending: bundle.confirmations == 0, isPending: bundle.confirmations == 0,
fee: fee, fee: fee,
@ -152,8 +153,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
if (addresses != null) { if (addresses != null) {
tx.outs.forEach((out) { tx.outs.forEach((out) {
try { try {
final p2pkh = bitcoin.P2PKH( final p2pkh =
data: PaymentData(output: out.script), network: bitcoin.bitcoin); bitcoin.P2PKH(data: PaymentData(output: out.script), network: bitcoin.bitcoin);
exist = addresses.contains(p2pkh.data.address); exist = addresses.contains(p2pkh.data.address);
if (exist) { if (exist) {
@ -163,9 +164,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
}); });
} }
final date = timestamp != null final date =
? DateTime.fromMillisecondsSinceEpoch(timestamp * 1000) timestamp != null ? DateTime.fromMillisecondsSinceEpoch(timestamp * 1000) : DateTime.now();
: DateTime.now();
return ElectrumTransactionInfo(type, return ElectrumTransactionInfo(type,
id: tx.getId(), id: tx.getId(),
@ -178,8 +178,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
confirmations: confirmations); confirmations: confirmations);
} }
factory ElectrumTransactionInfo.fromJson( factory ElectrumTransactionInfo.fromJson(Map<String, dynamic> data, WalletType type) {
Map<String, dynamic> data, WalletType type) {
return ElectrumTransactionInfo(type, return ElectrumTransactionInfo(type,
id: data['id'] as String, id: data['id'] as String,
height: data['height'] as int, height: data['height'] as int,

View file

@ -3,9 +3,10 @@ import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:bitcoin_base/bitcoin_base.dart' as bitcoin_base;
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:cw_bitcoin/address_to_output_script.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
import 'package:cw_bitcoin/bitcoin_transaction_no_inputs_exception.dart'; import 'package:cw_bitcoin/bitcoin_transaction_no_inputs_exception.dart';
@ -18,6 +19,7 @@ import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/electrum_transaction_history.dart'; import 'package:cw_bitcoin/electrum_transaction_history.dart';
import 'package:cw_bitcoin/electrum_transaction_info.dart'; import 'package:cw_bitcoin/electrum_transaction_info.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:cw_bitcoin/litecoin_network.dart';
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart'; import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
import 'package:cw_bitcoin/script_hash.dart'; import 'package:cw_bitcoin/script_hash.dart';
import 'package:cw_bitcoin/utils.dart'; import 'package:cw_bitcoin/utils.dart';
@ -37,6 +39,7 @@ import 'package:hex/hex.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:rxdart/subjects.dart'; import 'package:rxdart/subjects.dart';
import 'package:http/http.dart' as http;
part 'electrum_wallet.g.dart'; part 'electrum_wallet.g.dart';
@ -73,6 +76,12 @@ abstract class ElectrumWalletBase
} }
: {}), : {}),
this.unspentCoinsInfo = unspentCoinsInfo, this.unspentCoinsInfo = unspentCoinsInfo,
this.network = networkType == bitcoin.bitcoin
? BitcoinNetwork.mainnet
: networkType == litecoinNetwork
? LitecoinNetwork.mainnet
: BitcoinNetwork.testnet,
this.isTestnet = networkType == bitcoin.testnet,
super(walletInfo) { super(walletInfo) {
this.electrumClient = electrumClient ?? ElectrumClient(); this.electrumClient = electrumClient ?? ElectrumClient();
this.walletInfo = walletInfo; this.walletInfo = walletInfo;
@ -106,13 +115,13 @@ abstract class ElectrumWalletBase
@observable @observable
SyncStatus syncStatus; SyncStatus syncStatus;
List<String> get scriptHashes => walletAddresses.addresses List<String> get scriptHashes => walletAddresses.addressesByReceiveType
.map((addr) => scriptHash(addr.address, networkType: networkType)) .map((addr) => scriptHash(addr.address, network: network))
.toList(); .toList();
List<String> get publicScriptHashes => walletAddresses.addresses List<String> get publicScriptHashes => walletAddresses.allAddresses
.where((addr) => !addr.isHidden) .where((addr) => !addr.isHidden)
.map((addr) => scriptHash(addr.address, networkType: networkType)) .map((addr) => scriptHash(addr.address, network: network))
.toList(); .toList();
String get xpub => hd.base58!; String get xpub => hd.base58!;
@ -121,6 +130,10 @@ abstract class ElectrumWalletBase
String get seed => mnemonic; String get seed => mnemonic;
bitcoin.NetworkType networkType; bitcoin.NetworkType networkType;
BasedUtxoNetwork network;
@override
bool? isTestnet;
@override @override
BitcoinWalletKeys get keys => BitcoinWalletKeys get keys =>
@ -145,12 +158,11 @@ abstract class ElectrumWalletBase
Future<void> startSync() async { Future<void> startSync() async {
try { try {
syncStatus = AttemptingSyncStatus(); syncStatus = AttemptingSyncStatus();
await walletAddresses.discoverAddresses();
await updateTransactions(); await updateTransactions();
_subscribeForUpdates(); _subscribeForUpdates();
await updateUnspent(); await updateUnspent();
await updateBalance(); await updateBalance();
_feeRates = await electrumClient.feeRates(); _feeRates = await electrumClient.feeRates(network: network);
Timer.periodic( Timer.periodic(
const Duration(minutes: 1), (timer) async => _feeRates = await electrumClient.feeRates()); const Duration(minutes: 1), (timer) async => _feeRates = await electrumClient.feeRates());
@ -181,183 +193,206 @@ abstract class ElectrumWalletBase
} }
} }
@override Future<EstimatedTxResult> _estimateTxFeeAndInputsToUse(
Future<PendingTransaction> createTransaction(Object credentials) async { int credentialsAmount,
const minAmount = 546; bool sendAll,
final transactionCredentials = credentials as BitcoinTransactionCredentials; List<BitcoinBaseAddress> outputAddresses,
final inputs = <BitcoinUnspent>[]; List<BitcoinOutput> outputs,
final outputs = transactionCredentials.outputs; BitcoinTransactionCredentials transactionCredentials,
final hasMultiDestination = outputs.length > 1; {int? inputsCount}) async {
final utxos = <UtxoWithAddress>[];
List<ECPrivate> privateKeys = [];
var leftAmount = credentialsAmount;
var allInputsAmount = 0; var allInputsAmount = 0;
if (unspentCoins.isEmpty) { for (int i = 0; i < unspentCoins.length; i++) {
await updateUnspent(); final utx = unspentCoins[i];
}
for (final utx in unspentCoins) {
if (utx.isSending) { if (utx.isSending) {
allInputsAmount += utx.value; allInputsAmount += utx.value;
inputs.add(utx);
}
}
if (inputs.isEmpty) {
throw BitcoinTransactionNoInputsException();
}
final allAmountFee = transactionCredentials.feeRate != null
? feeAmountWithFeeRate(transactionCredentials.feeRate!, inputs.length, outputs.length)
: feeAmountForPriority(transactionCredentials.priority!, inputs.length, outputs.length);
final allAmount = allInputsAmount - allAmountFee;
var credentialsAmount = 0;
var amount = 0;
var fee = 0;
if (hasMultiDestination) {
if (outputs.any((item) => item.sendAll || item.formattedCryptoAmount! <= 0)) {
throw BitcoinTransactionWrongBalanceException(currency);
}
credentialsAmount = outputs.fold(0, (acc, value) => acc + value.formattedCryptoAmount!);
if (allAmount - credentialsAmount < minAmount) {
throw BitcoinTransactionWrongBalanceException(currency);
}
amount = credentialsAmount;
if (transactionCredentials.feeRate != null) {
fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount,
outputsCount: outputs.length + 1);
} else {
fee = calculateEstimatedFee(transactionCredentials.priority, amount,
outputsCount: outputs.length + 1);
}
} else {
final output = outputs.first;
credentialsAmount = !output.sendAll ? output.formattedCryptoAmount! : 0;
if (credentialsAmount > allAmount) {
throw BitcoinTransactionWrongBalanceException(currency);
}
amount = output.sendAll || allAmount - credentialsAmount < minAmount
? allAmount
: credentialsAmount;
if (output.sendAll || amount == allAmount) {
fee = allAmountFee;
} else if (transactionCredentials.feeRate != null) {
fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount);
} else {
fee = calculateEstimatedFee(transactionCredentials.priority, amount);
}
}
if (fee == 0) {
throw BitcoinTransactionWrongBalanceException(currency);
}
final totalAmount = amount + fee;
if (totalAmount > balance[currency]!.confirmed || totalAmount > allInputsAmount) {
throw BitcoinTransactionWrongBalanceException(currency);
}
final txb = bitcoin.TransactionBuilder(network: networkType);
final changeAddress = await walletAddresses.getChangeAddress();
var leftAmount = totalAmount;
var totalInputAmount = 0;
inputs.clear();
for (final utx in unspentCoins) {
if (utx.isSending) {
leftAmount = leftAmount - utx.value; leftAmount = leftAmount - utx.value;
totalInputAmount += utx.value;
inputs.add(utx);
if (leftAmount <= 0) { final address = _addressTypeFromStr(utx.address, network);
final privkey = generateECPrivate(
hd: utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
index: utx.bitcoinAddressRecord.index,
network: network);
privateKeys.add(privkey);
utxos.add(
UtxoWithAddress(
utxo: BitcoinUtxo(
txHash: utx.hash,
value: BigInt.from(utx.value),
vout: utx.vout,
scriptType: _getScriptType(address),
),
ownerDetails:
UtxoAddressDetails(publicKey: privkey.getPublic().toHex(), address: address),
),
);
bool amountIsAcquired = !sendAll && leftAmount <= 0;
if ((inputsCount == null && amountIsAcquired) || inputsCount == i + 1) {
break; break;
} }
} }
} }
if (inputs.isEmpty) { if (utxos.isEmpty) {
throw BitcoinTransactionNoInputsException(); throw BitcoinTransactionNoInputsException();
} }
if (amount <= 0 || totalInputAmount < totalAmount) { var changeValue = allInputsAmount - credentialsAmount;
if (!sendAll) {
if (changeValue > 0) {
final changeAddress = await walletAddresses.getChangeAddress();
final address = _addressTypeFromStr(changeAddress, network);
outputAddresses.add(address);
outputs.add(BitcoinOutput(address: address, value: BigInt.from(changeValue)));
}
}
final estimatedSize = BitcoinTransactionBuilder.estimateTransactionSize(
utxos: utxos, outputs: outputs, network: network);
final fee = transactionCredentials.feeRate != null
? feeAmountWithFeeRate(transactionCredentials.feeRate!, 0, 0, size: estimatedSize)
: feeAmountForPriority(transactionCredentials.priority!, 0, 0, size: estimatedSize);
if (fee == 0) {
throw BitcoinTransactionWrongBalanceException(currency); throw BitcoinTransactionWrongBalanceException(currency);
} }
txb.setVersion(1); var amount = credentialsAmount;
inputs.forEach((input) {
if (input.isP2wpkh) {
final p2wpkh = bitcoin
.P2WPKH(
data: generatePaymentData(
hd: input.bitcoinAddressRecord.isHidden
? walletAddresses.sideHd
: walletAddresses.mainHd,
index: input.bitcoinAddressRecord.index),
network: networkType)
.data;
txb.addInput(input.hash, input.vout, null, p2wpkh.output); final lastOutput = outputs.last;
} else { if (!sendAll) {
txb.addInput(input.hash, input.vout); if (changeValue > fee) {
// Here, lastOutput is change, deduct the fee from it
outputs[outputs.length - 1] =
BitcoinOutput(address: lastOutput.address, value: lastOutput.value - BigInt.from(fee));
} }
});
outputs.forEach((item) {
final outputAmount = hasMultiDestination ? item.formattedCryptoAmount : amount;
final outputAddress = item.isParsedAddress ? item.extractedAddress! : item.address;
txb.addOutput(addressToOutputScript(outputAddress, networkType), outputAmount!);
});
final estimatedSize = estimatedTransactionSize(inputs.length, outputs.length + 1);
var feeAmount = 0;
if (transactionCredentials.feeRate != null) {
feeAmount = transactionCredentials.feeRate! * estimatedSize;
} else { } else {
feeAmount = feeRate(transactionCredentials.priority!) * estimatedSize; // 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
amount = allInputsAmount - fee;
outputs[outputs.length - 1] =
BitcoinOutput(address: lastOutput.address, value: BigInt.from(amount));
} }
final changeValue = totalInputAmount - amount - feeAmount; final totalAmount = amount + fee;
if (changeValue > minAmount) { if (totalAmount > balance[currency]!.confirmed) {
txb.addOutput(changeAddress, changeValue); throw BitcoinTransactionWrongBalanceException(currency);
} }
for (var i = 0; i < inputs.length; i++) { if (totalAmount > allInputsAmount) {
final input = inputs[i]; if (unspentCoins.where((utx) => utx.isSending).length == utxos.length) {
final keyPair = generateKeyPair( throw BitcoinTransactionWrongBalanceException(currency);
hd: input.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd, } else {
index: input.bitcoinAddressRecord.index, if (changeValue > fee) {
network: networkType); outputAddresses.removeLast();
final witnessValue = input.isP2wpkh ? input.value : null; outputs.removeLast();
}
txb.sign(vin: i, keyPair: keyPair, witnessValue: witnessValue); return _estimateTxFeeAndInputsToUse(
credentialsAmount, sendAll, outputAddresses, outputs, transactionCredentials,
inputsCount: utxos.length + 1);
}
} }
return PendingBitcoinTransaction(txb.build(), type, return EstimatedTxResult(utxos: utxos, privateKeys: privateKeys, fee: fee, amount: amount);
electrumClient: electrumClient, amount: amount, fee: fee) }
..addListener((transaction) async {
transactionHistory.addOne(transaction); @override
await updateBalance(); Future<PendingTransaction> createTransaction(Object credentials) async {
try {
final outputs = <BitcoinOutput>[];
final outputAddresses = <BitcoinBaseAddress>[];
final transactionCredentials = credentials as BitcoinTransactionCredentials;
final hasMultiDestination = transactionCredentials.outputs.length > 1;
final sendAll = !hasMultiDestination && transactionCredentials.outputs.first.sendAll;
var credentialsAmount = 0;
for (final out in transactionCredentials.outputs) {
final outputAddress = out.isParsedAddress ? out.extractedAddress! : out.address;
final address = _addressTypeFromStr(outputAddress, network);
outputAddresses.add(address);
if (hasMultiDestination) {
if (out.sendAll || out.formattedCryptoAmount! <= 0) {
throw BitcoinTransactionWrongBalanceException(currency);
}
final outputAmount = out.formattedCryptoAmount!;
credentialsAmount += outputAmount;
outputs.add(BitcoinOutput(address: address, value: BigInt.from(outputAmount)));
} else {
if (!sendAll) {
final outputAmount = out.formattedCryptoAmount!;
credentialsAmount += outputAmount;
outputs.add(BitcoinOutput(address: address, value: BigInt.from(outputAmount)));
} else {
// The value will be changed after estimating the Tx size and deducting the fee from the total
outputs.add(BitcoinOutput(address: address, value: BigInt.from(0)));
}
}
}
final estimatedTx = await _estimateTxFeeAndInputsToUse(
credentialsAmount, sendAll, outputAddresses, outputs, transactionCredentials);
final txb = BitcoinTransactionBuilder(
utxos: estimatedTx.utxos,
outputs: outputs,
fee: BigInt.from(estimatedTx.fee),
network: network);
final transaction = txb.buildTransaction((txDigest, utxo, publicKey, sighash) {
final key = estimatedTx.privateKeys
.firstWhereOrNull((element) => element.getPublic().toHex() == publicKey);
if (key == null) {
throw Exception("Cannot find private key");
}
if (utxo.utxo.isP2tr()) {
return key.signTapRoot(txDigest, sighash: sighash);
} else {
return key.signInput(txDigest, sigHash: sighash);
}
}); });
return PendingBitcoinTransaction(transaction, type,
electrumClient: electrumClient,
amount: estimatedTx.amount,
fee: estimatedTx.fee,
network: network)
..addListener((transaction) async {
transactionHistory.addOne(transaction);
await updateBalance();
});
} catch (e) {
throw e;
}
} }
String toJSON() => json.encode({ String toJSON() => json.encode({
'mnemonic': mnemonic, 'mnemonic': mnemonic,
'account_index': walletAddresses.currentReceiveAddressIndex.toString(), 'account_index': walletAddresses.currentReceiveAddressIndexByType,
'change_address_index': walletAddresses.currentChangeAddressIndex.toString(), 'change_address_index': walletAddresses.currentChangeAddressIndexByType,
'addresses': walletAddresses.addresses.map((addr) => addr.toJSON()).toList(), 'addresses': walletAddresses.allAddresses.map((addr) => addr.toJSON()).toList(),
'balance': balance[currency]?.toJSON() 'address_page_type': walletInfo.addressPageType == null
? SegwitAddresType.p2wpkh.toString()
: walletInfo.addressPageType.toString(),
'balance': balance[currency]?.toJSON(),
'network_type': network == BitcoinNetwork.testnet ? 'testnet' : 'mainnet',
}); });
int feeRate(TransactionPriority priority) { int feeRate(TransactionPriority priority) {
@ -372,24 +407,29 @@ abstract class ElectrumWalletBase
} }
} }
int feeAmountForPriority( int feeAmountForPriority(BitcoinTransactionPriority priority, int inputsCount, int outputsCount,
BitcoinTransactionPriority priority, int inputsCount, int outputsCount) => {int? size}) =>
feeRate(priority) * estimatedTransactionSize(inputsCount, outputsCount); feeRate(priority) * (size ?? estimatedTransactionSize(inputsCount, outputsCount));
int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount) => int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount, {int? size}) =>
feeRate * estimatedTransactionSize(inputsCount, outputsCount); feeRate * (size ?? estimatedTransactionSize(inputsCount, outputsCount));
@override @override
int calculateEstimatedFee(TransactionPriority? priority, int? amount, {int? outputsCount}) { int calculateEstimatedFee(TransactionPriority? priority, int? amount,
{int? outputsCount, int? size}) {
if (priority is BitcoinTransactionPriority) { if (priority is BitcoinTransactionPriority) {
return calculateEstimatedFeeWithFeeRate(feeRate(priority), amount, return calculateEstimatedFeeWithFeeRate(feeRate(priority), amount,
outputsCount: outputsCount); outputsCount: outputsCount, size: size);
} }
return 0; return 0;
} }
int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount}) { int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount, int? size}) {
if (size != null) {
return feeAmountWithFeeRate(feeRate, 0, 0, size: size);
}
int inputsCount = 0; int inputsCount = 0;
if (amount != null) { if (amount != null) {
@ -457,9 +497,6 @@ abstract class ElectrumWalletBase
await transactionHistory.changePassword(password); await transactionHistory.changePassword(password);
} }
bitcoin.ECPair keyPairFor({required int index}) =>
generateKeyPair(hd: hd, index: index, network: networkType);
@override @override
Future<void> rescan({required int height}) async => throw UnimplementedError(); Future<void> rescan({required int height}) async => throw UnimplementedError();
@ -473,20 +510,23 @@ abstract class ElectrumWalletBase
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
Future<void> updateUnspent() async { Future<void> updateUnspent() async {
final unspent = await Future.wait(walletAddresses.addresses.map((address) => electrumClient List<BitcoinUnspent> updatedUnspentCoins = [];
.getListUnspentWithAddress(address.address, networkType)
.then((unspent) => unspent.map((unspent) { final addressesSet = walletAddresses.allAddresses.map((addr) => addr.address).toSet();
await Future.wait(walletAddresses.allAddresses.map((address) => electrumClient
.getListUnspentWithAddress(address.address, network)
.then((unspent) => Future.forEach<Map<String, dynamic>>(unspent, (unspent) async {
try { try {
return BitcoinUnspent.fromJSON(address, unspent); final coin = BitcoinUnspent.fromJSON(address, unspent);
} catch (_) { final tx = await fetchTransactionInfo(
return null; hash: coin.hash, height: 0, myAddresses: addressesSet);
} coin.isChange = tx?.direction == TransactionDirection.outgoing;
}).whereNotNull()))); updatedUnspentCoins.add(coin);
unspentCoins = unspent.expand((e) => e).toList(); } catch (_) {}
unspentCoins.forEach((coin) async { }))));
final tx = await fetchTransactionInfo(hash: coin.hash, height: 0);
coin.isChange = tx?.direction == TransactionDirection.outgoing; unspentCoins = updatedUnspentCoins;
});
if (unspentCoinsInfo.isEmpty) { if (unspentCoinsInfo.isEmpty) {
unspentCoins.forEach((coin) => _addCoinInfo(coin)); unspentCoins.forEach((coin) => _addCoinInfo(coin));
@ -495,8 +535,10 @@ abstract class ElectrumWalletBase
if (unspentCoins.isNotEmpty) { if (unspentCoins.isNotEmpty) {
unspentCoins.forEach((coin) { unspentCoins.forEach((coin) {
final coinInfoList = unspentCoinsInfo.values final coinInfoList = unspentCoinsInfo.values.where((element) =>
.where((element) => element.walletId.contains(id) && element.hash.contains(coin.hash)); element.walletId.contains(id) &&
element.hash.contains(coin.hash) &&
element.vout == coin.vout);
if (coinInfoList.isNotEmpty) { if (coinInfoList.isNotEmpty) {
final coinInfo = coinInfoList.first; final coinInfo = coinInfoList.first;
@ -537,7 +579,8 @@ abstract class ElectrumWalletBase
if (currentWalletUnspentCoins.isNotEmpty) { if (currentWalletUnspentCoins.isNotEmpty) {
currentWalletUnspentCoins.forEach((element) { currentWalletUnspentCoins.forEach((element) {
final existUnspentCoins = unspentCoins.where((coin) => element.hash.contains(coin.hash)); final existUnspentCoins = unspentCoins
.where((coin) => element.hash.contains(coin.hash) && element.vout == coin.vout);
if (existUnspentCoins.isEmpty) { if (existUnspentCoins.isEmpty) {
keys.add(element.key); keys.add(element.key);
@ -555,92 +598,152 @@ abstract class ElectrumWalletBase
Future<ElectrumTransactionBundle> getTransactionExpanded( Future<ElectrumTransactionBundle> getTransactionExpanded(
{required String hash, required int height}) async { {required String hash, required int height}) async {
final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash); String transactionHex;
final transactionHex = verboseTransaction['hex'] as String; int? time;
final original = bitcoin.Transaction.fromHex(transactionHex); int confirmations = 0;
final ins = <bitcoin.Transaction>[]; if (network == BitcoinNetwork.testnet) {
final time = verboseTransaction['time'] as int?; // Testnet public electrum server does not support verbose transaction fetching
final confirmations = verboseTransaction['confirmations'] as int? ?? 0; transactionHex = await electrumClient.getTransactionHex(hash: hash);
for (final vin in original.ins) { final status = json.decode(
final id = HEX.encode(vin.hash!.reversed.toList()); (await http.get(Uri.parse("https://blockstream.info/testnet/api/tx/$hash/status"))).body);
final txHex = await electrumClient.getTransactionHex(hash: id);
final tx = bitcoin.Transaction.fromHex(txHex); time = status["block_time"] as int?;
ins.add(tx); final tip = await electrumClient.getCurrentBlockChainTip() ?? 0;
confirmations = tip - (status["block_height"] as int? ?? 0);
} else {
final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash);
transactionHex = verboseTransaction['hex'] as String;
time = verboseTransaction['time'] as int?;
confirmations = verboseTransaction['confirmations'] as int? ?? 0;
} }
return ElectrumTransactionBundle(original, ins: ins, time: time, confirmations: confirmations); final original = bitcoin_base.BtcTransaction.fromRaw(transactionHex);
final ins = <bitcoin_base.BtcTransaction>[];
for (final vin in original.inputs) {
try {
final id = HEX.encode(HEX.decode(vin.txId).reversed.toList());
final txHex = await electrumClient.getTransactionHex(hash: id);
final tx = bitcoin_base.BtcTransaction.fromRaw(txHex);
ins.add(tx);
} catch (_) {
ins.add(bitcoin_base.BtcTransaction.fromRaw(
await electrumClient.getTransactionHex(hash: vin.txId),
));
}
}
return ElectrumTransactionBundle(original,
ins: ins, time: time, confirmations: confirmations, height: height);
} }
Future<ElectrumTransactionInfo?> fetchTransactionInfo( Future<ElectrumTransactionInfo?> fetchTransactionInfo(
{required String hash, required int height}) async { {required String hash,
required int height,
required Set<String> myAddresses,
bool? retryOnFailure}) async {
try { try {
final tx = await getTransactionExpanded(hash: hash, height: height); return ElectrumTransactionInfo.fromElectrumBundle(
final addresses = walletAddresses.addresses.map((addr) => addr.address).toSet(); await getTransactionExpanded(hash: hash, height: height), walletInfo.type, network,
return ElectrumTransactionInfo.fromElectrumBundle(tx, walletInfo.type, networkType, addresses: myAddresses, height: height);
addresses: addresses, height: height); } catch (e) {
} catch (_) { if (e is FormatException && retryOnFailure == true) {
await Future.delayed(const Duration(seconds: 2));
return fetchTransactionInfo(hash: hash, height: height, myAddresses: myAddresses);
}
return null; return null;
} }
} }
@override @override
Future<Map<String, ElectrumTransactionInfo>> fetchTransactions() async { Future<Map<String, ElectrumTransactionInfo>> fetchTransactions() async {
final addressHashes = <String, BitcoinAddressRecord>{};
final normalizedHistories = <Map<String, dynamic>>[];
final newTxCounts = <String, int>{};
walletAddresses.addresses.forEach((addressRecord) {
final sh = scriptHash(addressRecord.address, networkType: networkType);
addressHashes[sh] = addressRecord;
newTxCounts[sh] = 0;
});
try { try {
final histories = addressHashes.keys.map((scriptHash) => final Map<String, ElectrumTransactionInfo> historiesWithDetails = {};
electrumClient.getHistory(scriptHash).then((history) => {scriptHash: history})); final addressesSet = walletAddresses.allAddresses.map((addr) => addr.address).toSet();
final historyResults = await Future.wait(histories); final currentHeight = await electrumClient.getCurrentBlockChainTip() ?? 0;
historyResults.forEach((history) { await Future.wait(ADDRESS_TYPES.map((type) {
history.entries.forEach((historyItem) { final addressesByType = walletAddresses.allAddresses.where((addr) => addr.type == type);
if (historyItem.value.isNotEmpty) {
final address = addressHashes[historyItem.key]; return Future.wait(addressesByType.map((addressRecord) async {
address?.setAsUsed(); final history = await _fetchAddressHistory(addressRecord, addressesSet, currentHeight);
newTxCounts[historyItem.key] = historyItem.value.length;
normalizedHistories.addAll(historyItem.value); if (history.isNotEmpty) {
addressRecord.txCount = history.length;
historiesWithDetails.addAll(history);
final matchedAddresses =
addressesByType.where((addr) => addr.isHidden == addressRecord.isHidden);
final isLastUsedAddress =
history.isNotEmpty && addressRecord.address == matchedAddresses.last.address;
if (isLastUsedAddress) {
await walletAddresses.discoverAddresses(
matchedAddresses.toList(),
addressRecord.isHidden,
(address, addressesSet) =>
_fetchAddressHistory(address, addressesSet, currentHeight)
.then((history) => history.isNotEmpty ? address.address : null),
type: type);
}
} }
}); }));
});
for (var sh in addressHashes.keys) {
var balanceData = await electrumClient.getBalance(sh);
var addressRecord = addressHashes[sh];
if (addressRecord != null) {
addressRecord.balance = balanceData['confirmed'] as int? ?? 0;
}
}
addressHashes.forEach((sh, addressRecord) {
addressRecord.txCount = newTxCounts[sh] ?? 0;
});
final historiesWithDetails = await Future.wait(normalizedHistories.map((transaction) {
try {
return fetchTransactionInfo(
hash: transaction['tx_hash'] as String, height: transaction['height'] as int);
} catch (_) {
return Future.value(null);
}
})); }));
return historiesWithDetails.fold<Map<String, ElectrumTransactionInfo>>( return historiesWithDetails;
<String, ElectrumTransactionInfo>{}, (acc, tx) { } catch (e) {
if (tx == null) { print(e.toString());
return acc; return {};
} }
acc[tx.id] = acc[tx.id]?.updated(tx) ?? tx; }
return acc;
}); Future<Map<String, ElectrumTransactionInfo>> _fetchAddressHistory(
BitcoinAddressRecord addressRecord, Set<String> addressesSet, int currentHeight) async {
try {
final Map<String, ElectrumTransactionInfo> historiesWithDetails = {};
final history = await electrumClient
.getHistory(addressRecord.scriptHash ?? addressRecord.updateScriptHash(network));
if (history.isNotEmpty) {
addressRecord.setAsUsed();
await Future.wait(history.map((transaction) async {
final txid = transaction['tx_hash'] as String;
final height = transaction['height'] as int;
final storedTx = transactionHistory.transactions[txid];
if (storedTx != null) {
if (height > 0) {
storedTx.height = height;
// the tx's block itself is the first confirmation so add 1
storedTx.confirmations = currentHeight - height + 1;
storedTx.isPending = storedTx.confirmations == 0;
}
historiesWithDetails[txid] = storedTx;
} else {
final tx = await fetchTransactionInfo(
hash: txid, height: height, myAddresses: addressesSet, retryOnFailure: true);
if (tx != null) {
historiesWithDetails[txid] = tx;
// Got a new transaction fetched, add it to the transaction history
// instead of waiting all to finish, and next time it will be faster
transactionHistory.addOne(tx);
await transactionHistory.save();
}
}
return Future.value(null);
}));
}
return historiesWithDetails;
} catch (e) { } catch (e) {
print(e.toString()); print(e.toString());
return {}; return {};
@ -654,10 +757,8 @@ abstract class ElectrumWalletBase
} }
_isTransactionUpdating = true; _isTransactionUpdating = true;
final transactions = await fetchTransactions(); await fetchTransactions();
transactionHistory.addMany(transactions);
walletAddresses.updateReceiveAddresses(); walletAddresses.updateReceiveAddresses();
await transactionHistory.save();
_isTransactionUpdating = false; _isTransactionUpdating = false;
} catch (e, stacktrace) { } catch (e, stacktrace) {
print(stacktrace); print(stacktrace);
@ -688,11 +789,11 @@ abstract class ElectrumWalletBase
} }
Future<ElectrumBalance> _fetchBalances() async { Future<ElectrumBalance> _fetchBalances() async {
final addresses = walletAddresses.addresses.toList(); final addresses = walletAddresses.allAddresses.toList();
final balanceFutures = <Future<Map<String, dynamic>>>[]; final balanceFutures = <Future<Map<String, dynamic>>>[];
for (var i = 0; i < addresses.length; i++) { for (var i = 0; i < addresses.length; i++) {
final addressRecord = addresses[i]; final addressRecord = addresses[i];
final sh = scriptHash(addressRecord.address, networkType: networkType); final sh = scriptHash(addressRecord.address, network: network);
final balanceFuture = electrumClient.getBalance(sh); final balanceFuture = electrumClient.getBalance(sh);
balanceFutures.add(balanceFuture); balanceFutures.add(balanceFuture);
} }
@ -701,6 +802,7 @@ abstract class ElectrumWalletBase
unspentCoinsInfo.values.forEach((info) { unspentCoinsInfo.values.forEach((info) {
unspentCoins.forEach((element) { unspentCoins.forEach((element) {
if (element.hash == info.hash && if (element.hash == info.hash &&
element.vout == info.vout &&
info.isFrozen && info.isFrozen &&
element.bitcoinAddressRecord.address == info.address && element.bitcoinAddressRecord.address == info.address &&
element.value == info.value) { element.value == info.value) {
@ -738,10 +840,10 @@ abstract class ElectrumWalletBase
String getChangeAddress() { String getChangeAddress() {
const minCountOfHiddenAddresses = 5; const minCountOfHiddenAddresses = 5;
final random = Random(); final random = Random();
var addresses = walletAddresses.addresses.where((addr) => addr.isHidden).toList(); var addresses = walletAddresses.allAddresses.where((addr) => addr.isHidden).toList();
if (addresses.length < minCountOfHiddenAddresses) { if (addresses.length < minCountOfHiddenAddresses) {
addresses = walletAddresses.addresses.toList(); addresses = walletAddresses.allAddresses.toList();
} }
return addresses[random.nextInt(addresses.length)].address; return addresses[random.nextInt(addresses.length)].address;
@ -753,9 +855,62 @@ abstract class ElectrumWalletBase
@override @override
String signMessage(String message, {String? address = null}) { String signMessage(String message, {String? address = null}) {
final index = address != null final index = address != null
? walletAddresses.addresses.firstWhere((element) => element.address == address).index ? walletAddresses.allAddresses.firstWhere((element) => element.address == address).index
: null; : null;
final HD = index == null ? hd : hd.derive(index); final HD = index == null ? hd : hd.derive(index);
return base64Encode(HD.signMessage(message)); return base64Encode(HD.signMessage(message));
} }
} }
class EstimateTxParams {
EstimateTxParams(
{required this.amount,
required this.feeRate,
required this.priority,
required this.outputsCount,
required this.size});
final int amount;
final int feeRate;
final TransactionPriority priority;
final int outputsCount;
final int size;
}
class EstimatedTxResult {
EstimatedTxResult(
{required this.utxos, required this.privateKeys, required this.fee, required this.amount});
final List<UtxoWithAddress> utxos;
final List<ECPrivate> privateKeys;
final int fee;
final int amount;
}
BitcoinBaseAddress _addressTypeFromStr(String address, BasedUtxoNetwork network) {
if (P2pkhAddress.regex.hasMatch(address)) {
return P2pkhAddress.fromAddress(address: address, network: network);
} else if (P2shAddress.regex.hasMatch(address)) {
return P2shAddress.fromAddress(address: address, network: network);
} else if (P2wshAddress.regex.hasMatch(address)) {
return P2wshAddress.fromAddress(address: address, network: network);
} else if (P2trAddress.regex.hasMatch(address)) {
return P2trAddress.fromAddress(address: address, network: network);
} else {
return P2wpkhAddress.fromAddress(address: address, network: network);
}
}
BitcoinAddressType _getScriptType(BitcoinBaseAddress type) {
if (type is P2pkhAddress) {
return P2pkhAddressType.p2pkh;
} else if (type is P2shAddress) {
return P2shAddressType.p2wpkhInP2sh;
} else if (type is P2wshAddress) {
return SegwitAddresType.p2wsh;
} else if (type is P2trAddress) {
return SegwitAddresType.p2tr;
} else {
return SegwitAddresType.p2wpkh;
}
}

View file

@ -1,8 +1,8 @@
import 'package:bitbox/bitbox.dart' as bitbox; import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:bitbox/bitbox.dart' as bitbox;
import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum.dart'; import 'package:cw_bitcoin/electrum.dart';
import 'package:cw_bitcoin/script_hash.dart';
import 'package:cw_core/wallet_addresses.dart'; import 'package:cw_core/wallet_addresses.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
@ -12,25 +12,41 @@ part 'electrum_wallet_addresses.g.dart';
class ElectrumWalletAddresses = ElectrumWalletAddressesBase with _$ElectrumWalletAddresses; class ElectrumWalletAddresses = ElectrumWalletAddressesBase with _$ElectrumWalletAddresses;
const List<BitcoinAddressType> ADDRESS_TYPES = [
SegwitAddresType.p2wpkh,
P2pkhAddressType.p2pkh,
SegwitAddresType.p2tr,
SegwitAddresType.p2wsh,
P2shAddressType.p2wpkhInP2sh,
];
abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
ElectrumWalletAddressesBase(WalletInfo walletInfo, ElectrumWalletAddressesBase(
{required this.mainHd, WalletInfo walletInfo, {
required this.sideHd, required this.mainHd,
required this.electrumClient, required this.sideHd,
required this.networkType, required this.electrumClient,
List<BitcoinAddressRecord>? initialAddresses, required this.network,
int initialRegularAddressIndex = 0, List<BitcoinAddressRecord>? initialAddresses,
int initialChangeAddressIndex = 0}) Map<String, int>? initialRegularAddressIndex,
: addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()), Map<String, int>? initialChangeAddressIndex,
}) : _addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
addressesByReceiveType =
ObservableList<BitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()),
receiveAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []) receiveAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed) .where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed)
.toSet()), .toSet()),
changeAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []) changeAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed) .where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed)
.toSet()), .toSet()),
currentReceiveAddressIndex = initialRegularAddressIndex, currentReceiveAddressIndexByType = initialRegularAddressIndex ?? {},
currentChangeAddressIndex = initialChangeAddressIndex, currentChangeAddressIndexByType = initialChangeAddressIndex ?? {},
super(walletInfo); _addressPageType = walletInfo.addressPageType != null
? BitcoinAddressType.fromValue(walletInfo.addressPageType!)
: SegwitAddresType.p2wpkh,
super(walletInfo) {
updateAddressesByMatch();
}
static const defaultReceiveAddressesCount = 22; static const defaultReceiveAddressesCount = 22;
static const defaultChangeAddressesCount = 17; static const defaultChangeAddressesCount = 17;
@ -40,37 +56,48 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
static String toLegacy(String address) => bitbox.Address.toLegacyAddress(address); static String toLegacy(String address) => bitbox.Address.toLegacyAddress(address);
final ObservableList<BitcoinAddressRecord> addresses; final ObservableList<BitcoinAddressRecord> _addresses;
// Matched by addressPageType
late ObservableList<BitcoinAddressRecord> addressesByReceiveType;
final ObservableList<BitcoinAddressRecord> receiveAddresses; final ObservableList<BitcoinAddressRecord> receiveAddresses;
final ObservableList<BitcoinAddressRecord> changeAddresses; final ObservableList<BitcoinAddressRecord> changeAddresses;
final ElectrumClient electrumClient; final ElectrumClient electrumClient;
final bitcoin.NetworkType networkType; final BasedUtxoNetwork network;
final bitcoin.HDWallet mainHd; final bitcoin.HDWallet mainHd;
final bitcoin.HDWallet sideHd; final bitcoin.HDWallet sideHd;
@observable
BitcoinAddressType _addressPageType = SegwitAddresType.p2wpkh;
@computed
BitcoinAddressType get addressPageType => _addressPageType;
@computed
List<BitcoinAddressRecord> get allAddresses => _addresses;
@override @override
@computed @computed
String get address { String get address {
if (isEnabledAutoGenerateSubaddress) { String receiveAddress;
if (receiveAddresses.isEmpty) {
final newAddress = generateNewAddress(hd: mainHd).address;
return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(newAddress) : newAddress;
}
final receiveAddress = receiveAddresses.first.address;
return walletInfo.type == WalletType.bitcoinCash final typeMatchingReceiveAddresses = receiveAddresses.where(_isAddressPageTypeMatch);
? toCashAddr(receiveAddress)
: receiveAddress; if ((isEnabledAutoGenerateSubaddress && receiveAddresses.isEmpty) ||
typeMatchingReceiveAddresses.isEmpty) {
receiveAddress = generateNewAddress().address;
} else { } else {
final receiveAddress = (receiveAddresses.first.address != addresses.first.address && final previousAddressMatchesType =
previousAddressRecord != null) previousAddressRecord != null && previousAddressRecord!.type == addressPageType;
? previousAddressRecord!.address
: addresses.first.address;
return walletInfo.type == WalletType.bitcoinCash if (previousAddressMatchesType &&
? toCashAddr(receiveAddress) typeMatchingReceiveAddresses.first.address != addressesByReceiveType.first.address) {
: receiveAddress; receiveAddress = previousAddressRecord!.address;
} else {
receiveAddress = typeMatchingReceiveAddresses.first.address;
}
} }
return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(receiveAddress) : receiveAddress;
} }
@observable @observable
@ -81,7 +108,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
if (addr.startsWith('bitcoincash:')) { if (addr.startsWith('bitcoincash:')) {
addr = toLegacy(addr); addr = toLegacy(addr);
} }
final addressRecord = addresses.firstWhere((addressRecord) => addressRecord.address == addr); final addressRecord = _addresses.firstWhere((addressRecord) => addressRecord.address == addr);
previousAddressRecord = addressRecord; previousAddressRecord = addressRecord;
receiveAddresses.remove(addressRecord); receiveAddresses.remove(addressRecord);
@ -89,16 +116,29 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
} }
@override @override
String get primaryAddress => getAddress(index: 0, hd: mainHd); String get primaryAddress => getAddress(index: 0, hd: mainHd, addressType: addressPageType);
int currentReceiveAddressIndex; Map<String, int> currentReceiveAddressIndexByType;
int currentChangeAddressIndex;
int get currentReceiveAddressIndex =>
currentReceiveAddressIndexByType[_addressPageType.toString()] ?? 0;
void set currentReceiveAddressIndex(int index) =>
currentReceiveAddressIndexByType[_addressPageType.toString()] = index;
Map<String, int> currentChangeAddressIndexByType;
int get currentChangeAddressIndex =>
currentChangeAddressIndexByType[_addressPageType.toString()] ?? 0;
void set currentChangeAddressIndex(int index) =>
currentChangeAddressIndexByType[_addressPageType.toString()] = index;
@observable @observable
BitcoinAddressRecord? previousAddressRecord; BitcoinAddressRecord? previousAddressRecord;
@computed @computed
int get totalCountOfReceiveAddresses => addresses.fold(0, (acc, addressRecord) { int get totalCountOfReceiveAddresses => addressesByReceiveType.fold(0, (acc, addressRecord) {
if (!addressRecord.isHidden) { if (!addressRecord.isHidden) {
return acc + 1; return acc + 1;
} }
@ -106,22 +146,21 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
}); });
@computed @computed
int get totalCountOfChangeAddresses => addresses.fold(0, (acc, addressRecord) { int get totalCountOfChangeAddresses => addressesByReceiveType.fold(0, (acc, addressRecord) {
if (addressRecord.isHidden) { if (addressRecord.isHidden) {
return acc + 1; return acc + 1;
} }
return acc; return acc;
}); });
Future<void> discoverAddresses() async {
await _discoverAddresses(mainHd, false);
await _discoverAddresses(sideHd, true);
await updateAddressesInBox();
}
@override @override
Future<void> init() async { Future<void> init() async {
await _generateInitialAddresses(); await _generateInitialAddresses();
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh);
await _generateInitialAddresses(type: SegwitAddresType.p2tr);
await _generateInitialAddresses(type: SegwitAddresType.p2wsh);
updateAddressesByMatch();
updateReceiveAddresses(); updateReceiveAddresses();
updateChangeAddresses(); updateChangeAddresses();
await updateAddressesInBox(); await updateAddressesInBox();
@ -141,10 +180,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
if (changeAddresses.isEmpty) { if (changeAddresses.isEmpty) {
final newAddresses = await _createNewAddresses(gap, final newAddresses = await _createNewAddresses(gap,
hd: sideHd,
startIndex: totalCountOfChangeAddresses > 0 ? totalCountOfChangeAddresses - 1 : 0, startIndex: totalCountOfChangeAddresses > 0 ? totalCountOfChangeAddresses - 1 : 0,
isHidden: true); isHidden: true);
_addAddresses(newAddresses); addAddresses(newAddresses);
} }
if (currentChangeAddressIndex >= changeAddresses.length) { if (currentChangeAddressIndex >= changeAddresses.length) {
@ -157,19 +195,26 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
return address; return address;
} }
BitcoinAddressRecord generateNewAddress({bitcoin.HDWallet? hd, String? label}) { BitcoinAddressRecord generateNewAddress({String label = ''}) {
final isHidden = hd == sideHd; final newAddressIndex = addressesByReceiveType.fold(
0, (int acc, addressRecord) => addressRecord.isHidden == false ? acc + 1 : acc);
final newAddressIndex = addresses.fold( final address = BitcoinAddressRecord(
0, (int acc, addressRecord) => isHidden == addressRecord.isHidden ? acc + 1 : acc); getAddress(index: newAddressIndex, hd: mainHd, addressType: addressPageType),
index: newAddressIndex,
final address = BitcoinAddressRecord(getAddress(index: newAddressIndex, hd: hd ?? sideHd), isHidden: false,
index: newAddressIndex, isHidden: isHidden, name: label ?? ''); name: label,
addresses.add(address); type: addressPageType,
network: network,
);
_addresses.add(address);
updateAddressesByMatch();
return address; return address;
} }
String getAddress({required int index, required bitcoin.HDWallet hd}) => ''; String getAddress(
{required int index, required bitcoin.HDWallet hd, BitcoinAddressType? addressType}) =>
'';
@override @override
Future<void> updateAddressesInBox() async { Future<void> updateAddressesInBox() async {
@ -187,126 +232,138 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
if (address.startsWith('bitcoincash:')) { if (address.startsWith('bitcoincash:')) {
address = toLegacy(address); address = toLegacy(address);
} }
final addressRecord = addresses.firstWhere((addressRecord) => addressRecord.address == address); final addressRecord =
_addresses.firstWhere((addressRecord) => addressRecord.address == address);
addressRecord.setNewName(label); addressRecord.setNewName(label);
final index = addresses.indexOf(addressRecord); final index = _addresses.indexOf(addressRecord);
addresses.remove(addressRecord); _addresses.remove(addressRecord);
addresses.insert(index, addressRecord); _addresses.insert(index, addressRecord);
}
@action
void updateAddressesByMatch() {
addressesByReceiveType.clear();
addressesByReceiveType.addAll(_addresses.where(_isAddressPageTypeMatch).toList());
} }
@action @action
void updateReceiveAddresses() { void updateReceiveAddresses() {
receiveAddresses.removeRange(0, receiveAddresses.length); receiveAddresses.removeRange(0, receiveAddresses.length);
final newAddresses = final newAddresses =
addresses.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed); _addresses.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed);
receiveAddresses.addAll(newAddresses); receiveAddresses.addAll(newAddresses);
} }
@action @action
void updateChangeAddresses() { void updateChangeAddresses() {
changeAddresses.removeRange(0, changeAddresses.length); changeAddresses.removeRange(0, changeAddresses.length);
final newAddresses = final newAddresses = _addresses.where((addressRecord) =>
addresses.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed); addressRecord.isHidden &&
!addressRecord.isUsed &&
// TODO: feature to change change address type. For now fixed to p2wpkh, the cheapest type
addressRecord.type == SegwitAddresType.p2wpkh);
changeAddresses.addAll(newAddresses); changeAddresses.addAll(newAddresses);
} }
Future<void> _discoverAddresses(bitcoin.HDWallet hd, bool isHidden) async { @action
var hasAddrUse = true; Future<void> discoverAddresses(List<BitcoinAddressRecord> addressList, bool isHidden,
List<BitcoinAddressRecord> addrs; Future<String?> Function(BitcoinAddressRecord, Set<String>) getAddressHistory,
{BitcoinAddressType type = SegwitAddresType.p2wpkh}) async {
if (addresses.isNotEmpty) { if (!isHidden) {
_validateSideHdAddresses(addressList.toList());
if(!isHidden) {
final receiveAddressesList = addresses.where((addr) => !addr.isHidden).toList();
validateSideHdAddresses(receiveAddressesList);
}
addrs = addresses.where((addr) => addr.isHidden == isHidden).toList();
} else {
addrs = await _createNewAddresses(
isHidden ? defaultChangeAddressesCount : defaultReceiveAddressesCount,
startIndex: 0,
hd: hd,
isHidden: isHidden);
} }
while (hasAddrUse) { final newAddresses = await _createNewAddresses(gap,
final addr = addrs.last.address; startIndex: addressList.length, isHidden: isHidden, type: type);
hasAddrUse = await _hasAddressUsed(addr); addAddresses(newAddresses);
if (!hasAddrUse) { final addressesWithHistory = await Future.wait(newAddresses
break; .map((addr) => getAddressHistory(addr, _addresses.map((e) => e.address).toSet())));
} final isLastAddressUsed = addressesWithHistory.last == addressList.last.address;
final start = addrs.length; if (isLastAddressUsed) {
final count = start + gap; discoverAddresses(addressList, isHidden, getAddressHistory, type: type);
final batch = await _createNewAddresses(count, startIndex: start, hd: hd, isHidden: isHidden);
addrs.addAll(batch);
}
if (addresses.length < addrs.length) {
_addAddresses(addrs);
} }
} }
Future<void> _generateInitialAddresses() async { Future<void> _generateInitialAddresses(
{BitcoinAddressType type = SegwitAddresType.p2wpkh}) async {
var countOfReceiveAddresses = 0; var countOfReceiveAddresses = 0;
var countOfHiddenAddresses = 0; var countOfHiddenAddresses = 0;
addresses.forEach((addr) { _addresses.forEach((addr) {
if (addr.isHidden) { if (addr.type == type) {
countOfHiddenAddresses += 1; if (addr.isHidden) {
return; countOfHiddenAddresses += 1;
} return;
}
countOfReceiveAddresses += 1; countOfReceiveAddresses += 1;
}
}); });
if (countOfReceiveAddresses < defaultReceiveAddressesCount) { if (countOfReceiveAddresses < defaultReceiveAddressesCount) {
final addressesCount = defaultReceiveAddressesCount - countOfReceiveAddresses; final addressesCount = defaultReceiveAddressesCount - countOfReceiveAddresses;
final newAddresses = await _createNewAddresses(addressesCount, final newAddresses = await _createNewAddresses(addressesCount,
startIndex: countOfReceiveAddresses, hd: mainHd, isHidden: false); startIndex: countOfReceiveAddresses, isHidden: false, type: type);
addresses.addAll(newAddresses); addAddresses(newAddresses);
} }
if (countOfHiddenAddresses < defaultChangeAddressesCount) { if (countOfHiddenAddresses < defaultChangeAddressesCount) {
final addressesCount = defaultChangeAddressesCount - countOfHiddenAddresses; final addressesCount = defaultChangeAddressesCount - countOfHiddenAddresses;
final newAddresses = await _createNewAddresses(addressesCount, final newAddresses = await _createNewAddresses(addressesCount,
startIndex: countOfHiddenAddresses, hd: sideHd, isHidden: true); startIndex: countOfHiddenAddresses, isHidden: true, type: type);
addresses.addAll(newAddresses); addAddresses(newAddresses);
} }
} }
Future<List<BitcoinAddressRecord>> _createNewAddresses(int count, Future<List<BitcoinAddressRecord>> _createNewAddresses(int count,
{required bitcoin.HDWallet hd, int startIndex = 0, bool isHidden = false}) async { {int startIndex = 0, bool isHidden = false, BitcoinAddressType? type}) async {
final list = <BitcoinAddressRecord>[]; final list = <BitcoinAddressRecord>[];
for (var i = startIndex; i < count + startIndex; i++) { for (var i = startIndex; i < count + startIndex; i++) {
final address = final address = BitcoinAddressRecord(
BitcoinAddressRecord(getAddress(index: i, hd: hd), index: i, isHidden: isHidden); getAddress(index: i, hd: _getHd(isHidden), addressType: type ?? addressPageType),
index: i,
isHidden: isHidden,
type: type ?? addressPageType,
network: network,
);
list.add(address); list.add(address);
} }
return list; return list;
} }
void _addAddresses(Iterable<BitcoinAddressRecord> addresses) { @action
final addressesSet = this.addresses.toSet(); void addAddresses(Iterable<BitcoinAddressRecord> addresses) {
final addressesSet = this._addresses.toSet();
addressesSet.addAll(addresses); addressesSet.addAll(addresses);
this.addresses.removeRange(0, this.addresses.length); this._addresses.clear();
this.addresses.addAll(addressesSet); this._addresses.addAll(addressesSet);
updateAddressesByMatch();
} }
Future<bool> _hasAddressUsed(String address) async { void _validateSideHdAddresses(List<BitcoinAddressRecord> addrWithTransactions) {
final sh = scriptHash(address, networkType: networkType);
final transactionHistory = await electrumClient.getHistory(sh);
return transactionHistory.isNotEmpty;
}
void validateSideHdAddresses(List<BitcoinAddressRecord> addrWithTransactions) {
addrWithTransactions.forEach((element) { addrWithTransactions.forEach((element) {
if (element.address != getAddress(index: element.index, hd: mainHd)) element.isHidden = true; if (element.address !=
getAddress(index: element.index, hd: mainHd, addressType: element.type))
element.isHidden = true;
}); });
} }
@action
Future<void> setAddressType(BitcoinAddressType type) async {
_addressPageType = type;
updateAddressesByMatch();
walletInfo.addressPageType = addressPageType.toString();
await walletInfo.save();
}
bool _isAddressPageTypeMatch(BitcoinAddressRecord addressRecord) {
return _isAddressByType(addressRecord, addressPageType);
}
bitcoin.HDWallet _getHd(bool isHidden) => isHidden ? sideHd : mainHd;
bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type;
} }

View file

@ -1,12 +1,13 @@
import 'dart:convert'; import 'dart:convert';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum_balance.dart'; import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/utils/file.dart'; import 'package:cw_core/utils/file.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
class ElectrumWallletSnapshot { class ElectrumWalletSnapshot {
ElectrumWallletSnapshot({ ElectrumWalletSnapshot({
required this.name, required this.name,
required this.type, required this.type,
required this.password, required this.password,
@ -14,19 +15,24 @@ class ElectrumWallletSnapshot {
required this.addresses, required this.addresses,
required this.balance, required this.balance,
required this.regularAddressIndex, required this.regularAddressIndex,
required this.changeAddressIndex}); required this.changeAddressIndex,
required this.addressPageType,
required this.network,
});
final String name; final String name;
final String password; final String password;
final WalletType type; final WalletType type;
final String addressPageType;
final BasedUtxoNetwork network;
String mnemonic; String mnemonic;
List<BitcoinAddressRecord> addresses; List<BitcoinAddressRecord> addresses;
ElectrumBalance balance; ElectrumBalance balance;
int regularAddressIndex; Map<String, int> regularAddressIndex;
int changeAddressIndex; Map<String, int> changeAddressIndex;
static Future<ElectrumWallletSnapshot> load(String name, WalletType type, String password) async { static Future<ElectrumWalletSnapshot> load(String name, WalletType type, String password, BasedUtxoNetwork? network) async {
final path = await pathForWallet(name: name, type: type); final path = await pathForWallet(name: name, type: type);
final jsonSource = await read(path: path, password: password); final jsonSource = await read(path: path, password: password);
final data = json.decode(jsonSource) as Map; final data = json.decode(jsonSource) as Map;
@ -34,26 +40,39 @@ class ElectrumWallletSnapshot {
final mnemonic = data['mnemonic'] as String; final mnemonic = data['mnemonic'] as String;
final addresses = addressesTmp final addresses = addressesTmp
.whereType<String>() .whereType<String>()
.map((addr) => BitcoinAddressRecord.fromJSON(addr)) .map((addr) => BitcoinAddressRecord.fromJSON(addr, network))
.toList(); .toList();
final balance = ElectrumBalance.fromJSON(data['balance'] as String) ?? final balance = ElectrumBalance.fromJSON(data['balance'] as String) ??
ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0); ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
var regularAddressIndex = 0; var regularAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
var changeAddressIndex = 0; var changeAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
try { try {
regularAddressIndex = int.parse(data['account_index'] as String? ?? '0'); regularAddressIndexByType = {
changeAddressIndex = int.parse(data['change_address_index'] as String? ?? '0'); SegwitAddresType.p2wpkh.toString(): int.parse(data['account_index'] as String? ?? '0')
} catch (_) {} };
changeAddressIndexByType = {
SegwitAddresType.p2wpkh.toString():
int.parse(data['change_address_index'] as String? ?? '0')
};
} catch (_) {
try {
regularAddressIndexByType = data["account_index"] as Map<String, int>? ?? {};
changeAddressIndexByType = data["change_address_index"] as Map<String, int>? ?? {};
} catch (_) {}
}
return ElectrumWallletSnapshot( return ElectrumWalletSnapshot(
name: name, name: name,
type: type, type: type,
password: password, password: password,
mnemonic: mnemonic, mnemonic: mnemonic,
addresses: addresses, addresses: addresses,
balance: balance, balance: balance,
regularAddressIndex: regularAddressIndex, regularAddressIndex: regularAddressIndexByType,
changeAddressIndex: changeAddressIndex); changeAddressIndex: changeAddressIndexByType,
addressPageType: data['address_page_type'] as String? ?? SegwitAddresType.p2wpkh.toString(),
network: data['network_type'] == 'testnet' ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet,
);
} }
} }

View file

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

View file

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

View file

@ -25,7 +25,7 @@ class LitecoinWalletService extends WalletService<
WalletType getType() => WalletType.litecoin; WalletType getType() => WalletType.litecoin;
@override @override
Future<LitecoinWallet> create(BitcoinNewWalletCredentials credentials) async { Future<LitecoinWallet> create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async {
final wallet = await LitecoinWalletBase.create( final wallet = await LitecoinWalletBase.create(
mnemonic: await generateMnemonic(), mnemonic: await generateMnemonic(),
password: credentials.password!, password: credentials.password!,
@ -94,12 +94,12 @@ class LitecoinWalletService extends WalletService<
@override @override
Future<LitecoinWallet> restoreFromKeys( Future<LitecoinWallet> restoreFromKeys(
BitcoinRestoreWalletFromWIFCredentials credentials) async => BitcoinRestoreWalletFromWIFCredentials credentials, {bool? isTestnet}) async =>
throw UnimplementedError(); throw UnimplementedError();
@override @override
Future<LitecoinWallet> restoreFromSeed( Future<LitecoinWallet> restoreFromSeed(
BitcoinRestoreWalletFromSeedCredentials credentials) async { BitcoinRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async {
if (!validateMnemonic(credentials.mnemonic)) { if (!validateMnemonic(credentials.mnemonic)) {
throw BitcoinMnemonicIsIncorrectException(); throw BitcoinMnemonicIsIncorrectException();
} }

View file

@ -1,5 +1,5 @@
import 'package:cw_bitcoin/bitcoin_commit_transaction_exception.dart'; import 'package:cw_bitcoin/bitcoin_commit_transaction_exception.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/pending_transaction.dart';
import 'package:cw_bitcoin/electrum.dart'; import 'package:cw_bitcoin/electrum.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart'; import 'package:cw_bitcoin/bitcoin_amount_format.dart';
@ -9,22 +9,21 @@ import 'package:cw_core/wallet_type.dart';
class PendingBitcoinTransaction with PendingTransaction { class PendingBitcoinTransaction with PendingTransaction {
PendingBitcoinTransaction(this._tx, this.type, PendingBitcoinTransaction(this._tx, this.type,
{required this.electrumClient, {required this.electrumClient, required this.amount, required this.fee, this.network})
required this.amount,
required this.fee})
: _listeners = <void Function(ElectrumTransactionInfo transaction)>[]; : _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
final WalletType type; final WalletType type;
final bitcoin.Transaction _tx; final BtcTransaction _tx;
final ElectrumClient electrumClient; final ElectrumClient electrumClient;
final int amount; final int amount;
final int fee; final int fee;
final BasedUtxoNetwork? network;
@override @override
String get id => _tx.getId(); String get id => _tx.txId();
@override @override
String get hex => _tx.toHex(); String get hex => _tx.serialize();
@override @override
String get amountFormatted => bitcoinAmountToString(amount: amount); String get amountFormatted => bitcoinAmountToString(amount: amount);
@ -36,18 +35,16 @@ class PendingBitcoinTransaction with PendingTransaction {
@override @override
Future<void> commit() async { Future<void> commit() async {
final result = final result = await electrumClient.broadcastTransaction(transactionRaw: hex, network: network);
await electrumClient.broadcastTransaction(transactionRaw: _tx.toHex());
if (result.isEmpty) { if (result.isEmpty) {
throw BitcoinCommitTransactionException(); throw BitcoinCommitTransactionException();
} }
_listeners?.forEach((listener) => listener(transactionInfo())); _listeners.forEach((listener) => listener(transactionInfo()));
} }
void addListener( void addListener(void Function(ElectrumTransactionInfo transaction) listener) =>
void Function(ElectrumTransactionInfo transaction) listener) =>
_listeners.add(listener); _listeners.add(listener);
ElectrumTransactionInfo transactionInfo() => ElectrumTransactionInfo(type, ElectrumTransactionInfo transactionInfo() => ElectrumTransactionInfo(type,

View file

@ -1,9 +1,8 @@
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.dart';
String scriptHash(String address, {required bitcoin.NetworkType networkType}) { String scriptHash(String address, {required BasedUtxoNetwork network}) {
final outputScript = final outputScript = addressToOutputScript(address: address, network: network);
bitcoin.Address.addressToOutputScript(address, networkType);
final parts = sha256.convert(outputScript).toString().split(''); final parts = sha256.convert(outputScript).toString().split('');
var res = ''; var res = '';

View file

@ -1,55 +1,33 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData; import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
import 'package:hex/hex.dart'; import 'package:hex/hex.dart';
bitcoin.PaymentData generatePaymentData( bitcoin.PaymentData generatePaymentData({required bitcoin.HDWallet hd, required int index}) =>
{required bitcoin.HDWallet hd, required int index}) => PaymentData(pubkey: Uint8List.fromList(HEX.decode(hd.derive(index).pubKey!)));
PaymentData(
pubkey: Uint8List.fromList(HEX.decode(hd.derive(index).pubKey!)));
bitcoin.ECPair generateKeyPair( ECPrivate generateECPrivate(
{required bitcoin.HDWallet hd, {required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
required int index, ECPrivate.fromWif(hd.derive(index).wif!, netVersion: network.wifNetVer);
required bitcoin.NetworkType network}) =>
bitcoin.ECPair.fromWIF(hd.derive(index).wif!, network: network);
String generateP2WPKHAddress( String generateP2WPKHAddress(
{required bitcoin.HDWallet hd, {required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
required int index, ECPublic.fromHex(hd.derive(index).pubKey!).toP2wpkhAddress().toAddress(network);
required bitcoin.NetworkType networkType}) =>
bitcoin
.P2WPKH(
data: PaymentData(
pubkey:
Uint8List.fromList(HEX.decode(hd.derive(index).pubKey!))),
network: networkType)
.data
.address!;
String generateP2WPKHAddressByPath( String generateP2SHAddress(
{required bitcoin.HDWallet hd, {required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
required String path, ECPublic.fromHex(hd.derive(index).pubKey!).toP2wpkhInP2sh().toAddress(network);
required bitcoin.NetworkType networkType}) =>
bitcoin String generateP2WSHAddress(
.P2WPKH( {required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
data: PaymentData( ECPublic.fromHex(hd.derive(index).pubKey!).toP2wshAddress().toAddress(network);
pubkey:
Uint8List.fromList(HEX.decode(hd.derivePath(path).pubKey!))),
network: networkType)
.data
.address!;
String generateP2PKHAddress( String generateP2PKHAddress(
{required bitcoin.HDWallet hd, {required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
required int index, ECPublic.fromHex(hd.derive(index).pubKey!).toP2pkhAddress().toAddress(network);
required bitcoin.NetworkType networkType}) =>
bitcoin String generateP2TRAddress(
.P2PKH( {required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
data: PaymentData( ECPublic.fromHex(hd.derive(index).pubKey!).toTaprootAddress().toAddress(network);
pubkey:
Uint8List.fromList(HEX.decode(hd.derive(index).pubKey!))),
network: networkType)
.data
.address!;

View file

@ -21,18 +21,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: args name: args
sha256: "139d809800a412ebb26a3892da228b2d0ba36f0ef5d9a82166e5e52ec8d61611" sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.2" version: "2.4.2"
asn1lib: asn1lib:
dependency: transitive dependency: transitive
description: description:
name: asn1lib name: asn1lib
sha256: ab96a1cb3beeccf8145c52e449233fe68364c9641623acd3adad66f8184f1039 sha256: c9c85fedbe2188b95133cbe960e16f5f448860f7133330e272edbbca5893ddc6
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.5.2"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -75,6 +75,15 @@ packages:
url: "https://github.com/cake-tech/bitbox-flutter.git" url: "https://github.com/cake-tech/bitbox-flutter.git"
source: git source: git
version: "1.0.1" version: "1.0.1"
bitcoin_base:
dependency: "direct main"
description:
path: "."
ref: cake-update-v1
resolved-ref: "9611e9db77e92a8434e918cdfb620068f6fcb1aa"
url: "https://github.com/cake-tech/bitcoin_base.git"
source: git
version: "4.0.0"
bitcoin_flutter: bitcoin_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
@ -84,6 +93,14 @@ packages:
url: "https://github.com/cake-tech/bitcoin_flutter.git" url: "https://github.com/cake-tech/bitcoin_flutter.git"
source: git source: git
version: "2.1.0" version: "2.1.0"
blockchain_utils:
dependency: "direct main"
description:
name: blockchain_utils
sha256: "9701dfaa74caad4daae1785f1ec4445cf7fb94e45620bc3a4aca1b9b281dc6c9"
url: "https://pub.dev"
source: hosted
version: "1.6.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -104,10 +121,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: build name: build
sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.1" version: "2.4.1"
build_config: build_config:
dependency: transitive dependency: transitive
description: description:
@ -120,10 +137,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: build_daemon name: build_daemon
sha256: "6bc5544ea6ce4428266e7ea680e945c68806c4aae2da0eb5e9ccf38df8d6acbf" sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.0" version: "4.0.1"
build_resolvers: build_resolvers:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -136,18 +153,18 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727 sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.3" version: "2.4.8"
build_runner_core: build_runner_core:
dependency: transitive dependency: transitive
description: description:
name: build_runner_core name: build_runner_core
sha256: "14febe0f5bac5ae474117a36099b4de6f1dbc52df6c5e55534b3da9591bf4292" sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.2.7" version: "7.2.10"
built_collection: built_collection:
dependency: transitive dependency: transitive
description: description:
@ -160,10 +177,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: built_value name: built_value
sha256: "169565c8ad06adb760c3645bf71f00bff161b00002cace266cad42c5d22a7725" sha256: a3ec2e0f967bc47f69f95009bb93db936288d61d5343b9436e378b28a2f830c6
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.4.3" version: "8.9.0"
characters: characters:
dependency: transitive dependency: transitive
description: description:
@ -176,10 +193,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: checked_yaml name: checked_yaml
sha256: "3d1505d91afa809d177efd4eed5bb0eb65805097a1463abdd2add076effae311" sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.2" version: "2.0.3"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@ -192,10 +209,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: code_builder name: code_builder
sha256: "0d43dd1288fd145de1ecc9a3948ad4a6d5a82f0a14c4fdd0892260787d975cbe" sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.4.0" version: "4.10.0"
collection: collection:
dependency: transitive dependency: transitive
description: description:
@ -216,18 +233,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: crypto name: crypto
sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.2" version: "3.0.3"
cryptography: cryptography:
dependency: "direct main" dependency: "direct main"
description: description:
name: cryptography name: cryptography
sha256: e0e37f79665cd5c86e8897f9abe1accfe813c0cc5299dab22256e22fddc1fef8 sha256: df156c5109286340817d21fa7b62f9140f17915077127dd70f8bd7a2a0997a35
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.5" version: "2.5.0"
cw_core: cw_core:
dependency: "direct main" dependency: "direct main"
description: description:
@ -247,10 +264,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: encrypt name: encrypt
sha256: "4fd4e4fdc21b9d7d4141823e1e6515cd94e7b8d84749504c232999fba25d9bbb" sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.1" version: "5.0.3"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -263,10 +280,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: ffi name: ffi
sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978 sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "2.1.0"
file: file:
dependency: transitive dependency: transitive
description: description:
@ -292,10 +309,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_mobx name: flutter_mobx
sha256: "0da4add0016387a7bf309a0d0c41d36c6b3ae25ed7a176409267f166509e723e" sha256: "4a5d062ff85ed3759f4aac6410ff0ffae32e324b2e71ca722ae1b37b32e865f4"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.6+5" version: "2.2.0+2"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -313,18 +330,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: glob name: glob
sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c" sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.2"
graphs: graphs:
dependency: transitive dependency: transitive
description: description:
name: graphs name: graphs
sha256: f9e130f3259f52d26f0cfc0e964513796dafed572fa52e45d2f8d6ca14db39b2 sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.3.1"
hex: hex:
dependency: transitive dependency: transitive
description: description:
@ -401,18 +418,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: json_annotation name: json_annotation
sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317 sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.8.0" version: "4.8.1"
logging: logging:
dependency: transitive dependency: transitive
description: description:
name: logging name: logging
sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d" sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" version: "1.2.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@ -449,18 +466,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: mobx name: mobx
sha256: f1862bd92c6a903fab67338f27e2f731117c3cb9ea37cee1a487f9e4e0de314a sha256: "74ee54012dc7c1b3276eaa960a600a7418ef5f9997565deb8fca1fd88fb36b78"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.3+1" version: "2.3.0+1"
mobx_codegen: mobx_codegen:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: mobx_codegen name: mobx_codegen
sha256: "86122e410d8ea24dda0c69adb5c2a6ccadd5ce02ad46e144764e0d0184a06181" sha256: d4beb9cea4b7b014321235f8fdc7c2193ee0fe1d1198e9da7403f8bc85c4407c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.3.0"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
package_config: package_config:
dependency: transitive dependency: transitive
description: description:
@ -481,26 +506,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: path_provider name: path_provider
sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.2"
path_provider_android: path_provider_android:
dependency: transitive dependency: transitive
description: description:
name: path_provider_android name: path_provider_android
sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.1" version: "2.2.2"
path_provider_foundation: path_provider_foundation:
dependency: transitive dependency: transitive
description: description:
name: path_provider_foundation name: path_provider_foundation
sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.1" version: "2.3.2"
path_provider_linux: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
@ -513,10 +538,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path_provider_platform_interface name: path_provider_platform_interface
sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.2"
path_provider_windows: path_provider_windows:
dependency: transitive dependency: transitive
description: description:
@ -529,26 +554,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: platform name: platform
sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.0" version: "3.1.4"
plugin_platform_interface: plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: plugin_platform_interface name: plugin_platform_interface
sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.3" version: "2.1.8"
pointycastle: pointycastle:
dependency: transitive dependency: transitive
description: description:
name: pointycastle name: pointycastle
sha256: db7306cf0249f838d1a24af52b5a5887c5bf7f31d8bb4e827d071dc0939ad346 sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.6.2" version: "3.7.4"
pool: pool:
dependency: transitive dependency: transitive
description: description:
@ -557,30 +582,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.5.1" version: "1.5.1"
process: provider:
dependency: transitive dependency: transitive
description: description:
name: process name: provider
sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.2.4" version: "6.1.1"
pub_semver: pub_semver:
dependency: transitive dependency: transitive
description: description:
name: pub_semver name: pub_semver
sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17" sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.3" version: "2.1.4"
pubspec_parse: pubspec_parse:
dependency: transitive dependency: transitive
description: description:
name: pubspec_parse name: pubspec_parse
sha256: "75f6614d6dde2dc68948dffbaa4fe5dae32cd700eb9fb763fe11dfb45a3c4d0a" sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.1" version: "1.2.3"
rxdart: rxdart:
dependency: "direct main" dependency: "direct main"
description: description:
@ -593,18 +618,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shelf name: shelf
sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.4.1"
shelf_web_socket: shelf_web_socket:
dependency: transitive dependency: transitive
description: description:
name: shelf_web_socket name: shelf_web_socket
sha256: a988c0e8d8ffbdb8a28aa7ec8e449c260f3deb808781fe1284d22c5bba7156e8 sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.3" version: "1.0.4"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -702,10 +727,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: typed_data name: typed_data
sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.1" version: "1.3.2"
unorm_dart: unorm_dart:
dependency: "direct main" dependency: "direct main"
description: description:
@ -726,42 +751,42 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: watcher name: watcher
sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.2" version: "1.1.0"
web_socket_channel: web_socket_channel:
dependency: transitive dependency: transitive
description: description:
name: web_socket_channel name: web_socket_channel
sha256: ca49c0bc209c687b887f30527fb6a9d80040b072cc2990f34b9bec3e7663101b sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.4.0"
win32: win32:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46 sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.3" version: "5.0.9"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
name: xdg_directories name: xdg_directories
sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86 sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.0+3" version: "1.0.4"
yaml: yaml:
dependency: transitive dependency: transitive
description: description:
name: yaml name: yaml
sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370" sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.1" version: "3.1.2"
sdks: sdks:
dart: ">=3.0.0 <4.0.0" dart: ">=3.0.0 <4.0.0"
flutter: ">=3.7.0" flutter: ">=3.10.0"

View file

@ -30,6 +30,11 @@ dependencies:
rxdart: ^0.27.5 rxdart: ^0.27.5
unorm_dart: ^0.2.0 unorm_dart: ^0.2.0
cryptography: ^2.0.5 cryptography: ^2.0.5
bitcoin_base:
git:
url: https://github.com/cake-tech/bitcoin_base.git
ref: cake-update-v1
blockchain_utils: ^1.6.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View file

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

View file

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

View file

@ -2,10 +2,7 @@ import 'dart:io';
import 'package:bip39/bip39.dart'; import 'package:bip39/bip39.dart';
import 'package:cw_bitcoin_cash/cw_bitcoin_cash.dart'; import 'package:cw_bitcoin_cash/cw_bitcoin_cash.dart';
import 'package:cw_core/balance.dart';
import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
@ -15,8 +12,7 @@ import 'package:collection/collection.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredentials, class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredentials,
BitcoinCashRestoreWalletFromSeedCredentials, BitcoinCashRestoreWalletFromSeedCredentials, BitcoinCashRestoreWalletFromWIFCredentials> {
BitcoinCashRestoreWalletFromWIFCredentials> {
BitcoinCashWalletService(this.walletInfoSource, this.unspentCoinsInfoSource); BitcoinCashWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
final Box<WalletInfo> walletInfoSource; final Box<WalletInfo> walletInfoSource;
@ -30,13 +26,9 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
File(await pathForWallet(name: name, type: getType())).existsSync(); File(await pathForWallet(name: name, type: getType())).existsSync();
@override @override
Future<BitcoinCashWallet> create( Future<BitcoinCashWallet> create(credentials, {bool? isTestnet}) async {
credentials) async { final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
final strength = (credentials.seedPhraseLength == 12)
? 128
: (credentials.seedPhraseLength == 24)
? 256
: 128;
final wallet = await BitcoinCashWalletBase.create( final wallet = await BitcoinCashWalletBase.create(
mnemonic: await Mnemonic.generate(strength: strength), mnemonic: await Mnemonic.generate(strength: strength),
password: credentials.password!, password: credentials.password!,
@ -49,21 +41,25 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
@override @override
Future<BitcoinCashWallet> openWallet(String name, String password) async { Future<BitcoinCashWallet> openWallet(String name, String password) async {
final walletInfo = walletInfoSource.values.firstWhereOrNull( final walletInfo = walletInfoSource.values
(info) => info.id == WalletBase.idFor(name, getType()))!; .firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
try { try {
final wallet = await BitcoinCashWalletBase.open( final wallet = await BitcoinCashWalletBase.open(
password: password, name: name, walletInfo: walletInfo, password: password,
name: name,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource); unspentCoinsInfo: unspentCoinsInfoSource);
await wallet.init(); await wallet.init();
saveBackup(name); saveBackup(name);
return wallet; return wallet;
} catch(_) { } catch (_) {
await restoreWalletFilesFromBackup(name); await restoreWalletFilesFromBackup(name);
final wallet = await BitcoinCashWalletBase.open( final wallet = await BitcoinCashWalletBase.open(
password: password, name: name, walletInfo: walletInfo, password: password,
unspentCoinsInfo: unspentCoinsInfoSource); name: name,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource);
await wallet.init(); await wallet.init();
return wallet; return wallet;
} }
@ -71,17 +67,16 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
@override @override
Future<void> remove(String wallet) async { Future<void> remove(String wallet) async {
File(await pathForWalletDir(name: wallet, type: getType())) File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
.delete(recursive: true); final walletInfo = walletInfoSource.values
final walletInfo = walletInfoSource.values.firstWhereOrNull( .firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!;
(info) => info.id == WalletBase.idFor(wallet, getType()))!;
await walletInfoSource.delete(walletInfo.key); await walletInfoSource.delete(walletInfo.key);
} }
@override @override
Future<void> rename(String currentName, String password, String newName) async { Future<void> rename(String currentName, String password, String newName) async {
final currentWalletInfo = walletInfoSource.values.firstWhereOrNull( final currentWalletInfo = walletInfoSource.values
(info) => info.id == WalletBase.idFor(currentName, getType()))!; .firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!;
final currentWallet = await BitcoinCashWalletBase.open( final currentWallet = await BitcoinCashWalletBase.open(
password: password, password: password,
name: currentName, name: currentName,
@ -99,15 +94,14 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
} }
@override @override
Future<BitcoinCashWallet> Future<BitcoinCashWallet> restoreFromKeys(credentials, {bool? isTestnet}) {
restoreFromKeys(credentials) {
// TODO: implement restoreFromKeys // TODO: implement restoreFromKeys
throw UnimplementedError('restoreFromKeys() is not implemented'); throw UnimplementedError('restoreFromKeys() is not implemented');
} }
@override @override
Future<BitcoinCashWallet> restoreFromSeed( Future<BitcoinCashWallet> restoreFromSeed(BitcoinCashRestoreWalletFromSeedCredentials credentials,
BitcoinCashRestoreWalletFromSeedCredentials credentials) async { {bool? isTestnet}) async {
if (!validateMnemonic(credentials.mnemonic)) { if (!validateMnemonic(credentials.mnemonic)) {
throw BitcoinCashMnemonicIsIncorrectException(); throw BitcoinCashMnemonicIsIncorrectException();
} }

View file

@ -29,7 +29,10 @@ dependencies:
git: git:
url: https://github.com/cake-tech/bitbox-flutter.git url: https://github.com/cake-tech/bitbox-flutter.git
ref: master ref: master
bitcoin_base: ^3.0.1 bitcoin_base:
git:
url: https://github.com/cake-tech/bitcoin_base.git
ref: cake-update-v1

View file

@ -0,0 +1,13 @@
abstract class Enumerate {
String get value;
@override
operator ==(other) {
if (identical(other, this)) return true;
if (other is! Enumerate) return false;
return other.runtimeType == runtimeType && value == other.value;
}
@override
int get hashCode => value.hashCode;
}

View file

@ -0,0 +1,21 @@
import 'package:cw_core/enumerate.dart';
class ReceivePageOption implements Enumerate {
static const mainnet = ReceivePageOption._('mainnet');
static const anonPayInvoice = ReceivePageOption._('anonPayInvoice');
static const anonPayDonationLink = ReceivePageOption._('anonPayDonationLink');
const ReceivePageOption._(this.value);
final String value;
String toString() {
return value;
}
}
const ReceivePageOptions = [
ReceivePageOption.mainnet,
ReceivePageOption.anonPayInvoice,
ReceivePageOption.anonPayDonationLink
];

View file

@ -88,4 +88,6 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
Future<void> renameWalletFiles(String newWalletName); Future<void> renameWalletFiles(String newWalletName);
String signMessage(String message, {String? address = null}) => throw UnimplementedError(); String signMessage(String message, {String? address = null}) => throw UnimplementedError();
bool? isTestnet;
} }

View file

@ -148,6 +148,12 @@ class WalletInfo extends HiveObject {
@HiveField(17) @HiveField(17)
String? derivationPath; String? derivationPath;
@HiveField(18)
String? addressPageType;
@HiveField(19)
String? network;
String get yatLastUsedAddress => yatLastUsedAddressRaw ?? ''; String get yatLastUsedAddress => yatLastUsedAddressRaw ?? '';
set yatLastUsedAddress(String address) { set yatLastUsedAddress(String address) {

View file

@ -9,11 +9,11 @@ abstract class WalletService<N extends WalletCredentials, RFS extends WalletCred
RFK extends WalletCredentials> { RFK extends WalletCredentials> {
WalletType getType(); WalletType getType();
Future<WalletBase> create(N credentials); Future<WalletBase> create(N credentials, {bool? isTestnet});
Future<WalletBase> restoreFromSeed(RFS credentials); Future<WalletBase> restoreFromSeed(RFS credentials, {bool? isTestnet});
Future<WalletBase> restoreFromKeys(RFK credentials); Future<WalletBase> restoreFromKeys(RFK credentials, {bool? isTestnet});
Future<WalletBase> openWallet(String name, String password); Future<WalletBase> openWallet(String name, String password);

View file

@ -5,34 +5,34 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: _fe_analyzer_shared name: _fe_analyzer_shared
sha256: "4897882604d919befd350648c7f91926a9d5de99e67b455bf0917cc2362f4bb8" sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "47.0.0" version: "64.0.0"
analyzer: analyzer:
dependency: transitive dependency: transitive
description: description:
name: analyzer name: analyzer
sha256: "690e335554a8385bc9d787117d9eb52c0c03ee207a607e593de3c9d71b1cfe80" sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.7.0" version: "6.2.0"
args: args:
dependency: transitive dependency: transitive
description: description:
name: args name: args
sha256: "139d809800a412ebb26a3892da228b2d0ba36f0ef5d9a82166e5e52ec8d61611" sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.2" version: "2.4.2"
asn1lib: asn1lib:
dependency: transitive dependency: transitive
description: description:
name: asn1lib name: asn1lib
sha256: ab96a1cb3beeccf8145c52e449233fe68364c9641623acd3adad66f8184f1039 sha256: "21afe4333076c02877d14f4a89df111e658a6d466cbfc802eb705eb91bd5adfd"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.5.0"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -53,10 +53,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: build name: build
sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.1" version: "2.4.1"
build_config: build_config:
dependency: transitive dependency: transitive
description: description:
@ -69,34 +69,34 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: build_daemon name: build_daemon
sha256: "6bc5544ea6ce4428266e7ea680e945c68806c4aae2da0eb5e9ccf38df8d6acbf" sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.0" version: "4.0.1"
build_resolvers: build_resolvers:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_resolvers name: build_resolvers
sha256: "687cf90a3951affac1bd5f9ecb5e3e90b60487f3d9cdc359bb310f8876bb02a6" sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.10" version: "2.4.2"
build_runner: build_runner:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727 sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.3" version: "2.4.8"
build_runner_core: build_runner_core:
dependency: transitive dependency: transitive
description: description:
name: build_runner_core name: build_runner_core
sha256: "14febe0f5bac5ae474117a36099b4de6f1dbc52df6c5e55534b3da9591bf4292" sha256: c9e32d21dd6626b5c163d48b037ce906bbe428bc23ab77bcd77bb21e593b6185
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.2.7" version: "7.2.11"
built_collection: built_collection:
dependency: transitive dependency: transitive
description: description:
@ -109,10 +109,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: built_value name: built_value
sha256: "169565c8ad06adb760c3645bf71f00bff161b00002cace266cad42c5d22a7725" sha256: c9aabae0718ec394e5bc3c7272e6bb0dc0b32201a08fe185ec1d8401d3e39309
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.4.3" version: "8.8.1"
characters: characters:
dependency: transitive dependency: transitive
description: description:
@ -125,10 +125,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: checked_yaml name: checked_yaml
sha256: "3d1505d91afa809d177efd4eed5bb0eb65805097a1463abdd2add076effae311" sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.2" version: "2.0.3"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@ -141,10 +141,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: code_builder name: code_builder
sha256: "0d43dd1288fd145de1ecc9a3948ad4a6d5a82f0a14c4fdd0892260787d975cbe" sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.4.0" version: "4.10.0"
collection: collection:
dependency: transitive dependency: transitive
description: description:
@ -165,26 +165,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: crypto name: crypto
sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.2" version: "3.0.3"
dart_style: dart_style:
dependency: transitive dependency: transitive
description: description:
name: dart_style name: dart_style
sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4" sha256: "40ae61a5d43feea6d24bd22c0537a6629db858963b99b4bc1c3db80676f32368"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.4" version: "2.3.4"
encrypt: encrypt:
dependency: "direct main" dependency: "direct main"
description: description:
name: encrypt name: encrypt
sha256: "4fd4e4fdc21b9d7d4141823e1e6515cd94e7b8d84749504c232999fba25d9bbb" sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.1" version: "5.0.3"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -197,10 +197,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: ffi name: ffi
sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978 sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "2.1.0"
file: file:
dependency: "direct main" dependency: "direct main"
description: description:
@ -226,10 +226,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_mobx name: flutter_mobx
sha256: "0da4add0016387a7bf309a0d0c41d36c6b3ae25ed7a176409267f166509e723e" sha256: "4a5d062ff85ed3759f4aac6410ff0ffae32e324b2e71ca722ae1b37b32e865f4"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.6+5" version: "2.2.0+2"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -247,18 +247,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: glob name: glob
sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c" sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.2"
graphs: graphs:
dependency: transitive dependency: transitive
description: description:
name: graphs name: graphs
sha256: f9e130f3259f52d26f0cfc0e964513796dafed572fa52e45d2f8d6ca14db39b2 sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.3.1"
hive: hive:
dependency: transitive dependency: transitive
description: description:
@ -327,18 +327,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: json_annotation name: json_annotation
sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317 sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.8.0" version: "4.8.1"
logging: logging:
dependency: transitive dependency: transitive
description: description:
name: logging name: logging
sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d" sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" version: "1.2.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@ -375,18 +375,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: mobx name: mobx
sha256: f1862bd92c6a903fab67338f27e2f731117c3cb9ea37cee1a487f9e4e0de314a sha256: "74ee54012dc7c1b3276eaa960a600a7418ef5f9997565deb8fca1fd88fb36b78"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.3+1" version: "2.3.0+1"
mobx_codegen: mobx_codegen:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: mobx_codegen name: mobx_codegen
sha256: "86122e410d8ea24dda0c69adb5c2a6ccadd5ce02ad46e144764e0d0184a06181" sha256: b26c7f9c20b38f0ea572c1ed3f29d8e027cb265538bbd1aed3ec198642cfca42
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.6.0+1"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
package_config: package_config:
dependency: transitive dependency: transitive
description: description:
@ -407,26 +415,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: path_provider name: path_provider
sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.2"
path_provider_android: path_provider_android:
dependency: transitive dependency: transitive
description: description:
name: path_provider_android name: path_provider_android
sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.1" version: "2.2.2"
path_provider_foundation: path_provider_foundation:
dependency: transitive dependency: transitive
description: description:
name: path_provider_foundation name: path_provider_foundation
sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.1" version: "2.3.2"
path_provider_linux: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
@ -439,10 +447,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path_provider_platform_interface name: path_provider_platform_interface
sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.2"
path_provider_windows: path_provider_windows:
dependency: transitive dependency: transitive
description: description:
@ -455,26 +463,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: platform name: platform
sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.0" version: "3.1.4"
plugin_platform_interface: plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: plugin_platform_interface name: plugin_platform_interface
sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.3" version: "2.1.8"
pointycastle: pointycastle:
dependency: transitive dependency: transitive
description: description:
name: pointycastle name: pointycastle
sha256: db7306cf0249f838d1a24af52b5a5887c5bf7f31d8bb4e827d071dc0939ad346 sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.6.2" version: "3.7.4"
pool: pool:
dependency: transitive dependency: transitive
description: description:
@ -483,46 +491,46 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.5.1" version: "1.5.1"
process: provider:
dependency: transitive dependency: transitive
description: description:
name: process name: provider
sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.2.4" version: "6.1.1"
pub_semver: pub_semver:
dependency: transitive dependency: transitive
description: description:
name: pub_semver name: pub_semver
sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17" sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.3" version: "2.1.4"
pubspec_parse: pubspec_parse:
dependency: transitive dependency: transitive
description: description:
name: pubspec_parse name: pubspec_parse
sha256: "75f6614d6dde2dc68948dffbaa4fe5dae32cd700eb9fb763fe11dfb45a3c4d0a" sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.1" version: "1.2.3"
shelf: shelf:
dependency: transitive dependency: transitive
description: description:
name: shelf name: shelf
sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.4.1"
shelf_web_socket: shelf_web_socket:
dependency: transitive dependency: transitive
description: description:
name: shelf_web_socket name: shelf_web_socket
sha256: a988c0e8d8ffbdb8a28aa7ec8e449c260f3deb808781fe1284d22c5bba7156e8 sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.3" version: "1.0.4"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -540,18 +548,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: source_gen name: source_gen
sha256: "2d79738b6bbf38a43920e2b8d189e9a3ce6cc201f4b8fc76be5e4fe377b1c38d" sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.6" version: "1.5.0"
source_helper: source_helper:
dependency: transitive dependency: transitive
description: description:
name: source_helper name: source_helper
sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f" sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.3" version: "1.3.4"
source_span: source_span:
dependency: transitive dependency: transitive
description: description:
@ -620,10 +628,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: typed_data name: typed_data
sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.1" version: "1.3.2"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -636,42 +644,42 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: watcher name: watcher
sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.2" version: "1.1.0"
web_socket_channel: web_socket_channel:
dependency: transitive dependency: transitive
description: description:
name: web_socket_channel name: web_socket_channel
sha256: ca49c0bc209c687b887f30527fb6a9d80040b072cc2990f34b9bec3e7663101b sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.4.0"
win32: win32:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46 sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.3" version: "5.0.9"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
name: xdg_directories name: xdg_directories
sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86 sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.0+3" version: "1.0.4"
yaml: yaml:
dependency: transitive dependency: transitive
description: description:
name: yaml name: yaml
sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370" sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.1" version: "3.1.2"
sdks: sdks:
dart: ">=3.0.0 <4.0.0" dart: ">=3.0.0 <4.0.0"
flutter: ">=3.7.0" flutter: ">=3.10.0"

View file

@ -16,7 +16,7 @@ class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
WalletType getType() => WalletType.ethereum; WalletType getType() => WalletType.ethereum;
@override @override
Future<EthereumWallet> create(EVMChainNewWalletCredentials credentials) async { Future<EthereumWallet> create(EVMChainNewWalletCredentials credentials, {bool? isTestnet}) async {
final strength = credentials.seedPhraseLength == 24 ? 256 : 128; final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
final mnemonic = bip39.generateMnemonic(strength: strength); final mnemonic = bip39.generateMnemonic(strength: strength);
@ -52,7 +52,6 @@ class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
saveBackup(name); saveBackup(name);
return wallet; return wallet;
} catch (_) { } catch (_) {
await restoreWalletFilesFromBackup(name); await restoreWalletFilesFromBackup(name);
final wallet = await EthereumWallet.open( final wallet = await EthereumWallet.open(
@ -84,7 +83,8 @@ class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
} }
@override @override
Future<EthereumWallet> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials) async { Future<EthereumWallet> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials,
{bool? isTestnet}) async {
final wallet = EthereumWallet( final wallet = EthereumWallet(
password: credentials.password!, password: credentials.password!,
privateKey: credentials.privateKey, privateKey: credentials.privateKey,
@ -100,8 +100,8 @@ class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
} }
@override @override
Future<EthereumWallet> restoreFromSeed( Future<EthereumWallet> restoreFromSeed(EVMChainRestoreWalletFromSeedCredentials credentials,
EVMChainRestoreWalletFromSeedCredentials credentials) async { {bool? isTestnet}) async {
if (!bip39.validateMnemonic(credentials.mnemonic)) { if (!bip39.validateMnemonic(credentials.mnemonic)) {
throw EthereumMnemonicIsIncorrectException(); throw EthereumMnemonicIsIncorrectException();
} }

View file

@ -22,7 +22,7 @@ abstract class EVMChainWalletService<T extends EVMChainWallet> extends WalletSer
WalletType getType(); WalletType getType();
@override @override
Future<T> create(EVMChainNewWalletCredentials credentials); Future<T> create(EVMChainNewWalletCredentials credentials, {bool? isTestnet});
@override @override
Future<T> openWallet(String name, String password); Future<T> openWallet(String name, String password);
@ -31,10 +31,10 @@ abstract class EVMChainWalletService<T extends EVMChainWallet> extends WalletSer
Future<void> rename(String currentName, String password, String newName); Future<void> rename(String currentName, String password, String newName);
@override @override
Future<T> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials); Future<T> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials, {bool? isTestnet});
@override @override
Future<T> restoreFromSeed(EVMChainRestoreWalletFromSeedCredentials credentials); Future<T> restoreFromSeed(EVMChainRestoreWalletFromSeedCredentials credentials, {bool? isTestnet});
@override @override
Future<bool> isWalletExit(String name) async => Future<bool> isWalletExit(String name) async =>

View file

@ -68,7 +68,7 @@ class HavenWalletService extends WalletService<
WalletType getType() => WalletType.haven; WalletType getType() => WalletType.haven;
@override @override
Future<HavenWallet> create(HavenNewWalletCredentials credentials) async { Future<HavenWallet> create(HavenNewWalletCredentials credentials, {bool? isTestnet}) async {
try { try {
final path = await pathForWallet(name: credentials.name, type: getType()); final path = await pathForWallet(name: credentials.name, type: getType());
await haven_wallet_manager.createWallet( await haven_wallet_manager.createWallet(
@ -174,7 +174,7 @@ class HavenWalletService extends WalletService<
@override @override
Future<HavenWallet> restoreFromKeys( Future<HavenWallet> restoreFromKeys(
HavenRestoreWalletFromKeysCredentials credentials) async { HavenRestoreWalletFromKeysCredentials credentials, {bool? isTestnet}) async {
try { try {
final path = await pathForWallet(name: credentials.name, type: getType()); final path = await pathForWallet(name: credentials.name, type: getType());
await haven_wallet_manager.restoreFromKeys( await haven_wallet_manager.restoreFromKeys(
@ -198,7 +198,7 @@ class HavenWalletService extends WalletService<
@override @override
Future<HavenWallet> restoreFromSeed( Future<HavenWallet> restoreFromSeed(
HavenRestoreWalletFromSeedCredentials credentials) async { HavenRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async {
try { try {
final path = await pathForWallet(name: credentials.name, type: getType()); final path = await pathForWallet(name: credentials.name, type: getType());
await haven_wallet_manager.restoreFromSeed( await haven_wallet_manager.restoreFromSeed(

View file

@ -68,7 +68,7 @@ class MoneroWalletService extends WalletService<MoneroNewWalletCredentials,
WalletType getType() => WalletType.monero; WalletType getType() => WalletType.monero;
@override @override
Future<MoneroWallet> create(MoneroNewWalletCredentials credentials) async { Future<MoneroWallet> create(MoneroNewWalletCredentials credentials, {bool? isTestnet}) async {
try { try {
final path = await pathForWallet(name: credentials.name, type: getType()); final path = await pathForWallet(name: credentials.name, type: getType());
@ -203,7 +203,8 @@ class MoneroWalletService extends WalletService<MoneroNewWalletCredentials,
} }
@override @override
Future<MoneroWallet> restoreFromKeys(MoneroRestoreWalletFromKeysCredentials credentials) async { Future<MoneroWallet> restoreFromKeys(MoneroRestoreWalletFromKeysCredentials credentials,
{bool? isTestnet}) async {
try { try {
final path = await pathForWallet(name: credentials.name, type: getType()); final path = await pathForWallet(name: credentials.name, type: getType());
await monero_wallet_manager.restoreFromKeys( await monero_wallet_manager.restoreFromKeys(
@ -227,7 +228,8 @@ class MoneroWalletService extends WalletService<MoneroNewWalletCredentials,
} }
@override @override
Future<MoneroWallet> restoreFromSeed(MoneroRestoreWalletFromSeedCredentials credentials) async { Future<MoneroWallet> restoreFromSeed(MoneroRestoreWalletFromSeedCredentials credentials,
{bool? isTestnet}) async {
// Restore from Polyseed // Restore from Polyseed
if (Polyseed.isValidSeed(credentials.mnemonic)) { if (Polyseed.isValidSeed(credentials.mnemonic)) {
return restoreFromPolyseed(credentials); return restoreFromPolyseed(credentials);

View file

@ -26,7 +26,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
WalletType getType() => WalletType.nano; WalletType getType() => WalletType.nano;
@override @override
Future<WalletBase> create(NanoNewWalletCredentials credentials) async { Future<WalletBase> create(NanoNewWalletCredentials credentials, {bool? isTestnet}) async {
// nano standard: // nano standard:
DerivationType derivationType = DerivationType.nano; DerivationType derivationType = DerivationType.nano;
String seedKey = NanoSeeds.generateSeed(); String seedKey = NanoSeeds.generateSeed();
@ -79,7 +79,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
} }
@override @override
Future<NanoWallet> restoreFromKeys(NanoRestoreWalletFromKeysCredentials credentials) async { Future<NanoWallet> restoreFromKeys(NanoRestoreWalletFromKeysCredentials credentials, {bool? isTestnet}) async {
if (credentials.seedKey.contains(' ')) { if (credentials.seedKey.contains(' ')) {
throw Exception("Invalid key!"); throw Exception("Invalid key!");
} else { } else {
@ -113,7 +113,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
} }
@override @override
Future<NanoWallet> restoreFromSeed(NanoRestoreWalletFromSeedCredentials credentials) async { Future<NanoWallet> restoreFromSeed(NanoRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async {
if (credentials.mnemonic.contains(' ')) { if (credentials.mnemonic.contains(' ')) {
if (!bip39.validateMnemonic(credentials.mnemonic)) { if (!bip39.validateMnemonic(credentials.mnemonic)) {
throw nm.NanoMnemonicIsIncorrectException(); throw nm.NanoMnemonicIsIncorrectException();

View file

@ -19,7 +19,7 @@ class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
WalletType getType() => WalletType.polygon; WalletType getType() => WalletType.polygon;
@override @override
Future<PolygonWallet> create(EVMChainNewWalletCredentials credentials) async { Future<PolygonWallet> create(EVMChainNewWalletCredentials credentials, {bool? isTestnet}) async {
final strength = credentials.seedPhraseLength == 24 ? 256 : 128; final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
final mnemonic = bip39.generateMnemonic(strength: strength); final mnemonic = bip39.generateMnemonic(strength: strength);
@ -62,7 +62,7 @@ class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
password: password, password: password,
walletInfo: walletInfo, walletInfo: walletInfo,
); );
await wallet.init(); await wallet.init();
await wallet.save(); await wallet.save();
return wallet; return wallet;
@ -70,8 +70,8 @@ class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
} }
@override @override
Future<PolygonWallet> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials) async { Future<PolygonWallet> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials,
{bool? isTestnet}) async {
final wallet = PolygonWallet( final wallet = PolygonWallet(
password: credentials.password!, password: credentials.password!,
privateKey: credentials.privateKey, privateKey: credentials.privateKey,
@ -87,8 +87,8 @@ class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
} }
@override @override
Future<PolygonWallet> restoreFromSeed( Future<PolygonWallet> restoreFromSeed(EVMChainRestoreWalletFromSeedCredentials credentials,
EVMChainRestoreWalletFromSeedCredentials credentials) async { {bool? isTestnet}) async {
if (!bip39.validateMnemonic(credentials.mnemonic)) { if (!bip39.validateMnemonic(credentials.mnemonic)) {
throw PolygonMnemonicIsIncorrectException(); throw PolygonMnemonicIsIncorrectException();
} }

View file

@ -1,181 +1,191 @@
part of 'bitcoin.dart'; part of 'bitcoin.dart';
class CWBitcoin extends Bitcoin { class CWBitcoin extends Bitcoin {
@override @override
TransactionPriority getMediumTransactionPriority() => BitcoinTransactionPriority.medium; TransactionPriority getMediumTransactionPriority() => BitcoinTransactionPriority.medium;
@override
WalletCredentials createBitcoinRestoreWalletFromSeedCredentials({
required String name,
required String mnemonic,
required String password})
=> BitcoinRestoreWalletFromSeedCredentials(name: name, mnemonic: mnemonic, password: password);
@override
WalletCredentials createBitcoinRestoreWalletFromWIFCredentials({
required String name,
required String password,
required String wif,
WalletInfo? walletInfo})
=> BitcoinRestoreWalletFromWIFCredentials(name: name, password: password, wif: wif, walletInfo: walletInfo);
@override
WalletCredentials createBitcoinNewWalletCredentials({
required String name,
WalletInfo? walletInfo})
=> BitcoinNewWalletCredentials(name: name, walletInfo: walletInfo);
@override
List<String> getWordList() => wordlist;
@override
Map<String, String> getWalletKeys(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
final keys = bitcoinWallet.keys;
return <String, String>{
'wif': keys.wif,
'privateKey': keys.privateKey,
'publicKey': keys.publicKey
};
}
@override
List<TransactionPriority> getTransactionPriorities()
=> BitcoinTransactionPriority.all;
@override
List<TransactionPriority> getLitecoinTransactionPriorities()
=> LitecoinTransactionPriority.all;
@override
TransactionPriority deserializeBitcoinTransactionPriority(int raw)
=> BitcoinTransactionPriority.deserialize(raw: raw);
@override
TransactionPriority deserializeLitecoinTransactionPriority(int raw)
=> LitecoinTransactionPriority.deserialize(raw: raw);
@override
int getFeeRate(Object wallet, TransactionPriority priority) {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.feeRate(priority);
}
@override
Future<void> generateNewAddress(Object wallet, String label) async {
final bitcoinWallet = wallet as ElectrumWallet;
await bitcoinWallet.walletAddresses.generateNewAddress(label: label, hd: bitcoinWallet.hd);
await wallet.save();
}
@override
Future<void> updateAddress(Object wallet,String address, String label) async {
final bitcoinWallet = wallet as ElectrumWallet;
bitcoinWallet.walletAddresses.updateAddress(address, label);
await wallet.save();
}
@override
Object createBitcoinTransactionCredentials(List<Output> outputs, {required TransactionPriority priority, int? feeRate})
=> BitcoinTransactionCredentials(
outputs.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount))
.toList(),
priority: priority as BitcoinTransactionPriority,
feeRate: feeRate);
@override
Object createBitcoinTransactionCredentialsRaw(List<OutputInfo> outputs, {TransactionPriority? priority, required int feeRate})
=> BitcoinTransactionCredentials(
outputs,
priority: priority != null ? priority as BitcoinTransactionPriority : null,
feeRate: feeRate);
@override
List<String> getAddresses(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.walletAddresses.addresses
.map((BitcoinAddressRecord addr) => addr.address)
.toList();
}
@override
@computed
List<ElectrumSubAddress> getSubAddresses(Object wallet) {
final electrumWallet = wallet as ElectrumWallet;
return electrumWallet.walletAddresses.addresses
.map((BitcoinAddressRecord addr) => ElectrumSubAddress(
id: addr.index,
name: addr.name,
address: electrumWallet.type == WalletType.bitcoinCash ? addr.cashAddr : addr.address,
txCount: addr.txCount,
balance: addr.balance,
isChange: addr.isHidden))
.toList();
}
@override
String getAddress(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.walletAddresses.address;
}
@override
String formatterBitcoinAmountToString({required int amount})
=> bitcoinAmountToString(amount: amount);
@override
double formatterBitcoinAmountToDouble({required int amount})
=> bitcoinAmountToDouble(amount: amount);
@override
int formatterStringDoubleToBitcoinAmount(String amount)
=> stringDoubleToBitcoinAmount(amount);
@override @override
String bitcoinTransactionPriorityWithLabel(TransactionPriority priority, int rate) WalletCredentials createBitcoinRestoreWalletFromSeedCredentials(
=> (priority as BitcoinTransactionPriority).labelWithRate(rate); {required String name, required String mnemonic, required String password}) =>
BitcoinRestoreWalletFromSeedCredentials(name: name, mnemonic: mnemonic, password: password);
@override
List<BitcoinUnspent> getUnspents(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.unspentCoins;
}
Future<void> updateUnspents(Object wallet) async {
final bitcoinWallet = wallet as ElectrumWallet;
await bitcoinWallet.updateUnspent();
}
WalletService createBitcoinWalletService(Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource) {
return BitcoinWalletService(walletInfoSource, unspentCoinSource);
}
WalletService createLitecoinWalletService(Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource) {
return LitecoinWalletService(walletInfoSource, unspentCoinSource);
}
@override
TransactionPriority getBitcoinTransactionPriorityMedium()
=> BitcoinTransactionPriority.medium;
@override @override
TransactionPriority getLitecoinTransactionPriorityMedium() WalletCredentials createBitcoinRestoreWalletFromWIFCredentials(
=> LitecoinTransactionPriority.medium; {required String name,
required String password,
required String wif,
WalletInfo? walletInfo}) =>
BitcoinRestoreWalletFromWIFCredentials(
name: name, password: password, wif: wif, walletInfo: walletInfo);
@override @override
TransactionPriority getBitcoinTransactionPrioritySlow() WalletCredentials createBitcoinNewWalletCredentials(
=> BitcoinTransactionPriority.slow; {required String name, WalletInfo? walletInfo}) =>
BitcoinNewWalletCredentials(name: name, walletInfo: walletInfo);
@override @override
TransactionPriority getLitecoinTransactionPrioritySlow() List<String> getWordList() => wordlist;
=> LitecoinTransactionPriority.slow;
} @override
Map<String, String> getWalletKeys(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
final keys = bitcoinWallet.keys;
return <String, String>{
'wif': keys.wif,
'privateKey': keys.privateKey,
'publicKey': keys.publicKey
};
}
@override
List<TransactionPriority> getTransactionPriorities() => BitcoinTransactionPriority.all;
@override
List<TransactionPriority> getLitecoinTransactionPriorities() => LitecoinTransactionPriority.all;
@override
TransactionPriority deserializeBitcoinTransactionPriority(int raw) =>
BitcoinTransactionPriority.deserialize(raw: raw);
@override
TransactionPriority deserializeLitecoinTransactionPriority(int raw) =>
LitecoinTransactionPriority.deserialize(raw: raw);
@override
int getFeeRate(Object wallet, TransactionPriority priority) {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.feeRate(priority);
}
@override
Future<void> generateNewAddress(Object wallet, String label) async {
final bitcoinWallet = wallet as ElectrumWallet;
await bitcoinWallet.walletAddresses.generateNewAddress(label: label);
await wallet.save();
}
@override
Future<void> updateAddress(Object wallet, String address, String label) async {
final bitcoinWallet = wallet as ElectrumWallet;
bitcoinWallet.walletAddresses.updateAddress(address, label);
await wallet.save();
}
@override
Object createBitcoinTransactionCredentials(List<Output> outputs,
{required TransactionPriority priority, int? feeRate}) =>
BitcoinTransactionCredentials(
outputs
.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount))
.toList(),
priority: priority as BitcoinTransactionPriority,
feeRate: feeRate);
@override
Object createBitcoinTransactionCredentialsRaw(List<OutputInfo> outputs,
{TransactionPriority? priority, required int feeRate}) =>
BitcoinTransactionCredentials(outputs,
priority: priority != null ? priority as BitcoinTransactionPriority : null,
feeRate: feeRate);
@override
List<String> getAddresses(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.walletAddresses.addressesByReceiveType
.map((BitcoinAddressRecord addr) => addr.address)
.toList();
}
@override
@computed
List<ElectrumSubAddress> getSubAddresses(Object wallet) {
final electrumWallet = wallet as ElectrumWallet;
return electrumWallet.walletAddresses.addressesByReceiveType
.map((BitcoinAddressRecord addr) => ElectrumSubAddress(
id: addr.index,
name: addr.name,
address: electrumWallet.type == WalletType.bitcoinCash ? addr.cashAddr : addr.address,
txCount: addr.txCount,
balance: addr.balance,
isChange: addr.isHidden))
.toList();
}
@override
String getAddress(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.walletAddresses.address;
}
@override
String formatterBitcoinAmountToString({required int amount}) =>
bitcoinAmountToString(amount: amount);
@override
double formatterBitcoinAmountToDouble({required int amount}) =>
bitcoinAmountToDouble(amount: amount);
@override
int formatterStringDoubleToBitcoinAmount(String amount) => stringDoubleToBitcoinAmount(amount);
@override
String bitcoinTransactionPriorityWithLabel(TransactionPriority priority, int rate) =>
(priority as BitcoinTransactionPriority).labelWithRate(rate);
@override
List<BitcoinUnspent> getUnspents(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.unspentCoins;
}
Future<void> updateUnspents(Object wallet) async {
final bitcoinWallet = wallet as ElectrumWallet;
await bitcoinWallet.updateUnspent();
}
WalletService createBitcoinWalletService(
Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource) {
return BitcoinWalletService(walletInfoSource, unspentCoinSource);
}
WalletService createLitecoinWalletService(
Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource) {
return LitecoinWalletService(walletInfoSource, unspentCoinSource);
}
@override
TransactionPriority getBitcoinTransactionPriorityMedium() => BitcoinTransactionPriority.medium;
@override
TransactionPriority getLitecoinTransactionPriorityMedium() => LitecoinTransactionPriority.medium;
@override
TransactionPriority getBitcoinTransactionPrioritySlow() => BitcoinTransactionPriority.slow;
@override
TransactionPriority getLitecoinTransactionPrioritySlow() => LitecoinTransactionPriority.slow;
@override
Future<void> setAddressType(Object wallet, dynamic option) async {
final bitcoinWallet = wallet as ElectrumWallet;
await bitcoinWallet.walletAddresses.setAddressType(option as BitcoinAddressType);
}
@override
BitcoinReceivePageOption getSelectedAddressType(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
return BitcoinReceivePageOption.fromType(bitcoinWallet.walletAddresses.addressPageType);
}
@override
List<BitcoinReceivePageOption> getBitcoinReceivePageOptions() => BitcoinReceivePageOption.all;
}

View file

@ -1,4 +1,4 @@
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/validator.dart'; import 'package:cake_wallet/core/validator.dart';
import 'package:cake_wallet/solana/solana.dart'; import 'package:cake_wallet/solana/solana.dart';
@ -9,7 +9,9 @@ class AddressValidator extends TextValidator {
AddressValidator({required CryptoCurrency type}) AddressValidator({required CryptoCurrency type})
: super( : super(
errorMessage: S.current.error_text_address, errorMessage: S.current.error_text_address,
useAdditionalValidation: type == CryptoCurrency.btc ? bitcoin.Address.validateAddress : null, useAdditionalValidation: type == CryptoCurrency.btc
? (String txt) => validateAddress(address: txt, network: BitcoinNetwork.mainnet)
: null,
pattern: getPattern(type), pattern: getPattern(type),
length: getLength(type)); length: getLength(type));
@ -24,7 +26,7 @@ class AddressValidator extends TextValidator {
return '^[0-9a-zA-Z]{59}\$|^[0-9a-zA-Z]{92}\$|^[0-9a-zA-Z]{104}\$' return '^[0-9a-zA-Z]{59}\$|^[0-9a-zA-Z]{92}\$|^[0-9a-zA-Z]{104}\$'
'|^[0-9a-zA-Z]{105}\$|^addr1[0-9a-zA-Z]{98}\$'; '|^[0-9a-zA-Z]{105}\$|^addr1[0-9a-zA-Z]{98}\$';
case CryptoCurrency.btc: case CryptoCurrency.btc:
return '^3[0-9a-zA-Z]{32}\$|^3[0-9a-zA-Z]{33}\$|^bc1[0-9a-zA-Z]{59}\$'; return '^${P2pkhAddress.regex.pattern}\$|^${P2shAddress.regex.pattern}\$|^${P2wpkhAddress.regex.pattern}\$|${P2trAddress.regex.pattern}\$|^${P2wshAddress.regex.pattern}\$';
case CryptoCurrency.nano: case CryptoCurrency.nano:
return '[0-9a-zA-Z_]'; return '[0-9a-zA-Z_]';
case CryptoCurrency.banano: case CryptoCurrency.banano:
@ -89,7 +91,7 @@ class AddressValidator extends TextValidator {
case CryptoCurrency.dai: case CryptoCurrency.dai:
case CryptoCurrency.dash: case CryptoCurrency.dash:
case CryptoCurrency.eos: case CryptoCurrency.eos:
return '[0-9a-zA-Z]'; return '[0-9a-zA-Z]';
case CryptoCurrency.bch: case CryptoCurrency.bch:
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}\$'; 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}\$';
case CryptoCurrency.bnb: case CryptoCurrency.bnb:
@ -268,12 +270,11 @@ class AddressValidator extends TextValidator {
'|([^0-9a-zA-Z]|^)8[0-9a-zA-Z]{94}([^0-9a-zA-Z]|\$)' '|([^0-9a-zA-Z]|^)8[0-9a-zA-Z]{94}([^0-9a-zA-Z]|\$)'
'|([^0-9a-zA-Z]|^)[0-9a-zA-Z]{106}([^0-9a-zA-Z]|\$)'; '|([^0-9a-zA-Z]|^)[0-9a-zA-Z]{106}([^0-9a-zA-Z]|\$)';
case CryptoCurrency.btc: case CryptoCurrency.btc:
return '([^0-9a-zA-Z]|^)1[0-9a-zA-Z]{32}([^0-9a-zA-Z]|\$)' return '([^0-9a-zA-Z]|^)${P2pkhAddress.regex.pattern}|\$)'
'|([^0-9a-zA-Z]|^)1[0-9a-zA-Z]{33}([^0-9a-zA-Z]|\$)' '([^0-9a-zA-Z]|^)${P2shAddress.regex.pattern}|\$)'
'|([^0-9a-zA-Z]|^)3[0-9a-zA-Z]{32}([^0-9a-zA-Z]|\$)' '([^0-9a-zA-Z]|^)${P2wpkhAddress.regex.pattern}|\$)'
'|([^0-9a-zA-Z]|^)3[0-9a-zA-Z]{33}([^0-9a-zA-Z]|\$)' '([^0-9a-zA-Z]|^)${P2wshAddress.regex.pattern}|\$)'
'|([^0-9a-zA-Z]|^)bc1[0-9a-zA-Z]{39}([^0-9a-zA-Z]|\$)' '([^0-9a-zA-Z]|^)${P2trAddress.regex.pattern}|\$)';
'|([^0-9a-zA-Z]|^)bc1[0-9a-zA-Z]{59}([^0-9a-zA-Z]|\$)';
case CryptoCurrency.ltc: case CryptoCurrency.ltc:
return '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)' return '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)'
'|([^0-9a-zA-Z]|^)[LM][a-km-zA-HJ-NP-Z1-9]{26,33}([^0-9a-zA-Z]|\$)' '|([^0-9a-zA-Z]|^)[LM][a-km-zA-HJ-NP-Z1-9]{26,33}([^0-9a-zA-Z]|\$)'
@ -297,4 +298,4 @@ class AddressValidator extends TextValidator {
return null; return null;
} }
} }
} }

View file

@ -2,7 +2,6 @@ import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@ -55,7 +54,7 @@ class WalletCreationService {
} }
} }
Future<WalletBase> create(WalletCredentials credentials) async { Future<WalletBase> create(WalletCredentials credentials, {bool? isTestnet}) async {
checkIfExists(credentials.name); checkIfExists(credentials.name);
final password = generateWalletPassword(); final password = generateWalletPassword();
credentials.password = password; credentials.password = password;
@ -63,7 +62,7 @@ class WalletCreationService {
credentials.seedPhraseLength = settingsStore.seedPhraseLength.value; credentials.seedPhraseLength = settingsStore.seedPhraseLength.value;
} }
await keyService.saveWalletPassword(password: password, walletName: credentials.name); await keyService.saveWalletPassword(password: password, walletName: credentials.name);
final wallet = await _service!.create(credentials); final wallet = await _service!.create(credentials, isTestnet: isTestnet);
if (wallet.type == WalletType.monero) { if (wallet.type == WalletType.monero) {
await sharedPreferences.setBool( await sharedPreferences.setBool(
@ -73,12 +72,12 @@ class WalletCreationService {
return wallet; return wallet;
} }
Future<WalletBase> restoreFromKeys(WalletCredentials credentials) async { Future<WalletBase> restoreFromKeys(WalletCredentials credentials, {bool? isTestnet}) async {
checkIfExists(credentials.name); checkIfExists(credentials.name);
final password = generateWalletPassword(); final password = generateWalletPassword();
credentials.password = password; credentials.password = password;
await keyService.saveWalletPassword(password: password, walletName: credentials.name); await keyService.saveWalletPassword(password: password, walletName: credentials.name);
final wallet = await _service!.restoreFromKeys(credentials); final wallet = await _service!.restoreFromKeys(credentials, isTestnet: isTestnet);
if (wallet.type == WalletType.monero) { if (wallet.type == WalletType.monero) {
await sharedPreferences.setBool( await sharedPreferences.setBool(
@ -88,12 +87,12 @@ class WalletCreationService {
return wallet; return wallet;
} }
Future<WalletBase> restoreFromSeed(WalletCredentials credentials) async { Future<WalletBase> restoreFromSeed(WalletCredentials credentials, {bool? isTestnet}) async {
checkIfExists(credentials.name); checkIfExists(credentials.name);
final password = generateWalletPassword(); final password = generateWalletPassword();
credentials.password = password; credentials.password = password;
await keyService.saveWalletPassword(password: password, walletName: credentials.name); await keyService.saveWalletPassword(password: password, walletName: credentials.name);
final wallet = await _service!.restoreFromSeed(credentials); final wallet = await _service!.restoreFromSeed(credentials, isTestnet: isTestnet);
if (wallet.type == WalletType.monero) { if (wallet.type == WalletType.monero) {
await sharedPreferences.setBool( await sharedPreferences.setBool(

View file

@ -13,7 +13,7 @@ import 'package:cake_wallet/core/yat_service.dart';
import 'package:cake_wallet/entities/background_tasks.dart'; import 'package:cake_wallet/entities/background_tasks.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/parse_address_from_domain.dart'; import 'package:cake_wallet/entities/parse_address_from_domain.dart';
import 'package:cake_wallet/entities/receive_page_option.dart'; import 'package:cw_core/receive_page_option.dart';
import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/ionia/ionia_anypay.dart'; import 'package:cake_wallet/ionia/ionia_anypay.dart';

View file

@ -23,6 +23,10 @@ import 'package:collection/collection.dart';
const newCakeWalletMoneroUri = 'xmr-node.cakewallet.com:18081'; const newCakeWalletMoneroUri = 'xmr-node.cakewallet.com:18081';
const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002'; const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002';
const publicBitcoinTestnetElectrumAddress = 'electrum.blockstream.info';
const publicBitcoinTestnetElectrumPort = '60002';
const publicBitcoinTestnetElectrumUri =
'$publicBitcoinTestnetElectrumAddress:$publicBitcoinTestnetElectrumPort';
const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002'; const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002';
const havenDefaultNodeUri = 'nodes.havenprotocol.org:443'; const havenDefaultNodeUri = 'nodes.havenprotocol.org:443';
const ethereumDefaultNodeUri = 'ethereum.publicnode.com'; const ethereumDefaultNodeUri = 'ethereum.publicnode.com';
@ -334,6 +338,12 @@ Node? getBitcoinDefaultElectrumServer({required Box<Node> nodes}) {
nodes.values.firstWhereOrNull((node) => node.type == WalletType.bitcoin); nodes.values.firstWhereOrNull((node) => node.type == WalletType.bitcoin);
} }
Node? getBitcoinTestnetDefaultElectrumServer({required Box<Node> nodes}) {
return nodes.values
.firstWhereOrNull((Node node) => node.uriRaw == publicBitcoinTestnetElectrumUri) ??
nodes.values.firstWhereOrNull((node) => node.type == WalletType.bitcoin);
}
Node? getLitecoinDefaultElectrumServer({required Box<Node> nodes}) { Node? getLitecoinDefaultElectrumServer({required Box<Node> nodes}) {
return nodes.values return nodes.values
.firstWhereOrNull((Node node) => node.uriRaw == cakeWalletLitecoinElectrumUri) ?? .firstWhereOrNull((Node node) => node.uriRaw == cakeWalletLitecoinElectrumUri) ??
@ -503,8 +513,15 @@ Future<void> rewriteSecureStoragePin({required FlutterSecureStorage secureStorag
} }
Future<void> changeBitcoinCurrentElectrumServerToDefault( Future<void> changeBitcoinCurrentElectrumServerToDefault(
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async { {required SharedPreferences sharedPreferences,
final server = getBitcoinDefaultElectrumServer(nodes: nodes); required Box<Node> nodes,
bool? isTestnet}) async {
Node? server;
if (isTestnet == true) {
server = getBitcoinTestnetDefaultElectrumServer(nodes: nodes);
} else {
server = getBitcoinDefaultElectrumServer(nodes: nodes);
}
final serverId = server?.key as int? ?? 0; final serverId = server?.key as int? ?? 0;
await sharedPreferences.setInt(PreferencesKey.currentBitcoinElectrumSererIdKey, serverId); await sharedPreferences.setInt(PreferencesKey.currentBitcoinElectrumSererIdKey, serverId);

View file

@ -1,23 +0,0 @@
enum ReceivePageOption {
mainnet,
anonPayInvoice,
anonPayDonationLink;
@override
String toString() {
String label = '';
switch (this) {
case ReceivePageOption.mainnet:
label = 'Mainnet';
break;
case ReceivePageOption.anonPayInvoice:
label = 'Trocador AnonPay Invoice';
break;
case ReceivePageOption.anonPayDonationLink:
label = 'Trocador AnonPay Donation Link';
break;
}
return label;
}
}

View file

@ -532,13 +532,19 @@ Route<dynamic> createRoute(RouteSettings settings) {
builder: (_) => getIt.get<WebViewPage>(param1: title, param2: url)); builder: (_) => getIt.get<WebViewPage>(param1: title, param2: url));
case Routes.advancedPrivacySettings: case Routes.advancedPrivacySettings:
final type = settings.arguments as WalletType; final args = settings.arguments as Map<String, dynamic>;
final type = args['type'] as WalletType;
final useTestnet = args['useTestnet'] as bool;
final toggleTestnet = args['toggleTestnet'] as Function(bool? val);
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
builder: (_) => AdvancedPrivacySettingsPage( builder: (_) => AdvancedPrivacySettingsPage(
getIt.get<AdvancedPrivacySettingsViewModel>(param1: type), useTestnet,
getIt.get<NodeCreateOrEditViewModel>(param1: type, param2: false), toggleTestnet,
getIt.get<SeedTypeViewModel>())); getIt.get<AdvancedPrivacySettingsViewModel>(param1: type),
getIt.get<NodeCreateOrEditViewModel>(param1: type, param2: false),
getIt.get<SeedTypeViewModel>(),
));
case Routes.anonPayInvoicePage: case Routes.anonPayInvoicePage:
final args = settings.arguments as List; final args = settings.arguments as List;

View file

@ -5,7 +5,8 @@ import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/monero_accounts/monero_account_list_page.dart'; import 'package:cake_wallet/src/screens/monero_accounts/monero_account_list_page.dart';
import 'package:cake_wallet/anonpay/anonpay_donation_link_info.dart'; import 'package:cake_wallet/anonpay/anonpay_donation_link_info.dart';
import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/receive_page_option.dart'; import 'package:cw_core/receive_page_option.dart';
import 'package:cw_bitcoin/bitcoin_receive_page_option.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/present_receive_option_picker.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/present_receive_option_picker.dart';
import 'package:cake_wallet/src/widgets/gradient_background.dart'; import 'package:cake_wallet/src/widgets/gradient_background.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
@ -27,6 +28,7 @@ import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart'; import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
import 'package:bitcoin_base/bitcoin_base.dart';
class AddressPage extends BasePage { class AddressPage extends BasePage {
AddressPage({ AddressPage({
@ -69,7 +71,7 @@ class AddressPage extends BasePage {
size: 16, size: 16,
); );
final _closeButton = final _closeButton =
currentTheme.type == ThemeType.dark ? closeButtonImageDarkTheme : closeButtonImage; currentTheme.type == ThemeType.dark ? closeButtonImageDarkTheme : closeButtonImage;
bool isMobileView = responsiveLayoutUtil.shouldRenderMobileUI; bool isMobileView = responsiveLayoutUtil.shouldRenderMobileUI;
@ -163,11 +165,10 @@ class AddressPage extends BasePage {
return SelectButton( return SelectButton(
text: addressListViewModel.buttonTitle, text: addressListViewModel.buttonTitle,
onTap: () async => dashboardViewModel.isAutoGenerateSubaddressesEnabled && onTap: () async => dashboardViewModel.isAutoGenerateSubaddressesEnabled &&
(WalletType.monero == addressListViewModel.wallet.type || (WalletType.monero == addressListViewModel.wallet.type ||
WalletType.haven == addressListViewModel.wallet.type) WalletType.haven == addressListViewModel.wallet.type)
? await showPopUp<void>( ? await showPopUp<void>(
context: context, context: context, builder: (_) => getIt.get<MoneroAccountListPage>())
builder: (_) => getIt.get<MoneroAccountListPage>())
: Navigator.of(context).pushNamed(Routes.receive), : Navigator.of(context).pushNamed(Routes.receive),
textColor: Theme.of(context).extension<SyncIndicatorTheme>()!.textColor, textColor: Theme.of(context).extension<SyncIndicatorTheme>()!.textColor,
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor, color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
@ -229,6 +230,21 @@ class AddressPage extends BasePage {
); );
} }
break; break;
case BitcoinReceivePageOption.p2pkh:
addressListViewModel.setAddressType(P2pkhAddressType.p2pkh);
break;
case BitcoinReceivePageOption.p2sh:
addressListViewModel.setAddressType(P2shAddressType.p2wpkhInP2sh);
break;
case BitcoinReceivePageOption.p2wpkh:
addressListViewModel.setAddressType(SegwitAddresType.p2wpkh);
break;
case BitcoinReceivePageOption.p2tr:
addressListViewModel.setAddressType(SegwitAddresType.p2tr);
break;
case BitcoinReceivePageOption.p2wsh:
addressListViewModel.setAddressType(SegwitAddresType.p2wsh);
break;
default: default:
} }
}); });

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/entities/default_settings_migration.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/entities/seed_phrase_length.dart'; 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.
import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart'; import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
import 'package:cake_wallet/view_model/seed_type_view_model.dart'; import 'package:cake_wallet/view_model/seed_type_view_model.dart';
import 'package:cake_wallet/view_model/settings/choices_list_item.dart'; import 'package:cake_wallet/view_model/settings/choices_list_item.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
@ -19,7 +21,7 @@ import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
class AdvancedPrivacySettingsPage extends BasePage { class AdvancedPrivacySettingsPage extends BasePage {
AdvancedPrivacySettingsPage( AdvancedPrivacySettingsPage(this.useTestnet, this.toggleUseTestnet,
this.advancedPrivacySettingsViewModel, this.nodeViewModel, this.seedTypeViewModel); this.advancedPrivacySettingsViewModel, this.nodeViewModel, this.seedTypeViewModel);
final AdvancedPrivacySettingsViewModel advancedPrivacySettingsViewModel; final AdvancedPrivacySettingsViewModel advancedPrivacySettingsViewModel;
@ -29,13 +31,16 @@ class AdvancedPrivacySettingsPage extends BasePage {
@override @override
String get title => S.current.privacy_settings; String get title => S.current.privacy_settings;
final bool useTestnet;
final Function(bool? val) toggleUseTestnet;
@override @override
Widget body(BuildContext context) => AdvancedPrivacySettingsBody( Widget body(BuildContext context) => AdvancedPrivacySettingsBody(useTestnet, toggleUseTestnet,
advancedPrivacySettingsViewModel, nodeViewModel, seedTypeViewModel); advancedPrivacySettingsViewModel, nodeViewModel, seedTypeViewModel);
} }
class AdvancedPrivacySettingsBody extends StatefulWidget { class AdvancedPrivacySettingsBody extends StatefulWidget {
const AdvancedPrivacySettingsBody( const AdvancedPrivacySettingsBody(this.useTestnet, this.toggleUseTestnet,
this.privacySettingsViewModel, this.nodeViewModel, this.seedTypeViewModel, this.privacySettingsViewModel, this.nodeViewModel, this.seedTypeViewModel,
{Key? key}) {Key? key})
: super(key: key); : super(key: key);
@ -44,6 +49,9 @@ class AdvancedPrivacySettingsBody extends StatefulWidget {
final NodeCreateOrEditViewModel nodeViewModel; final NodeCreateOrEditViewModel nodeViewModel;
final SeedTypeViewModel seedTypeViewModel; final SeedTypeViewModel seedTypeViewModel;
final bool useTestnet;
final Function(bool? val) toggleUseTestnet;
@override @override
_AdvancedPrivacySettingsBodyState createState() => _AdvancedPrivacySettingsBodyState(); _AdvancedPrivacySettingsBodyState createState() => _AdvancedPrivacySettingsBodyState();
} }
@ -52,9 +60,14 @@ class _AdvancedPrivacySettingsBodyState extends State<AdvancedPrivacySettingsBod
_AdvancedPrivacySettingsBodyState(); _AdvancedPrivacySettingsBodyState();
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
bool? testnetValue;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (testnetValue == null && widget.useTestnet != null) {
testnetValue = widget.useTestnet;
}
return Container( return Container(
padding: EdgeInsets.only(top: 24), padding: EdgeInsets.only(top: 24),
child: ScrollableWithBottomSection( child: ScrollableWithBottomSection(
@ -125,6 +138,19 @@ class _AdvancedPrivacySettingsBodyState extends State<AdvancedPrivacySettingsBod
), ),
); );
}), }),
if (widget.privacySettingsViewModel.type == WalletType.bitcoin)
Builder(builder: (_) {
final val = testnetValue!;
return SettingsSwitcherCell(
title: S.current.use_testnet,
value: val,
onValueChange: (_, __) {
setState(() {
testnetValue = !val;
});
widget.toggleUseTestnet!.call(testnetValue);
});
}),
], ],
), ),
bottomSectionPadding: EdgeInsets.all(24), bottomSectionPadding: EdgeInsets.all(24),
@ -137,6 +163,13 @@ class _AdvancedPrivacySettingsBodyState extends State<AdvancedPrivacySettingsBod
return; return;
} }
widget.nodeViewModel.save();
} else if (testnetValue == true) {
// TODO: add type (mainnet/testnet) to Node class so when switching wallets the node can be switched to a matching type
// 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
widget.nodeViewModel.address = publicBitcoinTestnetElectrumAddress;
widget.nodeViewModel.port = publicBitcoinTestnetElectrumPort;
widget.nodeViewModel.save(); widget.nodeViewModel.save();
} }

View file

@ -40,7 +40,9 @@ class NewWalletPage extends BasePage {
@override @override
Widget body(BuildContext context) => WalletNameForm( Widget body(BuildContext context) => WalletNameForm(
_walletNewVM, currentTheme.type == ThemeType.dark ? walletNameImage : walletNameLightImage, _seedTypeViewModel); _walletNewVM,
currentTheme.type == ThemeType.dark ? walletNameImage : walletNameLightImage,
_seedTypeViewModel);
} }
class WalletNameForm extends StatefulWidget { class WalletNameForm extends StatefulWidget {
@ -187,7 +189,6 @@ class _WalletNameFormState extends State<WalletNameForm> {
), ),
), ),
), ),
if (_walletNewVM.hasLanguageSelector) ...[ if (_walletNewVM.hasLanguageSelector) ...[
if (_walletNewVM.hasSeedType) ...[ if (_walletNewVM.hasSeedType) ...[
Observer( Observer(
@ -222,7 +223,7 @@ class _WalletNameFormState extends State<WalletNameForm> {
), ),
), ),
) )
] ],
], ],
), ),
), ),
@ -245,8 +246,11 @@ class _WalletNameFormState extends State<WalletNameForm> {
const SizedBox(height: 25), const SizedBox(height: 25),
GestureDetector( GestureDetector(
onTap: () { onTap: () {
Navigator.of(context) Navigator.of(context).pushNamed(Routes.advancedPrivacySettings, arguments: {
.pushNamed(Routes.advancedPrivacySettings, arguments: _walletNewVM.type); "type": _walletNewVM.type,
"useTestnet": _walletNewVM.useTestnet,
"toggleTestnet": _walletNewVM.toggleUseTestnet
});
}, },
child: Text(S.of(context).advanced_settings), child: Text(S.of(context).advanced_settings),
), ),

View file

@ -4,7 +4,7 @@ import 'package:cake_wallet/anonpay/anonpay_donation_link_info.dart';
import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/receive_page_option.dart'; import 'package:cw_core/receive_page_option.dart';
import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/present_receive_option_picker.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/present_receive_option_picker.dart';
import 'package:cake_wallet/src/screens/receive/widgets/anonpay_input_form.dart'; import 'package:cake_wallet/src/screens/receive/widgets/anonpay_input_form.dart';

View file

@ -1,7 +1,7 @@
import 'package:cake_wallet/anonpay/anonpay_info_base.dart'; import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart'; import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
import 'package:cake_wallet/entities/qr_view_data.dart'; import 'package:cake_wallet/entities/qr_view_data.dart';
import 'package:cake_wallet/entities/receive_page_option.dart'; import 'package:cw_core/receive_page_option.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/base_page.dart';

View file

@ -210,8 +210,12 @@ class WalletRestorePage extends BasePage {
const SizedBox(height: 25), const SizedBox(height: 25),
GestureDetector( GestureDetector(
onTap: () { onTap: () {
Navigator.of(context).pushNamed(Routes.advancedPrivacySettings, Navigator.of(context)
arguments: walletRestoreViewModel.type); .pushNamed(Routes.advancedPrivacySettings, arguments: {
'type': walletRestoreViewModel.type,
'useTestnet': walletRestoreViewModel.useTestnet,
'toggleTestnet': walletRestoreViewModel.toggleUseTestnet
});
}, },
child: Text(S.of(context).advanced_settings), child: Text(S.of(context).advanced_settings),
), ),

View file

@ -4,7 +4,7 @@ import 'package:cake_wallet/anonpay/anonpay_request.dart';
import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/receive_page_option.dart'; import 'package:cw_core/receive_page_option.dart';
import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/currency.dart'; import 'package:cw_core/currency.dart';

View file

@ -148,17 +148,21 @@ abstract class DashboardViewModelBase with Store {
monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id) monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id)
.toList(); .toList();
transactions = ObservableList.of(_accountTransactions.map((transaction) => final sortedTransactions = [..._accountTransactions];
TransactionListItem( sortedTransactions.sort((a, b) => a.date.compareTo(b.date));
transaction: transaction,
balanceViewModel: balanceViewModel, transactions = ObservableList.of(sortedTransactions.map((transaction) => TransactionListItem(
settingsStore: appStore.settingsStore))); transaction: transaction,
balanceViewModel: balanceViewModel,
settingsStore: appStore.settingsStore)));
} else { } else {
transactions = ObservableList.of(wallet.transactionHistory.transactions.values.map( final sortedTransactions = [...wallet.transactionHistory.transactions.values];
(transaction) => TransactionListItem( sortedTransactions.sort((a, b) => a.date.compareTo(b.date));
transaction: transaction,
balanceViewModel: balanceViewModel, transactions = ObservableList.of(sortedTransactions.map((transaction) => TransactionListItem(
settingsStore: appStore.settingsStore))); transaction: transaction,
balanceViewModel: balanceViewModel,
settingsStore: appStore.settingsStore)));
} }
// TODO: nano sub-account generation is disabled: // TODO: nano sub-account generation is disabled:

View file

@ -1,4 +1,5 @@
import 'package:cake_wallet/entities/receive_page_option.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cw_core/receive_page_option.dart';
import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
@ -9,11 +10,20 @@ class ReceiveOptionViewModel = ReceiveOptionViewModelBase with _$ReceiveOptionVi
abstract class ReceiveOptionViewModelBase with Store { abstract class ReceiveOptionViewModelBase with Store {
ReceiveOptionViewModelBase(this._wallet, this.initialPageOption) ReceiveOptionViewModelBase(this._wallet, this.initialPageOption)
: selectedReceiveOption = initialPageOption ?? ReceivePageOption.mainnet, : selectedReceiveOption = initialPageOption ??
(_wallet.type == WalletType.bitcoin
? bitcoin!.getSelectedAddressType(_wallet)
: ReceivePageOption.mainnet),
_options = [] { _options = [] {
final walletType = _wallet.type; final walletType = _wallet.type;
_options = _options = walletType == WalletType.haven
walletType == WalletType.haven ? [ReceivePageOption.mainnet] : ReceivePageOption.values; ? [ReceivePageOption.mainnet]
: walletType == WalletType.bitcoin
? [
...bitcoin!.getBitcoinReceivePageOptions(),
...ReceivePageOptions.where((element) => element != ReceivePageOption.mainnet)
]
: ReceivePageOptions;
} }
final WalletBase _wallet; final WalletBase _wallet;

View file

@ -65,6 +65,8 @@ abstract class NodeCreateOrEditViewModelBase with Store {
bool get hasAuthCredentials => bool get hasAuthCredentials =>
_walletType == WalletType.monero || _walletType == WalletType.haven; _walletType == WalletType.monero || _walletType == WalletType.haven;
bool get hasTestnetSupport => _walletType == WalletType.bitcoin;
String get uri { String get uri {
var uri = address; var uri = address;

View file

@ -52,7 +52,11 @@ abstract class NodeListViewModelBase with Store {
switch (_appStore.wallet!.type) { switch (_appStore.wallet!.type) {
case WalletType.bitcoin: case WalletType.bitcoin:
node = getBitcoinDefaultElectrumServer(nodes: _nodeSource)!; if (_appStore.wallet!.isTestnet == true) {
node = getBitcoinTestnetDefaultElectrumServer(nodes: _nodeSource)!;
} else {
node = getBitcoinDefaultElectrumServer(nodes: _nodeSource)!;
}
break; break;
case WalletType.monero: case WalletType.monero:
node = getMoneroDefaultNode(nodes: _nodeSource); node = getMoneroDefaultNode(nodes: _nodeSource);

View file

@ -119,7 +119,7 @@ abstract class TransactionDetailsViewModelBase with Store {
case WalletType.monero: case WalletType.monero:
return 'https://monero.com/tx/${txId}'; return 'https://monero.com/tx/${txId}';
case WalletType.bitcoin: case WalletType.bitcoin:
return 'https://mempool.space/tx/${txId}'; return 'https://mempool.space/${wallet.isTestnet == true ? "testnet/" : ""}tx/${txId}';
case WalletType.litecoin: case WalletType.litecoin:
return 'https://blockchair.com/litecoin/transaction/${txId}'; return 'https://blockchair.com/litecoin/transaction/${txId}';
case WalletType.bitcoinCash: case WalletType.bitcoinCash:

View file

@ -388,9 +388,6 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
wallet.type == WalletType.bitcoin || wallet.type == WalletType.bitcoin ||
wallet.type == WalletType.litecoin; wallet.type == WalletType.litecoin;
// wallet.type == WalletType.nano ||
// wallet.type == WalletType.banano; TODO: nano accounts are disabled for now
@computed @computed
bool get isElectrumWallet => bool get isElectrumWallet =>
wallet.type == WalletType.bitcoin || wallet.type == WalletType.bitcoin ||
@ -409,16 +406,17 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
void setAddress(WalletAddressListItem address) => void setAddress(WalletAddressListItem address) =>
wallet.walletAddresses.address = address.address; wallet.walletAddresses.address = address.address;
@action
Future<void> setAddressType(dynamic option) async {
if (wallet.type == WalletType.bitcoin) {
await bitcoin!.setAddressType(wallet, option);
}
}
void _init() { void _init() {
_baseItems = []; _baseItems = [];
if (wallet.type == WalletType.monero || if (wallet.type == WalletType.monero || wallet.type == WalletType.haven) {
wallet.type ==
WalletType
.haven /*||
wallet.type == WalletType.nano ||
wallet.type == WalletType.banano*/
) {
_baseItems.add(WalletAccountListHeader()); _baseItems.add(WalletAccountListHeader());
} }

View file

@ -23,6 +23,12 @@ abstract class WalletCreationVMBase with Store {
: state = InitialExecutionState(), : state = InitialExecutionState(),
name = ''; name = '';
@observable
bool _useTestnet = false;
@computed
bool get useTestnet => _useTestnet;
@observable @observable
String name; String name;
@ -94,4 +100,9 @@ abstract class WalletCreationVMBase with Store {
Future<WalletBase> processFromRestoredWallet( Future<WalletBase> processFromRestoredWallet(
WalletCredentials credentials, RestoredWallet restoreWallet) => WalletCredentials credentials, RestoredWallet restoreWallet) =>
throw UnimplementedError(); throw UnimplementedError();
@action
void toggleUseTestnet(bool? value) {
_useTestnet = value ?? !_useTestnet;
}
} }

View file

@ -87,6 +87,6 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
@override @override
Future<WalletBase> process(WalletCredentials credentials) async { Future<WalletBase> process(WalletCredentials credentials) async {
walletCreationService.changeWalletType(type: type); walletCreationService.changeWalletType(type: type);
return walletCreationService.create(credentials); return walletCreationService.create(credentials, isTestnet: useTestnet);
} }
} }

View file

@ -207,9 +207,9 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
@override @override
Future<WalletBase> process(WalletCredentials credentials) async { Future<WalletBase> process(WalletCredentials credentials) async {
if (mode == WalletRestoreMode.keys) { if (mode == WalletRestoreMode.keys) {
return walletCreationService.restoreFromKeys(credentials); return walletCreationService.restoreFromKeys(credentials, isTestnet: useTestnet);
} }
return walletCreationService.restoreFromSeed(credentials); return walletCreationService.restoreFromSeed(credentials, isTestnet: useTestnet);
} }
} }

View file

@ -719,6 +719,7 @@
"use_card_info_two": "يتم تحويل الأموال إلى الدولار الأمريكي عند الاحتفاظ بها في الحساب المدفوع مسبقًا ، وليس بالعملات الرقمية.", "use_card_info_two": "يتم تحويل الأموال إلى الدولار الأمريكي عند الاحتفاظ بها في الحساب المدفوع مسبقًا ، وليس بالعملات الرقمية.",
"use_ssl": "استخدم SSL", "use_ssl": "استخدم SSL",
"use_suggested": "استخدام المقترح", "use_suggested": "استخدام المقترح",
"use_testnet": "استخدم testnet",
"variable_pair_not_supported": "هذا الزوج المتغير غير مدعوم في التبادلات المحددة", "variable_pair_not_supported": "هذا الزوج المتغير غير مدعوم في التبادلات المحددة",
"verification": "تَحَقّق", "verification": "تَحَقّق",
"verify_with_2fa": "تحقق مع Cake 2FA", "verify_with_2fa": "تحقق مع Cake 2FA",

View file

@ -719,6 +719,7 @@
"use_card_info_two": "Средствата се обръщат в USD, когато биват запазени в предплатената карта, а не в дигитална валута.", "use_card_info_two": "Средствата се обръщат в USD, когато биват запазени в предплатената карта, а не в дигитална валута.",
"use_ssl": "Използване на SSL", "use_ssl": "Използване на SSL",
"use_suggested": "Използване на предложеното", "use_suggested": "Използване на предложеното",
"use_testnet": "Използвайте TestNet",
"variable_pair_not_supported": "Този variable pair не се поддържа от избраната борса", "variable_pair_not_supported": "Този variable pair не се поддържа от избраната борса",
"verification": "Потвърждаване", "verification": "Потвърждаване",
"verify_with_2fa": "Проверете с Cake 2FA", "verify_with_2fa": "Проверете с Cake 2FA",

View file

@ -719,6 +719,7 @@
"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.", "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.",
"use_ssl": "Použít SSL", "use_ssl": "Použít SSL",
"use_suggested": "Použít doporučený", "use_suggested": "Použít doporučený",
"use_testnet": "Použijte testNet",
"variable_pair_not_supported": "Tento pár s tržním kurzem není ve zvolené směnárně podporován", "variable_pair_not_supported": "Tento pár s tržním kurzem není ve zvolené směnárně podporován",
"verification": "Ověření", "verification": "Ověření",
"verify_with_2fa": "Ověřte pomocí Cake 2FA", "verify_with_2fa": "Ověřte pomocí Cake 2FA",

View file

@ -721,6 +721,7 @@
"use_card_info_two": "Guthaben werden auf dem Prepaid-Konto in USD umgerechnet, nicht in digitale Währung.", "use_card_info_two": "Guthaben werden auf dem Prepaid-Konto in USD umgerechnet, nicht in digitale Währung.",
"use_ssl": "SSL verwenden", "use_ssl": "SSL verwenden",
"use_suggested": "Vorgeschlagen verwenden", "use_suggested": "Vorgeschlagen verwenden",
"use_testnet": "TESTNET verwenden",
"variable_pair_not_supported": "Dieses Variablenpaar wird von den ausgewählten Börsen nicht unterstützt", "variable_pair_not_supported": "Dieses Variablenpaar wird von den ausgewählten Börsen nicht unterstützt",
"verification": "Verifizierung", "verification": "Verifizierung",
"verify_with_2fa": "Verifizieren Sie mit Cake 2FA", "verify_with_2fa": "Verifizieren Sie mit Cake 2FA",

View file

@ -719,6 +719,7 @@
"use_card_info_two": "Funds are converted to USD when they're held in the prepaid account, not in digital currencies.", "use_card_info_two": "Funds are converted to USD when they're held in the prepaid account, not in digital currencies.",
"use_ssl": "Use SSL", "use_ssl": "Use SSL",
"use_suggested": "Use Suggested", "use_suggested": "Use Suggested",
"use_testnet": "Use Testnet",
"variable_pair_not_supported": "This variable pair is not supported with the selected exchanges", "variable_pair_not_supported": "This variable pair is not supported with the selected exchanges",
"verification": "Verification", "verification": "Verification",
"verify_with_2fa": "Verify with Cake 2FA", "verify_with_2fa": "Verify with Cake 2FA",

View file

@ -720,6 +720,7 @@
"use_card_info_two": "Los fondos se convierten a USD cuando se mantienen en la cuenta prepaga, no en monedas digitales.", "use_card_info_two": "Los fondos se convierten a USD cuando se mantienen en la cuenta prepaga, no en monedas digitales.",
"use_ssl": "Utilice SSL", "use_ssl": "Utilice SSL",
"use_suggested": "Usar sugerido", "use_suggested": "Usar sugerido",
"use_testnet": "Use TestNet",
"variable_pair_not_supported": "Este par de variables no es compatible con los intercambios seleccionados", "variable_pair_not_supported": "Este par de variables no es compatible con los intercambios seleccionados",
"verification": "Verificación", "verification": "Verificación",
"verify_with_2fa": "Verificar con Cake 2FA", "verify_with_2fa": "Verificar con Cake 2FA",

View file

@ -719,6 +719,7 @@
"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.", "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.",
"use_ssl": "Utiliser SSL", "use_ssl": "Utiliser SSL",
"use_suggested": "Suivre la suggestion", "use_suggested": "Suivre la suggestion",
"use_testnet": "Utiliser TestNet",
"variable_pair_not_supported": "Cette paire variable n'est pas prise en charge avec les échanges sélectionnés", "variable_pair_not_supported": "Cette paire variable n'est pas prise en charge avec les échanges sélectionnés",
"verification": "Vérification", "verification": "Vérification",
"verify_with_2fa": "Vérifier avec Cake 2FA", "verify_with_2fa": "Vérifier avec Cake 2FA",

View file

@ -721,6 +721,7 @@
"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.", "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.",
"use_ssl": "Yi amfani da SSL", "use_ssl": "Yi amfani da SSL",
"use_suggested": "Amfani da Shawarwari", "use_suggested": "Amfani da Shawarwari",
"use_testnet": "Amfani da gwaji",
"variable_pair_not_supported": "Ba a samun goyan bayan wannan m biyu tare da zaɓaɓɓun musayar", "variable_pair_not_supported": "Ba a samun goyan bayan wannan m biyu tare da zaɓaɓɓun musayar",
"verification": "tabbatar", "verification": "tabbatar",
"verify_with_2fa": "Tabbatar da Cake 2FA", "verify_with_2fa": "Tabbatar da Cake 2FA",

View file

@ -721,6 +721,7 @@
"use_card_info_two": "डिजिटल मुद्राओं में नहीं, प्रीपेड खाते में रखे जाने पर निधियों को यूएसडी में बदल दिया जाता है।", "use_card_info_two": "डिजिटल मुद्राओं में नहीं, प्रीपेड खाते में रखे जाने पर निधियों को यूएसडी में बदल दिया जाता है।",
"use_ssl": "उपयोग SSL", "use_ssl": "उपयोग SSL",
"use_suggested": "सुझाए गए का प्रयोग करें", "use_suggested": "सुझाए गए का प्रयोग करें",
"use_testnet": "टेस्टनेट का उपयोग करें",
"variable_pair_not_supported": "यह परिवर्तनीय जोड़ी चयनित एक्सचेंजों के साथ समर्थित नहीं है", "variable_pair_not_supported": "यह परिवर्तनीय जोड़ी चयनित एक्सचेंजों के साथ समर्थित नहीं है",
"verification": "सत्यापन", "verification": "सत्यापन",
"verify_with_2fa": "केक 2FA के साथ सत्यापित करें", "verify_with_2fa": "केक 2FA के साथ सत्यापित करें",

View file

@ -719,6 +719,7 @@
"use_card_info_two": "Sredstva se pretvaraju u USD kada se drže na prepaid računu, a ne u digitalnim valutama.", "use_card_info_two": "Sredstva se pretvaraju u USD kada se drže na prepaid računu, a ne u digitalnim valutama.",
"use_ssl": "Koristi SSL", "use_ssl": "Koristi SSL",
"use_suggested": "Koristite predloženo", "use_suggested": "Koristite predloženo",
"use_testnet": "Koristite TestNet",
"variable_pair_not_supported": "Ovaj par varijabli nije podržan s odabranim burzama", "variable_pair_not_supported": "Ovaj par varijabli nije podržan s odabranim burzama",
"verification": "Potvrda", "verification": "Potvrda",
"verify_with_2fa": "Provjerite s Cake 2FA", "verify_with_2fa": "Provjerite s Cake 2FA",

View file

@ -722,6 +722,7 @@
"use_card_info_two": "Dana dikonversi ke USD ketika disimpan dalam akun pra-bayar, bukan dalam mata uang digital.", "use_card_info_two": "Dana dikonversi ke USD ketika disimpan dalam akun pra-bayar, bukan dalam mata uang digital.",
"use_ssl": "Gunakan SSL", "use_ssl": "Gunakan SSL",
"use_suggested": "Gunakan yang Disarankan", "use_suggested": "Gunakan yang Disarankan",
"use_testnet": "Gunakan TestNet",
"variable_pair_not_supported": "Pasangan variabel ini tidak didukung dengan bursa yang dipilih", "variable_pair_not_supported": "Pasangan variabel ini tidak didukung dengan bursa yang dipilih",
"verification": "Verifikasi", "verification": "Verifikasi",
"verify_with_2fa": "Verifikasi dengan Cake 2FA", "verify_with_2fa": "Verifikasi dengan Cake 2FA",

View file

@ -721,6 +721,7 @@
"use_card_info_two": "I fondi vengono convertiti in USD quando sono detenuti nel conto prepagato, non in valute digitali.", "use_card_info_two": "I fondi vengono convertiti in USD quando sono detenuti nel conto prepagato, non in valute digitali.",
"use_ssl": "Usa SSL", "use_ssl": "Usa SSL",
"use_suggested": "Usa suggerito", "use_suggested": "Usa suggerito",
"use_testnet": "Usa TestNet",
"variable_pair_not_supported": "Questa coppia di variabili non è supportata con gli scambi selezionati", "variable_pair_not_supported": "Questa coppia di variabili non è supportata con gli scambi selezionati",
"verification": "Verifica", "verification": "Verifica",
"verify_with_2fa": "Verifica con Cake 2FA", "verify_with_2fa": "Verifica con Cake 2FA",

View file

@ -720,6 +720,7 @@
"use_card_info_two": "デジタル通貨ではなく、プリペイドアカウントで保持されている場合、資金は米ドルに変換されます。", "use_card_info_two": "デジタル通貨ではなく、プリペイドアカウントで保持されている場合、資金は米ドルに変換されます。",
"use_ssl": "SSLを使用する", "use_ssl": "SSLを使用する",
"use_suggested": "推奨を使用", "use_suggested": "推奨を使用",
"use_testnet": "テストネットを使用します",
"variable_pair_not_supported": "この変数ペアは、選択した取引所ではサポートされていません", "variable_pair_not_supported": "この変数ペアは、選択した取引所ではサポートされていません",
"verification": "検証", "verification": "検証",
"verify_with_2fa": "Cake 2FA で検証する", "verify_with_2fa": "Cake 2FA で検証する",

View file

@ -720,6 +720,7 @@
"use_card_info_two": "디지털 화폐가 아닌 선불 계정에 보유하면 자금이 USD로 변환됩니다.", "use_card_info_two": "디지털 화폐가 아닌 선불 계정에 보유하면 자금이 USD로 변환됩니다.",
"use_ssl": "SSL 사용", "use_ssl": "SSL 사용",
"use_suggested": "추천 사용", "use_suggested": "추천 사용",
"use_testnet": "TestNet을 사용하십시오",
"variable_pair_not_supported": "이 변수 쌍은 선택한 교환에서 지원되지 않습니다.", "variable_pair_not_supported": "이 변수 쌍은 선택한 교환에서 지원되지 않습니다.",
"verification": "검증", "verification": "검증",
"verify_with_2fa": "케이크 2FA로 확인", "verify_with_2fa": "케이크 2FA로 확인",

View file

@ -719,6 +719,7 @@
"use_card_info_two": "ဒစ်ဂျစ်တယ်ငွေကြေးများဖြင့်မဟုတ်ဘဲ ကြိုတင်ငွေပေးချေသည့်အကောင့်တွင် သိမ်းထားသည့်အခါ ရန်ပုံငွေများကို USD သို့ ပြောင်းလဲပါသည်။", "use_card_info_two": "ဒစ်ဂျစ်တယ်ငွေကြေးများဖြင့်မဟုတ်ဘဲ ကြိုတင်ငွေပေးချေသည့်အကောင့်တွင် သိမ်းထားသည့်အခါ ရန်ပုံငွေများကို USD သို့ ပြောင်းလဲပါသည်။",
"use_ssl": "SSL ကိုသုံးပါ။", "use_ssl": "SSL ကိုသုံးပါ။",
"use_suggested": "အကြံပြုထားသည်ကို အသုံးပြုပါ။", "use_suggested": "အကြံပြုထားသည်ကို အသုံးပြုပါ။",
"use_testnet": "testnet ကိုသုံးပါ",
"variable_pair_not_supported": "ရွေးချယ်ထားသော ဖလှယ်မှုများဖြင့် ဤပြောင်းလဲနိုင်သောအတွဲကို ပံ့ပိုးမထားပါ။", "variable_pair_not_supported": "ရွေးချယ်ထားသော ဖလှယ်မှုများဖြင့် ဤပြောင်းလဲနိုင်သောအတွဲကို ပံ့ပိုးမထားပါ။",
"verification": "စိစစ်ခြင်း။", "verification": "စိစစ်ခြင်း။",
"verify_with_2fa": "Cake 2FA ဖြင့် စစ်ဆေးပါ။", "verify_with_2fa": "Cake 2FA ဖြင့် စစ်ဆေးပါ။",

View file

@ -719,6 +719,7 @@
"use_card_info_two": "Tegoeden worden omgezet naar USD wanneer ze op de prepaid-rekening staan, niet in digitale valuta.", "use_card_info_two": "Tegoeden worden omgezet naar USD wanneer ze op de prepaid-rekening staan, niet in digitale valuta.",
"use_ssl": "Gebruik SSL", "use_ssl": "Gebruik SSL",
"use_suggested": "Gebruik aanbevolen", "use_suggested": "Gebruik aanbevolen",
"use_testnet": "Gebruik testnet",
"variable_pair_not_supported": "Dit variabelenpaar wordt niet ondersteund met de geselecteerde uitwisselingen", "variable_pair_not_supported": "Dit variabelenpaar wordt niet ondersteund met de geselecteerde uitwisselingen",
"verification": "Verificatie", "verification": "Verificatie",
"verify_with_2fa": "Controleer met Cake 2FA", "verify_with_2fa": "Controleer met Cake 2FA",

View file

@ -719,6 +719,7 @@
"use_card_info_two": "Środki są przeliczane na USD, gdy są przechowywane na koncie przedpłaconym, a nie w walutach cyfrowych.", "use_card_info_two": "Środki są przeliczane na USD, gdy są przechowywane na koncie przedpłaconym, a nie w walutach cyfrowych.",
"use_ssl": "Użyj SSL", "use_ssl": "Użyj SSL",
"use_suggested": "Użyj sugerowane", "use_suggested": "Użyj sugerowane",
"use_testnet": "Użyj testne",
"variable_pair_not_supported": "Ta para zmiennych nie jest obsługiwana na wybranych giełdach", "variable_pair_not_supported": "Ta para zmiennych nie jest obsługiwana na wybranych giełdach",
"verification": "Weryfikacja", "verification": "Weryfikacja",
"verify_with_2fa": "Sprawdź za pomocą Cake 2FA", "verify_with_2fa": "Sprawdź za pomocą Cake 2FA",

View file

@ -721,6 +721,7 @@
"use_card_info_two": "Os fundos são convertidos para USD quando mantidos na conta pré-paga, não em moedas digitais.", "use_card_info_two": "Os fundos são convertidos para USD quando mantidos na conta pré-paga, não em moedas digitais.",
"use_ssl": "Use SSL", "use_ssl": "Use SSL",
"use_suggested": "Uso sugerido", "use_suggested": "Uso sugerido",
"use_testnet": "Use testNet",
"variable_pair_not_supported": "Este par de variáveis não é compatível com as trocas selecionadas", "variable_pair_not_supported": "Este par de variáveis não é compatível com as trocas selecionadas",
"verification": "Verificação", "verification": "Verificação",
"verify_with_2fa": "Verificar com Cake 2FA", "verify_with_2fa": "Verificar com Cake 2FA",

View file

@ -720,6 +720,7 @@
"use_card_info_two": "Средства конвертируются в доллары США, когда они хранятся на предоплаченном счете, а не в цифровых валютах.", "use_card_info_two": "Средства конвертируются в доллары США, когда они хранятся на предоплаченном счете, а не в цифровых валютах.",
"use_ssl": "Использовать SSL", "use_ssl": "Использовать SSL",
"use_suggested": "Использовать предложенный", "use_suggested": "Использовать предложенный",
"use_testnet": "Используйте Testnet",
"variable_pair_not_supported": "Эта пара переменных не поддерживается выбранными биржами.", "variable_pair_not_supported": "Эта пара переменных не поддерживается выбранными биржами.",
"verification": "Проверка", "verification": "Проверка",
"verify_with_2fa": "Подтвердить с помощью Cake 2FA", "verify_with_2fa": "Подтвердить с помощью Cake 2FA",

View file

@ -719,6 +719,7 @@
"use_card_info_two": "เงินจะถูกแปลงค่าเป็นดอลลาร์สหรัฐเมื่อถือไว้ในบัญชีสำรองเงิน ไม่ใช่สกุลเงินดิจิตอล", "use_card_info_two": "เงินจะถูกแปลงค่าเป็นดอลลาร์สหรัฐเมื่อถือไว้ในบัญชีสำรองเงิน ไม่ใช่สกุลเงินดิจิตอล",
"use_ssl": "ใช้ SSL", "use_ssl": "ใช้ SSL",
"use_suggested": "ใช้ที่แนะนำ", "use_suggested": "ใช้ที่แนะนำ",
"use_testnet": "ใช้ testnet",
"variable_pair_not_supported": "คู่ความสัมพันธ์ที่เปลี่ยนแปลงได้นี้ไม่สนับสนุนกับหุ้นที่เลือก", "variable_pair_not_supported": "คู่ความสัมพันธ์ที่เปลี่ยนแปลงได้นี้ไม่สนับสนุนกับหุ้นที่เลือก",
"verification": "การตรวจสอบ", "verification": "การตรวจสอบ",
"verify_with_2fa": "ตรวจสอบกับ Cake 2FA", "verify_with_2fa": "ตรวจสอบกับ Cake 2FA",

View file

@ -719,6 +719,7 @@
"use_card_info_two": "Ang mga pondo ay na -convert sa USD kapag gaganapin sila sa prepaid account, hindi sa mga digital na pera.", "use_card_info_two": "Ang mga pondo ay na -convert sa USD kapag gaganapin sila sa prepaid account, hindi sa mga digital na pera.",
"use_ssl": "Gumamit ng SSL", "use_ssl": "Gumamit ng SSL",
"use_suggested": "Gumamit ng iminungkahing", "use_suggested": "Gumamit ng iminungkahing",
"use_testnet": "Gumamit ng testnet",
"variable_pair_not_supported": "Ang variable na pares na ito ay hindi suportado sa mga napiling palitan", "variable_pair_not_supported": "Ang variable na pares na ito ay hindi suportado sa mga napiling palitan",
"verification": "Pag -verify", "verification": "Pag -verify",
"verify_with_2fa": "Mag -verify sa cake 2FA", "verify_with_2fa": "Mag -verify sa cake 2FA",

View file

@ -719,6 +719,7 @@
"use_card_info_two": "Paralar, dijital para birimlerinde değil, ön ödemeli hesapta tutulduğunda USD'ye dönüştürülür.", "use_card_info_two": "Paralar, dijital para birimlerinde değil, ön ödemeli hesapta tutulduğunda USD'ye dönüştürülür.",
"use_ssl": "SSL kullan", "use_ssl": "SSL kullan",
"use_suggested": "Önerileni Kullan", "use_suggested": "Önerileni Kullan",
"use_testnet": "TestNet kullanın",
"variable_pair_not_supported": "Bu değişken paritesi seçilen borsalarda desteklenmemekte", "variable_pair_not_supported": "Bu değişken paritesi seçilen borsalarda desteklenmemekte",
"verification": "Doğrulama", "verification": "Doğrulama",
"verify_with_2fa": "Cake 2FA ile Doğrulayın", "verify_with_2fa": "Cake 2FA ile Doğrulayın",

View file

@ -720,6 +720,7 @@
"use_card_info_two": "Кошти конвертуються в долари США, якщо вони зберігаються на передплаченому рахунку, а не в цифрових валютах.", "use_card_info_two": "Кошти конвертуються в долари США, якщо вони зберігаються на передплаченому рахунку, а не в цифрових валютах.",
"use_ssl": "Використати SSL", "use_ssl": "Використати SSL",
"use_suggested": "Використати запропоноване", "use_suggested": "Використати запропоноване",
"use_testnet": "Використовуйте тестову мережу",
"variable_pair_not_supported": "Ця пара змінних не підтримується вибраними біржами", "variable_pair_not_supported": "Ця пара змінних не підтримується вибраними біржами",
"verification": "Перевірка", "verification": "Перевірка",
"verify_with_2fa": "Перевірте за допомогою Cake 2FA", "verify_with_2fa": "Перевірте за допомогою Cake 2FA",

View file

@ -721,6 +721,7 @@
"use_card_info_two": "رقوم کو امریکی ڈالر میں تبدیل کیا جاتا ہے جب پری پیڈ اکاؤنٹ میں رکھا جاتا ہے، ڈیجیٹل کرنسیوں میں نہیں۔", "use_card_info_two": "رقوم کو امریکی ڈالر میں تبدیل کیا جاتا ہے جب پری پیڈ اکاؤنٹ میں رکھا جاتا ہے، ڈیجیٹل کرنسیوں میں نہیں۔",
"use_ssl": "SSL استعمال کریں۔", "use_ssl": "SSL استعمال کریں۔",
"use_suggested": "تجویز کردہ استعمال کریں۔", "use_suggested": "تجویز کردہ استعمال کریں۔",
"use_testnet": "ٹیسٹ نیٹ استعمال کریں",
"variable_pair_not_supported": "یہ متغیر جوڑا منتخب ایکسچینجز کے ساتھ تعاون یافتہ نہیں ہے۔", "variable_pair_not_supported": "یہ متغیر جوڑا منتخب ایکسچینجز کے ساتھ تعاون یافتہ نہیں ہے۔",
"verification": "تصدیق", "verification": "تصدیق",
"verify_with_2fa": "کیک 2FA سے تصدیق کریں۔", "verify_with_2fa": "کیک 2FA سے تصدیق کریں۔",

View file

@ -720,6 +720,7 @@
"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.", "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.",
"use_ssl": "Lo SSL", "use_ssl": "Lo SSL",
"use_suggested": "Lo àbá", "use_suggested": "Lo àbá",
"use_testnet": "Lo tele",
"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ìí", "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ìí",
"verification": "Ìjẹ́rìísí", "verification": "Ìjẹ́rìísí",
"verify_with_2fa": "Ṣeẹda pẹlu Cake 2FA", "verify_with_2fa": "Ṣeẹda pẹlu Cake 2FA",

View file

@ -719,6 +719,7 @@
"use_card_info_two": "预付账户中的资金转换为美元,不是数字货币。", "use_card_info_two": "预付账户中的资金转换为美元,不是数字货币。",
"use_ssl": "使用SSL", "use_ssl": "使用SSL",
"use_suggested": "使用建议", "use_suggested": "使用建议",
"use_testnet": "使用TestNet",
"variable_pair_not_supported": "所选交易所不支持此变量对", "variable_pair_not_supported": "所选交易所不支持此变量对",
"verification": "验证", "verification": "验证",
"verify_with_2fa": "用 Cake 2FA 验证", "verify_with_2fa": "用 Cake 2FA 验证",

View file

@ -22,8 +22,8 @@ MONERO_COM_PACKAGE="com.monero.app"
MONERO_COM_SCHEME="monero.com" MONERO_COM_SCHEME="monero.com"
CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="4.13.3" CAKEWALLET_VERSION="4.14.0"
CAKEWALLET_BUILD_NUMBER=192 CAKEWALLET_BUILD_NUMBER=193
CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet"
CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet"
CAKEWALLET_SCHEME="cakewallet" CAKEWALLET_SCHEME="cakewallet"

16
scripts/android/shell.nix Normal file
View file

@ -0,0 +1,16 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
buildInputs = [
pkgs.curl
pkgs.unzip
pkgs.automake
pkgs.file
pkgs.pkg-config
pkgs.git
pkgs.libtool
pkgs.ncurses5
pkgs.openjdk8
pkgs.clang
];
}

View file

@ -18,8 +18,8 @@ MONERO_COM_BUILD_NUMBER=73
MONERO_COM_BUNDLE_ID="com.cakewallet.monero" MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="4.13.3" CAKEWALLET_VERSION="4.14.0"
CAKEWALLET_BUILD_NUMBER=212 CAKEWALLET_BUILD_NUMBER=213
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
HAVEN_NAME="Haven" HAVEN_NAME="Haven"

View file

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