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
EVM wallets enhancements (Ethereum and Polygon)
Improve wallet recovery and error tolerance
Enhance Background sync for Monero wallets
Bug fixes
Support ALL Bitcoin address types (Legacy, Segwit (both variants), Taproot)
Enhance Sending/Receiving flow for Bitcoin
Improve fee calculations in Bitcoin

View file

@ -1,23 +1,23 @@
import 'dart:typed_data';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
import 'package:bitcoin_base/bitcoin_base.dart';
String addressFromOutput(Uint8List script, bitcoin.NetworkType networkType) {
String addressFromOutputScript(Script script, BasedUtxoNetwork network) {
try {
return bitcoin.P2PKH(
data: PaymentData(output: script),
network: networkType)
.data
.address!;
switch (script.getAddressType()) {
case P2pkhAddressType.p2pkh:
return P2pkhAddress.fromScriptPubkey(script: script).toAddress(network);
case P2shAddressType.p2pkInP2sh:
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 (_) {}
try {
return bitcoin.P2WPKH(
data: PaymentData(output: script),
network: networkType)
.data
.address!;
} catch(_) {}
return '';
}

View file

@ -1,27 +1,9 @@
import 'dart:typed_data';
import 'package:bitcoin_flutter/bitcoin_flutter.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';
import 'package:bitcoin_base/bitcoin_base.dart' as bitcoin;
Uint8List p2shAddressToOutputScript(String address) {
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) {
List<int> addressToOutputScript(String address, bitcoin.BasedUtxoNetwork network) {
try {
// FIXME: improve validation for p2sh addresses
// 3 for bitcoin
// m for litecoin
if (address.startsWith('3') || address.toLowerCase().startsWith('m')) {
return p2shAddressToOutputScript(address);
}
return Address.addressToOutputScript(address, networkType);
return bitcoin.addressToOutputScript(address: address, network: network);
} catch (err) {
print(err);
return Uint8List(0);

View file

@ -1,6 +1,9 @@
import 'dart:convert';
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 {
BitcoinAddressRecord(
this.address, {
@ -10,23 +13,41 @@ class BitcoinAddressRecord {
int balance = 0,
String name = '',
bool isUsed = false,
required this.type,
String? scriptHash,
required this.network,
}) : _txCount = txCount,
_balance = balance,
_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;
return BitcoinAddressRecord(decoded['address'] as String,
index: decoded['index'] as int,
isHidden: decoded['isHidden'] as bool? ?? false,
isUsed: decoded['isUsed'] as bool? ?? false,
txCount: decoded['txCount'] as int? ?? 0,
name: decoded['name'] as String? ?? '',
balance: decoded['balance'] as int? ?? 0);
return BitcoinAddressRecord(
decoded['address'] as String,
index: decoded['index'] as int,
isHidden: decoded['isHidden'] as bool? ?? false,
isUsed: decoded['isUsed'] as bool? ?? false,
txCount: decoded['txCount'] 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;
bool isHidden;
final int index;
@ -34,6 +55,8 @@ class BitcoinAddressRecord {
int _balance;
String _name;
bool _isUsed;
String? scriptHash;
BasedUtxoNetwork? network;
int get txCount => _txCount;
@ -50,21 +73,28 @@ class BitcoinAddressRecord {
void setAsUsed() => _isUsed = true;
void setNewName(String label) => _name = label;
@override
bool operator ==(Object o) => o is BitcoinAddressRecord && address == o.address;
@override
int get hashCode => address.hashCode;
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({
'address': address,
'index': index,
'isHidden': isHidden,
'isUsed': isUsed,
'txCount': txCount,
'name': name,
'isUsed': isUsed,
'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,
super(addressRecord.address, hash, value, vout, null);
factory BitcoinUnspent.fromJSON(
BitcoinAddressRecord address, Map<String, dynamic> json) =>
BitcoinUnspent(address, json['tx_hash'] as String, json['value'] as int,
json['tx_pos'] as int);
factory BitcoinUnspent.fromJSON(BitcoinAddressRecord address, Map<String, dynamic> json) =>
BitcoinUnspent(
address, json['tx_hash'] as String, json['value'] as int, json['tx_pos'] as int);
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_core/crypto_currency.dart';
import 'package:cw_core/unspent_coins_info.dart';
@ -17,36 +18,42 @@ part 'bitcoin_wallet.g.dart';
class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet;
abstract class BitcoinWalletBase extends ElectrumWallet with Store {
BitcoinWalletBase(
{required String mnemonic,
required String password,
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required Uint8List seedBytes,
List<BitcoinAddressRecord>? initialAddresses,
ElectrumBalance? initialBalance,
int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0})
: super(
BitcoinWalletBase({
required String mnemonic,
required String password,
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required Uint8List seedBytes,
String? addressPageType,
BasedUtxoNetwork? networkParam,
List<BitcoinAddressRecord>? initialAddresses,
ElectrumBalance? initialBalance,
Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex,
}) : super(
mnemonic: mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
networkType: bitcoin.bitcoin,
networkType: networkParam == null
? bitcoin.bitcoin
: networkParam == BitcoinNetwork.mainnet
? bitcoin.bitcoin
: bitcoin.testnet,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
seedBytes: seedBytes,
currency: CryptoCurrency.btc) {
walletAddresses = BitcoinWalletAddresses(
walletInfo,
electrumClient: electrumClient,
initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: hd,
sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType)
.derivePath("m/0'/1"),
networkType: networkType);
walletInfo,
electrumClient: electrumClient,
initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: hd,
sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/1"),
network: networkParam ?? network,
);
autorun((_) {
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
});
@ -57,21 +64,26 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
required String password,
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
String? addressPageType,
BasedUtxoNetwork? network,
List<BitcoinAddressRecord>? initialAddresses,
ElectrumBalance? initialBalance,
int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0
Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex,
}) async {
return BitcoinWallet(
mnemonic: mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
seedBytes: await mnemonicToSeedBytes(mnemonic),
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex);
mnemonic: mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
seedBytes: await mnemonicToSeedBytes(mnemonic),
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
addressPageType: addressPageType,
networkParam: network,
);
}
static Future<BitcoinWallet> open({
@ -80,16 +92,21 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required String password,
}) 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(
mnemonic: snp.mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: snp.addresses,
initialBalance: snp.balance,
seedBytes: await mnemonicToSeedBytes(snp.mnemonic),
initialRegularAddressIndex: snp.regularAddressIndex,
initialChangeAddressIndex: snp.changeAddressIndex);
mnemonic: snp.mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: snp.addresses,
initialBalance: snp.balance,
seedBytes: await mnemonicToSeedBytes(snp.mnemonic),
initialRegularAddressIndex: snp.regularAddressIndex,
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:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum.dart';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:cw_bitcoin/utils.dart';
import 'package:cw_core/wallet_info.dart';
@ -11,24 +10,31 @@ part 'bitcoin_wallet_addresses.g.dart';
class BitcoinWalletAddresses = BitcoinWalletAddressesBase with _$BitcoinWalletAddresses;
abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
BitcoinWalletAddressesBase(WalletInfo walletInfo,
{required bitcoin.HDWallet mainHd,
required bitcoin.HDWallet sideHd,
required bitcoin.NetworkType networkType,
required ElectrumClient electrumClient,
List<BitcoinAddressRecord>? initialAddresses,
int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0})
: super(walletInfo,
initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: mainHd,
sideHd: sideHd,
electrumClient: electrumClient,
networkType: networkType);
BitcoinWalletAddressesBase(
WalletInfo walletInfo, {
required super.mainHd,
required super.sideHd,
required super.network,
required super.electrumClient,
super.initialAddresses,
super.initialRegularAddressIndex,
super.initialChangeAddressIndex,
}) : super(walletInfo);
@override
String getAddress({required int index, required bitcoin.HDWallet hd}) =>
generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
String getAddress({required int index, required HDWallet hd, BitcoinAddressType? addressType}) {
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 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart';
import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart';
@ -23,12 +24,17 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
WalletType getType() => WalletType.bitcoin;
@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(
mnemonic: await generateMnemonic(),
password: credentials.password!,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource);
mnemonic: await generateMnemonic(),
password: credentials.password!,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource,
network: network,
);
await wallet.save();
await wallet.init();
return wallet;
@ -92,20 +98,27 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
}
@override
Future<BitcoinWallet> restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials) async =>
Future<BitcoinWallet> restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials,
{bool? isTestnet}) async =>
throw UnimplementedError();
@override
Future<BitcoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials) async {
Future<BitcoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials,
{bool? isTestnet}) async {
if (!validateMnemonic(credentials.mnemonic)) {
throw BitcoinMnemonicIsIncorrectException();
}
final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
credentials.walletInfo?.network = network.value;
final wallet = await BitcoinWalletBase.create(
password: credentials.password!,
mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource);
password: credentials.password!,
mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource,
network: network,
);
await wallet.save();
await wallet.init();
return wallet;

View file

@ -2,12 +2,12 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
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/script_hash.dart';
import 'package:flutter/foundation.dart';
import 'package:rxdart/rxdart.dart';
import 'package:collection/collection.dart';
import 'package:http/http.dart' as http;
String jsonrpcparams(List<Object> params) {
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';
class SocketTask {
SocketTask({
required this.isSubscription,
this.completer,
this.subject});
SocketTask({required this.isSubscription, this.completer, this.subject});
final Completer<dynamic>? completer;
final BehaviorSubject<dynamic>? subject;
@ -51,8 +48,7 @@ class ElectrumClient {
Timer? _aliveTimer;
String unterminatedString;
Future<void> connectToUri(Uri uri) async =>
await connect(host: uri.host, port: uri.port);
Future<void> connectToUri(Uri uri) async => await connect(host: uri.host, port: uri.port);
Future<void> connect({required String host, required int port}) async {
try {
@ -104,21 +100,20 @@ class ElectrumClient {
}
if (isJSONStringCorrect(unterminatedString)) {
final response =
json.decode(unterminatedString) as Map<String, dynamic>;
final response = json.decode(unterminatedString) as Map<String, dynamic>;
_handleResponse(response);
unterminatedString = '';
}
} 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;
}
unterminatedString += message;
if (isJSONStringCorrect(unterminatedString)) {
final response =
json.decode(unterminatedString) as Map<String, dynamic>;
final response = json.decode(unterminatedString) as Map<String, dynamic>;
_handleResponse(response);
// unterminatedString = null;
unterminatedString = '';
@ -142,8 +137,7 @@ class ElectrumClient {
}
}
Future<List<String>> version() =>
call(method: 'server.version').then((dynamic result) {
Future<List<String>> version() => call(method: 'server.version').then((dynamic result) {
if (result is List) {
return result.map((dynamic val) => val.toString()).toList();
}
@ -178,11 +172,10 @@ class ElectrumClient {
});
Future<List<Map<String, dynamic>>> getListUnspentWithAddress(
String address, NetworkType networkType) =>
String address, BasedUtxoNetwork network) =>
call(
method: 'blockchain.scripthash.listunspent',
params: [scriptHash(address, networkType: networkType)])
.then((dynamic result) {
method: 'blockchain.scripthash.listunspent',
params: [scriptHash(address, network: network)]).then((dynamic result) {
if (result is List) {
return result.map((dynamic val) {
if (val is Map<String, dynamic>) {
@ -229,8 +222,7 @@ class ElectrumClient {
return [];
});
Future<Map<String, dynamic>> getTransactionRaw(
{required String hash}) async =>
Future<Map<String, dynamic>> getTransactionRaw({required String hash}) async =>
callWithTimeout(method: 'blockchain.transaction.get', params: [hash, true], timeout: 10000)
.then((dynamic result) {
if (result is Map<String, dynamic>) {
@ -240,8 +232,7 @@ class ElectrumClient {
return <String, dynamic>{};
});
Future<String> getTransactionHex(
{required String hash}) async =>
Future<String> getTransactionHex({required String hash}) async =>
callWithTimeout(method: 'blockchain.transaction.get', params: [hash, false], timeout: 10000)
.then((dynamic result) {
if (result is String) {
@ -252,29 +243,40 @@ class ElectrumClient {
});
Future<String> broadcastTransaction(
{required String transactionRaw}) async =>
call(method: 'blockchain.transaction.broadcast', params: [transactionRaw])
.then((dynamic result) {
if (result is String) {
return result;
{required String transactionRaw, BasedUtxoNetwork? network}) async {
if (network == BitcoinNetwork.testnet) {
return http
.post(Uri(scheme: 'https', host: 'blockstream.info', path: '/testnet/api/tx'),
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(
{required String hash, required int height}) async =>
await call(
method: 'blockchain.transaction.get_merkle',
params: [hash, height]) as Map<String, dynamic>;
return call(method: 'blockchain.transaction.broadcast', params: [transactionRaw])
.then((dynamic result) {
if (result is String) {
return result;
}
Future<Map<String, dynamic>> getHeader({required int height}) async =>
await call(method: 'blockchain.block.get_header', params: [height])
return '';
});
}
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>;
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}) =>
call(method: 'blockchain.estimatefee', params: [p])
.then((dynamic result) {
call(method: 'blockchain.estimatefee', params: [p]).then((dynamic result) {
if (result is double) {
return result;
}
@ -314,20 +316,17 @@ class ElectrumClient {
return [];
});
Future<List<int>> feeRates() async {
Future<List<int>> feeRates({BasedUtxoNetwork? network}) async {
if (network == BitcoinNetwork.testnet) {
return [1, 1, 1];
}
try {
final topDoubleString = await estimatefee(p: 1);
final middleDoubleString = await estimatefee(p: 5);
final bottomDoubleString = await estimatefee(p: 100);
final top =
(stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000)
.round();
final middle =
(stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000)
.round();
final bottom =
(stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000)
.round();
final top = (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000).round();
final middle = (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000).round();
final bottom = (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000).round();
return [bottom, middle, top];
} catch (_) {
@ -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) {
_id += 1;
return subscribe<Object>(
@ -344,16 +358,14 @@ class ElectrumClient {
}
BehaviorSubject<T>? subscribe<T>(
{required String id,
required String method,
List<Object> params = const []}) {
{required String id, required String method, List<Object> params = const []}) {
try {
final subscription = BehaviorSubject<T>();
_regisrySubscription(id, subscription);
socket!.write(jsonrpc(method: method, id: _id, params: params));
return subscription;
} catch(e) {
} catch (e) {
print(e.toString());
return null;
}
@ -370,9 +382,7 @@ class ElectrumClient {
}
Future<dynamic> callWithTimeout(
{required String method,
List<Object> params = const [],
int timeout = 4000}) async {
{required String method, List<Object> params = const [], int timeout = 4000}) async {
try {
final completer = Completer<dynamic>();
_id += 1;
@ -386,7 +396,7 @@ class ElectrumClient {
});
return completer.future;
} catch(e) {
} catch (e) {
print(e.toString());
}
}
@ -397,8 +407,8 @@ class ElectrumClient {
onConnectionStatusChange = null;
}
void _registryTask(int id, Completer<dynamic> completer) => _tasks[id.toString()] =
SocketTask(completer: completer, isSubscription: false);
void _registryTask(int id, Completer<dynamic> completer) =>
_tasks[id.toString()] = SocketTask(completer: completer, isSubscription: false);
void _regisrySubscription(String id, BehaviorSubject<dynamic> subject) =>
_tasks[id] = SocketTask(subject: subject, isSubscription: true);
@ -419,8 +429,7 @@ class ElectrumClient {
}
}
void _methodHandler(
{required String method, required Map<String, dynamic> request}) {
void _methodHandler({required String method, required Map<String, dynamic> request}) {
switch (method) {
case 'blockchain.scripthash.subscribe':
final params = request['params'] as List<dynamic>;
@ -452,7 +461,7 @@ class ElectrumClient {
return;
}
if (id != null){
if (id != null) {
_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/src/payments/index.dart' show PaymentData;
import 'package:cw_bitcoin/address_from_output.dart';
@ -10,13 +11,12 @@ import 'package:cw_core/wallet_type.dart';
class ElectrumTransactionBundle {
ElectrumTransactionBundle(this.originalTransaction,
{required this.ins,
required this.confirmations,
this.time});
final bitcoin.Transaction originalTransaction;
final List<bitcoin.Transaction> ins;
{required this.ins, required this.confirmations, this.time, required this.height});
final BtcTransaction originalTransaction;
final List<BtcTransaction> ins;
final int? time;
final int confirmations;
final int height;
}
class ElectrumTransactionInfo extends TransactionInfo {
@ -39,8 +39,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
this.confirmations = confirmations;
}
factory ElectrumTransactionInfo.fromElectrumVerbose(
Map<String, Object> obj, WalletType type,
factory ElectrumTransactionInfo.fromElectrumVerbose(Map<String, Object> obj, WalletType type,
{required List<BitcoinAddressRecord> addresses, required int height}) {
final addressesSet = addresses.map((addr) => addr.address).toSet();
final id = obj['txid'] as String;
@ -58,10 +57,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
for (dynamic vin in vins) {
final vout = vin['vout'] as int;
final out = vin['tx']['vout'][vout] as Map;
final outAddresses =
(out['scriptPubKey']['addresses'] as List<Object>?)?.toSet();
inputsAmount +=
stringDoubleToBitcoinAmount((out['value'] as double? ?? 0).toString());
final outAddresses = (out['scriptPubKey']['addresses'] as List<Object>?)?.toSet();
inputsAmount += stringDoubleToBitcoinAmount((out['value'] as double? ?? 0).toString());
if (outAddresses?.intersection(addressesSet).isNotEmpty ?? false) {
direction = TransactionDirection.outgoing;
@ -69,11 +66,9 @@ class ElectrumTransactionInfo extends TransactionInfo {
}
for (dynamic out in vout) {
final outAddresses =
out['scriptPubKey']['addresses'] as List<Object>? ?? [];
final outAddresses = out['scriptPubKey']['addresses'] as List<Object>? ?? [];
final ntrs = outAddresses.toSet().intersection(addressesSet);
final value = stringDoubleToBitcoinAmount(
(out['value'] as double? ?? 0.0).toString());
final value = stringDoubleToBitcoinAmount((out['value'] as double? ?? 0.0).toString());
totalOutAmount += value;
if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) ||
@ -96,44 +91,50 @@ class ElectrumTransactionInfo extends TransactionInfo {
}
factory ElectrumTransactionInfo.fromElectrumBundle(
ElectrumTransactionBundle bundle,
WalletType type,
bitcoin.NetworkType networkType,
{required Set<String> addresses,
required int height}) {
ElectrumTransactionBundle bundle, WalletType type, BasedUtxoNetwork network,
{required Set<String> addresses, required int height}) {
final date = bundle.time != null
? DateTime.fromMillisecondsSinceEpoch(bundle.time! * 1000)
: DateTime.now();
? DateTime.fromMillisecondsSinceEpoch(bundle.time! * 1000)
: DateTime.now();
var direction = TransactionDirection.incoming;
var amount = 0;
var inputAmount = 0;
var totalOutAmount = 0;
for (var i = 0; i < bundle.originalTransaction.ins.length; i++) {
final input = bundle.originalTransaction.ins[i];
for (var i = 0; i < bundle.originalTransaction.inputs.length; i++) {
final input = bundle.originalTransaction.inputs[i];
final inputTransaction = bundle.ins[i];
final vout = input.index;
final outTransaction = inputTransaction.outs[vout!];
final address = addressFromOutput(outTransaction.script!, networkType);
inputAmount += outTransaction.value!;
if (addresses.contains(address)) {
final outTransaction = inputTransaction.outputs[input.txIndex];
inputAmount += outTransaction.amount.toInt();
if (addresses.contains(addressFromOutputScript(outTransaction.scriptPubKey, network))) {
direction = TransactionDirection.outgoing;
}
}
for (final out in bundle.originalTransaction.outs) {
totalOutAmount += out.value!;
final address = addressFromOutput(out.script!, networkType);
final addressExists = addresses.contains(address);
final receivedAmounts = <int>[];
for (final out in bundle.originalTransaction.outputs) {
totalOutAmount += out.amount.toInt();
final addressExists = addresses.contains(addressFromOutputScript(out.scriptPubKey, network));
if (addressExists) {
receivedAmounts.add(out.amount.toInt());
}
if ((direction == TransactionDirection.incoming && 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;
return ElectrumTransactionInfo(type,
id: bundle.originalTransaction.getId(),
id: bundle.originalTransaction.txId(),
height: height,
isPending: bundle.confirmations == 0,
fee: fee,
@ -152,8 +153,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
if (addresses != null) {
tx.outs.forEach((out) {
try {
final p2pkh = bitcoin.P2PKH(
data: PaymentData(output: out.script), network: bitcoin.bitcoin);
final p2pkh =
bitcoin.P2PKH(data: PaymentData(output: out.script), network: bitcoin.bitcoin);
exist = addresses.contains(p2pkh.data.address);
if (exist) {
@ -163,9 +164,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
});
}
final date = timestamp != null
? DateTime.fromMillisecondsSinceEpoch(timestamp * 1000)
: DateTime.now();
final date =
timestamp != null ? DateTime.fromMillisecondsSinceEpoch(timestamp * 1000) : DateTime.now();
return ElectrumTransactionInfo(type,
id: tx.getId(),
@ -178,8 +178,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
confirmations: confirmations);
}
factory ElectrumTransactionInfo.fromJson(
Map<String, dynamic> data, WalletType type) {
factory ElectrumTransactionInfo.fromJson(Map<String, dynamic> data, WalletType type) {
return ElectrumTransactionInfo(type,
id: data['id'] as String,
height: data['height'] as int,

View file

@ -3,9 +3,10 @@ import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:bitcoin_base/bitcoin_base.dart';
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:cw_bitcoin/address_to_output_script.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_transaction_credentials.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_info.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/script_hash.dart';
import 'package:cw_bitcoin/utils.dart';
@ -37,6 +39,7 @@ import 'package:hex/hex.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:rxdart/subjects.dart';
import 'package:http/http.dart' as http;
part 'electrum_wallet.g.dart';
@ -73,6 +76,12 @@ abstract class ElectrumWalletBase
}
: {}),
this.unspentCoinsInfo = unspentCoinsInfo,
this.network = networkType == bitcoin.bitcoin
? BitcoinNetwork.mainnet
: networkType == litecoinNetwork
? LitecoinNetwork.mainnet
: BitcoinNetwork.testnet,
this.isTestnet = networkType == bitcoin.testnet,
super(walletInfo) {
this.electrumClient = electrumClient ?? ElectrumClient();
this.walletInfo = walletInfo;
@ -106,13 +115,13 @@ abstract class ElectrumWalletBase
@observable
SyncStatus syncStatus;
List<String> get scriptHashes => walletAddresses.addresses
.map((addr) => scriptHash(addr.address, networkType: networkType))
List<String> get scriptHashes => walletAddresses.addressesByReceiveType
.map((addr) => scriptHash(addr.address, network: network))
.toList();
List<String> get publicScriptHashes => walletAddresses.addresses
List<String> get publicScriptHashes => walletAddresses.allAddresses
.where((addr) => !addr.isHidden)
.map((addr) => scriptHash(addr.address, networkType: networkType))
.map((addr) => scriptHash(addr.address, network: network))
.toList();
String get xpub => hd.base58!;
@ -121,6 +130,10 @@ abstract class ElectrumWalletBase
String get seed => mnemonic;
bitcoin.NetworkType networkType;
BasedUtxoNetwork network;
@override
bool? isTestnet;
@override
BitcoinWalletKeys get keys =>
@ -145,12 +158,11 @@ abstract class ElectrumWalletBase
Future<void> startSync() async {
try {
syncStatus = AttemptingSyncStatus();
await walletAddresses.discoverAddresses();
await updateTransactions();
_subscribeForUpdates();
await updateUnspent();
await updateBalance();
_feeRates = await electrumClient.feeRates();
_feeRates = await electrumClient.feeRates(network: network);
Timer.periodic(
const Duration(minutes: 1), (timer) async => _feeRates = await electrumClient.feeRates());
@ -181,183 +193,206 @@ abstract class ElectrumWalletBase
}
}
@override
Future<PendingTransaction> createTransaction(Object credentials) async {
const minAmount = 546;
final transactionCredentials = credentials as BitcoinTransactionCredentials;
final inputs = <BitcoinUnspent>[];
final outputs = transactionCredentials.outputs;
final hasMultiDestination = outputs.length > 1;
Future<EstimatedTxResult> _estimateTxFeeAndInputsToUse(
int credentialsAmount,
bool sendAll,
List<BitcoinBaseAddress> outputAddresses,
List<BitcoinOutput> outputs,
BitcoinTransactionCredentials transactionCredentials,
{int? inputsCount}) async {
final utxos = <UtxoWithAddress>[];
List<ECPrivate> privateKeys = [];
var leftAmount = credentialsAmount;
var allInputsAmount = 0;
if (unspentCoins.isEmpty) {
await updateUnspent();
}
for (int i = 0; i < unspentCoins.length; i++) {
final utx = unspentCoins[i];
for (final utx in unspentCoins) {
if (utx.isSending) {
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;
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;
}
}
}
if (inputs.isEmpty) {
if (utxos.isEmpty) {
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);
}
txb.setVersion(1);
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;
var amount = credentialsAmount;
txb.addInput(input.hash, input.vout, null, p2wpkh.output);
} else {
txb.addInput(input.hash, input.vout);
final lastOutput = outputs.last;
if (!sendAll) {
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 {
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) {
txb.addOutput(changeAddress, changeValue);
if (totalAmount > balance[currency]!.confirmed) {
throw BitcoinTransactionWrongBalanceException(currency);
}
for (var i = 0; i < inputs.length; i++) {
final input = inputs[i];
final keyPair = generateKeyPair(
hd: input.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
index: input.bitcoinAddressRecord.index,
network: networkType);
final witnessValue = input.isP2wpkh ? input.value : null;
if (totalAmount > allInputsAmount) {
if (unspentCoins.where((utx) => utx.isSending).length == utxos.length) {
throw BitcoinTransactionWrongBalanceException(currency);
} else {
if (changeValue > fee) {
outputAddresses.removeLast();
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,
electrumClient: electrumClient, amount: amount, fee: fee)
..addListener((transaction) async {
transactionHistory.addOne(transaction);
await updateBalance();
return EstimatedTxResult(utxos: utxos, privateKeys: privateKeys, fee: fee, amount: amount);
}
@override
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({
'mnemonic': mnemonic,
'account_index': walletAddresses.currentReceiveAddressIndex.toString(),
'change_address_index': walletAddresses.currentChangeAddressIndex.toString(),
'addresses': walletAddresses.addresses.map((addr) => addr.toJSON()).toList(),
'balance': balance[currency]?.toJSON()
'account_index': walletAddresses.currentReceiveAddressIndexByType,
'change_address_index': walletAddresses.currentChangeAddressIndexByType,
'addresses': walletAddresses.allAddresses.map((addr) => addr.toJSON()).toList(),
'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) {
@ -372,24 +407,29 @@ abstract class ElectrumWalletBase
}
}
int feeAmountForPriority(
BitcoinTransactionPriority priority, int inputsCount, int outputsCount) =>
feeRate(priority) * estimatedTransactionSize(inputsCount, outputsCount);
int feeAmountForPriority(BitcoinTransactionPriority priority, int inputsCount, int outputsCount,
{int? size}) =>
feeRate(priority) * (size ?? estimatedTransactionSize(inputsCount, outputsCount));
int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount) =>
feeRate * estimatedTransactionSize(inputsCount, outputsCount);
int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount, {int? size}) =>
feeRate * (size ?? estimatedTransactionSize(inputsCount, outputsCount));
@override
int calculateEstimatedFee(TransactionPriority? priority, int? amount, {int? outputsCount}) {
int calculateEstimatedFee(TransactionPriority? priority, int? amount,
{int? outputsCount, int? size}) {
if (priority is BitcoinTransactionPriority) {
return calculateEstimatedFeeWithFeeRate(feeRate(priority), amount,
outputsCount: outputsCount);
outputsCount: outputsCount, size: size);
}
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;
if (amount != null) {
@ -457,9 +497,6 @@ abstract class ElectrumWalletBase
await transactionHistory.changePassword(password);
}
bitcoin.ECPair keyPairFor({required int index}) =>
generateKeyPair(hd: hd, index: index, network: networkType);
@override
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<void> updateUnspent() async {
final unspent = await Future.wait(walletAddresses.addresses.map((address) => electrumClient
.getListUnspentWithAddress(address.address, networkType)
.then((unspent) => unspent.map((unspent) {
List<BitcoinUnspent> updatedUnspentCoins = [];
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 {
return BitcoinUnspent.fromJSON(address, unspent);
} catch (_) {
return null;
}
}).whereNotNull())));
unspentCoins = unspent.expand((e) => e).toList();
unspentCoins.forEach((coin) async {
final tx = await fetchTransactionInfo(hash: coin.hash, height: 0);
coin.isChange = tx?.direction == TransactionDirection.outgoing;
});
final coin = BitcoinUnspent.fromJSON(address, unspent);
final tx = await fetchTransactionInfo(
hash: coin.hash, height: 0, myAddresses: addressesSet);
coin.isChange = tx?.direction == TransactionDirection.outgoing;
updatedUnspentCoins.add(coin);
} catch (_) {}
}))));
unspentCoins = updatedUnspentCoins;
if (unspentCoinsInfo.isEmpty) {
unspentCoins.forEach((coin) => _addCoinInfo(coin));
@ -495,8 +535,10 @@ abstract class ElectrumWalletBase
if (unspentCoins.isNotEmpty) {
unspentCoins.forEach((coin) {
final coinInfoList = unspentCoinsInfo.values
.where((element) => element.walletId.contains(id) && element.hash.contains(coin.hash));
final coinInfoList = unspentCoinsInfo.values.where((element) =>
element.walletId.contains(id) &&
element.hash.contains(coin.hash) &&
element.vout == coin.vout);
if (coinInfoList.isNotEmpty) {
final coinInfo = coinInfoList.first;
@ -537,7 +579,8 @@ abstract class ElectrumWalletBase
if (currentWalletUnspentCoins.isNotEmpty) {
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) {
keys.add(element.key);
@ -555,92 +598,152 @@ abstract class ElectrumWalletBase
Future<ElectrumTransactionBundle> getTransactionExpanded(
{required String hash, required int height}) async {
final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash);
final transactionHex = verboseTransaction['hex'] as String;
final original = bitcoin.Transaction.fromHex(transactionHex);
final ins = <bitcoin.Transaction>[];
final time = verboseTransaction['time'] as int?;
final confirmations = verboseTransaction['confirmations'] as int? ?? 0;
String transactionHex;
int? time;
int confirmations = 0;
if (network == BitcoinNetwork.testnet) {
// Testnet public electrum server does not support verbose transaction fetching
transactionHex = await electrumClient.getTransactionHex(hash: hash);
for (final vin in original.ins) {
final id = HEX.encode(vin.hash!.reversed.toList());
final txHex = await electrumClient.getTransactionHex(hash: id);
final tx = bitcoin.Transaction.fromHex(txHex);
ins.add(tx);
final status = json.decode(
(await http.get(Uri.parse("https://blockstream.info/testnet/api/tx/$hash/status"))).body);
time = status["block_time"] as int?;
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(
{required String hash, required int height}) async {
{required String hash,
required int height,
required Set<String> myAddresses,
bool? retryOnFailure}) async {
try {
final tx = await getTransactionExpanded(hash: hash, height: height);
final addresses = walletAddresses.addresses.map((addr) => addr.address).toSet();
return ElectrumTransactionInfo.fromElectrumBundle(tx, walletInfo.type, networkType,
addresses: addresses, height: height);
} catch (_) {
return ElectrumTransactionInfo.fromElectrumBundle(
await getTransactionExpanded(hash: hash, height: height), walletInfo.type, network,
addresses: myAddresses, height: height);
} catch (e) {
if (e is FormatException && retryOnFailure == true) {
await Future.delayed(const Duration(seconds: 2));
return fetchTransactionInfo(hash: hash, height: height, myAddresses: myAddresses);
}
return null;
}
}
@override
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 {
final histories = addressHashes.keys.map((scriptHash) =>
electrumClient.getHistory(scriptHash).then((history) => {scriptHash: history}));
final historyResults = await Future.wait(histories);
final Map<String, ElectrumTransactionInfo> historiesWithDetails = {};
final addressesSet = walletAddresses.allAddresses.map((addr) => addr.address).toSet();
final currentHeight = await electrumClient.getCurrentBlockChainTip() ?? 0;
historyResults.forEach((history) {
history.entries.forEach((historyItem) {
if (historyItem.value.isNotEmpty) {
final address = addressHashes[historyItem.key];
address?.setAsUsed();
newTxCounts[historyItem.key] = historyItem.value.length;
normalizedHistories.addAll(historyItem.value);
await Future.wait(ADDRESS_TYPES.map((type) {
final addressesByType = walletAddresses.allAddresses.where((addr) => addr.type == type);
return Future.wait(addressesByType.map((addressRecord) async {
final history = await _fetchAddressHistory(addressRecord, addressesSet, currentHeight);
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>>(
<String, ElectrumTransactionInfo>{}, (acc, tx) {
if (tx == null) {
return acc;
}
acc[tx.id] = acc[tx.id]?.updated(tx) ?? tx;
return acc;
});
return historiesWithDetails;
} catch (e) {
print(e.toString());
return {};
}
}
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) {
print(e.toString());
return {};
@ -654,10 +757,8 @@ abstract class ElectrumWalletBase
}
_isTransactionUpdating = true;
final transactions = await fetchTransactions();
transactionHistory.addMany(transactions);
await fetchTransactions();
walletAddresses.updateReceiveAddresses();
await transactionHistory.save();
_isTransactionUpdating = false;
} catch (e, stacktrace) {
print(stacktrace);
@ -688,11 +789,11 @@ abstract class ElectrumWalletBase
}
Future<ElectrumBalance> _fetchBalances() async {
final addresses = walletAddresses.addresses.toList();
final addresses = walletAddresses.allAddresses.toList();
final balanceFutures = <Future<Map<String, dynamic>>>[];
for (var i = 0; i < addresses.length; i++) {
final addressRecord = addresses[i];
final sh = scriptHash(addressRecord.address, networkType: networkType);
final sh = scriptHash(addressRecord.address, network: network);
final balanceFuture = electrumClient.getBalance(sh);
balanceFutures.add(balanceFuture);
}
@ -701,6 +802,7 @@ abstract class ElectrumWalletBase
unspentCoinsInfo.values.forEach((info) {
unspentCoins.forEach((element) {
if (element.hash == info.hash &&
element.vout == info.vout &&
info.isFrozen &&
element.bitcoinAddressRecord.address == info.address &&
element.value == info.value) {
@ -738,10 +840,10 @@ abstract class ElectrumWalletBase
String getChangeAddress() {
const minCountOfHiddenAddresses = 5;
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) {
addresses = walletAddresses.addresses.toList();
addresses = walletAddresses.allAddresses.toList();
}
return addresses[random.nextInt(addresses.length)].address;
@ -753,9 +855,62 @@ abstract class ElectrumWalletBase
@override
String signMessage(String message, {String? address = null}) {
final index = address != null
? walletAddresses.addresses.firstWhere((element) => element.address == address).index
? walletAddresses.allAddresses.firstWhere((element) => element.address == address).index
: null;
final HD = index == null ? hd : hd.derive(index);
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:bitbox/bitbox.dart' as bitbox;
import 'package:cw_bitcoin/bitcoin_address_record.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_info.dart';
import 'package:cw_core/wallet_type.dart';
@ -12,25 +12,41 @@ part 'electrum_wallet_addresses.g.dart';
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 {
ElectrumWalletAddressesBase(WalletInfo walletInfo,
{required this.mainHd,
required this.sideHd,
required this.electrumClient,
required this.networkType,
List<BitcoinAddressRecord>? initialAddresses,
int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0})
: addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
ElectrumWalletAddressesBase(
WalletInfo walletInfo, {
required this.mainHd,
required this.sideHd,
required this.electrumClient,
required this.network,
List<BitcoinAddressRecord>? initialAddresses,
Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex,
}) : _addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
addressesByReceiveType =
ObservableList<BitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()),
receiveAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed)
.toSet()),
changeAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed)
.toSet()),
currentReceiveAddressIndex = initialRegularAddressIndex,
currentChangeAddressIndex = initialChangeAddressIndex,
super(walletInfo);
currentReceiveAddressIndexByType = initialRegularAddressIndex ?? {},
currentChangeAddressIndexByType = initialChangeAddressIndex ?? {},
_addressPageType = walletInfo.addressPageType != null
? BitcoinAddressType.fromValue(walletInfo.addressPageType!)
: SegwitAddresType.p2wpkh,
super(walletInfo) {
updateAddressesByMatch();
}
static const defaultReceiveAddressesCount = 22;
static const defaultChangeAddressesCount = 17;
@ -40,37 +56,48 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
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> changeAddresses;
final ElectrumClient electrumClient;
final bitcoin.NetworkType networkType;
final BasedUtxoNetwork network;
final bitcoin.HDWallet mainHd;
final bitcoin.HDWallet sideHd;
@observable
BitcoinAddressType _addressPageType = SegwitAddresType.p2wpkh;
@computed
BitcoinAddressType get addressPageType => _addressPageType;
@computed
List<BitcoinAddressRecord> get allAddresses => _addresses;
@override
@computed
String get address {
if (isEnabledAutoGenerateSubaddress) {
if (receiveAddresses.isEmpty) {
final newAddress = generateNewAddress(hd: mainHd).address;
return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(newAddress) : newAddress;
}
final receiveAddress = receiveAddresses.first.address;
String receiveAddress;
return walletInfo.type == WalletType.bitcoinCash
? toCashAddr(receiveAddress)
: receiveAddress;
final typeMatchingReceiveAddresses = receiveAddresses.where(_isAddressPageTypeMatch);
if ((isEnabledAutoGenerateSubaddress && receiveAddresses.isEmpty) ||
typeMatchingReceiveAddresses.isEmpty) {
receiveAddress = generateNewAddress().address;
} else {
final receiveAddress = (receiveAddresses.first.address != addresses.first.address &&
previousAddressRecord != null)
? previousAddressRecord!.address
: addresses.first.address;
final previousAddressMatchesType =
previousAddressRecord != null && previousAddressRecord!.type == addressPageType;
return walletInfo.type == WalletType.bitcoinCash
? toCashAddr(receiveAddress)
: receiveAddress;
if (previousAddressMatchesType &&
typeMatchingReceiveAddresses.first.address != addressesByReceiveType.first.address) {
receiveAddress = previousAddressRecord!.address;
} else {
receiveAddress = typeMatchingReceiveAddresses.first.address;
}
}
return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(receiveAddress) : receiveAddress;
}
@observable
@ -81,7 +108,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
if (addr.startsWith('bitcoincash:')) {
addr = toLegacy(addr);
}
final addressRecord = addresses.firstWhere((addressRecord) => addressRecord.address == addr);
final addressRecord = _addresses.firstWhere((addressRecord) => addressRecord.address == addr);
previousAddressRecord = addressRecord;
receiveAddresses.remove(addressRecord);
@ -89,16 +116,29 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
}
@override
String get primaryAddress => getAddress(index: 0, hd: mainHd);
String get primaryAddress => getAddress(index: 0, hd: mainHd, addressType: addressPageType);
int currentReceiveAddressIndex;
int currentChangeAddressIndex;
Map<String, int> currentReceiveAddressIndexByType;
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
BitcoinAddressRecord? previousAddressRecord;
@computed
int get totalCountOfReceiveAddresses => addresses.fold(0, (acc, addressRecord) {
int get totalCountOfReceiveAddresses => addressesByReceiveType.fold(0, (acc, addressRecord) {
if (!addressRecord.isHidden) {
return acc + 1;
}
@ -106,22 +146,21 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
});
@computed
int get totalCountOfChangeAddresses => addresses.fold(0, (acc, addressRecord) {
int get totalCountOfChangeAddresses => addressesByReceiveType.fold(0, (acc, addressRecord) {
if (addressRecord.isHidden) {
return acc + 1;
}
return acc;
});
Future<void> discoverAddresses() async {
await _discoverAddresses(mainHd, false);
await _discoverAddresses(sideHd, true);
await updateAddressesInBox();
}
@override
Future<void> init() async {
await _generateInitialAddresses();
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh);
await _generateInitialAddresses(type: SegwitAddresType.p2tr);
await _generateInitialAddresses(type: SegwitAddresType.p2wsh);
updateAddressesByMatch();
updateReceiveAddresses();
updateChangeAddresses();
await updateAddressesInBox();
@ -141,10 +180,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
if (changeAddresses.isEmpty) {
final newAddresses = await _createNewAddresses(gap,
hd: sideHd,
startIndex: totalCountOfChangeAddresses > 0 ? totalCountOfChangeAddresses - 1 : 0,
isHidden: true);
_addAddresses(newAddresses);
addAddresses(newAddresses);
}
if (currentChangeAddressIndex >= changeAddresses.length) {
@ -157,19 +195,26 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
return address;
}
BitcoinAddressRecord generateNewAddress({bitcoin.HDWallet? hd, String? label}) {
final isHidden = hd == sideHd;
BitcoinAddressRecord generateNewAddress({String label = ''}) {
final newAddressIndex = addressesByReceiveType.fold(
0, (int acc, addressRecord) => addressRecord.isHidden == false ? acc + 1 : acc);
final newAddressIndex = addresses.fold(
0, (int acc, addressRecord) => isHidden == addressRecord.isHidden ? acc + 1 : acc);
final address = BitcoinAddressRecord(getAddress(index: newAddressIndex, hd: hd ?? sideHd),
index: newAddressIndex, isHidden: isHidden, name: label ?? '');
addresses.add(address);
final address = BitcoinAddressRecord(
getAddress(index: newAddressIndex, hd: mainHd, addressType: addressPageType),
index: newAddressIndex,
isHidden: false,
name: label,
type: addressPageType,
network: network,
);
_addresses.add(address);
updateAddressesByMatch();
return address;
}
String getAddress({required int index, required bitcoin.HDWallet hd}) => '';
String getAddress(
{required int index, required bitcoin.HDWallet hd, BitcoinAddressType? addressType}) =>
'';
@override
Future<void> updateAddressesInBox() async {
@ -187,126 +232,138 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
if (address.startsWith('bitcoincash:')) {
address = toLegacy(address);
}
final addressRecord = addresses.firstWhere((addressRecord) => addressRecord.address == address);
final addressRecord =
_addresses.firstWhere((addressRecord) => addressRecord.address == address);
addressRecord.setNewName(label);
final index = addresses.indexOf(addressRecord);
addresses.remove(addressRecord);
addresses.insert(index, addressRecord);
final index = _addresses.indexOf(addressRecord);
_addresses.remove(addressRecord);
_addresses.insert(index, addressRecord);
}
@action
void updateAddressesByMatch() {
addressesByReceiveType.clear();
addressesByReceiveType.addAll(_addresses.where(_isAddressPageTypeMatch).toList());
}
@action
void updateReceiveAddresses() {
receiveAddresses.removeRange(0, receiveAddresses.length);
final newAddresses =
addresses.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed);
_addresses.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed);
receiveAddresses.addAll(newAddresses);
}
@action
void updateChangeAddresses() {
changeAddresses.removeRange(0, changeAddresses.length);
final newAddresses =
addresses.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed);
final newAddresses = _addresses.where((addressRecord) =>
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);
}
Future<void> _discoverAddresses(bitcoin.HDWallet hd, bool isHidden) async {
var hasAddrUse = true;
List<BitcoinAddressRecord> addrs;
if (addresses.isNotEmpty) {
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);
@action
Future<void> discoverAddresses(List<BitcoinAddressRecord> addressList, bool isHidden,
Future<String?> Function(BitcoinAddressRecord, Set<String>) getAddressHistory,
{BitcoinAddressType type = SegwitAddresType.p2wpkh}) async {
if (!isHidden) {
_validateSideHdAddresses(addressList.toList());
}
while (hasAddrUse) {
final addr = addrs.last.address;
hasAddrUse = await _hasAddressUsed(addr);
final newAddresses = await _createNewAddresses(gap,
startIndex: addressList.length, isHidden: isHidden, type: type);
addAddresses(newAddresses);
if (!hasAddrUse) {
break;
}
final addressesWithHistory = await Future.wait(newAddresses
.map((addr) => getAddressHistory(addr, _addresses.map((e) => e.address).toSet())));
final isLastAddressUsed = addressesWithHistory.last == addressList.last.address;
final start = addrs.length;
final count = start + gap;
final batch = await _createNewAddresses(count, startIndex: start, hd: hd, isHidden: isHidden);
addrs.addAll(batch);
}
if (addresses.length < addrs.length) {
_addAddresses(addrs);
if (isLastAddressUsed) {
discoverAddresses(addressList, isHidden, getAddressHistory, type: type);
}
}
Future<void> _generateInitialAddresses() async {
Future<void> _generateInitialAddresses(
{BitcoinAddressType type = SegwitAddresType.p2wpkh}) async {
var countOfReceiveAddresses = 0;
var countOfHiddenAddresses = 0;
addresses.forEach((addr) {
if (addr.isHidden) {
countOfHiddenAddresses += 1;
return;
}
_addresses.forEach((addr) {
if (addr.type == type) {
if (addr.isHidden) {
countOfHiddenAddresses += 1;
return;
}
countOfReceiveAddresses += 1;
countOfReceiveAddresses += 1;
}
});
if (countOfReceiveAddresses < defaultReceiveAddressesCount) {
final addressesCount = defaultReceiveAddressesCount - countOfReceiveAddresses;
final newAddresses = await _createNewAddresses(addressesCount,
startIndex: countOfReceiveAddresses, hd: mainHd, isHidden: false);
addresses.addAll(newAddresses);
startIndex: countOfReceiveAddresses, isHidden: false, type: type);
addAddresses(newAddresses);
}
if (countOfHiddenAddresses < defaultChangeAddressesCount) {
final addressesCount = defaultChangeAddressesCount - countOfHiddenAddresses;
final newAddresses = await _createNewAddresses(addressesCount,
startIndex: countOfHiddenAddresses, hd: sideHd, isHidden: true);
addresses.addAll(newAddresses);
startIndex: countOfHiddenAddresses, isHidden: true, type: type);
addAddresses(newAddresses);
}
}
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>[];
for (var i = startIndex; i < count + startIndex; i++) {
final address =
BitcoinAddressRecord(getAddress(index: i, hd: hd), index: i, isHidden: isHidden);
final address = BitcoinAddressRecord(
getAddress(index: i, hd: _getHd(isHidden), addressType: type ?? addressPageType),
index: i,
isHidden: isHidden,
type: type ?? addressPageType,
network: network,
);
list.add(address);
}
return list;
}
void _addAddresses(Iterable<BitcoinAddressRecord> addresses) {
final addressesSet = this.addresses.toSet();
@action
void addAddresses(Iterable<BitcoinAddressRecord> addresses) {
final addressesSet = this._addresses.toSet();
addressesSet.addAll(addresses);
this.addresses.removeRange(0, this.addresses.length);
this.addresses.addAll(addressesSet);
this._addresses.clear();
this._addresses.addAll(addressesSet);
updateAddressesByMatch();
}
Future<bool> _hasAddressUsed(String address) async {
final sh = scriptHash(address, networkType: networkType);
final transactionHistory = await electrumClient.getHistory(sh);
return transactionHistory.isNotEmpty;
}
void validateSideHdAddresses(List<BitcoinAddressRecord> addrWithTransactions) {
void _validateSideHdAddresses(List<BitcoinAddressRecord> addrWithTransactions) {
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 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/utils/file.dart';
import 'package:cw_core/wallet_type.dart';
class ElectrumWallletSnapshot {
ElectrumWallletSnapshot({
class ElectrumWalletSnapshot {
ElectrumWalletSnapshot({
required this.name,
required this.type,
required this.password,
@ -14,19 +15,24 @@ class ElectrumWallletSnapshot {
required this.addresses,
required this.balance,
required this.regularAddressIndex,
required this.changeAddressIndex});
required this.changeAddressIndex,
required this.addressPageType,
required this.network,
});
final String name;
final String password;
final WalletType type;
final String addressPageType;
final BasedUtxoNetwork network;
String mnemonic;
List<BitcoinAddressRecord> addresses;
ElectrumBalance balance;
int regularAddressIndex;
int changeAddressIndex;
Map<String, int> regularAddressIndex;
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 jsonSource = await read(path: path, password: password);
final data = json.decode(jsonSource) as Map;
@ -34,26 +40,39 @@ class ElectrumWallletSnapshot {
final mnemonic = data['mnemonic'] as String;
final addresses = addressesTmp
.whereType<String>()
.map((addr) => BitcoinAddressRecord.fromJSON(addr))
.map((addr) => BitcoinAddressRecord.fromJSON(addr, network))
.toList();
final balance = ElectrumBalance.fromJSON(data['balance'] as String) ??
ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
var regularAddressIndex = 0;
var changeAddressIndex = 0;
var regularAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
var changeAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
try {
regularAddressIndex = int.parse(data['account_index'] as String? ?? '0');
changeAddressIndex = int.parse(data['change_address_index'] as String? ?? '0');
} catch (_) {}
regularAddressIndexByType = {
SegwitAddresType.p2wpkh.toString(): int.parse(data['account_index'] as String? ?? '0')
};
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,
type: type,
password: password,
mnemonic: mnemonic,
addresses: addresses,
balance: balance,
regularAddressIndex: regularAddressIndex,
changeAddressIndex: changeAddressIndex);
regularAddressIndex: regularAddressIndexByType,
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_transaction_priority.dart';
import 'package:cw_core/crypto_currency.dart';
@ -20,17 +21,18 @@ part 'litecoin_wallet.g.dart';
class LitecoinWallet = LitecoinWalletBase with _$LitecoinWallet;
abstract class LitecoinWalletBase extends ElectrumWallet with Store {
LitecoinWalletBase(
{required String mnemonic,
required String password,
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required Uint8List seedBytes,
List<BitcoinAddressRecord>? initialAddresses,
ElectrumBalance? initialBalance,
int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0})
: super(
LitecoinWalletBase({
required String mnemonic,
required String password,
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required Uint8List seedBytes,
String? addressPageType,
List<BitcoinAddressRecord>? initialAddresses,
ElectrumBalance? initialBalance,
Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex,
}) : super(
mnemonic: mnemonic,
password: password,
walletInfo: walletInfo,
@ -41,41 +43,42 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
seedBytes: seedBytes,
currency: CryptoCurrency.ltc) {
walletAddresses = LitecoinWalletAddresses(
walletInfo,
electrumClient: electrumClient,
initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: hd,
sideHd: bitcoin.HDWallet
.fromSeed(seedBytes, network: networkType)
.derivePath("m/0'/1"),
networkType: networkType,);
walletInfo,
electrumClient: electrumClient,
initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: hd,
sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/1"),
network: network,
);
autorun((_) {
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
});
}
static Future<LitecoinWallet> create({
required String mnemonic,
required String password,
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
List<BitcoinAddressRecord>? initialAddresses,
ElectrumBalance? initialBalance,
int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0
}) async {
static Future<LitecoinWallet> create(
{required String mnemonic,
required String password,
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
String? addressPageType,
List<BitcoinAddressRecord>? initialAddresses,
ElectrumBalance? initialBalance,
Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex}) async {
return LitecoinWallet(
mnemonic: mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
seedBytes: await mnemonicToSeedBytes(mnemonic),
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex);
mnemonic: mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
seedBytes: await mnemonicToSeedBytes(mnemonic),
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
addressPageType: addressPageType,
);
}
static Future<LitecoinWallet> open({
@ -84,17 +87,20 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required String password,
}) async {
final snp = await ElectrumWallletSnapshot.load (name, walletInfo.type, password);
final snp =
await ElectrumWalletSnapshot.load(name, walletInfo.type, password, LitecoinNetwork.mainnet);
return LitecoinWallet(
mnemonic: snp.mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: snp.addresses,
initialBalance: snp.balance,
seedBytes: await mnemonicToSeedBytes(snp.mnemonic),
initialRegularAddressIndex: snp.regularAddressIndex,
initialChangeAddressIndex: snp.changeAddressIndex);
mnemonic: snp.mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: snp.addresses,
initialBalance: snp.balance,
seedBytes: await mnemonicToSeedBytes(snp.mnemonic),
initialRegularAddressIndex: snp.regularAddressIndex,
initialChangeAddressIndex: snp.changeAddressIndex,
addressPageType: snp.addressPageType,
);
}
@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:cw_bitcoin/electrum.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_core/wallet_info.dart';
import 'package:flutter/foundation.dart';
import 'package:mobx/mobx.dart';
part 'litecoin_wallet_addresses.g.dart';
class LitecoinWalletAddresses = LitecoinWalletAddressesBase
with _$LitecoinWalletAddresses;
class LitecoinWalletAddresses = LitecoinWalletAddressesBase with _$LitecoinWalletAddresses;
abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses
with Store {
abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
LitecoinWalletAddressesBase(
WalletInfo walletInfo,
{required bitcoin.HDWallet mainHd,
required bitcoin.HDWallet sideHd,
required bitcoin.NetworkType networkType,
required ElectrumClient electrumClient,
List<BitcoinAddressRecord>? initialAddresses,
int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0})
: super(
walletInfo,
initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: mainHd,
sideHd: sideHd,
electrumClient: electrumClient,
networkType: networkType);
WalletInfo walletInfo, {
required super.mainHd,
required super.sideHd,
required super.network,
required super.electrumClient,
super.initialAddresses,
super.initialRegularAddressIndex,
super.initialChangeAddressIndex,
}) : super(walletInfo);
@override
String getAddress({required int index, required bitcoin.HDWallet hd}) =>
generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
String getAddress(
{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;
@override
Future<LitecoinWallet> create(BitcoinNewWalletCredentials credentials) async {
Future<LitecoinWallet> create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async {
final wallet = await LitecoinWalletBase.create(
mnemonic: await generateMnemonic(),
password: credentials.password!,
@ -94,12 +94,12 @@ class LitecoinWalletService extends WalletService<
@override
Future<LitecoinWallet> restoreFromKeys(
BitcoinRestoreWalletFromWIFCredentials credentials) async =>
BitcoinRestoreWalletFromWIFCredentials credentials, {bool? isTestnet}) async =>
throw UnimplementedError();
@override
Future<LitecoinWallet> restoreFromSeed(
BitcoinRestoreWalletFromSeedCredentials credentials) async {
BitcoinRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async {
if (!validateMnemonic(credentials.mnemonic)) {
throw BitcoinMnemonicIsIncorrectException();
}

View file

@ -1,5 +1,5 @@
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_bitcoin/electrum.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
@ -9,22 +9,21 @@ import 'package:cw_core/wallet_type.dart';
class PendingBitcoinTransaction with PendingTransaction {
PendingBitcoinTransaction(this._tx, this.type,
{required this.electrumClient,
required this.amount,
required this.fee})
{required this.electrumClient, required this.amount, required this.fee, this.network})
: _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
final WalletType type;
final bitcoin.Transaction _tx;
final BtcTransaction _tx;
final ElectrumClient electrumClient;
final int amount;
final int fee;
final BasedUtxoNetwork? network;
@override
String get id => _tx.getId();
String get id => _tx.txId();
@override
String get hex => _tx.toHex();
String get hex => _tx.serialize();
@override
String get amountFormatted => bitcoinAmountToString(amount: amount);
@ -36,18 +35,16 @@ class PendingBitcoinTransaction with PendingTransaction {
@override
Future<void> commit() async {
final result =
await electrumClient.broadcastTransaction(transactionRaw: _tx.toHex());
final result = await electrumClient.broadcastTransaction(transactionRaw: hex, network: network);
if (result.isEmpty) {
throw BitcoinCommitTransactionException();
}
_listeners?.forEach((listener) => listener(transactionInfo()));
_listeners.forEach((listener) => listener(transactionInfo()));
}
void addListener(
void Function(ElectrumTransactionInfo transaction) listener) =>
void addListener(void Function(ElectrumTransactionInfo transaction) listener) =>
_listeners.add(listener);
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';
String scriptHash(String address, {required bitcoin.NetworkType networkType}) {
final outputScript =
bitcoin.Address.addressToOutputScript(address, networkType);
String scriptHash(String address, {required BasedUtxoNetwork network}) {
final outputScript = addressToOutputScript(address: address, network: network);
final parts = sha256.convert(outputScript).toString().split('');
var res = '';

View file

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

View file

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

View file

@ -28,17 +28,18 @@ part 'bitcoin_cash_wallet.g.dart';
class BitcoinCashWallet = BitcoinCashWalletBase with _$BitcoinCashWallet;
abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
BitcoinCashWalletBase(
{required String mnemonic,
required String password,
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required Uint8List seedBytes,
List<BitcoinAddressRecord>? initialAddresses,
ElectrumBalance? initialBalance,
int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0})
: super(
BitcoinCashWalletBase({
required String mnemonic,
required String password,
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required Uint8List seedBytes,
String? addressPageType,
List<BitcoinAddressRecord>? initialAddresses,
ElectrumBalance? initialBalance,
Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex,
}) : super(
mnemonic: mnemonic,
password: password,
walletInfo: walletInfo,
@ -48,40 +49,43 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
initialBalance: initialBalance,
seedBytes: seedBytes,
currency: CryptoCurrency.bch) {
walletAddresses = BitcoinCashWalletAddresses(walletInfo,
electrumClient: electrumClient,
initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: hd,
sideHd: bitcoin.HDWallet.fromSeed(seedBytes)
.derivePath("m/44'/145'/0'/1"),
networkType: networkType);
walletAddresses = BitcoinCashWalletAddresses(
walletInfo,
electrumClient: electrumClient,
initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: hd,
sideHd: bitcoin.HDWallet.fromSeed(seedBytes).derivePath("m/44'/145'/0'/1"),
network: network,
);
autorun((_) {
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
});
}
static Future<BitcoinCashWallet> create(
{required String mnemonic,
required String password,
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
String? addressPageType,
List<BitcoinAddressRecord>? initialAddresses,
ElectrumBalance? initialBalance,
int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0}) async {
Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex}) async {
return BitcoinCashWallet(
mnemonic: mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
seedBytes: await Mnemonic.toSeed(mnemonic),
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex);
mnemonic: mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
seedBytes: await Mnemonic.toSeed(mnemonic),
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
addressPageType: addressPageType,
);
}
static Future<BitcoinCashWallet> open({
@ -90,17 +94,20 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required String password,
}) async {
final snp = await ElectrumWallletSnapshot.load(name, walletInfo.type, password);
final snp = await ElectrumWalletSnapshot.load(
name, walletInfo.type, password, BitcoinCashNetwork.mainnet);
return BitcoinCashWallet(
mnemonic: snp.mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: snp.addresses,
initialBalance: snp.balance,
seedBytes: await Mnemonic.toSeed(snp.mnemonic),
initialRegularAddressIndex: snp.regularAddressIndex,
initialChangeAddressIndex: snp.changeAddressIndex);
mnemonic: snp.mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: snp.addresses,
initialBalance: snp.balance,
seedBytes: await Mnemonic.toSeed(snp.mnemonic),
initialRegularAddressIndex: snp.regularAddressIndex,
initialChangeAddressIndex: snp.changeAddressIndex,
addressPageType: snp.addressPageType,
);
}
@override
@ -270,20 +277,18 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
electrumClient: electrumClient, amount: amount, fee: fee);
}
bitbox.ECPair generateKeyPair(
{required bitcoin.HDWallet hd,
required int index}) =>
bitbox.ECPair generateKeyPair({required bitcoin.HDWallet hd, required int index}) =>
bitbox.ECPair.fromWIF(hd.derive(index).wif!);
@override
int feeAmountForPriority(
BitcoinTransactionPriority priority, int inputsCount, int outputsCount) =>
int feeAmountForPriority(BitcoinTransactionPriority priority, int inputsCount, int outputsCount,
{int? size}) =>
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);
int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount}) {
int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount, int? size}) {
int inputsCount = 0;
int totalValue = 0;
@ -323,9 +328,10 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
@override
String signMessage(String message, {String? address = null}) {
final index = address != null
? walletAddresses.addresses
? walletAddresses.allAddresses
.firstWhere((element) => element.address == AddressUtils.toLegacyAddress(address))
.index : null;
.index
: null;
final HD = index == null ? hd : hd.derive(index);
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:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:cw_bitcoin/utils.dart';
import 'package:cw_core/wallet_info.dart';
@ -11,24 +10,19 @@ part 'bitcoin_cash_wallet_addresses.g.dart';
class BitcoinCashWalletAddresses = BitcoinCashWalletAddressesBase with _$BitcoinCashWalletAddresses;
abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses with Store {
BitcoinCashWalletAddressesBase(WalletInfo walletInfo,
{required bitcoin.HDWallet mainHd,
required bitcoin.HDWallet sideHd,
required bitcoin.NetworkType networkType,
required ElectrumClient electrumClient,
List<BitcoinAddressRecord>? initialAddresses,
int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0})
: super(walletInfo,
initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: mainHd,
sideHd: sideHd,
electrumClient: electrumClient,
networkType: networkType);
BitcoinCashWalletAddressesBase(
WalletInfo walletInfo, {
required super.mainHd,
required super.sideHd,
required super.network,
required super.electrumClient,
super.initialAddresses,
super.initialRegularAddressIndex,
super.initialChangeAddressIndex,
}) : super(walletInfo);
@override
String getAddress({required int index, required bitcoin.HDWallet hd}) =>
generateP2PKHAddress(hd: hd, index: index, networkType: networkType);
String getAddress(
{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:cw_bitcoin_cash/cw_bitcoin_cash.dart';
import 'package:cw_core/balance.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/wallet_base.dart';
import 'package:cw_core/wallet_info.dart';
@ -15,8 +12,7 @@ import 'package:collection/collection.dart';
import 'package:hive/hive.dart';
class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredentials,
BitcoinCashRestoreWalletFromSeedCredentials,
BitcoinCashRestoreWalletFromWIFCredentials> {
BitcoinCashRestoreWalletFromSeedCredentials, BitcoinCashRestoreWalletFromWIFCredentials> {
BitcoinCashWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
final Box<WalletInfo> walletInfoSource;
@ -30,13 +26,9 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
File(await pathForWallet(name: name, type: getType())).existsSync();
@override
Future<BitcoinCashWallet> create(
credentials) async {
final strength = (credentials.seedPhraseLength == 12)
? 128
: (credentials.seedPhraseLength == 24)
? 256
: 128;
Future<BitcoinCashWallet> create(credentials, {bool? isTestnet}) async {
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
final wallet = await BitcoinCashWalletBase.create(
mnemonic: await Mnemonic.generate(strength: strength),
password: credentials.password!,
@ -49,21 +41,25 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
@override
Future<BitcoinCashWallet> openWallet(String name, String password) async {
final walletInfo = walletInfoSource.values.firstWhereOrNull(
(info) => info.id == WalletBase.idFor(name, getType()))!;
final walletInfo = walletInfoSource.values
.firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
try {
final wallet = await BitcoinCashWalletBase.open(
password: password, name: name, walletInfo: walletInfo,
password: password,
name: name,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource);
await wallet.init();
saveBackup(name);
return wallet;
} catch(_) {
} catch (_) {
await restoreWalletFilesFromBackup(name);
final wallet = await BitcoinCashWalletBase.open(
password: password, name: name, walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource);
password: password,
name: name,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource);
await wallet.init();
return wallet;
}
@ -71,17 +67,16 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
@override
Future<void> remove(String wallet) async {
File(await pathForWalletDir(name: wallet, type: getType()))
.delete(recursive: true);
final walletInfo = walletInfoSource.values.firstWhereOrNull(
(info) => info.id == WalletBase.idFor(wallet, getType()))!;
File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
final walletInfo = walletInfoSource.values
.firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!;
await walletInfoSource.delete(walletInfo.key);
}
@override
Future<void> rename(String currentName, String password, String newName) async {
final currentWalletInfo = walletInfoSource.values.firstWhereOrNull(
(info) => info.id == WalletBase.idFor(currentName, getType()))!;
final currentWalletInfo = walletInfoSource.values
.firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!;
final currentWallet = await BitcoinCashWalletBase.open(
password: password,
name: currentName,
@ -99,15 +94,14 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
}
@override
Future<BitcoinCashWallet>
restoreFromKeys(credentials) {
Future<BitcoinCashWallet> restoreFromKeys(credentials, {bool? isTestnet}) {
// TODO: implement restoreFromKeys
throw UnimplementedError('restoreFromKeys() is not implemented');
}
@override
Future<BitcoinCashWallet> restoreFromSeed(
BitcoinCashRestoreWalletFromSeedCredentials credentials) async {
Future<BitcoinCashWallet> restoreFromSeed(BitcoinCashRestoreWalletFromSeedCredentials credentials,
{bool? isTestnet}) async {
if (!validateMnemonic(credentials.mnemonic)) {
throw BitcoinCashMnemonicIsIncorrectException();
}

View file

@ -29,7 +29,10 @@ dependencies:
git:
url: https://github.com/cake-tech/bitbox-flutter.git
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);
String signMessage(String message, {String? address = null}) => throw UnimplementedError();
bool? isTestnet;
}

View file

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

View file

@ -9,11 +9,11 @@ abstract class WalletService<N extends WalletCredentials, RFS extends WalletCred
RFK extends WalletCredentials> {
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);

View file

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

View file

@ -22,7 +22,7 @@ abstract class EVMChainWalletService<T extends EVMChainWallet> extends WalletSer
WalletType getType();
@override
Future<T> create(EVMChainNewWalletCredentials credentials);
Future<T> create(EVMChainNewWalletCredentials credentials, {bool? isTestnet});
@override
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);
@override
Future<T> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials);
Future<T> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials, {bool? isTestnet});
@override
Future<T> restoreFromSeed(EVMChainRestoreWalletFromSeedCredentials credentials);
Future<T> restoreFromSeed(EVMChainRestoreWalletFromSeedCredentials credentials, {bool? isTestnet});
@override
Future<bool> isWalletExit(String name) async =>

View file

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

View file

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

View file

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

View file

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

View file

@ -1,181 +1,191 @@
part of 'bitcoin.dart';
class CWBitcoin extends Bitcoin {
@override
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
TransactionPriority getMediumTransactionPriority() => BitcoinTransactionPriority.medium;
@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);
}
WalletCredentials createBitcoinRestoreWalletFromSeedCredentials(
{required String name, required String mnemonic, required String password}) =>
BitcoinRestoreWalletFromSeedCredentials(name: name, mnemonic: mnemonic, password: password);
@override
TransactionPriority getBitcoinTransactionPriorityMedium()
=> BitcoinTransactionPriority.medium;
WalletCredentials createBitcoinRestoreWalletFromWIFCredentials(
{required String name,
required String password,
required String wif,
WalletInfo? walletInfo}) =>
BitcoinRestoreWalletFromWIFCredentials(
name: name, password: password, wif: wif, walletInfo: walletInfo);
@override
TransactionPriority getLitecoinTransactionPriorityMedium()
=> LitecoinTransactionPriority.medium;
WalletCredentials createBitcoinNewWalletCredentials(
{required String name, WalletInfo? walletInfo}) =>
BitcoinNewWalletCredentials(name: name, walletInfo: walletInfo);
@override
TransactionPriority getBitcoinTransactionPrioritySlow()
=> BitcoinTransactionPriority.slow;
List<String> getWordList() => wordlist;
@override
TransactionPriority getLitecoinTransactionPrioritySlow()
=> LitecoinTransactionPriority.slow;
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/core/validator.dart';
import 'package:cake_wallet/solana/solana.dart';
@ -9,7 +9,9 @@ class AddressValidator extends TextValidator {
AddressValidator({required CryptoCurrency type})
: super(
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),
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}\$'
'|^[0-9a-zA-Z]{105}\$|^addr1[0-9a-zA-Z]{98}\$';
case CryptoCurrency.btc:
return '^3[0-9a-zA-Z]{32}\$|^3[0-9a-zA-Z]{33}\$|^bc1[0-9a-zA-Z]{59}\$';
return '^${P2pkhAddress.regex.pattern}\$|^${P2shAddress.regex.pattern}\$|^${P2wpkhAddress.regex.pattern}\$|${P2trAddress.regex.pattern}\$|^${P2wshAddress.regex.pattern}\$';
case CryptoCurrency.nano:
return '[0-9a-zA-Z_]';
case CryptoCurrency.banano:
@ -89,7 +91,7 @@ class AddressValidator extends TextValidator {
case CryptoCurrency.dai:
case CryptoCurrency.dash:
case CryptoCurrency.eos:
return '[0-9a-zA-Z]';
return '[0-9a-zA-Z]';
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}\$';
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]|^)[0-9a-zA-Z]{106}([^0-9a-zA-Z]|\$)';
case CryptoCurrency.btc:
return '([^0-9a-zA-Z]|^)1[0-9a-zA-Z]{32}([^0-9a-zA-Z]|\$)'
'|([^0-9a-zA-Z]|^)1[0-9a-zA-Z]{33}([^0-9a-zA-Z]|\$)'
'|([^0-9a-zA-Z]|^)3[0-9a-zA-Z]{32}([^0-9a-zA-Z]|\$)'
'|([^0-9a-zA-Z]|^)3[0-9a-zA-Z]{33}([^0-9a-zA-Z]|\$)'
'|([^0-9a-zA-Z]|^)bc1[0-9a-zA-Z]{39}([^0-9a-zA-Z]|\$)'
'|([^0-9a-zA-Z]|^)bc1[0-9a-zA-Z]{59}([^0-9a-zA-Z]|\$)';
return '([^0-9a-zA-Z]|^)${P2pkhAddress.regex.pattern}|\$)'
'([^0-9a-zA-Z]|^)${P2shAddress.regex.pattern}|\$)'
'([^0-9a-zA-Z]|^)${P2wpkhAddress.regex.pattern}|\$)'
'([^0-9a-zA-Z]|^)${P2wshAddress.regex.pattern}|\$)'
'([^0-9a-zA-Z]|^)${P2trAddress.regex.pattern}|\$)';
case CryptoCurrency.ltc:
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]|\$)'

View file

@ -2,7 +2,6 @@ import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/wallet_info.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:hive/hive.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);
final password = generateWalletPassword();
credentials.password = password;
@ -63,7 +62,7 @@ class WalletCreationService {
credentials.seedPhraseLength = settingsStore.seedPhraseLength.value;
}
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) {
await sharedPreferences.setBool(
@ -73,12 +72,12 @@ class WalletCreationService {
return wallet;
}
Future<WalletBase> restoreFromKeys(WalletCredentials credentials) async {
Future<WalletBase> restoreFromKeys(WalletCredentials credentials, {bool? isTestnet}) async {
checkIfExists(credentials.name);
final password = generateWalletPassword();
credentials.password = password;
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) {
await sharedPreferences.setBool(
@ -88,12 +87,12 @@ class WalletCreationService {
return wallet;
}
Future<WalletBase> restoreFromSeed(WalletCredentials credentials) async {
Future<WalletBase> restoreFromSeed(WalletCredentials credentials, {bool? isTestnet}) async {
checkIfExists(credentials.name);
final password = generateWalletPassword();
credentials.password = password;
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) {
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/exchange_api_mode.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/nano/nano.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 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 havenDefaultNodeUri = 'nodes.havenprotocol.org:443';
const ethereumDefaultNodeUri = 'ethereum.publicnode.com';
@ -334,6 +338,12 @@ Node? getBitcoinDefaultElectrumServer({required Box<Node> nodes}) {
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}) {
return nodes.values
.firstWhereOrNull((Node node) => node.uriRaw == cakeWalletLitecoinElectrumUri) ??
@ -503,8 +513,15 @@ Future<void> rewriteSecureStoragePin({required FlutterSecureStorage secureStorag
}
Future<void> changeBitcoinCurrentElectrumServerToDefault(
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
final server = getBitcoinDefaultElectrumServer(nodes: nodes);
{required SharedPreferences sharedPreferences,
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;
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));
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>(
builder: (_) => AdvancedPrivacySettingsPage(
getIt.get<AdvancedPrivacySettingsViewModel>(param1: type),
getIt.get<NodeCreateOrEditViewModel>(param1: type, param2: false),
getIt.get<SeedTypeViewModel>()));
useTestnet,
toggleTestnet,
getIt.get<AdvancedPrivacySettingsViewModel>(param1: type),
getIt.get<NodeCreateOrEditViewModel>(param1: type, param2: false),
getIt.get<SeedTypeViewModel>(),
));
case Routes.anonPayInvoicePage:
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/anonpay/anonpay_donation_link_info.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/widgets/gradient_background.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:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
import 'package:bitcoin_base/bitcoin_base.dart';
class AddressPage extends BasePage {
AddressPage({
@ -69,7 +71,7 @@ class AddressPage extends BasePage {
size: 16,
);
final _closeButton =
currentTheme.type == ThemeType.dark ? closeButtonImageDarkTheme : closeButtonImage;
currentTheme.type == ThemeType.dark ? closeButtonImageDarkTheme : closeButtonImage;
bool isMobileView = responsiveLayoutUtil.shouldRenderMobileUI;
@ -163,11 +165,10 @@ class AddressPage extends BasePage {
return SelectButton(
text: addressListViewModel.buttonTitle,
onTap: () async => dashboardViewModel.isAutoGenerateSubaddressesEnabled &&
(WalletType.monero == addressListViewModel.wallet.type ||
WalletType.haven == addressListViewModel.wallet.type)
(WalletType.monero == addressListViewModel.wallet.type ||
WalletType.haven == addressListViewModel.wallet.type)
? await showPopUp<void>(
context: context,
builder: (_) => getIt.get<MoneroAccountListPage>())
context: context, builder: (_) => getIt.get<MoneroAccountListPage>())
: Navigator.of(context).pushNamed(Routes.receive),
textColor: Theme.of(context).extension<SyncIndicatorTheme>()!.textColor,
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
@ -229,6 +230,21 @@ class AddressPage extends BasePage {
);
}
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:
}
});

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/fiat_api_mode.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/seed_type_view_model.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/material.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';
class AdvancedPrivacySettingsPage extends BasePage {
AdvancedPrivacySettingsPage(
AdvancedPrivacySettingsPage(this.useTestnet, this.toggleUseTestnet,
this.advancedPrivacySettingsViewModel, this.nodeViewModel, this.seedTypeViewModel);
final AdvancedPrivacySettingsViewModel advancedPrivacySettingsViewModel;
@ -29,13 +31,16 @@ class AdvancedPrivacySettingsPage extends BasePage {
@override
String get title => S.current.privacy_settings;
final bool useTestnet;
final Function(bool? val) toggleUseTestnet;
@override
Widget body(BuildContext context) => AdvancedPrivacySettingsBody(
Widget body(BuildContext context) => AdvancedPrivacySettingsBody(useTestnet, toggleUseTestnet,
advancedPrivacySettingsViewModel, nodeViewModel, seedTypeViewModel);
}
class AdvancedPrivacySettingsBody extends StatefulWidget {
const AdvancedPrivacySettingsBody(
const AdvancedPrivacySettingsBody(this.useTestnet, this.toggleUseTestnet,
this.privacySettingsViewModel, this.nodeViewModel, this.seedTypeViewModel,
{Key? key})
: super(key: key);
@ -44,6 +49,9 @@ class AdvancedPrivacySettingsBody extends StatefulWidget {
final NodeCreateOrEditViewModel nodeViewModel;
final SeedTypeViewModel seedTypeViewModel;
final bool useTestnet;
final Function(bool? val) toggleUseTestnet;
@override
_AdvancedPrivacySettingsBodyState createState() => _AdvancedPrivacySettingsBodyState();
}
@ -52,9 +60,14 @@ class _AdvancedPrivacySettingsBodyState extends State<AdvancedPrivacySettingsBod
_AdvancedPrivacySettingsBodyState();
final _formKey = GlobalKey<FormState>();
bool? testnetValue;
@override
Widget build(BuildContext context) {
if (testnetValue == null && widget.useTestnet != null) {
testnetValue = widget.useTestnet;
}
return Container(
padding: EdgeInsets.only(top: 24),
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),
@ -137,6 +163,13 @@ class _AdvancedPrivacySettingsBodyState extends State<AdvancedPrivacySettingsBod
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();
}

View file

@ -40,7 +40,9 @@ class NewWalletPage extends BasePage {
@override
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 {
@ -187,7 +189,6 @@ class _WalletNameFormState extends State<WalletNameForm> {
),
),
),
if (_walletNewVM.hasLanguageSelector) ...[
if (_walletNewVM.hasSeedType) ...[
Observer(
@ -222,7 +223,7 @@ class _WalletNameFormState extends State<WalletNameForm> {
),
),
)
]
],
],
),
),
@ -245,8 +246,11 @@ class _WalletNameFormState extends State<WalletNameForm> {
const SizedBox(height: 25),
GestureDetector(
onTap: () {
Navigator.of(context)
.pushNamed(Routes.advancedPrivacySettings, arguments: _walletNewVM.type);
Navigator.of(context).pushNamed(Routes.advancedPrivacySettings, arguments: {
"type": _walletNewVM.type,
"useTestnet": _walletNewVM.useTestnet,
"toggleTestnet": _walletNewVM.toggleUseTestnet
});
},
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/di.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/src/screens/dashboard/widgets/present_receive_option_picker.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_invoice_info.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/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';

View file

@ -210,8 +210,12 @@ class WalletRestorePage extends BasePage {
const SizedBox(height: 25),
GestureDetector(
onTap: () {
Navigator.of(context).pushNamed(Routes.advancedPrivacySettings,
arguments: walletRestoreViewModel.type);
Navigator.of(context)
.pushNamed(Routes.advancedPrivacySettings, arguments: {
'type': walletRestoreViewModel.type,
'useTestnet': walletRestoreViewModel.useTestnet,
'toggleTestnet': walletRestoreViewModel.toggleUseTestnet
});
},
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/entities/fiat_currency.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:cw_core/crypto_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)
.toList();
transactions = ObservableList.of(_accountTransactions.map((transaction) =>
TransactionListItem(
transaction: transaction,
balanceViewModel: balanceViewModel,
settingsStore: appStore.settingsStore)));
final sortedTransactions = [..._accountTransactions];
sortedTransactions.sort((a, b) => a.date.compareTo(b.date));
transactions = ObservableList.of(sortedTransactions.map((transaction) => TransactionListItem(
transaction: transaction,
balanceViewModel: balanceViewModel,
settingsStore: appStore.settingsStore)));
} else {
transactions = ObservableList.of(wallet.transactionHistory.transactions.values.map(
(transaction) => TransactionListItem(
transaction: transaction,
balanceViewModel: balanceViewModel,
settingsStore: appStore.settingsStore)));
final sortedTransactions = [...wallet.transactionHistory.transactions.values];
sortedTransactions.sort((a, b) => a.date.compareTo(b.date));
transactions = ObservableList.of(sortedTransactions.map((transaction) => TransactionListItem(
transaction: transaction,
balanceViewModel: balanceViewModel,
settingsStore: appStore.settingsStore)));
}
// 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_type.dart';
import 'package:mobx/mobx.dart';
@ -9,11 +10,20 @@ class ReceiveOptionViewModel = ReceiveOptionViewModelBase with _$ReceiveOptionVi
abstract class ReceiveOptionViewModelBase with Store {
ReceiveOptionViewModelBase(this._wallet, this.initialPageOption)
: selectedReceiveOption = initialPageOption ?? ReceivePageOption.mainnet,
: selectedReceiveOption = initialPageOption ??
(_wallet.type == WalletType.bitcoin
? bitcoin!.getSelectedAddressType(_wallet)
: ReceivePageOption.mainnet),
_options = [] {
final walletType = _wallet.type;
_options =
walletType == WalletType.haven ? [ReceivePageOption.mainnet] : ReceivePageOption.values;
_options = walletType == WalletType.haven
? [ReceivePageOption.mainnet]
: walletType == WalletType.bitcoin
? [
...bitcoin!.getBitcoinReceivePageOptions(),
...ReceivePageOptions.where((element) => element != ReceivePageOption.mainnet)
]
: ReceivePageOptions;
}
final WalletBase _wallet;

View file

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

View file

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

View file

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

View file

@ -388,9 +388,6 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
wallet.type == WalletType.bitcoin ||
wallet.type == WalletType.litecoin;
// wallet.type == WalletType.nano ||
// wallet.type == WalletType.banano; TODO: nano accounts are disabled for now
@computed
bool get isElectrumWallet =>
wallet.type == WalletType.bitcoin ||
@ -409,16 +406,17 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
void setAddress(WalletAddressListItem 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() {
_baseItems = [];
if (wallet.type == WalletType.monero ||
wallet.type ==
WalletType
.haven /*||
wallet.type == WalletType.nano ||
wallet.type == WalletType.banano*/
) {
if (wallet.type == WalletType.monero || wallet.type == WalletType.haven) {
_baseItems.add(WalletAccountListHeader());
}

View file

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

View file

@ -87,6 +87,6 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
@override
Future<WalletBase> process(WalletCredentials credentials) async {
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
Future<WalletBase> process(WalletCredentials credentials) async {
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_ssl": "استخدم SSL",
"use_suggested": "استخدام المقترح",
"use_testnet": "استخدم testnet",
"variable_pair_not_supported": "هذا الزوج المتغير غير مدعوم في التبادلات المحددة",
"verification": "تَحَقّق",
"verify_with_2fa": "تحقق مع Cake 2FA",

View file

@ -719,6 +719,7 @@
"use_card_info_two": "Средствата се обръщат в USD, когато биват запазени в предплатената карта, а не в дигитална валута.",
"use_ssl": "Използване на SSL",
"use_suggested": "Използване на предложеното",
"use_testnet": "Използвайте TestNet",
"variable_pair_not_supported": "Този variable pair не се поддържа от избраната борса",
"verification": "Потвърждаване",
"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_ssl": "Použít SSL",
"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",
"verification": "Ověření",
"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_ssl": "SSL 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",
"verification": "Verifizierung",
"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_ssl": "Use SSL",
"use_suggested": "Use Suggested",
"use_testnet": "Use Testnet",
"variable_pair_not_supported": "This variable pair is not supported with the selected exchanges",
"verification": "Verification",
"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_ssl": "Utilice SSL",
"use_suggested": "Usar sugerido",
"use_testnet": "Use TestNet",
"variable_pair_not_supported": "Este par de variables no es compatible con los intercambios seleccionados",
"verification": "Verificación",
"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_ssl": "Utiliser SSL",
"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",
"verification": "Vérification",
"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_ssl": "Yi amfani da SSL",
"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",
"verification": "tabbatar",
"verify_with_2fa": "Tabbatar da Cake 2FA",

View file

@ -721,6 +721,7 @@
"use_card_info_two": "डिजिटल मुद्राओं में नहीं, प्रीपेड खाते में रखे जाने पर निधियों को यूएसडी में बदल दिया जाता है।",
"use_ssl": "उपयोग SSL",
"use_suggested": "सुझाए गए का प्रयोग करें",
"use_testnet": "टेस्टनेट का उपयोग करें",
"variable_pair_not_supported": "यह परिवर्तनीय जोड़ी चयनित एक्सचेंजों के साथ समर्थित नहीं है",
"verification": "सत्यापन",
"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_ssl": "Koristi SSL",
"use_suggested": "Koristite predloženo",
"use_testnet": "Koristite TestNet",
"variable_pair_not_supported": "Ovaj par varijabli nije podržan s odabranim burzama",
"verification": "Potvrda",
"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_ssl": "Gunakan SSL",
"use_suggested": "Gunakan yang Disarankan",
"use_testnet": "Gunakan TestNet",
"variable_pair_not_supported": "Pasangan variabel ini tidak didukung dengan bursa yang dipilih",
"verification": "Verifikasi",
"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_ssl": "Usa SSL",
"use_suggested": "Usa suggerito",
"use_testnet": "Usa TestNet",
"variable_pair_not_supported": "Questa coppia di variabili non è supportata con gli scambi selezionati",
"verification": "Verifica",
"verify_with_2fa": "Verifica con Cake 2FA",

View file

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

View file

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

View file

@ -719,6 +719,7 @@
"use_card_info_two": "ဒစ်ဂျစ်တယ်ငွေကြေးများဖြင့်မဟုတ်ဘဲ ကြိုတင်ငွေပေးချေသည့်အကောင့်တွင် သိမ်းထားသည့်အခါ ရန်ပုံငွေများကို USD သို့ ပြောင်းလဲပါသည်။",
"use_ssl": "SSL ကိုသုံးပါ။",
"use_suggested": "အကြံပြုထားသည်ကို အသုံးပြုပါ။",
"use_testnet": "testnet ကိုသုံးပါ",
"variable_pair_not_supported": "ရွေးချယ်ထားသော ဖလှယ်မှုများဖြင့် ဤပြောင်းလဲနိုင်သောအတွဲကို ပံ့ပိုးမထားပါ။",
"verification": "စိစစ်ခြင်း။",
"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_ssl": "Gebruik SSL",
"use_suggested": "Gebruik aanbevolen",
"use_testnet": "Gebruik testnet",
"variable_pair_not_supported": "Dit variabelenpaar wordt niet ondersteund met de geselecteerde uitwisselingen",
"verification": "Verificatie",
"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_ssl": "Użyj SSL",
"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",
"verification": "Weryfikacja",
"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_ssl": "Use SSL",
"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",
"verification": "Verificação",
"verify_with_2fa": "Verificar com Cake 2FA",

View file

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

View file

@ -719,6 +719,7 @@
"use_card_info_two": "เงินจะถูกแปลงค่าเป็นดอลลาร์สหรัฐเมื่อถือไว้ในบัญชีสำรองเงิน ไม่ใช่สกุลเงินดิจิตอล",
"use_ssl": "ใช้ SSL",
"use_suggested": "ใช้ที่แนะนำ",
"use_testnet": "ใช้ testnet",
"variable_pair_not_supported": "คู่ความสัมพันธ์ที่เปลี่ยนแปลงได้นี้ไม่สนับสนุนกับหุ้นที่เลือก",
"verification": "การตรวจสอบ",
"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_ssl": "Gumamit ng SSL",
"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",
"verification": "Pag -verify",
"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_ssl": "SSL kullan",
"use_suggested": "Önerileni Kullan",
"use_testnet": "TestNet kullanın",
"variable_pair_not_supported": "Bu değişken paritesi seçilen borsalarda desteklenmemekte",
"verification": "Doğrulama",
"verify_with_2fa": "Cake 2FA ile Doğrulayın",

View file

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

View file

@ -721,6 +721,7 @@
"use_card_info_two": "رقوم کو امریکی ڈالر میں تبدیل کیا جاتا ہے جب پری پیڈ اکاؤنٹ میں رکھا جاتا ہے، ڈیجیٹل کرنسیوں میں نہیں۔",
"use_ssl": "SSL استعمال کریں۔",
"use_suggested": "تجویز کردہ استعمال کریں۔",
"use_testnet": "ٹیسٹ نیٹ استعمال کریں",
"variable_pair_not_supported": "یہ متغیر جوڑا منتخب ایکسچینجز کے ساتھ تعاون یافتہ نہیں ہے۔",
"verification": "تصدیق",
"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_ssl": "Lo SSL",
"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ìí",
"verification": "Ìjẹ́rìísí",
"verify_with_2fa": "Ṣeẹda pẹlu Cake 2FA",

View file

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

View file

@ -22,8 +22,8 @@ MONERO_COM_PACKAGE="com.monero.app"
MONERO_COM_SCHEME="monero.com"
CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="4.13.3"
CAKEWALLET_BUILD_NUMBER=192
CAKEWALLET_VERSION="4.14.0"
CAKEWALLET_BUILD_NUMBER=193
CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet"
CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet"
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"
CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="4.13.3"
CAKEWALLET_BUILD_NUMBER=212
CAKEWALLET_VERSION="4.14.0"
CAKEWALLET_BUILD_NUMBER=213
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
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:hive/hive.dart';""";
const bitcoinCWHeaders = """
import 'package:cw_bitcoin/bitcoin_receive_page_option.dart';
import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_bitcoin/bitcoin_unspent.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_transaction_credentials.dart';
import 'package:cw_bitcoin/litecoin_wallet_service.dart';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:mobx/mobx.dart';
""";
const bitcoinCwPart = "part 'cw_bitcoin.dart';";
@ -139,6 +141,10 @@ abstract class Bitcoin {
TransactionPriority getLitecoinTransactionPriorityMedium();
TransactionPriority getBitcoinTransactionPrioritySlow();
TransactionPriority getLitecoinTransactionPrioritySlow();
Future<void> setAddressType(Object wallet, dynamic option);
BitcoinReceivePageOption getSelectedAddressType(Object wallet);
List<BitcoinReceivePageOption> getBitcoinReceivePageOptions();
}
""";