mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-22 03:29:36 +00:00
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:
parent
109bba4301
commit
a3a35f05e1
91 changed files with 1851 additions and 1333 deletions
|
@ -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
|
|
@ -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 '';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
|
42
cw_bitcoin/lib/bitcoin_receive_page_option.dart
Normal file
42
cw_bitcoin/lib/bitcoin_receive_page_option.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>;
|
||||
|
@ -451,8 +460,8 @@ class ElectrumClient {
|
|||
_methodHandler(method: method, request: response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (id != null){
|
||||
|
||||
if (id != null) {
|
||||
_finish(id, result);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 = '';
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
|
13
cw_core/lib/enumerate.dart
Normal file
13
cw_core/lib/enumerate.dart
Normal 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;
|
||||
}
|
21
cw_core/lib/receive_page_option.dart
Normal file
21
cw_core/lib/receive_page_option.dart
Normal 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
|
||||
];
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 =>
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
@ -62,7 +62,7 @@ class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
|
|||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
);
|
||||
|
||||
|
||||
await wallet.init();
|
||||
await wallet.save();
|
||||
return wallet;
|
||||
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
@override
|
||||
TransactionPriority getBitcoinTransactionPriorityMedium()
|
||||
=> BitcoinTransactionPriority.medium;
|
||||
WalletCredentials createBitcoinRestoreWalletFromSeedCredentials(
|
||||
{required String name, required String mnemonic, required String password}) =>
|
||||
BitcoinRestoreWalletFromSeedCredentials(name: name, mnemonic: mnemonic, password: password);
|
||||
|
||||
@override
|
||||
TransactionPriority getLitecoinTransactionPriorityMedium()
|
||||
=> LitecoinTransactionPriority.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 getBitcoinTransactionPrioritySlow()
|
||||
=> BitcoinTransactionPriority.slow;
|
||||
|
||||
WalletCredentials createBitcoinNewWalletCredentials(
|
||||
{required String name, WalletInfo? walletInfo}) =>
|
||||
BitcoinNewWalletCredentials(name: name, walletInfo: walletInfo);
|
||||
|
||||
@override
|
||||
TransactionPriority getLitecoinTransactionPrioritySlow()
|
||||
=> LitecoinTransactionPriority.slow;
|
||||
}
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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]|\$)'
|
||||
|
@ -297,4 +298,4 @@ class AddressValidator extends TextValidator {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -721,6 +721,7 @@
|
|||
"use_card_info_two": "डिजिटल मुद्राओं में नहीं, प्रीपेड खाते में रखे जाने पर निधियों को यूएसडी में बदल दिया जाता है।",
|
||||
"use_ssl": "उपयोग SSL",
|
||||
"use_suggested": "सुझाए गए का प्रयोग करें",
|
||||
"use_testnet": "टेस्टनेट का उपयोग करें",
|
||||
"variable_pair_not_supported": "यह परिवर्तनीय जोड़ी चयनित एक्सचेंजों के साथ समर्थित नहीं है",
|
||||
"verification": "सत्यापन",
|
||||
"verify_with_2fa": "केक 2FA के साथ सत्यापित करें",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 で検証する",
|
||||
|
|
|
@ -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로 확인",
|
||||
|
|
|
@ -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 ဖြင့် စစ်ဆေးပါ။",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -721,6 +721,7 @@
|
|||
"use_card_info_two": "رقوم کو امریکی ڈالر میں تبدیل کیا جاتا ہے جب پری پیڈ اکاؤنٹ میں رکھا جاتا ہے، ڈیجیٹل کرنسیوں میں نہیں۔",
|
||||
"use_ssl": "SSL استعمال کریں۔",
|
||||
"use_suggested": "تجویز کردہ استعمال کریں۔",
|
||||
"use_testnet": "ٹیسٹ نیٹ استعمال کریں",
|
||||
"variable_pair_not_supported": "یہ متغیر جوڑا منتخب ایکسچینجز کے ساتھ تعاون یافتہ نہیں ہے۔",
|
||||
"verification": "تصدیق",
|
||||
"verify_with_2fa": "کیک 2FA سے تصدیق کریں۔",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 验证",
|
||||
|
|
|
@ -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
16
scripts/android/shell.nix
Normal 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
|
||||
];
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
""";
|
||||
|
||||
|
|
Loading…
Reference in a new issue