mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-06-28 20:39:51 +00:00
feat: balance and scanning improvements
This commit is contained in:
parent
2502a14b50
commit
fd4c310088
22 changed files with 655 additions and 558 deletions
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
||||||
|
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||||
|
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||||
import 'package:cw_bitcoin/seedbyte_types.dart';
|
import 'package:cw_bitcoin/seedbyte_types.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
|
||||||
|
@ -11,7 +12,7 @@ class BaseBitcoinAddressRecord {
|
||||||
required this.index,
|
required this.index,
|
||||||
bool isChange = false,
|
bool isChange = false,
|
||||||
int txCount = 0,
|
int txCount = 0,
|
||||||
int balance = 0,
|
ElectrumBalance? balance,
|
||||||
String name = '',
|
String name = '',
|
||||||
bool isUsed = false,
|
bool isUsed = false,
|
||||||
required this.type,
|
required this.type,
|
||||||
|
@ -20,7 +21,7 @@ class BaseBitcoinAddressRecord {
|
||||||
bool? isHidden,
|
bool? isHidden,
|
||||||
String? derivationPath,
|
String? derivationPath,
|
||||||
}) : _txCount = txCount,
|
}) : _txCount = txCount,
|
||||||
_balance = balance,
|
balance = balance ?? ElectrumBalance.zero(),
|
||||||
_name = name,
|
_name = name,
|
||||||
_isUsed = isUsed,
|
_isUsed = isUsed,
|
||||||
isHidden = isHidden ?? isChange,
|
isHidden = isHidden ?? isChange,
|
||||||
|
@ -38,7 +39,7 @@ class BaseBitcoinAddressRecord {
|
||||||
bool get isChange => _isChange;
|
bool get isChange => _isChange;
|
||||||
final int index;
|
final int index;
|
||||||
int _txCount;
|
int _txCount;
|
||||||
int _balance;
|
ElectrumBalance balance;
|
||||||
String _name;
|
String _name;
|
||||||
bool _isUsed;
|
bool _isUsed;
|
||||||
|
|
||||||
|
@ -48,12 +49,8 @@ class BaseBitcoinAddressRecord {
|
||||||
|
|
||||||
String get name => _name;
|
String get name => _name;
|
||||||
|
|
||||||
int get balance => _balance;
|
|
||||||
|
|
||||||
set txCount(int value) => _txCount = value;
|
set txCount(int value) => _txCount = value;
|
||||||
|
|
||||||
set balance(int value) => _balance = value;
|
|
||||||
|
|
||||||
bool get isUsed => _isUsed;
|
bool get isUsed => _isUsed;
|
||||||
|
|
||||||
void setAsUsed() {
|
void setAsUsed() {
|
||||||
|
@ -78,12 +75,12 @@ class BaseBitcoinAddressRecord {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getIsUsed() {
|
bool getIsUsed() {
|
||||||
return isUsed || txCount != 0 || balance != 0;
|
return isUsed || txCount != 0 || balance.hasBalance();
|
||||||
}
|
}
|
||||||
|
|
||||||
// An address not yet used for receiving funds
|
// An address not yet used for receiving funds
|
||||||
bool getIsStillReceiveable(bool autoGenerateAddresses) =>
|
bool getIsStillReceiveable(bool autoGenerateAddresses) =>
|
||||||
!autoGenerateAddresses || (!getIsUsed() && !isHidden);
|
!autoGenerateAddresses || (!getIsUsed() && (isChange || !isHidden));
|
||||||
|
|
||||||
String toJSON() => json.encode({
|
String toJSON() => json.encode({
|
||||||
'address': address,
|
'address': address,
|
||||||
|
@ -94,7 +91,7 @@ class BaseBitcoinAddressRecord {
|
||||||
'isUsed': isUsed,
|
'isUsed': isUsed,
|
||||||
'txCount': txCount,
|
'txCount': txCount,
|
||||||
'name': name,
|
'name': name,
|
||||||
'balance': balance,
|
'balance': balance.toJSON(),
|
||||||
'type': type.toString(),
|
'type': type.toString(),
|
||||||
'runtimeType': runtimeType.toString(),
|
'runtimeType': runtimeType.toString(),
|
||||||
'seedBytesType': seedBytesType?.value,
|
'seedBytesType': seedBytesType?.value,
|
||||||
|
@ -115,6 +112,10 @@ class BaseBitcoinAddressRecord {
|
||||||
: SeedBytesType.old_electrum)
|
: SeedBytesType.old_electrum)
|
||||||
: SeedBytesType.fromValue(seedBytesTypeSnp.toString());
|
: SeedBytesType.fromValue(seedBytesTypeSnp.toString());
|
||||||
|
|
||||||
|
final balance = decoded['balance'] is String
|
||||||
|
? ElectrumBalance.fromJSON(decoded['balance'] as String)
|
||||||
|
: ElectrumBalance.zero();
|
||||||
|
|
||||||
return BaseBitcoinAddressRecord(
|
return BaseBitcoinAddressRecord(
|
||||||
decoded['address'] as String,
|
decoded['address'] as String,
|
||||||
network: BasedUtxoNetwork.fromName(decoded['network'] as String),
|
network: BasedUtxoNetwork.fromName(decoded['network'] as String),
|
||||||
|
@ -125,7 +126,7 @@ class BaseBitcoinAddressRecord {
|
||||||
isUsed: decoded['isUsed'] as bool? ?? false,
|
isUsed: decoded['isUsed'] as bool? ?? false,
|
||||||
txCount: decoded['txCount'] as int? ?? 0,
|
txCount: decoded['txCount'] as int? ?? 0,
|
||||||
name: decoded['name'] as String? ?? '',
|
name: decoded['name'] as String? ?? '',
|
||||||
balance: decoded['balance'] as int? ?? 0,
|
balance: balance,
|
||||||
derivationPath: decoded['derivationPath'] as String? ?? '',
|
derivationPath: decoded['derivationPath'] as String? ?? '',
|
||||||
type: decoded['type'] != null && decoded['type'] != ''
|
type: decoded['type'] != null && decoded['type'] != ''
|
||||||
? BitcoinAddressType.values
|
? BitcoinAddressType.values
|
||||||
|
@ -140,7 +141,6 @@ class BaseBitcoinAddressRecord {
|
||||||
]) {
|
]) {
|
||||||
final decoded = json.decode(jsonSource) as Map;
|
final decoded = json.decode(jsonSource) as Map;
|
||||||
final base = buildFromJSON(jsonSource, derivationInfo);
|
final base = buildFromJSON(jsonSource, derivationInfo);
|
||||||
final network = base.network;
|
|
||||||
|
|
||||||
if (decoded['runtimeType'] == 'BitcoinAddressRecord') {
|
if (decoded['runtimeType'] == 'BitcoinAddressRecord') {
|
||||||
return BitcoinAddressRecord.fromJSON(jsonSource, base, derivationInfo);
|
return BitcoinAddressRecord.fromJSON(jsonSource, base, derivationInfo);
|
||||||
|
@ -173,7 +173,7 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
|
||||||
super.isHidden,
|
super.isHidden,
|
||||||
required super.isChange,
|
required super.isChange,
|
||||||
super.txCount = 0,
|
super.txCount = 0,
|
||||||
super.balance = 0,
|
super.balance,
|
||||||
super.name = '',
|
super.name = '',
|
||||||
super.isUsed = false,
|
super.isUsed = false,
|
||||||
required super.type,
|
required super.type,
|
||||||
|
@ -279,6 +279,10 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
|
||||||
}
|
}
|
||||||
|
|
||||||
class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
|
class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
|
||||||
|
static BitcoinDerivationInfo DEFAULT_DERIVATION_INFO =
|
||||||
|
BitcoinDerivationInfos.SILENT_PAYMENTS_SPEND;
|
||||||
|
static String DEFAULT_DERIVATION_PATH = DEFAULT_DERIVATION_INFO.derivationPath.toString();
|
||||||
|
|
||||||
String _derivationPath;
|
String _derivationPath;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -295,7 +299,7 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
|
||||||
required super.network,
|
required super.network,
|
||||||
String? derivationPath,
|
String? derivationPath,
|
||||||
super.txCount = 0,
|
super.txCount = 0,
|
||||||
super.balance = 0,
|
super.balance,
|
||||||
super.name = '',
|
super.name = '',
|
||||||
super.isUsed = false,
|
super.isUsed = false,
|
||||||
super.type = SilentPaymentsAddresType.p2sp,
|
super.type = SilentPaymentsAddresType.p2sp,
|
||||||
|
@ -303,12 +307,16 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
|
||||||
super.seedBytesType,
|
super.seedBytesType,
|
||||||
super.isHidden,
|
super.isHidden,
|
||||||
this.labelHex,
|
this.labelHex,
|
||||||
}) : _derivationPath = derivationPath ?? BitcoinDerivationPaths.SILENT_PAYMENTS_SPEND,
|
}) : _derivationPath = derivationPath ?? DEFAULT_DERIVATION_PATH,
|
||||||
super(index: labelIndex) {
|
super(index: labelIndex) {
|
||||||
if (labelIndex != 0 && labelHex == null) {
|
if (labelIndex != 0 && labelHex == null) {
|
||||||
throw ArgumentError('label must be provided for silent address index != 1');
|
throw ArgumentError('label must be provided for silent address index != 1');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_derivationPath != DEFAULT_DERIVATION_PATH) {
|
||||||
|
isHidden = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (labelIndex != 0 && derivationPath == null) {
|
if (labelIndex != 0 && derivationPath == null) {
|
||||||
_derivationPath = _derivationPath.replaceAll(RegExp(r'\d\/?$'), '$labelIndex');
|
_derivationPath = _derivationPath.replaceAll(RegExp(r'\d\/?$'), '$labelIndex');
|
||||||
}
|
}
|
||||||
|
@ -359,7 +367,7 @@ class BitcoinReceivedSPAddressRecord extends BitcoinSilentPaymentAddressRecord {
|
||||||
required super.labelIndex,
|
required super.labelIndex,
|
||||||
required super.network,
|
required super.network,
|
||||||
super.txCount = 0,
|
super.txCount = 0,
|
||||||
super.balance = 0,
|
super.balance,
|
||||||
super.name = '',
|
super.name = '',
|
||||||
super.isUsed = false,
|
super.isUsed = false,
|
||||||
required this.tweak,
|
required this.tweak,
|
||||||
|
@ -434,7 +442,7 @@ class LitecoinMWEBAddressRecord extends BaseBitcoinAddressRecord {
|
||||||
super.isHidden,
|
super.isHidden,
|
||||||
super.isChange = false,
|
super.isChange = false,
|
||||||
super.txCount = 0,
|
super.txCount = 0,
|
||||||
super.balance = 0,
|
super.balance,
|
||||||
super.name = '',
|
super.name = '',
|
||||||
super.isUsed = false,
|
super.isUsed = false,
|
||||||
}) : super(type: SegwitAddressType.mweb) {
|
}) : super(type: SegwitAddressType.mweb) {
|
||||||
|
|
|
@ -3,10 +3,10 @@ import 'dart:convert';
|
||||||
|
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||||
import 'package:cw_bitcoin/seedbyte_types.dart';
|
|
||||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
|
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_wallet_snapshot.dart';
|
import 'package:cw_bitcoin/bitcoin_wallet_snapshot.dart';
|
||||||
|
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||||
import 'package:cw_bitcoin/electrum_worker/methods/methods.dart';
|
import 'package:cw_bitcoin/electrum_worker/methods/methods.dart';
|
||||||
import 'package:cw_bitcoin/exceptions.dart';
|
import 'package:cw_bitcoin/exceptions.dart';
|
||||||
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
|
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
|
||||||
|
@ -62,23 +62,17 @@ abstract class BitcoinWalletBase extends ElectrumWallet<BitcoinWalletAddresses>
|
||||||
required super.unspentCoinsInfo,
|
required super.unspentCoinsInfo,
|
||||||
required super.encryptionFileUtils,
|
required super.encryptionFileUtils,
|
||||||
required super.hdWallets,
|
required super.hdWallets,
|
||||||
|
required super.network,
|
||||||
super.mnemonic,
|
super.mnemonic,
|
||||||
super.xpub,
|
super.xpub,
|
||||||
BasedUtxoNetwork? networkParam,
|
|
||||||
super.initialBalance,
|
super.initialBalance,
|
||||||
super.passphrase,
|
super.passphrase,
|
||||||
bool? alwaysScan,
|
|
||||||
super.initialUnspentCoins,
|
super.initialUnspentCoins,
|
||||||
|
bool? alwaysScan,
|
||||||
Map<String, dynamic>? walletAddressesSnapshot,
|
Map<String, dynamic>? walletAddressesSnapshot,
|
||||||
}) : _alwaysScan = alwaysScan ?? false,
|
}) : _alwaysScan = alwaysScan ?? false,
|
||||||
super(
|
super(
|
||||||
network: networkParam == null
|
currency: network == BitcoinNetwork.testnet ? CryptoCurrency.tbtc : CryptoCurrency.btc,
|
||||||
? BitcoinNetwork.mainnet
|
|
||||||
: networkParam == BitcoinNetwork.mainnet
|
|
||||||
? BitcoinNetwork.mainnet
|
|
||||||
: BitcoinNetwork.testnet,
|
|
||||||
currency:
|
|
||||||
networkParam == BitcoinNetwork.testnet ? CryptoCurrency.tbtc : CryptoCurrency.btc,
|
|
||||||
) {
|
) {
|
||||||
if (walletAddressesSnapshot != null) {
|
if (walletAddressesSnapshot != null) {
|
||||||
walletAddresses = BitcoinWalletAddressesBase.fromJson(
|
walletAddresses = BitcoinWalletAddressesBase.fromJson(
|
||||||
|
@ -91,7 +85,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet<BitcoinWalletAddresses>
|
||||||
} else {
|
} else {
|
||||||
this.walletAddresses = BitcoinWalletAddresses(
|
this.walletAddresses = BitcoinWalletAddresses(
|
||||||
walletInfo,
|
walletInfo,
|
||||||
network: networkParam ?? network,
|
network: network,
|
||||||
isHardwareWallet: isHardwareWallet,
|
isHardwareWallet: isHardwareWallet,
|
||||||
hdWallets: hdWallets,
|
hdWallets: hdWallets,
|
||||||
);
|
);
|
||||||
|
@ -114,9 +108,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet<BitcoinWalletAddresses>
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool?> initAddresses([bool? sync]) async {
|
Future<InitAddressesData> initAddresses([bool? sync]) async {
|
||||||
var isDiscovered = await super.initAddresses(sync);
|
final initData = await super.initAddresses(sync);
|
||||||
bool? discovered;
|
var isDiscovered = initData.isDiscovered;
|
||||||
|
var discovered = initData.discovered;
|
||||||
|
|
||||||
// NOTE: will initiate by priority from the first walletAddressTypes
|
// NOTE: will initiate by priority from the first walletAddressTypes
|
||||||
// then proceeds to following ones after got fully discovered response from worker response
|
// then proceeds to following ones after got fully discovered response from worker response
|
||||||
|
@ -195,11 +190,12 @@ abstract class BitcoinWalletBase extends ElectrumWallet<BitcoinWalletAddresses>
|
||||||
|
|
||||||
if (isDiscovered == true && sync == false)
|
if (isDiscovered == true && sync == false)
|
||||||
initAddresses(true);
|
initAddresses(true);
|
||||||
else if (isDiscovered == true)
|
|
||||||
syncStatus = SyncedSyncStatus();
|
|
||||||
else if (isDiscovered == false && discovered == false) initAddresses(sync);
|
else if (isDiscovered == false && discovered == false) initAddresses(sync);
|
||||||
|
|
||||||
return isDiscovered;
|
return InitAddressesData(
|
||||||
|
isDiscovered: isDiscovered,
|
||||||
|
discovered: discovered,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<BitcoinWallet> create({
|
static Future<BitcoinWallet> create({
|
||||||
|
@ -211,9 +207,15 @@ abstract class BitcoinWalletBase extends ElectrumWallet<BitcoinWalletAddresses>
|
||||||
String? passphrase,
|
String? passphrase,
|
||||||
BasedUtxoNetwork? network,
|
BasedUtxoNetwork? network,
|
||||||
}) async {
|
}) async {
|
||||||
|
network = network == null
|
||||||
|
? BitcoinNetwork.mainnet
|
||||||
|
: network == BitcoinNetwork.mainnet
|
||||||
|
? BitcoinNetwork.mainnet
|
||||||
|
: BitcoinNetwork.testnet;
|
||||||
|
|
||||||
final hdWallets = await ElectrumWalletBase.getAccountHDWallets(
|
final hdWallets = await ElectrumWalletBase.getAccountHDWallets(
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
network: network ?? BitcoinNetwork.mainnet,
|
network: network,
|
||||||
mnemonic: mnemonic,
|
mnemonic: mnemonic,
|
||||||
passphrase: passphrase,
|
passphrase: passphrase,
|
||||||
);
|
);
|
||||||
|
@ -225,7 +227,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet<BitcoinWalletAddresses>
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
unspentCoinsInfo: unspentCoinsInfo,
|
||||||
encryptionFileUtils: encryptionFileUtils,
|
encryptionFileUtils: encryptionFileUtils,
|
||||||
networkParam: network,
|
network: network,
|
||||||
hdWallets: hdWallets,
|
hdWallets: hdWallets,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -297,7 +299,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet<BitcoinWalletAddresses>
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
unspentCoinsInfo: unspentCoinsInfo,
|
||||||
initialBalance: snp?.balance,
|
initialBalance: snp?.balance,
|
||||||
encryptionFileUtils: encryptionFileUtils,
|
encryptionFileUtils: encryptionFileUtils,
|
||||||
networkParam: network,
|
network: network,
|
||||||
alwaysScan: snp?.alwaysScan,
|
alwaysScan: snp?.alwaysScan,
|
||||||
initialUnspentCoins: snp?.unspentCoins,
|
initialUnspentCoins: snp?.unspentCoins,
|
||||||
walletAddressesSnapshot: snp?.walletAddressesSnapshot,
|
walletAddressesSnapshot: snp?.walletAddressesSnapshot,
|
||||||
|
@ -531,11 +533,11 @@ abstract class BitcoinWalletBase extends ElectrumWallet<BitcoinWalletAddresses>
|
||||||
|
|
||||||
walletAddresses.silentPaymentAddresses.forEach((addressRecord) {
|
walletAddresses.silentPaymentAddresses.forEach((addressRecord) {
|
||||||
addressRecord.txCount = 0;
|
addressRecord.txCount = 0;
|
||||||
addressRecord.balance = 0;
|
addressRecord.balance = ElectrumBalance.zero();
|
||||||
});
|
});
|
||||||
walletAddresses.receivedSPAddresses.forEach((addressRecord) {
|
walletAddresses.receivedSPAddresses.forEach((addressRecord) {
|
||||||
addressRecord.txCount = 0;
|
addressRecord.txCount = 0;
|
||||||
addressRecord.balance = 0;
|
addressRecord.balance = ElectrumBalance.zero();
|
||||||
});
|
});
|
||||||
|
|
||||||
final silentPaymentWallet = walletAddresses.silentPaymentWallet;
|
final silentPaymentWallet = walletAddresses.silentPaymentWallet;
|
||||||
|
@ -560,13 +562,13 @@ abstract class BitcoinWalletBase extends ElectrumWallet<BitcoinWalletAddresses>
|
||||||
walletAddresses.silentPaymentAddresses.forEach((addressRecord) {
|
walletAddresses.silentPaymentAddresses.forEach((addressRecord) {
|
||||||
if (addressRecord.address == silentPaymentAddress.toAddress(network)) {
|
if (addressRecord.address == silentPaymentAddress.toAddress(network)) {
|
||||||
addressRecord.txCount += 1;
|
addressRecord.txCount += 1;
|
||||||
addressRecord.balance += unspent.value;
|
addressRecord.balance.confirmed += unspent.value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
walletAddresses.receivedSPAddresses.forEach((addressRecord) {
|
walletAddresses.receivedSPAddresses.forEach((addressRecord) {
|
||||||
if (addressRecord.address == receiveAddressRecord.address) {
|
if (addressRecord.address == receiveAddressRecord.address) {
|
||||||
addressRecord.txCount += 1;
|
addressRecord.txCount += 1;
|
||||||
addressRecord.balance += unspent.value;
|
addressRecord.balance.confirmed += unspent.value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -576,28 +578,6 @@ abstract class BitcoinWalletBase extends ElectrumWallet<BitcoinWalletAddresses>
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void updateCoin(BitcoinUnspent coin) {
|
|
||||||
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;
|
|
||||||
|
|
||||||
coin.isFrozen = coinInfo.isFrozen;
|
|
||||||
coin.isSending = coinInfo.isSending;
|
|
||||||
coin.note = coinInfo.note;
|
|
||||||
if (coin.bitcoinAddressRecord is! BitcoinSilentPaymentAddressRecord)
|
|
||||||
coin.bitcoinAddressRecord.balance += coinInfo.value;
|
|
||||||
} else {
|
|
||||||
addCoinInfo(coin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@action
|
@action
|
||||||
Future<void> addCoinInfo(BitcoinUnspent coin) async {
|
Future<void> addCoinInfo(BitcoinUnspent coin) async {
|
||||||
|
@ -620,6 +600,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet<BitcoinWalletAddresses>
|
||||||
);
|
);
|
||||||
|
|
||||||
await unspentCoinsInfo.add(newInfo);
|
await unspentCoinsInfo.add(newInfo);
|
||||||
|
} else if (coin.address is BitcoinReceivedSPAddressRecord) {
|
||||||
|
existingCoinInfo.isSilentPayment = true;
|
||||||
|
await unspentCoinsInfo.add(existingCoinInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -671,11 +654,11 @@ abstract class BitcoinWalletBase extends ElectrumWallet<BitcoinWalletAddresses>
|
||||||
if (result.transactions?.isNotEmpty == true) {
|
if (result.transactions?.isNotEmpty == true) {
|
||||||
walletAddresses.silentPaymentAddresses.forEach((addressRecord) {
|
walletAddresses.silentPaymentAddresses.forEach((addressRecord) {
|
||||||
addressRecord.txCount = 0;
|
addressRecord.txCount = 0;
|
||||||
addressRecord.balance = 0;
|
addressRecord.balance = ElectrumBalance.zero();
|
||||||
});
|
});
|
||||||
walletAddresses.receivedSPAddresses.forEach((addressRecord) {
|
walletAddresses.receivedSPAddresses.forEach((addressRecord) {
|
||||||
addressRecord.txCount = 0;
|
addressRecord.txCount = 0;
|
||||||
addressRecord.balance = 0;
|
addressRecord.balance = ElectrumBalance.zero();
|
||||||
});
|
});
|
||||||
|
|
||||||
for (final map in result.transactions!.entries) {
|
for (final map in result.transactions!.entries) {
|
||||||
|
@ -693,7 +676,12 @@ abstract class BitcoinWalletBase extends ElectrumWallet<BitcoinWalletAddresses>
|
||||||
existingTxInfo.amount = tx.amount;
|
existingTxInfo.amount = tx.amount;
|
||||||
existingTxInfo.confirmations = tx.confirmations;
|
existingTxInfo.confirmations = tx.confirmations;
|
||||||
existingTxInfo.height = tx.height;
|
existingTxInfo.height = tx.height;
|
||||||
|
existingTxInfo.direction = TransactionDirection.incoming;
|
||||||
|
existingTxInfo.inputAddresses = tx.inputAddresses;
|
||||||
|
existingTxInfo.outputAddresses = tx.outputAddresses;
|
||||||
|
existingTxInfo.isReceivedSilentPayment = tx.isReceivedSilentPayment;
|
||||||
|
|
||||||
|
// TODO: send from tweaks a histories, unspent and amount response
|
||||||
final newUnspents = unspents
|
final newUnspents = unspents
|
||||||
.where(
|
.where(
|
||||||
(unspent) => !unspentCoins.any((element) =>
|
(unspent) => !unspentCoins.any((element) =>
|
||||||
|
@ -710,29 +698,27 @@ abstract class BitcoinWalletBase extends ElectrumWallet<BitcoinWalletAddresses>
|
||||||
unspentCoins.forEach(updateCoin);
|
unspentCoins.forEach(updateCoin);
|
||||||
|
|
||||||
await refreshUnspentCoinsInfo();
|
await refreshUnspentCoinsInfo();
|
||||||
|
|
||||||
final newAmount = newUnspents.length > 1
|
|
||||||
? newUnspents.map((e) => e.value).reduce((value, unspent) => value + unspent)
|
|
||||||
: newUnspents[0].value;
|
|
||||||
|
|
||||||
if (existingTxInfo.direction == TransactionDirection.incoming) {
|
|
||||||
existingTxInfo.amount += newAmount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates existing TX
|
// Updates existing TX
|
||||||
transactionHistory.addOne(existingTxInfo);
|
transactionHistory.addOne(existingTxInfo);
|
||||||
// Update balance record
|
|
||||||
balance[currency]!.confirmed += newAmount;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// else: First time seeing this TX after scanning
|
// else: First time seeing this TX after scanning
|
||||||
unspentCoins.forEach(_updateSilentAddressRecord);
|
unspentCoins.forEach(_updateSilentAddressRecord);
|
||||||
|
|
||||||
transactionHistory.addOne(tx);
|
transactionHistory.addOne(tx);
|
||||||
balance[currency]!.confirmed += tx.amount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateAllUnspents();
|
// Update balance record
|
||||||
|
for (final addressRecord in walletAddresses.otherAddresses) {
|
||||||
|
final addressBalance = addressRecord.balance;
|
||||||
|
|
||||||
|
if (addressBalance.hasBalance()) {
|
||||||
|
balance[currency]!.confirmed += addressBalance.confirmed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -933,13 +919,12 @@ abstract class BitcoinWalletBase extends ElectrumWallet<BitcoinWalletAddresses>
|
||||||
isSilentPayment = true;
|
isSilentPayment = true;
|
||||||
} else if (!isHardwareWallet) {
|
} else if (!isHardwareWallet) {
|
||||||
final addressRecord = (utx.bitcoinAddressRecord as BitcoinAddressRecord);
|
final addressRecord = (utx.bitcoinAddressRecord as BitcoinAddressRecord);
|
||||||
final path = addressRecord.derivationInfo.derivationPath
|
|
||||||
.addElem(Bip32KeyIndex(
|
|
||||||
BitcoinAddressUtils.getAccountFromChange(addressRecord.isChange),
|
|
||||||
))
|
|
||||||
.addElem(Bip32KeyIndex(addressRecord.index));
|
|
||||||
|
|
||||||
privkey = ECPrivate.fromBip32(bip32: hdWallet.derive(path));
|
privkey = ECPrivate.fromBip32(
|
||||||
|
bip32: hdWallets[addressRecord.seedBytesType]!.derive(
|
||||||
|
Bip32PathParser.parse(addressRecord.indexedDerivationPath),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
vinOutpoints.add(Outpoint(txid: utx.hash, index: utx.vout));
|
vinOutpoints.add(Outpoint(txid: utx.hash, index: utx.vout));
|
||||||
|
|
|
@ -14,6 +14,8 @@ part 'bitcoin_wallet_addresses.g.dart';
|
||||||
class BitcoinWalletAddresses = BitcoinWalletAddressesBase with _$BitcoinWalletAddresses;
|
class BitcoinWalletAddresses = BitcoinWalletAddressesBase with _$BitcoinWalletAddresses;
|
||||||
|
|
||||||
abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
|
abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
|
||||||
|
static const _OLD_SP_PATH = "m/352'/1'/0'/#'/0";
|
||||||
|
|
||||||
BitcoinWalletAddressesBase(
|
BitcoinWalletAddressesBase(
|
||||||
WalletInfo walletInfo, {
|
WalletInfo walletInfo, {
|
||||||
required super.hdWallets,
|
required super.hdWallets,
|
||||||
|
@ -22,7 +24,6 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
|
||||||
super.initialAddressesRecords,
|
super.initialAddressesRecords,
|
||||||
super.initialActiveAddressIndex,
|
super.initialActiveAddressIndex,
|
||||||
super.initialAddressPageType,
|
super.initialAddressPageType,
|
||||||
this.loadedFromNewSnapshot = false,
|
|
||||||
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
|
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
|
||||||
List<BitcoinReceivedSPAddressRecord>? initialReceivedSPAddresses,
|
List<BitcoinReceivedSPAddressRecord>? initialReceivedSPAddresses,
|
||||||
}) : silentPaymentAddresses = ObservableList<BitcoinSilentPaymentAddressRecord>.of(
|
}) : silentPaymentAddresses = ObservableList<BitcoinSilentPaymentAddressRecord>.of(
|
||||||
|
@ -36,10 +37,6 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
|
||||||
silentPaymentWallets = [silentPaymentWallet!];
|
silentPaymentWallets = [silentPaymentWallet!];
|
||||||
}
|
}
|
||||||
|
|
||||||
final bool loadedFromNewSnapshot;
|
|
||||||
|
|
||||||
static const _OLD_SP_PATH = "m/352'/1'/0'/#'/0";
|
|
||||||
|
|
||||||
// NOTE: ordered in priority: eg. p2wpkh always first, most used address, etc.
|
// NOTE: ordered in priority: eg. p2wpkh always first, most used address, etc.
|
||||||
@override
|
@override
|
||||||
final walletAddressTypes = [
|
final walletAddressTypes = [
|
||||||
|
@ -50,6 +47,8 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
|
||||||
SegwitAddressType.p2wsh,
|
SegwitAddressType.p2wsh,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
List<BaseBitcoinAddressRecord> get otherAddresses => silentPaymentAddresses;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
SilentPaymentOwner? silentPaymentWallet;
|
SilentPaymentOwner? silentPaymentWallet;
|
||||||
final ObservableList<BitcoinSilentPaymentAddressRecord> silentPaymentAddresses;
|
final ObservableList<BitcoinSilentPaymentAddressRecord> silentPaymentAddresses;
|
||||||
|
@ -77,7 +76,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
|
||||||
Future<void> generateInitialSPAddresses() async {
|
Future<void> generateInitialSPAddresses() async {
|
||||||
final addAddresses = silentPaymentAddresses.isEmpty;
|
final addAddresses = silentPaymentAddresses.isEmpty;
|
||||||
|
|
||||||
// Only initiate these old addresses if restoring a wallet and possibly wants the older cake derivation path
|
// NOTE: Only initiate these old addresses if restoring a wallet and possibly wants the older cake derivation path
|
||||||
if (walletInfo.isRecovery || silentPaymentAddresses.length > 2) {
|
if (walletInfo.isRecovery || silentPaymentAddresses.length > 2) {
|
||||||
final oldScanPath = Bip32PathParser.parse(_OLD_SP_PATH.replaceFirst("#", "1"));
|
final oldScanPath = Bip32PathParser.parse(_OLD_SP_PATH.replaceFirst("#", "1"));
|
||||||
final oldSpendPath = Bip32PathParser.parse(_OLD_SP_PATH.replaceFirst("#", "0"));
|
final oldSpendPath = Bip32PathParser.parse(_OLD_SP_PATH.replaceFirst("#", "0"));
|
||||||
|
@ -255,43 +254,27 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
|
||||||
@override
|
@override
|
||||||
@action
|
@action
|
||||||
Future<void> updateAddressesInBox() async {
|
Future<void> updateAddressesInBox() async {
|
||||||
// receiveAddressesMapped.entries.forEach((e) {
|
await super.updateAddressesInBox();
|
||||||
// final addressType = e.key;
|
|
||||||
// final addresses = e.value;
|
|
||||||
|
|
||||||
// for (final addr in addresses) {
|
final addr = activeSilentAddress ??
|
||||||
// if (getIsReceive(addr)) {
|
silentPaymentAddresses.firstWhereOrNull(
|
||||||
// allAddressesMap[addr.address] = addr.name;
|
(addressRecord) => !addressRecord.isHidden && !addressRecord.isChange,
|
||||||
|
);
|
||||||
|
|
||||||
// final isCurrentType = addr.type == addressPageType;
|
if (addr != null) {
|
||||||
|
final addressString =
|
||||||
|
'${addr.address.substring(0, 9 + 5)}...${addr.address.substring(addr.address.length - 9, addr.address.length)}';
|
||||||
|
|
||||||
// if (addressType == SilentPaymentsAddresType.p2sp) {
|
final isCurrentType = addressPageType == SilentPaymentsAddresType.p2sp;
|
||||||
// final addressString =
|
|
||||||
// '${addr.address.substring(0, 9 + 5)}...${addr.address.substring(addr.address.length - 9, addr.address.length)}';
|
|
||||||
|
|
||||||
// if (!isCurrentType) {
|
if (!isCurrentType) {
|
||||||
// addressesMap[addr.address] = addr.name.isEmpty
|
addressesMap[addr.address] = addr.name.isEmpty
|
||||||
// ? "Silent Payments" + ': $addressString'
|
? "Silent Payments" + ': $addressString'
|
||||||
// : "Silent Payments - " + addr.name + ': $addressString';
|
: "Silent Payments - " + addr.name + ': $addressString';
|
||||||
// } else {
|
} else {
|
||||||
// addressesMap[address] = 'Active - Silent Payments' + ': $addressString';
|
addressesMap[addr.address] = 'Active - Silent Payments' + ': $addressString';
|
||||||
// }
|
}
|
||||||
|
}
|
||||||
// // Silent Payments address don't break the loop because all are used
|
|
||||||
// // break;
|
|
||||||
// } else {
|
|
||||||
// if (!isCurrentType) {
|
|
||||||
// addressesMap[addr.address] = '${addressType.value.toUpperCase()}: ${addr.address}';
|
|
||||||
// } else {
|
|
||||||
// addressesMap[address] = 'Active - ${addressType.value.toUpperCase()}: $address';
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Break the loop, already got the firt unused address
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
await saveAddressesInBox();
|
await saveAddressesInBox();
|
||||||
}
|
}
|
||||||
|
@ -332,6 +315,16 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
|
||||||
addressesSet.addAll(addresses);
|
addressesSet.addAll(addresses);
|
||||||
this.receivedSPAddresses.clear();
|
this.receivedSPAddresses.clear();
|
||||||
this.receivedSPAddresses.addAll(addressesSet);
|
this.receivedSPAddresses.addAll(addressesSet);
|
||||||
|
|
||||||
|
for (final receivedSPAddress in receivedSPAddresses) {
|
||||||
|
for (final addressRecord in silentPaymentAddresses) {
|
||||||
|
if (receivedSPAddress.spAddress == addressRecord.address) {
|
||||||
|
addressRecord.balance.confirmed += receivedSPAddress.balance.confirmed;
|
||||||
|
addressRecord.txCount += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -403,7 +396,6 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
|
||||||
silentPaymentAddresses.map((address) => address.toJSON()).toList();
|
silentPaymentAddresses.map((address) => address.toJSON()).toList();
|
||||||
json['receivedSPAddresses'] = receivedSPAddresses.map((address) => address.toJSON()).toList();
|
json['receivedSPAddresses'] = receivedSPAddresses.map((address) => address.toJSON()).toList();
|
||||||
json['silentAddressIndex'] = silentAddressIndex.toString();
|
json['silentAddressIndex'] = silentAddressIndex.toString();
|
||||||
json['loadedFromNewSnapshot'] = true;
|
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -475,7 +467,6 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
|
||||||
initialActiveAddressIndex: electrumJson.activeIndexByType,
|
initialActiveAddressIndex: electrumJson.activeIndexByType,
|
||||||
initialSilentAddresses: initialSilentAddresses,
|
initialSilentAddresses: initialSilentAddresses,
|
||||||
initialReceivedSPAddresses: initialReceivedSPAddresses,
|
initialReceivedSPAddresses: initialReceivedSPAddresses,
|
||||||
loadedFromNewSnapshot: snp['loadedFromNewSnapshot'] as bool? ?? false,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -158,7 +158,7 @@ class BitcoinWalletService extends WalletService<
|
||||||
xpub: credentials.hwAccountData.xpub,
|
xpub: credentials.hwAccountData.xpub,
|
||||||
walletInfo: credentials.walletInfo!,
|
walletInfo: credentials.walletInfo!,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
networkParam: network,
|
network: network,
|
||||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
hdWallets: hdWallets,
|
hdWallets: hdWallets,
|
||||||
);
|
);
|
||||||
|
|
|
@ -16,6 +16,20 @@ class ElectrumBalance extends Balance {
|
||||||
secondAdditional: secondUnconfirmed,
|
secondAdditional: secondUnconfirmed,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
factory ElectrumBalance.zero() {
|
||||||
|
return ElectrumBalance(
|
||||||
|
confirmed: 0,
|
||||||
|
unconfirmed: 0,
|
||||||
|
frozen: 0,
|
||||||
|
secondConfirmed: 0,
|
||||||
|
secondUnconfirmed: 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasBalance() {
|
||||||
|
return confirmed > 0 || unconfirmed > 0 || frozen > 0;
|
||||||
|
}
|
||||||
|
|
||||||
static ElectrumBalance? fromJSON(String? jsonSource) {
|
static ElectrumBalance? fromJSON(String? jsonSource) {
|
||||||
if (jsonSource == null) {
|
if (jsonSource == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -61,8 +75,8 @@ class ElectrumBalance extends Balance {
|
||||||
BitcoinAmountUtils.bitcoinAmountToString(amount: secondUnconfirmed);
|
BitcoinAmountUtils.bitcoinAmountToString(amount: secondUnconfirmed);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get formattedFullAvailableBalance =>
|
String get formattedFullAvailableBalance => BitcoinAmountUtils.bitcoinAmountToString(
|
||||||
BitcoinAmountUtils.bitcoinAmountToString(amount: (confirmed + unconfirmed) + secondConfirmed - frozen);
|
amount: (confirmed + unconfirmed) + secondConfirmed - frozen);
|
||||||
|
|
||||||
String toJSON() => json.encode({
|
String toJSON() => json.encode({
|
||||||
'confirmed': confirmed,
|
'confirmed': confirmed,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
|
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
import 'package:cw_core/transaction_direction.dart';
|
import 'package:cw_core/transaction_direction.dart';
|
||||||
import 'package:cw_core/transaction_info.dart';
|
import 'package:cw_core/transaction_info.dart';
|
||||||
|
@ -182,6 +183,56 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void updateInputsAndOutputs(ElectrumTransactionBundle bundle, BasedUtxoNetwork network) {
|
||||||
|
inputAddresses = inputAddresses?.where((address) => address.isNotEmpty).toList();
|
||||||
|
|
||||||
|
if (inputAddresses == null ||
|
||||||
|
inputAddresses!.isEmpty ||
|
||||||
|
outputAddresses == null ||
|
||||||
|
outputAddresses!.isEmpty) {
|
||||||
|
final tempInputAddresses = <String>[];
|
||||||
|
final tempOutputAddresses = <String>[];
|
||||||
|
|
||||||
|
if (bundle.ins.length == bundle.originalTransaction.inputs.length) {
|
||||||
|
for (int i = 0; i < bundle.originalTransaction.inputs.length; i++) {
|
||||||
|
final input = bundle.originalTransaction.inputs[i];
|
||||||
|
final inputTransaction = bundle.ins[i];
|
||||||
|
final vout = input.txIndex;
|
||||||
|
final outTransaction = inputTransaction.outputs[vout];
|
||||||
|
final address =
|
||||||
|
BitcoinAddressUtils.addressFromOutputScript(outTransaction.scriptPubKey, network);
|
||||||
|
|
||||||
|
if (address.isNotEmpty) tempInputAddresses.add(address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < bundle.originalTransaction.outputs.length; i++) {
|
||||||
|
final out = bundle.originalTransaction.outputs[i];
|
||||||
|
final address = BitcoinAddressUtils.addressFromOutputScript(out.scriptPubKey, network);
|
||||||
|
|
||||||
|
if (address.isNotEmpty) tempOutputAddresses.add(address);
|
||||||
|
|
||||||
|
// Check if the script contains OP_RETURN
|
||||||
|
final script = out.scriptPubKey.script;
|
||||||
|
if (script.contains('OP_RETURN')) {
|
||||||
|
final index = script.indexOf('OP_RETURN');
|
||||||
|
if (index + 1 <= script.length) {
|
||||||
|
try {
|
||||||
|
final opReturnData = script[index + 1].toString();
|
||||||
|
final decodedString = StringUtils.decode(BytesUtils.fromHexString(opReturnData));
|
||||||
|
tempOutputAddresses.add('OP_RETURN:$decodedString');
|
||||||
|
} catch (_) {
|
||||||
|
tempOutputAddresses.add('OP_RETURN:');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inputAddresses = tempInputAddresses;
|
||||||
|
outputAddresses = tempOutputAddresses;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
factory ElectrumTransactionInfo.fromElectrumBundle(
|
factory ElectrumTransactionInfo.fromElectrumBundle(
|
||||||
ElectrumTransactionBundle bundle,
|
ElectrumTransactionBundle bundle,
|
||||||
WalletType type,
|
WalletType type,
|
||||||
|
@ -254,9 +305,9 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||||
|
|
||||||
final weSent = ourSentAmounts.length > 0;
|
final weSent = ourSentAmounts.length > 0;
|
||||||
final weReceived = ourReceivedAmounts.length > 0;
|
final weReceived = ourReceivedAmounts.length > 0;
|
||||||
|
final weReceivedAll = ourReceivedAmounts.length == bundle.originalTransaction.outputs.length;
|
||||||
|
|
||||||
if (ourReceivedAmounts.length == bundle.originalTransaction.outputs.length) {
|
if (weReceivedAll) {
|
||||||
// All outputs in this tx were received
|
|
||||||
direction = TransactionDirection.incoming;
|
direction = TransactionDirection.incoming;
|
||||||
amount = ourTotalReceivedAmount;
|
amount = ourTotalReceivedAmount;
|
||||||
} else if (weSent && weReceived && ourTotalSentAmount > ourTotalReceivedAmount) {
|
} else if (weSent && weReceived && ourTotalSentAmount > ourTotalReceivedAmount) {
|
||||||
|
@ -277,7 +328,6 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
final fee = totalInputsAmount - totalOutsAmount;
|
final fee = totalInputsAmount - totalOutsAmount;
|
||||||
amount = amount - fee;
|
|
||||||
|
|
||||||
return ElectrumTransactionInfo(
|
return ElectrumTransactionInfo(
|
||||||
type,
|
type,
|
||||||
|
|
|
@ -271,7 +271,7 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
|
||||||
|
|
||||||
BasedUtxoNetwork network;
|
BasedUtxoNetwork network;
|
||||||
|
|
||||||
// TODO: networks enum
|
// TODO: change from isTestnet boolean to a networks enum (regtest, signet, etc)
|
||||||
@override
|
@override
|
||||||
bool isTestnet;
|
bool isTestnet;
|
||||||
|
|
||||||
|
@ -306,11 +306,11 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
|
||||||
|
|
||||||
void Function(FlutterErrorDetails)? _onError;
|
void Function(FlutterErrorDetails)? _onError;
|
||||||
Timer? _autoSaveTimer;
|
Timer? _autoSaveTimer;
|
||||||
Timer? _updateFeeRateTimer;
|
|
||||||
static const int _autoSaveInterval = 1;
|
static const int _autoSaveInterval = 1;
|
||||||
|
|
||||||
Future<bool?> initAddresses([bool? sync]) async {
|
Future<InitAddressesData> initAddresses([bool? sync]) async {
|
||||||
bool? isDiscovered = null;
|
bool? isDiscovered = null;
|
||||||
|
bool? discovered;
|
||||||
|
|
||||||
// NOTE: will initiate by priority from the first walletAddressTypes
|
// NOTE: will initiate by priority from the first walletAddressTypes
|
||||||
// then proceeds to following ones after got fully discovered response from worker response
|
// then proceeds to following ones after got fully discovered response from worker response
|
||||||
|
@ -334,10 +334,22 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
|
||||||
if (isDiscovered == false) {
|
if (isDiscovered == false) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// else if (sync == true)
|
||||||
|
// subscribeForStatuses(
|
||||||
|
// walletAddresses.addressesRecords
|
||||||
|
// .getRecords(
|
||||||
|
// addressType: addressType,
|
||||||
|
// seedBytesType: seedBytesType,
|
||||||
|
// derivationPath: derivationPath,
|
||||||
|
// isChange: isChange,
|
||||||
|
// )
|
||||||
|
// .whereType<BitcoinAddressRecord>()
|
||||||
|
// .toList(),
|
||||||
|
// );
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDiscovered == false) {
|
if (isDiscovered == false) {
|
||||||
await generateInitialAddresses(
|
discovered = await generateInitialAddresses(
|
||||||
addressType: addressType,
|
addressType: addressType,
|
||||||
seedBytesType: walletAddresses.walletSeedBytesType,
|
seedBytesType: walletAddresses.walletSeedBytesType,
|
||||||
bitcoinDerivationInfo: bitcoinDerivationInfo,
|
bitcoinDerivationInfo: bitcoinDerivationInfo,
|
||||||
|
@ -346,10 +358,20 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return isDiscovered;
|
// if (isDiscovered == true && sync == false)
|
||||||
|
// initAddresses(true);
|
||||||
|
// else if (isDiscovered == false && discovered == false) initAddresses(sync);
|
||||||
|
|
||||||
|
if (isDiscovered == true) syncStatus = SyncedSyncStatus();
|
||||||
|
|
||||||
|
return InitAddressesData(
|
||||||
|
isDiscovered: isDiscovered,
|
||||||
|
discovered: discovered,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
|
await walletAddresses.init();
|
||||||
await transactionHistory.init();
|
await transactionHistory.init();
|
||||||
|
|
||||||
_autoSaveTimer =
|
_autoSaveTimer =
|
||||||
|
@ -363,13 +385,9 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
|
||||||
syncStatus = SynchronizingSyncStatus();
|
syncStatus = SynchronizingSyncStatus();
|
||||||
|
|
||||||
await subscribeForHeaders(true);
|
await subscribeForHeaders(true);
|
||||||
|
|
||||||
await initAddresses(false);
|
|
||||||
|
|
||||||
await updateFeeRates();
|
await updateFeeRates();
|
||||||
|
|
||||||
_updateFeeRateTimer ??=
|
await initAddresses(false);
|
||||||
Timer.periodic(const Duration(seconds: 5), (timer) async => await updateFeeRates());
|
|
||||||
|
|
||||||
await save();
|
await save();
|
||||||
} catch (e, stacktrace) {
|
} catch (e, stacktrace) {
|
||||||
|
@ -437,7 +455,6 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: dynamic
|
|
||||||
int get dustAmount => 546;
|
int get dustAmount => 546;
|
||||||
|
|
||||||
bool isBelowDust(int amount) => amount <= dustAmount;
|
bool isBelowDust(int amount) => amount <= dustAmount;
|
||||||
|
@ -930,7 +947,6 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: LedgerElectrumWallet
|
|
||||||
void setLedgerConnection(ledger.LedgerConnection connection) => throw UnimplementedError();
|
void setLedgerConnection(ledger.LedgerConnection connection) => throw UnimplementedError();
|
||||||
|
|
||||||
Future<BtcTransaction> buildHardwareWalletTransaction({
|
Future<BtcTransaction> buildHardwareWalletTransaction({
|
||||||
|
@ -1031,6 +1047,7 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
|
||||||
address = fakePublicKey.toP2wshAddress();
|
address = fakePublicKey.toP2wshAddress();
|
||||||
break;
|
break;
|
||||||
case SegwitAddressType.p2tr:
|
case SegwitAddressType.p2tr:
|
||||||
|
case SilentPaymentsAddresType.p2sp:
|
||||||
address = fakePublicKey.toTaprootAddress();
|
address = fakePublicKey.toTaprootAddress();
|
||||||
break;
|
break;
|
||||||
case SegwitAddressType.mweb:
|
case SegwitAddressType.mweb:
|
||||||
|
@ -1115,7 +1132,6 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
|
||||||
_receivePort?.close();
|
_receivePort?.close();
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
_autoSaveTimer?.cancel();
|
_autoSaveTimer?.cancel();
|
||||||
_updateFeeRateTimer?.cancel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -1125,6 +1141,10 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
|
||||||
]) async {
|
]) async {
|
||||||
scripthashes ??= walletAddresses.allScriptHashes;
|
scripthashes ??= walletAddresses.allScriptHashes;
|
||||||
|
|
||||||
|
if (scripthashes.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (wait == true) {
|
if (wait == true) {
|
||||||
return ElectrumWorkerListUnspentResponse.fromJson(
|
return ElectrumWorkerListUnspentResponse.fromJson(
|
||||||
await waitSendWorker(
|
await waitSendWorker(
|
||||||
|
@ -1163,8 +1183,8 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
|
||||||
final updatedUnspentCoins = <BitcoinUnspent>[];
|
final updatedUnspentCoins = <BitcoinUnspent>[];
|
||||||
|
|
||||||
await Future.wait(unspents.entries.map((entry) async {
|
await Future.wait(unspents.entries.map((entry) async {
|
||||||
final unspent = entry.value;
|
|
||||||
final scriptHash = entry.key;
|
final scriptHash = entry.key;
|
||||||
|
final unspent = entry.value;
|
||||||
|
|
||||||
final addressRecord = walletAddresses.allAddresses.firstWhereOrNull(
|
final addressRecord = walletAddresses.allAddresses.firstWhereOrNull(
|
||||||
(element) => element.scriptHash == scriptHash,
|
(element) => element.scriptHash == scriptHash,
|
||||||
|
@ -1174,12 +1194,19 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (unspent.isEmpty) {
|
||||||
|
unspentCoins.removeWhere((coin) =>
|
||||||
|
coin.bitcoinAddressRecord is BitcoinAddressRecord &&
|
||||||
|
(coin.bitcoinAddressRecord as BitcoinAddressRecord).scriptHash == scriptHash);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
await Future.wait(unspent.map((unspent) async {
|
await Future.wait(unspent.map((unspent) async {
|
||||||
final coin = BitcoinUnspent.fromJSON(addressRecord, unspent.toJson());
|
final coin = BitcoinUnspent.fromJSON(addressRecord, unspent.toJson());
|
||||||
coin.isChange = addressRecord.isChange;
|
coin.isChange = addressRecord.isChange;
|
||||||
// <-
|
|
||||||
// TODO: \/
|
final tx = await getExistingTransaction(hash: coin.hash);
|
||||||
final tx = await fetchTransactionInfo(hash: coin.hash);
|
|
||||||
if (tx != null) {
|
if (tx != null) {
|
||||||
coin.confirmations = tx.confirmations;
|
coin.confirmations = tx.confirmations;
|
||||||
}
|
}
|
||||||
|
@ -1194,7 +1221,6 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
|
||||||
await refreshUnspentCoinsInfo();
|
await refreshUnspentCoinsInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: move to ElectrumUnspents
|
|
||||||
@action
|
@action
|
||||||
Future<void> addCoinInfo(BitcoinUnspent coin) async {
|
Future<void> addCoinInfo(BitcoinUnspent coin) async {
|
||||||
// Check if the coin is already in the unspentCoinsInfo for the wallet
|
// Check if the coin is already in the unspentCoinsInfo for the wallet
|
||||||
|
@ -1328,9 +1354,11 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> canReplaceByFee(ElectrumTransactionInfo tx) async {
|
Future<String?> canReplaceByFee(ElectrumTransactionInfo tx) async {
|
||||||
|
if (tx.confirmations > 0) return null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final bundle = await getTransactionExpanded(hash: tx.txHash);
|
final bundle = await getTransactionExpanded(hash: tx.txHash);
|
||||||
_updateInputsAndOutputs(tx, bundle);
|
tx.updateInputsAndOutputs(bundle, network);
|
||||||
if (bundle.confirmations > 0) return null;
|
if (bundle.confirmations > 0) return null;
|
||||||
return bundle.originalTransaction.canReplaceByFee ? bundle.originalTransaction.toHex() : null;
|
return bundle.originalTransaction.canReplaceByFee ? bundle.originalTransaction.toHex() : null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -1631,6 +1659,12 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
|
||||||
).result;
|
).result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<ElectrumTransactionInfo?> getExistingTransaction({required String hash}) async {
|
||||||
|
var tx = transactionHistory.transactions.values.firstWhereOrNull((tx) => tx.txHash == hash);
|
||||||
|
tx ??= await fetchTransactionInfo(hash: hash);
|
||||||
|
return tx;
|
||||||
|
}
|
||||||
|
|
||||||
Future<ElectrumTransactionInfo?> fetchTransactionInfo({required String hash, int? height}) async {
|
Future<ElectrumTransactionInfo?> fetchTransactionInfo({required String hash, int? height}) async {
|
||||||
try {
|
try {
|
||||||
return ElectrumTransactionInfo.fromElectrumBundle(
|
return ElectrumTransactionInfo.fromElectrumBundle(
|
||||||
|
@ -1694,6 +1728,10 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
|
||||||
addressByScripthashes[addressRecord.scriptHash] = addressRecord.address;
|
addressByScripthashes[addressRecord.scriptHash] = addressRecord.address;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (scripthashByAddress.isEmpty || addressByScripthashes.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (wait == true)
|
if (wait == true)
|
||||||
return ElectrumWorkerScripthashesSubscribeResponse.fromJson(
|
return ElectrumWorkerScripthashesSubscribeResponse.fromJson(
|
||||||
await waitSendWorker(
|
await waitSendWorker(
|
||||||
|
@ -1715,10 +1753,10 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void onBalanceResponse(ElectrumBalance balanceResult) {
|
void onBalanceResponse(ElectrumGetBalanceResponse balanceResults) {
|
||||||
var totalFrozen = 0;
|
var totalFrozen = 0;
|
||||||
var totalConfirmed = balanceResult.confirmed;
|
var totalConfirmed = 0;
|
||||||
var totalUnconfirmed = balanceResult.unconfirmed;
|
var totalUnconfirmed = 0;
|
||||||
|
|
||||||
unspentCoins.forInfo(unspentCoinsInfo.values).forEach((unspentCoinInfo) {
|
unspentCoins.forInfo(unspentCoinsInfo.values).forEach((unspentCoinInfo) {
|
||||||
if (unspentCoinInfo.isFrozen) {
|
if (unspentCoinInfo.isFrozen) {
|
||||||
|
@ -1726,6 +1764,30 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
for (int i = 0; i < balanceResults.balances.length; i++) {
|
||||||
|
final scripthash = balanceResults.scripthashes[i];
|
||||||
|
final addressRecord = walletAddresses.allAddresses.firstWhereOrNull(
|
||||||
|
(element) => element.scriptHash == scripthash,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (addressRecord != null) {
|
||||||
|
final balance = balanceResults.balances[i];
|
||||||
|
addressRecord.balance = balance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final addressRecord in [
|
||||||
|
...walletAddresses.allAddresses,
|
||||||
|
...walletAddresses.otherAddresses
|
||||||
|
]) {
|
||||||
|
final balance = addressRecord.balance;
|
||||||
|
|
||||||
|
if (balance.hasBalance()) {
|
||||||
|
totalConfirmed += balance.confirmed;
|
||||||
|
totalUnconfirmed += balance.unconfirmed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
balance[currency] = ElectrumBalance(
|
balance[currency] = ElectrumBalance(
|
||||||
confirmed: totalConfirmed,
|
confirmed: totalConfirmed,
|
||||||
unconfirmed: totalUnconfirmed,
|
unconfirmed: totalUnconfirmed,
|
||||||
|
@ -1740,6 +1802,10 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
|
||||||
]) async {
|
]) async {
|
||||||
scripthashes ??= walletAddresses.allScriptHashes;
|
scripthashes ??= walletAddresses.allScriptHashes;
|
||||||
|
|
||||||
|
if (scripthashes.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (wait == true) {
|
if (wait == true) {
|
||||||
return ElectrumWorkerGetBalanceResponse.fromJson(
|
return ElectrumWorkerGetBalanceResponse.fromJson(
|
||||||
await waitSendWorker(
|
await waitSendWorker(
|
||||||
|
@ -1897,55 +1963,6 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateInputsAndOutputs(ElectrumTransactionInfo tx, ElectrumTransactionBundle bundle) {
|
|
||||||
tx.inputAddresses = tx.inputAddresses?.where((address) => address.isNotEmpty).toList();
|
|
||||||
|
|
||||||
if (tx.inputAddresses == null ||
|
|
||||||
tx.inputAddresses!.isEmpty ||
|
|
||||||
tx.outputAddresses == null ||
|
|
||||||
tx.outputAddresses!.isEmpty) {
|
|
||||||
List<String> inputAddresses = [];
|
|
||||||
List<String> outputAddresses = [];
|
|
||||||
|
|
||||||
for (int i = 0; i < bundle.originalTransaction.inputs.length; i++) {
|
|
||||||
final input = bundle.originalTransaction.inputs[i];
|
|
||||||
final inputTransaction = bundle.ins[i];
|
|
||||||
final vout = input.txIndex;
|
|
||||||
final outTransaction = inputTransaction.outputs[vout];
|
|
||||||
final address =
|
|
||||||
BitcoinAddressUtils.addressFromOutputScript(outTransaction.scriptPubKey, network);
|
|
||||||
|
|
||||||
if (address.isNotEmpty) inputAddresses.add(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < bundle.originalTransaction.outputs.length; i++) {
|
|
||||||
final out = bundle.originalTransaction.outputs[i];
|
|
||||||
final address = BitcoinAddressUtils.addressFromOutputScript(out.scriptPubKey, network);
|
|
||||||
|
|
||||||
if (address.isNotEmpty) outputAddresses.add(address);
|
|
||||||
|
|
||||||
// Check if the script contains OP_RETURN
|
|
||||||
final script = out.scriptPubKey.script;
|
|
||||||
if (script.contains('OP_RETURN')) {
|
|
||||||
final index = script.indexOf('OP_RETURN');
|
|
||||||
if (index + 1 <= script.length) {
|
|
||||||
try {
|
|
||||||
final opReturnData = script[index + 1].toString();
|
|
||||||
final decodedString = StringUtils.decode(BytesUtils.fromHexString(opReturnData));
|
|
||||||
outputAddresses.add('OP_RETURN:$decodedString');
|
|
||||||
} catch (_) {
|
|
||||||
outputAddresses.add('OP_RETURN:');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tx.inputAddresses = inputAddresses;
|
|
||||||
tx.outputAddresses = outputAddresses;
|
|
||||||
|
|
||||||
transactionHistory.addOne(tx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
Future<void> _onAddressesDiscovered(List<BitcoinAddressRecord> addresses) async {
|
Future<void> _onAddressesDiscovered(List<BitcoinAddressRecord> addresses) async {
|
||||||
final scripthashByAddress = await subscribeForStatuses(addresses, true);
|
final scripthashByAddress = await subscribeForStatuses(addresses, true);
|
||||||
|
@ -2017,70 +2034,40 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
|
||||||
isElectrum: seedBytesType.isElectrum,
|
isElectrum: seedBytesType.isElectrum,
|
||||||
);
|
);
|
||||||
|
|
||||||
final existingReceiveAddresses = walletAddresses.addressesRecords.getRecords(
|
for (final isChange in [true, false]) {
|
||||||
|
final existingAddresses = walletAddresses.addressesRecords.getRecords(
|
||||||
addressType: addressType,
|
addressType: addressType,
|
||||||
seedBytesType: seedBytesType,
|
seedBytesType: seedBytesType,
|
||||||
derivationPath: bitcoinDerivationInfo.derivationPath.toString(),
|
derivationPath: bitcoinDerivationInfo.derivationPath.toString(),
|
||||||
isChange: false,
|
isChange: isChange,
|
||||||
);
|
);
|
||||||
final discoveredExistingReceiveAddresses =
|
final discoveredExistingAddresses = walletAddresses.discoveredAddressesRecord.getIsDiscovered(
|
||||||
walletAddresses.discoveredAddressesRecord.getIsDiscovered(
|
|
||||||
addressType: addressType,
|
addressType: addressType,
|
||||||
seedBytesType: seedBytesType,
|
seedBytesType: seedBytesType,
|
||||||
derivationPath: bitcoinDerivationInfo.derivationPath.toString(),
|
derivationPath: bitcoinDerivationInfo.derivationPath.toString(),
|
||||||
isChange: false,
|
isChange: isChange,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!discoveredExistingReceiveAddresses &&
|
final gapCount = isChange
|
||||||
existingReceiveAddresses.length < ElectrumWalletAddressesBase.INITIAL_RECEIVE_COUNT) {
|
? ElectrumWalletAddressesBase.INITIAL_CHANGE_COUNT
|
||||||
|
: ElectrumWalletAddressesBase.INITIAL_RECEIVE_COUNT;
|
||||||
|
|
||||||
|
if (!discoveredExistingAddresses && existingAddresses.length < gapCount) {
|
||||||
discoverNewAddresses(
|
discoverNewAddresses(
|
||||||
addressType: addressType,
|
addressType: addressType,
|
||||||
seedBytesType: seedBytesType,
|
seedBytesType: seedBytesType,
|
||||||
derivationInfo: bitcoinDerivationInfo,
|
derivationInfo: bitcoinDerivationInfo,
|
||||||
isChange: false,
|
isChange: isChange,
|
||||||
startIndex: existingReceiveAddresses.length,
|
startIndex: existingAddresses.length,
|
||||||
);
|
);
|
||||||
discovered = true;
|
discovered = true;
|
||||||
} else {
|
|
||||||
walletAddresses.discoveredAddressesRecord.addDiscovered(
|
|
||||||
addressType: addressType,
|
|
||||||
seedBytesType: seedBytesType,
|
|
||||||
derivationPath: bitcoinDerivationInfo.derivationPath.toString(),
|
|
||||||
isChange: false,
|
|
||||||
discovered: true,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final existingChangeAddresses = walletAddresses.addressesRecords.getRecords(
|
|
||||||
addressType: addressType,
|
|
||||||
seedBytesType: seedBytesType,
|
|
||||||
derivationPath: bitcoinDerivationInfo.derivationPath.toString(),
|
|
||||||
isChange: true,
|
|
||||||
);
|
|
||||||
final discoveredExistingChangeAddresses =
|
|
||||||
walletAddresses.discoveredAddressesRecord.getIsDiscovered(
|
|
||||||
addressType: addressType,
|
|
||||||
seedBytesType: seedBytesType,
|
|
||||||
derivationPath: bitcoinDerivationInfo.derivationPath.toString(),
|
|
||||||
isChange: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!discoveredExistingChangeAddresses &&
|
|
||||||
existingChangeAddresses.length < ElectrumWalletAddressesBase.INITIAL_CHANGE_COUNT) {
|
|
||||||
discoverNewAddresses(
|
|
||||||
addressType: addressType,
|
|
||||||
seedBytesType: seedBytesType,
|
|
||||||
derivationInfo: bitcoinDerivationInfo,
|
|
||||||
isChange: true,
|
|
||||||
startIndex: existingChangeAddresses.length,
|
|
||||||
);
|
|
||||||
discovered = true;
|
|
||||||
} else {
|
|
||||||
walletAddresses.discoveredAddressesRecord.addDiscovered(
|
walletAddresses.discoveredAddressesRecord.addDiscovered(
|
||||||
addressType: addressType,
|
addressType: addressType,
|
||||||
seedBytesType: seedBytesType,
|
seedBytesType: seedBytesType,
|
||||||
derivationPath: bitcoinDerivationInfo.derivationPath.toString(),
|
derivationPath: bitcoinDerivationInfo.derivationPath.toString(),
|
||||||
isChange: true,
|
isChange: isChange,
|
||||||
discovered: true,
|
discovered: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2119,8 +2106,7 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
|
||||||
needsToDiscover = recordList.sublist(recordList.length - countToDiscover).any(
|
needsToDiscover = recordList.sublist(recordList.length - countToDiscover).any(
|
||||||
(record) {
|
(record) {
|
||||||
return scripthashStatuses?.any(
|
return scripthashStatuses?.any(
|
||||||
(scripthashStatus) =>
|
(scripthashStatus) => scripthashStatus.scripthash == record.scriptHash,
|
||||||
scripthashStatus.scripthash == (record as BitcoinAddressRecord).scriptHash,
|
|
||||||
) ??
|
) ??
|
||||||
record.getIsUsed();
|
record.getIsUsed();
|
||||||
},
|
},
|
||||||
|
@ -2146,6 +2132,12 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
|
||||||
).toJson(),
|
).toJson(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String formatCryptoAmount(String amount) {
|
||||||
|
final amountInt = int.parse(amount);
|
||||||
|
return BitcoinAmountUtils.bitcoinAmountToString(amount: amountInt);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ElectrumEstimatedTx {
|
class ElectrumEstimatedTx {
|
||||||
|
@ -2255,3 +2247,13 @@ class CreateTxData {
|
||||||
required this.memo,
|
required this.memo,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class InitAddressesData {
|
||||||
|
bool? isDiscovered;
|
||||||
|
bool? discovered;
|
||||||
|
|
||||||
|
InitAddressesData({
|
||||||
|
required this.isDiscovered,
|
||||||
|
required this.discovered,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -49,9 +49,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
String get xpub => hdWallet.publicKey.toExtended;
|
String get xpub => hdWallet.publicKey.toExtended;
|
||||||
String get xpriv => hdWallet.privateKey.toExtended;
|
String get xpriv => hdWallet.privateKey.toExtended;
|
||||||
|
|
||||||
// NOTE: order matters in priority
|
|
||||||
List<SeedBytesType> get seedBytesTypes {
|
List<SeedBytesType> get seedBytesTypes {
|
||||||
final seedBytesTypes = <SeedBytesType>[];
|
final seedBytesTypes = <SeedBytesType>[];
|
||||||
|
// NOTE: order matters in priority
|
||||||
if (hdWallets.containsKey(SeedBytesType.bip39)) {
|
if (hdWallets.containsKey(SeedBytesType.bip39)) {
|
||||||
seedBytesTypes.add(SeedBytesType.bip39);
|
seedBytesTypes.add(SeedBytesType.bip39);
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
BitcoinDerivationInfo get _defaultAddressPageDerivationInfo =>
|
BitcoinDerivationInfo get defaultAddressPageDerivationInfo =>
|
||||||
BitcoinAddressUtils.getDerivationFromType(
|
BitcoinAddressUtils.getDerivationFromType(
|
||||||
addressPageType,
|
addressPageType,
|
||||||
network: network,
|
network: network,
|
||||||
|
@ -100,8 +100,12 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
@observable
|
@observable
|
||||||
BitcoinAddressRecordMap addressesRecords;
|
BitcoinAddressRecordMap addressesRecords;
|
||||||
|
|
||||||
Set<String> get hiddenAddresses =>
|
@observable
|
||||||
allAddresses.where((address) => address.isHidden).map((address) => address.address).toSet();
|
Set<String> _hiddenAddresses = {};
|
||||||
|
@computed
|
||||||
|
Set<String> get hiddenAddresses => _hiddenAddresses;
|
||||||
|
|
||||||
|
List<BaseBitcoinAddressRecord> get otherAddresses => [];
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
List<BitcoinAddressRecord> _allAddresses = [];
|
List<BitcoinAddressRecord> _allAddresses = [];
|
||||||
|
@ -110,32 +114,53 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
|
|
||||||
@action
|
@action
|
||||||
List<BitcoinAddressRecord> updateAllAddresses() {
|
List<BitcoinAddressRecord> updateAllAddresses() {
|
||||||
_allAddresses = addressesRecords.allRecords().whereType<BitcoinAddressRecord>().toList();
|
_allAddresses = addressesRecords.allRecords().toList();
|
||||||
|
|
||||||
updateAllScriptHashes();
|
|
||||||
updateSelectedReceiveAddresses();
|
updateSelectedReceiveAddresses();
|
||||||
updateSelectedChangeAddresses();
|
updateSelectedChangeAddresses();
|
||||||
|
|
||||||
allAddressesMap.clear();
|
allAddressesMap.clear();
|
||||||
_allAddresses.forEach((addressRecord) {
|
|
||||||
|
final hiddenAddresses = <String>{};
|
||||||
|
final allScriptHashes = <String>[];
|
||||||
|
|
||||||
|
for (final addressRecord in allAddresses) {
|
||||||
allAddressesMap[addressRecord.address] = addressRecord.name;
|
allAddressesMap[addressRecord.address] = addressRecord.name;
|
||||||
});
|
allScriptHashes.add(addressRecord.scriptHash);
|
||||||
|
|
||||||
|
if (addressRecord.isHidden) {
|
||||||
|
hiddenAddresses.add(addressRecord.address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: "otherAddresses" is used for wallets with different address types
|
||||||
|
// i.e. btc with silent paymnets or ltc with mweb
|
||||||
|
for (final addressRecord in otherAddresses) {
|
||||||
|
allAddressesMap[addressRecord.address] = addressRecord.name;
|
||||||
|
|
||||||
|
if (addressRecord.isHidden) {
|
||||||
|
hiddenAddresses.add(addressRecord.address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_allScriptHashes = allScriptHashes;
|
||||||
|
_hiddenAddresses = hiddenAddresses;
|
||||||
|
|
||||||
return _allAddresses;
|
return _allAddresses;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
// NOTE: Selected Receive Addresses = Receive addresses selected by current receive page type
|
// NOTE: Selected Receive Addresses = Receive addresses selected by current receive page type
|
||||||
List<BaseBitcoinAddressRecord> _selectedReceiveAddresses = [];
|
List<BitcoinAddressRecord> _selectedReceiveAddresses = [];
|
||||||
@computed
|
@computed
|
||||||
List<BaseBitcoinAddressRecord> get selectedReceiveAddresses => _selectedReceiveAddresses;
|
List<BitcoinAddressRecord> get selectedReceiveAddresses => _selectedReceiveAddresses;
|
||||||
|
|
||||||
@action
|
@action
|
||||||
List<BaseBitcoinAddressRecord> updateSelectedReceiveAddresses() {
|
List<BitcoinAddressRecord> updateSelectedReceiveAddresses() {
|
||||||
_selectedReceiveAddresses = addressesRecords.getRecords(
|
_selectedReceiveAddresses = addressesRecords.getRecords(
|
||||||
addressType: addressPageType,
|
addressType: addressPageType,
|
||||||
seedBytesType: walletSeedBytesType,
|
seedBytesType: walletSeedBytesType,
|
||||||
derivationPath: _defaultAddressPageDerivationInfo.derivationPath.toString(),
|
derivationPath: defaultAddressPageDerivationInfo.derivationPath.toString(),
|
||||||
isChange: false,
|
isChange: false,
|
||||||
);
|
);
|
||||||
updateNextReceiveAddress();
|
updateNextReceiveAddress();
|
||||||
|
@ -144,17 +169,17 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
// NOTE: Usable Receive Addresses = Receive addresses usable based on auto generate setting
|
// NOTE: Usable Receive Addresses = Receive addresses usable based on auto generate setting
|
||||||
List<BaseBitcoinAddressRecord> _usableReceiveAddresses = [];
|
List<BitcoinAddressRecord> _usableReceiveAddresses = [];
|
||||||
@computed
|
@computed
|
||||||
List<BaseBitcoinAddressRecord> get usableReceiveAddresses => _usableReceiveAddresses;
|
List<BitcoinAddressRecord> get usableReceiveAddresses => _usableReceiveAddresses;
|
||||||
|
|
||||||
@action
|
@action
|
||||||
List<BaseBitcoinAddressRecord> updateUsableReceiveAddresses() {
|
List<BitcoinAddressRecord> updateUsableReceiveAddresses() {
|
||||||
_usableReceiveAddresses = addressesRecords
|
_usableReceiveAddresses = addressesRecords
|
||||||
.getRecords(
|
.getRecords(
|
||||||
addressType: addressPageType,
|
addressType: addressPageType,
|
||||||
seedBytesType: walletSeedBytesType,
|
seedBytesType: walletSeedBytesType,
|
||||||
derivationPath: _defaultAddressPageDerivationInfo.derivationPath.toString(),
|
derivationPath: defaultAddressPageDerivationInfo.derivationPath.toString(),
|
||||||
isChange: false,
|
isChange: false,
|
||||||
)
|
)
|
||||||
.where(
|
.where(
|
||||||
|
@ -171,32 +196,27 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
|
|
||||||
@action
|
@action
|
||||||
BitcoinAddressRecord? updateNextReceiveAddress() {
|
BitcoinAddressRecord? updateNextReceiveAddress() {
|
||||||
final receiveAddresses = selectedReceiveAddresses.whereType<BitcoinAddressRecord>();
|
final receiveAddresses = selectedReceiveAddresses;
|
||||||
if (receiveAddresses.isEmpty) {
|
if (receiveAddresses.isEmpty) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_nextReceiveAddress = receiveAddresses.firstWhereOrNull(
|
_nextReceiveAddress = receiveAddresses.first;
|
||||||
(addressRecord) =>
|
|
||||||
addressRecord.getIsStillReceiveable(isEnabledAutoGenerateNewAddress) &&
|
|
||||||
!addressRecord.isChange,
|
|
||||||
) ??
|
|
||||||
receiveAddresses.first;
|
|
||||||
return _nextReceiveAddress;
|
return _nextReceiveAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
// NOTE: Selected Change Addresses = Change addresses selected by current receive page type
|
// NOTE: Selected Change Addresses = Change addresses selected by current receive page type
|
||||||
List<BaseBitcoinAddressRecord> _selectedChangeAddresses = [];
|
List<BitcoinAddressRecord> _selectedChangeAddresses = [];
|
||||||
@computed
|
@computed
|
||||||
List<BaseBitcoinAddressRecord> get selectedChangeAddresses => _selectedChangeAddresses;
|
List<BitcoinAddressRecord> get selectedChangeAddresses => _selectedChangeAddresses;
|
||||||
|
|
||||||
@action
|
@action
|
||||||
List<BaseBitcoinAddressRecord> updateSelectedChangeAddresses() {
|
List<BitcoinAddressRecord> updateSelectedChangeAddresses() {
|
||||||
_selectedChangeAddresses = addressesRecords.getRecords(
|
_selectedChangeAddresses = addressesRecords.getRecords(
|
||||||
addressType: addressPageType,
|
addressType: addressPageType,
|
||||||
seedBytesType: walletSeedBytesType,
|
seedBytesType: walletSeedBytesType,
|
||||||
derivationPath: _defaultAddressPageDerivationInfo.derivationPath.toString(),
|
derivationPath: defaultAddressPageDerivationInfo.derivationPath.toString(),
|
||||||
isChange: true,
|
isChange: true,
|
||||||
);
|
);
|
||||||
updateNextChangeAddress();
|
updateNextChangeAddress();
|
||||||
|
@ -206,17 +226,21 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
// NOTE: Usable Receive Addresses = Receive addresses usable based on auto generate setting
|
// NOTE: Usable Receive Addresses = Receive addresses usable based on auto generate setting
|
||||||
List<BaseBitcoinAddressRecord> _usableChangeAddresses = [];
|
List<BitcoinAddressRecord> _usableChangeAddresses = [];
|
||||||
@computed
|
@computed
|
||||||
List<BaseBitcoinAddressRecord> get usableChangeAddresses => _usableChangeAddresses;
|
List<BitcoinAddressRecord> get usableChangeAddresses => _usableChangeAddresses;
|
||||||
|
|
||||||
@action
|
@action
|
||||||
List<BaseBitcoinAddressRecord> updateUsableChangeAddresses() {
|
List<BitcoinAddressRecord> updateUsableChangeAddresses() {
|
||||||
_usableChangeAddresses = addressesRecords
|
_usableChangeAddresses = addressesRecords
|
||||||
.getRecords(
|
.getRecords(
|
||||||
addressType: changeAddressType,
|
addressType: changeAddressType,
|
||||||
seedBytesType: walletSeedBytesType,
|
seedBytesType: walletSeedBytesType,
|
||||||
derivationPath: _defaultAddressPageDerivationInfo.derivationPath.toString(),
|
derivationPath: BitcoinAddressUtils.getDerivationFromType(
|
||||||
|
changeAddressType,
|
||||||
|
network: network,
|
||||||
|
isElectrum: walletSeedBytesType.isElectrum,
|
||||||
|
).derivationPath.toString(),
|
||||||
isChange: true,
|
isChange: true,
|
||||||
)
|
)
|
||||||
.where(
|
.where(
|
||||||
|
@ -233,17 +257,12 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
|
|
||||||
@action
|
@action
|
||||||
BitcoinAddressRecord? updateNextChangeAddress() {
|
BitcoinAddressRecord? updateNextChangeAddress() {
|
||||||
final changeAddresses = selectedChangeAddresses.whereType<BitcoinAddressRecord>();
|
final changeAddresses = selectedChangeAddresses;
|
||||||
if (changeAddresses.isEmpty) {
|
if (changeAddresses.isEmpty) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_nextChangeAddress = changeAddresses.firstWhereOrNull(
|
_nextChangeAddress = changeAddresses.first;
|
||||||
(addressRecord) =>
|
|
||||||
addressRecord.getIsStillReceiveable(isEnabledAutoGenerateNewAddress) &&
|
|
||||||
addressRecord.isChange,
|
|
||||||
) ??
|
|
||||||
changeAddresses.first;
|
|
||||||
return _nextChangeAddress;
|
return _nextChangeAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,18 +271,11 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
@computed
|
@computed
|
||||||
List<String> get allScriptHashes => _allScriptHashes;
|
List<String> get allScriptHashes => _allScriptHashes;
|
||||||
|
|
||||||
@action
|
BitcoinAddressRecord getFromAddresses(String address) =>
|
||||||
List<String> updateAllScriptHashes() {
|
|
||||||
_allScriptHashes.clear();
|
|
||||||
_allScriptHashes.addAll(allAddresses.map((address) => address.scriptHash));
|
|
||||||
return _allScriptHashes;
|
|
||||||
}
|
|
||||||
|
|
||||||
BaseBitcoinAddressRecord getFromAddresses(String address) =>
|
|
||||||
allAddresses.firstWhere((element) => element.address == address);
|
allAddresses.firstWhere((element) => element.address == address);
|
||||||
|
|
||||||
// TODO: feature with toggle to switch change address type
|
// TODO: add feature to toggle from settings to switch what change address type is used when sending txs
|
||||||
// figure out how to manage on addres page type switch
|
// or add option that the current change address follows the current addres page type
|
||||||
@observable
|
@observable
|
||||||
BitcoinAddressType changeAddressType = SegwitAddressType.p2wpkh;
|
BitcoinAddressType changeAddressType = SegwitAddressType.p2wpkh;
|
||||||
|
|
||||||
|
@ -273,7 +285,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
@action
|
@action
|
||||||
void resetActiveAddress() {
|
void resetActiveAddress() {
|
||||||
try {
|
try {
|
||||||
final activeReceiveAddresses = selectedReceiveAddresses.whereType<BitcoinAddressRecord>();
|
final activeReceiveAddresses = selectedReceiveAddresses;
|
||||||
|
|
||||||
activeBitcoinAddress = activeReceiveAddresses.firstWhereOrNull(
|
activeBitcoinAddress = activeReceiveAddresses.firstWhereOrNull(
|
||||||
(addressRecord) => addressRecord.index == activeIndexByType[addressPageType],
|
(addressRecord) => addressRecord.index == activeIndexByType[addressPageType],
|
||||||
|
@ -311,15 +323,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@computed
|
@computed
|
||||||
String get primaryAddress =>
|
String get primaryAddress => nextReceiveAddress?.address ?? '';
|
||||||
selectedReceiveAddresses
|
|
||||||
.firstWhereOrNull(
|
|
||||||
(addressRecord) =>
|
|
||||||
addressRecord.getIsStillReceiveable(isEnabledAutoGenerateNewAddress) &&
|
|
||||||
!addressRecord.isChange,
|
|
||||||
)
|
|
||||||
?.address ??
|
|
||||||
'';
|
|
||||||
|
|
||||||
Map<BitcoinAddressType, int> activeIndexByType;
|
Map<BitcoinAddressType, int> activeIndexByType;
|
||||||
|
|
||||||
|
@ -342,7 +346,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
isChange: false,
|
isChange: false,
|
||||||
index: newAddressIndex,
|
index: newAddressIndex,
|
||||||
addressType: addressPageType,
|
addressType: addressPageType,
|
||||||
derivationInfo: _defaultAddressPageDerivationInfo,
|
derivationInfo: defaultAddressPageDerivationInfo,
|
||||||
hdWallet: hdWallet,
|
hdWallet: hdWallet,
|
||||||
seedBytesType: walletSeedBytesType,
|
seedBytesType: walletSeedBytesType,
|
||||||
network: network,
|
network: network,
|
||||||
|
@ -433,6 +437,34 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
@action
|
@action
|
||||||
Future<void> updateAddressesInBox() async {
|
Future<void> updateAddressesInBox() async {
|
||||||
addressesMap.clear();
|
addressesMap.clear();
|
||||||
|
|
||||||
|
for (final addressType in walletAddressTypes) {
|
||||||
|
final index = activeIndexByType[addressType] ?? 0;
|
||||||
|
|
||||||
|
final address = addressesRecords
|
||||||
|
.getRecords(
|
||||||
|
addressType: addressType,
|
||||||
|
seedBytesType: walletSeedBytesType,
|
||||||
|
derivationPath: BitcoinAddressUtils.getDerivationFromType(
|
||||||
|
addressType,
|
||||||
|
network: network,
|
||||||
|
isElectrum: walletSeedBytesType.isElectrum,
|
||||||
|
).derivationPath.toString(),
|
||||||
|
isChange: false,
|
||||||
|
)
|
||||||
|
.firstWhereOrNull((addressRecord) => addressRecord.index == index);
|
||||||
|
|
||||||
|
final isCurrentType = addressType == addressPageType;
|
||||||
|
|
||||||
|
if (address != null) {
|
||||||
|
if (isCurrentType) {
|
||||||
|
addressesMap[address.address] = 'Active - ' + addressPageType.toString() + ': $address';
|
||||||
|
} else {
|
||||||
|
addressesMap[address.address] = '${addressType.value.toUpperCase()}: ${address.address}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
addressesMap[address] = 'Active - ' + addressPageType.toString() + ': $address';
|
addressesMap[address] = 'Active - ' + addressPageType.toString() + ': $address';
|
||||||
|
|
||||||
await saveAddressesInBox();
|
await saveAddressesInBox();
|
||||||
|
@ -440,7 +472,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void updateAddress(String address, String label) {
|
void updateAddress(String address, String label) {
|
||||||
BaseBitcoinAddressRecord? foundAddress;
|
BitcoinAddressRecord? foundAddress;
|
||||||
|
|
||||||
for (final addressRecord in _allAddresses) {
|
for (final addressRecord in _allAddresses) {
|
||||||
if (addressRecord.address == address) {
|
if (addressRecord.address == address) {
|
||||||
|
@ -449,7 +481,6 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: verify this updates and keeps on re-open
|
|
||||||
if (foundAddress != null) {
|
if (foundAddress != null) {
|
||||||
foundAddress.setNewName(label);
|
foundAddress.setNewName(label);
|
||||||
}
|
}
|
||||||
|
@ -533,7 +564,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef AddressRecords = List<BaseBitcoinAddressRecord>;
|
typedef AddressRecords = List<BitcoinAddressRecord>;
|
||||||
|
|
||||||
typedef ItemsByIsChange<T> = Map<bool, T>;
|
typedef ItemsByIsChange<T> = Map<bool, T>;
|
||||||
typedef ItemsByDerivationPath<T> = Map<String, ItemsByIsChange<T>>;
|
typedef ItemsByDerivationPath<T> = Map<String, ItemsByIsChange<T>>;
|
||||||
|
@ -572,7 +603,7 @@ class BitcoinAddressRecordMap extends ItemsRecordMap<AddressRecordsBySeedType> {
|
||||||
required SeedBytesType seedBytesType,
|
required SeedBytesType seedBytesType,
|
||||||
required String derivationPath,
|
required String derivationPath,
|
||||||
required bool isChange,
|
required bool isChange,
|
||||||
required List<BaseBitcoinAddressRecord> addressRecords,
|
required List<BitcoinAddressRecord> addressRecords,
|
||||||
}) {
|
}) {
|
||||||
_data.putIfAbsent(
|
_data.putIfAbsent(
|
||||||
addressType,
|
addressType,
|
||||||
|
@ -614,18 +645,18 @@ class BitcoinAddressRecordMap extends ItemsRecordMap<AddressRecordsBySeedType> {
|
||||||
_data[addressType]![seedBytesType]![derivationPath]![isChange] = recordsList;
|
_data[addressType]![seedBytesType]![derivationPath]![isChange] = recordsList;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<BaseBitcoinAddressRecord> allRecords() {
|
List<BitcoinAddressRecord> allRecords() {
|
||||||
return _data.values
|
return _data.values
|
||||||
.expand((seedTypeMap) => seedTypeMap.values)
|
.expand((seedTypeMap) => seedTypeMap.values)
|
||||||
.expand((derivationMap) => derivationMap.values)
|
.expand((derivationMap) => derivationMap.values)
|
||||||
.expand((changeMap) => changeMap.values)
|
.expand((changeMap) => changeMap.values)
|
||||||
.fold<List<BaseBitcoinAddressRecord>>(
|
.fold<List<BitcoinAddressRecord>>(
|
||||||
[],
|
[],
|
||||||
(acc, records) => acc..addAll(records),
|
(acc, records) => acc..addAll(records),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<BaseBitcoinAddressRecord> getRecords({
|
List<BitcoinAddressRecord> getRecords({
|
||||||
required BitcoinAddressType addressType,
|
required BitcoinAddressType addressType,
|
||||||
required SeedBytesType seedBytesType,
|
required SeedBytesType seedBytesType,
|
||||||
required String derivationPath,
|
required String derivationPath,
|
||||||
|
@ -672,7 +703,7 @@ class BitcoinAddressRecordMap extends ItemsRecordMap<AddressRecordsBySeedType> {
|
||||||
(isChange, v) => MapEntry(
|
(isChange, v) => MapEntry(
|
||||||
isChange == 'true',
|
isChange == 'true',
|
||||||
(v as List<dynamic>)
|
(v as List<dynamic>)
|
||||||
.map((address) => BaseBitcoinAddressRecord.fromJSON(address as String))
|
.map((address) => BitcoinAddressRecord.fromJSON(address as String))
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -145,6 +145,56 @@ class ElectrumWorker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<DateResult> _getTxDate(
|
||||||
|
String txid,
|
||||||
|
int currentChainTip, {
|
||||||
|
int? confirmations,
|
||||||
|
DateTime? date,
|
||||||
|
}) async {
|
||||||
|
int? time;
|
||||||
|
int? height;
|
||||||
|
bool? isDateValidated;
|
||||||
|
|
||||||
|
final mempoolApi = ApiProvider.fromMempool(
|
||||||
|
_network!,
|
||||||
|
baseUrl: "https://mempool.cakewallet.com/api/v1",
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final txVerbose = await mempoolApi.getTransaction<MempoolTransaction>(txid);
|
||||||
|
|
||||||
|
final status = txVerbose.status;
|
||||||
|
height = status.blockHeight;
|
||||||
|
|
||||||
|
if (height != null) {
|
||||||
|
final blockHash = await mempoolApi.getBlockHeight(height);
|
||||||
|
final block = await mempoolApi.getBlock(blockHash);
|
||||||
|
|
||||||
|
time = int.parse(block['timestamp'].toString());
|
||||||
|
|
||||||
|
if (date != null) {
|
||||||
|
final newDate = DateTime.fromMillisecondsSinceEpoch(time * 1000);
|
||||||
|
isDateValidated = newDate == date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
if (confirmations == null && height != null) {
|
||||||
|
final tip = currentChainTip;
|
||||||
|
if (tip > 0 && height > 0) {
|
||||||
|
// Add one because the block itself is the first confirmation
|
||||||
|
confirmations = tip - height + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DateResult(
|
||||||
|
time: time,
|
||||||
|
height: height,
|
||||||
|
isDateValidated: isDateValidated,
|
||||||
|
confirmations: confirmations,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _handleConnect(ElectrumWorkerConnectionRequest request) async {
|
Future<void> _handleConnect(ElectrumWorkerConnectionRequest request) async {
|
||||||
_network = request.network;
|
_network = request.network;
|
||||||
_walletType = request.walletType;
|
_walletType = request.walletType;
|
||||||
|
@ -219,6 +269,9 @@ class ElectrumWorker {
|
||||||
tx.isPending = tx.confirmations == 0;
|
tx.isPending = tx.confirmations == 0;
|
||||||
anyTxWasUpdated = true;
|
anyTxWasUpdated = true;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
tx.height = newChainTip;
|
||||||
|
tx.confirmations = 0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -477,10 +530,11 @@ class ElectrumWorker {
|
||||||
final txInfo = ElectrumTransactionInfo.fromElectrumBundle(
|
final txInfo = ElectrumTransactionInfo.fromElectrumBundle(
|
||||||
txBundle,
|
txBundle,
|
||||||
request.walletType,
|
request.walletType,
|
||||||
request.network,
|
_network!,
|
||||||
addresses: addresses.toSet(),
|
addresses: addresses.toSet(),
|
||||||
height: transactionIdsForHeights[hash],
|
height: transactionIdsForHeights[hash],
|
||||||
);
|
);
|
||||||
|
txInfo.updateInputsAndOutputs(txBundle, _network!);
|
||||||
|
|
||||||
request.addresses.forEach(
|
request.addresses.forEach(
|
||||||
(addr) {
|
(addr) {
|
||||||
|
@ -600,7 +654,7 @@ class ElectrumWorker {
|
||||||
final txInfo = ElectrumTransactionInfo.fromElectrumBundle(
|
final txInfo = ElectrumTransactionInfo.fromElectrumBundle(
|
||||||
txBundle,
|
txBundle,
|
||||||
request.walletType,
|
request.walletType,
|
||||||
request.network,
|
_network!,
|
||||||
addresses: addresses.toSet(),
|
addresses: addresses.toSet(),
|
||||||
height: transactionIdsForHeights[hash],
|
height: transactionIdsForHeights[hash],
|
||||||
);
|
);
|
||||||
|
@ -758,9 +812,8 @@ class ElectrumWorker {
|
||||||
);
|
);
|
||||||
} else if (request.mempoolAPIEnabled) {
|
} else if (request.mempoolAPIEnabled) {
|
||||||
try {
|
try {
|
||||||
date = await getTxDate(
|
date = await _getTxDate(
|
||||||
hash,
|
hash,
|
||||||
_network!,
|
|
||||||
request.chainTip,
|
request.chainTip,
|
||||||
confirmations: storedTx?.confirmations,
|
confirmations: storedTx?.confirmations,
|
||||||
date: storedTx?.date,
|
date: storedTx?.date,
|
||||||
|
@ -788,7 +841,7 @@ class ElectrumWorker {
|
||||||
isDateValidated: date?.isDateValidated,
|
isDateValidated: date?.isDateValidated,
|
||||||
),
|
),
|
||||||
request.walletType,
|
request.walletType,
|
||||||
request.network,
|
_network!,
|
||||||
addresses: addresses.toSet(),
|
addresses: addresses.toSet(),
|
||||||
height: transactionsByIds[hash]?.height,
|
height: transactionsByIds[hash]?.height,
|
||||||
);
|
);
|
||||||
|
@ -940,9 +993,8 @@ class ElectrumWorker {
|
||||||
);
|
);
|
||||||
} else if (request.mempoolAPIEnabled) {
|
} else if (request.mempoolAPIEnabled) {
|
||||||
try {
|
try {
|
||||||
date = await getTxDate(
|
date = await _getTxDate(
|
||||||
hash,
|
hash,
|
||||||
_network!,
|
|
||||||
request.chainTip,
|
request.chainTip,
|
||||||
confirmations: storedTx?.confirmations,
|
confirmations: storedTx?.confirmations,
|
||||||
date: storedTx?.date,
|
date: storedTx?.date,
|
||||||
|
@ -973,7 +1025,7 @@ class ElectrumWorker {
|
||||||
isDateValidated: date?.isDateValidated,
|
isDateValidated: date?.isDateValidated,
|
||||||
),
|
),
|
||||||
request.walletType,
|
request.walletType,
|
||||||
request.network,
|
_network!,
|
||||||
addresses: addresses.toSet(),
|
addresses: addresses.toSet(),
|
||||||
height: transactionsByIds[hash]?.height,
|
height: transactionsByIds[hash]?.height,
|
||||||
);
|
);
|
||||||
|
@ -1023,37 +1075,6 @@ class ElectrumWorker {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Future<void> _handleListUnspents(ElectrumWorkerGetBalanceRequest request) async {
|
|
||||||
// final balanceFutures = <Future<Map<String, dynamic>>>[];
|
|
||||||
|
|
||||||
// for (final scripthash in request.scripthashes) {
|
|
||||||
// final balanceFuture = _electrumClient!.request(
|
|
||||||
// ElectrumGetScriptHashBalance(scriptHash: scripthash),
|
|
||||||
// );
|
|
||||||
// balanceFutures.add(balanceFuture);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var totalConfirmed = 0;
|
|
||||||
// var totalUnconfirmed = 0;
|
|
||||||
|
|
||||||
// final balances = await Future.wait(balanceFutures);
|
|
||||||
|
|
||||||
// for (final balance in balances) {
|
|
||||||
// final confirmed = balance['confirmed'] as int? ?? 0;
|
|
||||||
// final unconfirmed = balance['unconfirmed'] as int? ?? 0;
|
|
||||||
// totalConfirmed += confirmed;
|
|
||||||
// totalUnconfirmed += unconfirmed;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// _sendResponse(ElectrumWorkerGetBalanceResponse(
|
|
||||||
// result: ElectrumBalance(
|
|
||||||
// confirmed: totalConfirmed,
|
|
||||||
// unconfirmed: totalUnconfirmed,
|
|
||||||
// frozen: 0,
|
|
||||||
// ),
|
|
||||||
// ));
|
|
||||||
// }
|
|
||||||
|
|
||||||
Future<void> _handleGetBalance(ElectrumWorkerGetBalanceRequest request) async {
|
Future<void> _handleGetBalance(ElectrumWorkerGetBalanceRequest request) async {
|
||||||
final scripthashes = request.scripthashes.where((s) => s.isNotEmpty).toList();
|
final scripthashes = request.scripthashes.where((s) => s.isNotEmpty).toList();
|
||||||
final balanceResults = <Map<String, dynamic>>[];
|
final balanceResults = <Map<String, dynamic>>[];
|
||||||
|
@ -1078,22 +1099,26 @@ class ElectrumWorker {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
var totalConfirmed = 0;
|
final balances = <ElectrumBalance>[];
|
||||||
var totalUnconfirmed = 0;
|
|
||||||
|
|
||||||
for (final balance in balanceResults) {
|
for (final balance in balanceResults) {
|
||||||
final confirmed = balance['confirmed'] as int? ?? 0;
|
final confirmed = balance['confirmed'] as int? ?? 0;
|
||||||
final unconfirmed = balance['unconfirmed'] as int? ?? 0;
|
final unconfirmed = balance['unconfirmed'] as int? ?? 0;
|
||||||
totalConfirmed += confirmed;
|
|
||||||
totalUnconfirmed += unconfirmed;
|
balances.add(
|
||||||
|
ElectrumBalance(
|
||||||
|
confirmed: confirmed,
|
||||||
|
unconfirmed: unconfirmed,
|
||||||
|
frozen: 0,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_sendResponse(
|
_sendResponse(
|
||||||
ElectrumWorkerGetBalanceResponse(
|
ElectrumWorkerGetBalanceResponse(
|
||||||
result: ElectrumBalance(
|
result: ElectrumGetBalanceResponse(
|
||||||
confirmed: totalConfirmed,
|
balances: balances,
|
||||||
unconfirmed: totalUnconfirmed,
|
scripthashes: scripthashes,
|
||||||
frozen: 0,
|
|
||||||
),
|
),
|
||||||
id: request.id,
|
id: request.id,
|
||||||
),
|
),
|
||||||
|
@ -1114,9 +1139,7 @@ class ElectrumWorker {
|
||||||
)
|
)
|
||||||
.timeout(const Duration(seconds: 3));
|
.timeout(const Duration(seconds: 3));
|
||||||
|
|
||||||
if (scriptHashUnspents.isNotEmpty) {
|
|
||||||
unspents[scriptHash] = scriptHashUnspents;
|
unspents[scriptHash] = scriptHashUnspents;
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
_sendResponse(ElectrumWorkerListUnspentResponse(utxos: unspents, id: request.id));
|
_sendResponse(ElectrumWorkerListUnspentResponse(utxos: unspents, id: request.id));
|
||||||
|
@ -1337,9 +1360,8 @@ class ElectrumWorker {
|
||||||
if (getTime && _walletType == WalletType.bitcoin) {
|
if (getTime && _walletType == WalletType.bitcoin) {
|
||||||
if (mempoolAPIEnabled) {
|
if (mempoolAPIEnabled) {
|
||||||
try {
|
try {
|
||||||
dates = await getTxDate(
|
dates = await _getTxDate(
|
||||||
hash,
|
hash,
|
||||||
_network!,
|
|
||||||
currentChainTip,
|
currentChainTip,
|
||||||
confirmations: confirmations,
|
confirmations: confirmations,
|
||||||
date: date,
|
date: date,
|
||||||
|
@ -1418,7 +1440,7 @@ class ElectrumWorker {
|
||||||
int halfHour = recommendedFees.medium.satoshis;
|
int halfHour = recommendedFees.medium.satoshis;
|
||||||
int fastest = recommendedFees.high.satoshis;
|
int fastest = recommendedFees.high.satoshis;
|
||||||
|
|
||||||
// Bitcoin only: adjust fee rates to avoid equal fee values
|
// Adjust fee rates to avoid equal fee values
|
||||||
// elevated fee should be higher than normal fee
|
// elevated fee should be higher than normal fee
|
||||||
if (hour == halfHour) {
|
if (hour == halfHour) {
|
||||||
halfHour++;
|
halfHour++;
|
||||||
|
@ -1566,6 +1588,7 @@ class ElectrumWorker {
|
||||||
final syncingStatus = scanData.isSingleScan
|
final syncingStatus = scanData.isSingleScan
|
||||||
? SyncingSyncStatus(1, 0)
|
? SyncingSyncStatus(1, 0)
|
||||||
: SyncingSyncStatus.fromHeightValues(scanData.chainTip, initialSyncHeight, syncHeight);
|
: SyncingSyncStatus.fromHeightValues(scanData.chainTip, initialSyncHeight, syncHeight);
|
||||||
|
|
||||||
_sendResponse(ElectrumWorkerTweaksSubscribeResponse(
|
_sendResponse(ElectrumWorkerTweaksSubscribeResponse(
|
||||||
result: TweaksSyncResponse(
|
result: TweaksSyncResponse(
|
||||||
height: syncHeight,
|
height: syncHeight,
|
||||||
|
@ -1608,11 +1631,7 @@ class ElectrumWorker {
|
||||||
}
|
}
|
||||||
|
|
||||||
// placeholder ElectrumTransactionInfo object to update values based on new scanned unspent(s)
|
// placeholder ElectrumTransactionInfo object to update values based on new scanned unspent(s)
|
||||||
final txDate = await getTxDate(
|
final txDate = await _getTxDate(txid, scanData.chainTip);
|
||||||
txid,
|
|
||||||
scanData.network,
|
|
||||||
scanData.chainTip,
|
|
||||||
);
|
|
||||||
|
|
||||||
final txInfo = ElectrumTransactionInfo(
|
final txInfo = ElectrumTransactionInfo(
|
||||||
WalletType.bitcoin,
|
WalletType.bitcoin,
|
||||||
|
@ -1651,6 +1670,8 @@ class ElectrumWorker {
|
||||||
);
|
);
|
||||||
|
|
||||||
final labelIndex = labelValue != null ? scanData.labels[label] : 0;
|
final labelIndex = labelValue != null ? scanData.labels[label] : 0;
|
||||||
|
final balance = ElectrumBalance.zero();
|
||||||
|
balance.confirmed = amount;
|
||||||
|
|
||||||
final receivedAddressRecord = BitcoinReceivedSPAddressRecord(
|
final receivedAddressRecord = BitcoinReceivedSPAddressRecord(
|
||||||
receivingOutputAddress,
|
receivingOutputAddress,
|
||||||
|
@ -1661,7 +1682,7 @@ class ElectrumWorker {
|
||||||
isUsed: true,
|
isUsed: true,
|
||||||
tweak: t_k,
|
tweak: t_k,
|
||||||
txCount: 1,
|
txCount: 1,
|
||||||
balance: amount,
|
balance: balance,
|
||||||
spAddress: matchingSPWallet.toAddress(scanData.network),
|
spAddress: matchingSPWallet.toAddress(scanData.network),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1739,10 +1760,10 @@ class ElectrumWorker {
|
||||||
derivationInfo: request.derivationInfo,
|
derivationInfo: request.derivationInfo,
|
||||||
hdWallet: Bip32Slip10Secp256k1.fromExtendedKey(
|
hdWallet: Bip32Slip10Secp256k1.fromExtendedKey(
|
||||||
request.xpriv,
|
request.xpriv,
|
||||||
BitcoinAddressUtils.getKeyNetVersion(request.network),
|
BitcoinAddressUtils.getKeyNetVersion(_network!),
|
||||||
),
|
),
|
||||||
seedBytesType: request.seedBytesType,
|
seedBytesType: request.seedBytesType,
|
||||||
network: request.network,
|
network: _network!,
|
||||||
);
|
);
|
||||||
|
|
||||||
newAddresses.add(addressRecord);
|
newAddresses.add(addressRecord);
|
||||||
|
@ -1785,57 +1806,6 @@ class DateResult {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<DateResult> getTxDate(
|
|
||||||
String txid,
|
|
||||||
BasedUtxoNetwork network,
|
|
||||||
int currentChainTip, {
|
|
||||||
int? confirmations,
|
|
||||||
DateTime? date,
|
|
||||||
}) async {
|
|
||||||
int? time;
|
|
||||||
int? height;
|
|
||||||
bool? isDateValidated;
|
|
||||||
|
|
||||||
final mempoolApi = ApiProvider.fromMempool(
|
|
||||||
network,
|
|
||||||
baseUrl: "https://mempool.cakewallet.com/api/v1",
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
final txVerbose = await mempoolApi.getTransaction<MempoolTransaction>(txid);
|
|
||||||
|
|
||||||
final status = txVerbose.status;
|
|
||||||
height = status.blockHeight;
|
|
||||||
|
|
||||||
if (height != null) {
|
|
||||||
final blockHash = await mempoolApi.getBlockHeight(height);
|
|
||||||
final block = await mempoolApi.getBlock(blockHash);
|
|
||||||
|
|
||||||
time = int.parse(block['timestamp'].toString());
|
|
||||||
|
|
||||||
if (date != null) {
|
|
||||||
final newDate = DateTime.fromMillisecondsSinceEpoch(time * 1000);
|
|
||||||
isDateValidated = newDate == date;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
|
|
||||||
if (confirmations == null && height != null) {
|
|
||||||
final tip = currentChainTip;
|
|
||||||
if (tip > 0 && height > 0) {
|
|
||||||
// Add one because the block itself is the first confirmation
|
|
||||||
confirmations = tip - height + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return DateResult(
|
|
||||||
time: time,
|
|
||||||
height: height,
|
|
||||||
isDateValidated: isDateValidated,
|
|
||||||
confirmations: confirmations,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class TxToFetch {
|
class TxToFetch {
|
||||||
final ElectrumTransactionInfo? tx;
|
final ElectrumTransactionInfo? tx;
|
||||||
final int height;
|
final int height;
|
||||||
|
|
|
@ -44,8 +44,44 @@ class ElectrumWorkerGetBalanceError extends ElectrumWorkerErrorResponse {
|
||||||
final String method = ElectrumRequestMethods.getBalance.method;
|
final String method = ElectrumRequestMethods.getBalance.method;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ElectrumGetBalanceResponse {
|
||||||
|
ElectrumGetBalanceResponse({
|
||||||
|
required this.balances,
|
||||||
|
required this.scripthashes,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<ElectrumBalance> balances;
|
||||||
|
final List<String> scripthashes;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'balances': balances
|
||||||
|
.map((e) => {
|
||||||
|
'confirmed': e.confirmed,
|
||||||
|
'unconfirmed': e.unconfirmed,
|
||||||
|
'frozen': e.frozen,
|
||||||
|
})
|
||||||
|
.toList(),
|
||||||
|
'scripthashes': scripthashes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory ElectrumGetBalanceResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ElectrumGetBalanceResponse(
|
||||||
|
balances: (json['balances'] as List)
|
||||||
|
.map((e) => ElectrumBalance(
|
||||||
|
confirmed: e['confirmed'] as int,
|
||||||
|
unconfirmed: e['unconfirmed'] as int,
|
||||||
|
frozen: e['frozen'] as int,
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
scripthashes: (json['scripthashes'] as List).cast<String>(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ElectrumWorkerGetBalanceResponse
|
class ElectrumWorkerGetBalanceResponse
|
||||||
extends ElectrumWorkerResponse<ElectrumBalance, Map<String, int>?> {
|
extends ElectrumWorkerResponse<ElectrumGetBalanceResponse, Map<String, dynamic>> {
|
||||||
ElectrumWorkerGetBalanceResponse({
|
ElectrumWorkerGetBalanceResponse({
|
||||||
required super.result,
|
required super.result,
|
||||||
super.error,
|
super.error,
|
||||||
|
@ -54,18 +90,14 @@ class ElectrumWorkerGetBalanceResponse
|
||||||
}) : super(method: ElectrumRequestMethods.getBalance.method);
|
}) : super(method: ElectrumRequestMethods.getBalance.method);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, int>? resultJson(result) {
|
Map<String, dynamic> resultJson(result) {
|
||||||
return {"confirmed": result.confirmed, "unconfirmed": result.unconfirmed};
|
return result.toJson();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
factory ElectrumWorkerGetBalanceResponse.fromJson(Map<String, dynamic> json) {
|
factory ElectrumWorkerGetBalanceResponse.fromJson(Map<String, dynamic> json) {
|
||||||
return ElectrumWorkerGetBalanceResponse(
|
return ElectrumWorkerGetBalanceResponse(
|
||||||
result: ElectrumBalance(
|
result: ElectrumGetBalanceResponse.fromJson(json['result'] as Map<String, dynamic>),
|
||||||
confirmed: json['result']['confirmed'] as int,
|
|
||||||
unconfirmed: json['result']['unconfirmed'] as int,
|
|
||||||
frozen: 0,
|
|
||||||
),
|
|
||||||
error: json['error'] as String?,
|
error: json['error'] as String?,
|
||||||
id: json['id'] as int?,
|
id: json['id'] as int?,
|
||||||
completed: json['completed'] as bool? ?? false,
|
completed: json['completed'] as bool? ?? false,
|
||||||
|
|
|
@ -450,12 +450,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet<LitecoinWalletAddresses
|
||||||
|
|
||||||
// reset coin balances and txCount to 0:
|
// reset coin balances and txCount to 0:
|
||||||
unspentCoins.forEach((coin) {
|
unspentCoins.forEach((coin) {
|
||||||
coin.bitcoinAddressRecord.balance = 0;
|
coin.bitcoinAddressRecord.balance = ElectrumBalance.zero();
|
||||||
coin.bitcoinAddressRecord.txCount = 0;
|
coin.bitcoinAddressRecord.txCount = 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
for (var addressRecord in walletAddresses.mwebAddresses) {
|
for (var addressRecord in walletAddresses.mwebAddresses) {
|
||||||
addressRecord.balance = 0;
|
addressRecord.balance = ElectrumBalance.zero();
|
||||||
addressRecord.txCount = 0;
|
addressRecord.txCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -520,7 +520,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet<LitecoinWalletAddresses
|
||||||
|
|
||||||
// update the txCount:
|
// update the txCount:
|
||||||
addressRecord.txCount++;
|
addressRecord.txCount++;
|
||||||
addressRecord.balance += utxo.value.toInt();
|
addressRecord.balance.confirmed += utxo.value.toInt();
|
||||||
addressRecord.setAsUsed();
|
addressRecord.setAsUsed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -676,7 +676,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet<LitecoinWalletAddresses
|
||||||
if (!inputAddresses.contains(utxo.address)) {
|
if (!inputAddresses.contains(utxo.address)) {
|
||||||
addressRecord.txCount++;
|
addressRecord.txCount++;
|
||||||
}
|
}
|
||||||
addressRecord.balance -= utxo.value.toInt();
|
addressRecord.balance.confirmed -= utxo.value.toInt();
|
||||||
amount += utxo.value.toInt();
|
amount += utxo.value.toInt();
|
||||||
inputAddresses.add(utxo.address);
|
inputAddresses.add(utxo.address);
|
||||||
input.add(hex.decode(outputId));
|
input.add(hex.decode(outputId));
|
||||||
|
@ -855,7 +855,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet<LitecoinWalletAddresses
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
for (var addressRecord in walletAddresses.mwebAddresses) {
|
for (var addressRecord in walletAddresses.mwebAddresses) {
|
||||||
addressRecord.balance = 0;
|
addressRecord.balance = ElectrumBalance.zero();
|
||||||
addressRecord.txCount = 0;
|
addressRecord.txCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -873,7 +873,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet<LitecoinWalletAddresses
|
||||||
coin.isFrozen = coinInfo.isFrozen;
|
coin.isFrozen = coinInfo.isFrozen;
|
||||||
coin.isSending = coinInfo.isSending;
|
coin.isSending = coinInfo.isSending;
|
||||||
coin.note = coinInfo.note;
|
coin.note = coinInfo.note;
|
||||||
coin.bitcoinAddressRecord.balance += coinInfo.value;
|
coin.bitcoinAddressRecord.balance.confirmed += coinInfo.value;
|
||||||
} else {
|
} else {
|
||||||
super.addCoinInfo(coin);
|
super.addCoinInfo(coin);
|
||||||
}
|
}
|
||||||
|
@ -1537,7 +1537,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet<LitecoinWalletAddresses
|
||||||
if (!addresses.contains(utxo.address)) {
|
if (!addresses.contains(utxo.address)) {
|
||||||
addresses.add(utxo.address);
|
addresses.add(utxo.address);
|
||||||
}
|
}
|
||||||
addressRecord.balance -= utxo.value.toInt();
|
addressRecord.balance.confirmed -= utxo.value.toInt();
|
||||||
});
|
});
|
||||||
transaction.inputAddresses?.addAll(addresses);
|
transaction.inputAddresses?.addAll(addresses);
|
||||||
printV("isPegIn: $isPegIn, isPegOut: $isPegOut");
|
printV("isPegIn: $isPegIn, isPegOut: $isPegOut");
|
||||||
|
|
|
@ -67,24 +67,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
if (!super.isHardwareWallet) await initMwebAddresses();
|
if (!super.isHardwareWallet) await initMwebAddresses();
|
||||||
|
|
||||||
// for (final seedBytesType in hdWallets.keys) {
|
super.init();
|
||||||
// await generateInitialAddresses(
|
|
||||||
// addressType: SegwitAddressType.p2wpkh,
|
|
||||||
// seedBytesType: seedBytesType,
|
|
||||||
// bitcoinDerivationInfo: seedBytesType.isElectrum
|
|
||||||
// ? BitcoinDerivationInfos.ELECTRUM
|
|
||||||
// : BitcoinDerivationInfos.LITECOIN,
|
|
||||||
// );
|
|
||||||
|
|
||||||
// if ((Platform.isAndroid || Platform.isIOS) && !isHardwareWallet) {
|
|
||||||
// await generateInitialMWEBAddresses(
|
|
||||||
// addressType: SegwitAddressType.mweb,
|
|
||||||
// seedBytesType: seedBytesType,
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
await super.init();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
|
|
@ -80,7 +80,7 @@ packages:
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: cake-update-v15
|
ref: cake-update-v15
|
||||||
resolved-ref: "1a74961e9448d331a2d26099c616b8420a94681c"
|
resolved-ref: "29160733cbc4ef2c7b8c8fe9ed0297c9bffecfe2"
|
||||||
url: "https://github.com/cake-tech/bitcoin_base"
|
url: "https://github.com/cake-tech/bitcoin_base"
|
||||||
source: git
|
source: git
|
||||||
version: "6.1.0"
|
version: "6.1.0"
|
||||||
|
|
|
@ -47,10 +47,10 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi
|
||||||
);
|
);
|
||||||
|
|
||||||
return BitcoinCashWalletAddresses(
|
return BitcoinCashWalletAddresses(
|
||||||
walletInfo,
|
electrumJson.walletInfo,
|
||||||
hdWallets: hdWallets,
|
hdWallets: electrumJson.hdWallets,
|
||||||
network: network,
|
network: electrumJson.network,
|
||||||
isHardwareWallet: isHardwareWallet,
|
isHardwareWallet: electrumJson.isHardwareWallet,
|
||||||
initialAddressesRecords: electrumJson.addressesRecords,
|
initialAddressesRecords: electrumJson.addressesRecords,
|
||||||
initialAddressPageType: electrumJson.addressPageType,
|
initialAddressPageType: electrumJson.addressPageType,
|
||||||
initialActiveAddressIndex: electrumJson.activeIndexByType,
|
initialActiveAddressIndex: electrumJson.activeIndexByType,
|
||||||
|
|
|
@ -160,7 +160,7 @@ class CWBitcoin extends Bitcoin {
|
||||||
address: addr.address,
|
address: addr.address,
|
||||||
derivationPath: addr.indexedDerivationPath,
|
derivationPath: addr.indexedDerivationPath,
|
||||||
txCount: addr.txCount,
|
txCount: addr.txCount,
|
||||||
balance: addr.balance,
|
balance: addr.balance.confirmed,
|
||||||
isChange: addr.isChange,
|
isChange: addr.isChange,
|
||||||
isHidden: addr.isHidden || addr.isChange,
|
isHidden: addr.isHidden || addr.isChange,
|
||||||
),
|
),
|
||||||
|
@ -537,7 +537,7 @@ class CWBitcoin extends Bitcoin {
|
||||||
address: addr.address,
|
address: addr.address,
|
||||||
derivationPath: addr.indexedDerivationPath,
|
derivationPath: addr.indexedDerivationPath,
|
||||||
txCount: addr.txCount,
|
txCount: addr.txCount,
|
||||||
balance: addr.balance,
|
balance: addr.balance.confirmed,
|
||||||
isChange: addr.isChange,
|
isChange: addr.isChange,
|
||||||
isHidden: addr.isHidden || addr.isChange,
|
isHidden: addr.isHidden || addr.isChange,
|
||||||
))
|
))
|
||||||
|
@ -554,7 +554,7 @@ class CWBitcoin extends Bitcoin {
|
||||||
address: addr.address,
|
address: addr.address,
|
||||||
derivationPath: addr.indexedDerivationPath,
|
derivationPath: addr.indexedDerivationPath,
|
||||||
txCount: addr.txCount,
|
txCount: addr.txCount,
|
||||||
balance: addr.balance,
|
balance: addr.balance.confirmed,
|
||||||
isChange: addr.isChange,
|
isChange: addr.isChange,
|
||||||
isHidden: addr.isHidden || addr.isChange,
|
isHidden: addr.isHidden || addr.isChange,
|
||||||
))
|
))
|
||||||
|
|
|
@ -311,7 +311,6 @@ class CakePayBuyCardDetailPage extends BasePage {
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
await cakePayPurchaseViewModel.cakePayService.logout();
|
await cakePayPurchaseViewModel.cakePayService.logout();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
cakePayPurchaseViewModel.isPurchasing = false;
|
cakePayPurchaseViewModel.isPurchasing = false;
|
||||||
}
|
}
|
||||||
|
@ -390,10 +389,13 @@ class CakePayBuyCardDetailPage extends BasePage {
|
||||||
currency: cakePayPurchaseViewModel.sendViewModel.selectedCryptoCurrency,
|
currency: cakePayPurchaseViewModel.sendViewModel.selectedCryptoCurrency,
|
||||||
amount: S.of(popupContext).send_amount,
|
amount: S.of(popupContext).send_amount,
|
||||||
amountValue: cakePayPurchaseViewModel.sendViewModel.pendingTransaction!.amountFormatted,
|
amountValue: cakePayPurchaseViewModel.sendViewModel.pendingTransaction!.amountFormatted,
|
||||||
fiatAmountValue: cakePayPurchaseViewModel.sendViewModel.pendingTransactionFiatAmountFormatted,
|
fiatAmountValue:
|
||||||
|
cakePayPurchaseViewModel.sendViewModel.pendingTransactionFiatAmountFormatted,
|
||||||
fee: S.of(popupContext).send_fee,
|
fee: S.of(popupContext).send_fee,
|
||||||
|
feeRate: cakePayPurchaseViewModel.sendViewModel.pendingTransaction!.feeRate,
|
||||||
feeValue: cakePayPurchaseViewModel.sendViewModel.pendingTransaction!.feeFormatted,
|
feeValue: cakePayPurchaseViewModel.sendViewModel.pendingTransaction!.feeFormatted,
|
||||||
feeFiatAmount: cakePayPurchaseViewModel.sendViewModel.pendingTransactionFeeFiatAmountFormatted,
|
feeFiatAmount:
|
||||||
|
cakePayPurchaseViewModel.sendViewModel.pendingTransactionFeeFiatAmountFormatted,
|
||||||
outputs: cakePayPurchaseViewModel.sendViewModel.outputs,
|
outputs: cakePayPurchaseViewModel.sendViewModel.outputs,
|
||||||
onSlideComplete: () async {
|
onSlideComplete: () async {
|
||||||
Navigator.of(popupContext).pop();
|
Navigator.of(popupContext).pop();
|
||||||
|
|
|
@ -22,6 +22,7 @@ class SyncIndicator extends StatelessWidget {
|
||||||
final syncIndicatorWidth = 237.0;
|
final syncIndicatorWidth = 237.0;
|
||||||
final status = dashboardViewModel.status;
|
final status = dashboardViewModel.status;
|
||||||
final statusText = syncStatusTitle(status);
|
final statusText = syncStatusTitle(status);
|
||||||
|
final isScanning = status is SyncingSyncStatus;
|
||||||
final progress = status.progress();
|
final progress = status.progress();
|
||||||
final indicatorOffset = progress * syncIndicatorWidth;
|
final indicatorOffset = progress * syncIndicatorWidth;
|
||||||
final indicatorWidth = progress < 1
|
final indicatorWidth = progress < 1
|
||||||
|
@ -64,7 +65,17 @@ class SyncIndicator extends StatelessWidget {
|
||||||
SyncIndicatorIcon(isSynced: status is SyncedSyncStatus),
|
SyncIndicatorIcon(isSynced: status is SyncedSyncStatus),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(left: 6),
|
padding: EdgeInsets.only(left: 6),
|
||||||
child: RollingText(statusText),
|
child: isScanning
|
||||||
|
? Text(
|
||||||
|
statusText,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color:
|
||||||
|
Theme.of(context).extension<SyncIndicatorTheme>()!.textColor,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: RollingText(statusText),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -214,7 +214,6 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
|
||||||
|
|
||||||
_exchangeStateReaction = reaction((_) => this.widget.exchangeTradeViewModel.sendViewModel.state,
|
_exchangeStateReaction = reaction((_) => this.widget.exchangeTradeViewModel.sendViewModel.state,
|
||||||
(ExecutionState state) async {
|
(ExecutionState state) async {
|
||||||
|
|
||||||
if (dialogContext != null && dialogContext?.mounted == true) {
|
if (dialogContext != null && dialogContext?.mounted == true) {
|
||||||
Navigator.of(dialogContext!).pop();
|
Navigator.of(dialogContext!).pop();
|
||||||
}
|
}
|
||||||
|
@ -289,6 +288,7 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
|
||||||
fee: isEVMCompatibleChain(widget.exchangeTradeViewModel.sendViewModel.walletType)
|
fee: isEVMCompatibleChain(widget.exchangeTradeViewModel.sendViewModel.walletType)
|
||||||
? S.of(bottomSheetContext).send_estimated_fee
|
? S.of(bottomSheetContext).send_estimated_fee
|
||||||
: S.of(bottomSheetContext).send_fee,
|
: S.of(bottomSheetContext).send_fee,
|
||||||
|
feeRate: widget.exchangeTradeViewModel.sendViewModel.pendingTransaction!.feeRate,
|
||||||
feeValue:
|
feeValue:
|
||||||
widget.exchangeTradeViewModel.sendViewModel.pendingTransaction!.feeFormatted,
|
widget.exchangeTradeViewModel.sendViewModel.pendingTransaction!.feeFormatted,
|
||||||
feeFiatAmount: widget.exchangeTradeViewModel.sendViewModel
|
feeFiatAmount: widget.exchangeTradeViewModel.sendViewModel
|
||||||
|
|
|
@ -567,6 +567,7 @@ class SendPage extends BasePage {
|
||||||
fee: isEVMCompatibleChain(sendViewModel.walletType)
|
fee: isEVMCompatibleChain(sendViewModel.walletType)
|
||||||
? S.of(bottomSheetContext).send_estimated_fee
|
? S.of(bottomSheetContext).send_estimated_fee
|
||||||
: S.of(bottomSheetContext).send_fee,
|
: S.of(bottomSheetContext).send_fee,
|
||||||
|
feeRate: sendViewModel.pendingTransaction!.feeRate,
|
||||||
feeValue: sendViewModel.pendingTransaction!.feeFormatted,
|
feeValue: sendViewModel.pendingTransaction!.feeFormatted,
|
||||||
feeFiatAmount: sendViewModel.pendingTransactionFeeFiatAmountFormatted,
|
feeFiatAmount: sendViewModel.pendingTransactionFeeFiatAmountFormatted,
|
||||||
outputs: sendViewModel.outputs,
|
outputs: sendViewModel.outputs,
|
||||||
|
@ -604,8 +605,7 @@ class SendPage extends BasePage {
|
||||||
context: context,
|
context: context,
|
||||||
isDismissible: false,
|
isDismissible: false,
|
||||||
builder: (BuildContext bottomSheetContext) {
|
builder: (BuildContext bottomSheetContext) {
|
||||||
return showContactSheet &&
|
return showContactSheet && sendViewModel.ocpRequest == null
|
||||||
sendViewModel.ocpRequest == null
|
|
||||||
? InfoBottomSheet(
|
? InfoBottomSheet(
|
||||||
currentTheme: currentTheme,
|
currentTheme: currentTheme,
|
||||||
showDontAskMeCheckbox: true,
|
showDontAskMeCheckbox: true,
|
||||||
|
|
|
@ -119,7 +119,6 @@ class RBFDetailsPage extends BasePage {
|
||||||
}
|
}
|
||||||
|
|
||||||
reaction((_) => transactionDetailsViewModel.sendViewModel.state, (ExecutionState state) {
|
reaction((_) => transactionDetailsViewModel.sendViewModel.state, (ExecutionState state) {
|
||||||
|
|
||||||
if (state is! IsExecutingState &&
|
if (state is! IsExecutingState &&
|
||||||
loadingBottomSheetContext != null &&
|
loadingBottomSheetContext != null &&
|
||||||
loadingBottomSheetContext!.mounted) {
|
loadingBottomSheetContext!.mounted) {
|
||||||
|
@ -191,14 +190,20 @@ class RBFDetailsPage extends BasePage {
|
||||||
titleText: S.of(bottomSheetContext).confirm_transaction,
|
titleText: S.of(bottomSheetContext).confirm_transaction,
|
||||||
currentTheme: currentTheme,
|
currentTheme: currentTheme,
|
||||||
walletType: transactionDetailsViewModel.sendViewModel.walletType,
|
walletType: transactionDetailsViewModel.sendViewModel.walletType,
|
||||||
titleIconPath: transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency.iconPath,
|
titleIconPath:
|
||||||
|
transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency.iconPath,
|
||||||
currency: transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency,
|
currency: transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency,
|
||||||
amount: S.of(bottomSheetContext).send_amount,
|
amount: S.of(bottomSheetContext).send_amount,
|
||||||
amountValue: transactionDetailsViewModel.sendViewModel.pendingTransaction!.amountFormatted,
|
amountValue:
|
||||||
fiatAmountValue: transactionDetailsViewModel.sendViewModel.pendingTransactionFiatAmountFormatted,
|
transactionDetailsViewModel.sendViewModel.pendingTransaction!.amountFormatted,
|
||||||
|
fiatAmountValue: transactionDetailsViewModel
|
||||||
|
.sendViewModel.pendingTransactionFiatAmountFormatted,
|
||||||
fee: S.of(bottomSheetContext).send_fee,
|
fee: S.of(bottomSheetContext).send_fee,
|
||||||
feeValue: transactionDetailsViewModel.sendViewModel.pendingTransaction!.feeFormatted,
|
feeRate: transactionDetailsViewModel.sendViewModel.pendingTransaction!.feeRate,
|
||||||
feeFiatAmount: transactionDetailsViewModel.sendViewModel.pendingTransactionFeeFiatAmountFormatted,
|
feeValue:
|
||||||
|
transactionDetailsViewModel.sendViewModel.pendingTransaction!.feeFormatted,
|
||||||
|
feeFiatAmount: transactionDetailsViewModel
|
||||||
|
.sendViewModel.pendingTransactionFeeFiatAmountFormatted,
|
||||||
outputs: transactionDetailsViewModel.sendViewModel.outputs,
|
outputs: transactionDetailsViewModel.sendViewModel.outputs,
|
||||||
onSlideComplete: () async {
|
onSlideComplete: () async {
|
||||||
Navigator.of(bottomSheetContext).pop();
|
Navigator.of(bottomSheetContext).pop();
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||||
|
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/routes.dart';
|
import 'package:cake_wallet/routes.dart';
|
||||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||||
|
@ -44,7 +45,12 @@ class TransactionDetailsPage extends BasePage {
|
||||||
if (item is StandartListItem) {
|
if (item is StandartListItem) {
|
||||||
Widget? addressTextWidget;
|
Widget? addressTextWidget;
|
||||||
|
|
||||||
if (item.title.toLowerCase() == 'recipient addresses' ||
|
final isContactAddress = item.value !=
|
||||||
|
AddressResolver.extractAddressByType(
|
||||||
|
raw: item.value,
|
||||||
|
type: transactionDetailsViewModel.wallet.currency,
|
||||||
|
);
|
||||||
|
if (!isContactAddress && item.title.toLowerCase() == 'recipient addresses' ||
|
||||||
item.title.toLowerCase() == 'source address') {
|
item.title.toLowerCase() == 'source address') {
|
||||||
addressTextWidget = getFormattedAddress(
|
addressTextWidget = getFormattedAddress(
|
||||||
context: context,
|
context: context,
|
||||||
|
@ -96,8 +102,10 @@ class TransactionDetailsPage extends BasePage {
|
||||||
child: SelectButton(
|
child: SelectButton(
|
||||||
text: S.of(context).bump_fee,
|
text: S.of(context).bump_fee,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
Navigator.of(context).pushNamed(Routes.bumpFeePage,
|
Navigator.of(context).pushNamed(Routes.bumpFeePage, arguments: [
|
||||||
arguments: [transactionDetailsViewModel.transactionInfo, transactionDetailsViewModel.rawTransaction]);
|
transactionDetailsViewModel.transactionInfo,
|
||||||
|
transactionDetailsViewModel.rawTransaction
|
||||||
|
]);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -124,17 +132,9 @@ class TransactionDetailsPage extends BasePage {
|
||||||
final bool hasDoubleNewline = value.contains('\n\n');
|
final bool hasDoubleNewline = value.contains('\n\n');
|
||||||
|
|
||||||
if (hasDoubleNewline) {
|
if (hasDoubleNewline) {
|
||||||
final blocks = value
|
final blocks = value.split('\n\n').map((b) => b.trim()).where((b) => b.isNotEmpty).toList();
|
||||||
.split('\n\n')
|
|
||||||
.map((b) => b.trim())
|
|
||||||
.where((b) => b.isNotEmpty)
|
|
||||||
.toList();
|
|
||||||
for (final block in blocks) {
|
for (final block in blocks) {
|
||||||
final lines = block
|
final lines = block.split('\n').map((l) => l.trim()).where((l) => l.isNotEmpty).toList();
|
||||||
.split('\n')
|
|
||||||
.map((l) => l.trim())
|
|
||||||
.where((l) => l.isNotEmpty)
|
|
||||||
.toList();
|
|
||||||
if (lines.length > 1) {
|
if (lines.length > 1) {
|
||||||
children.add(Text(lines.first, style: textStyle));
|
children.add(Text(lines.first, style: textStyle));
|
||||||
for (int i = 1; i < lines.length; i++) {
|
for (int i = 1; i < lines.length; i++) {
|
||||||
|
@ -158,11 +158,7 @@ class TransactionDetailsPage extends BasePage {
|
||||||
children.add(SizedBox(height: 8));
|
children.add(SizedBox(height: 8));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final lines = value
|
final lines = value.split('\n').map((l) => l.trim()).where((l) => l.isNotEmpty).toList();
|
||||||
.split('\n')
|
|
||||||
.map((l) => l.trim())
|
|
||||||
.where((l) => l.isNotEmpty)
|
|
||||||
.toList();
|
|
||||||
bool firstLineIsContactName = (lines.length > 1 && lines.first.length < 20);
|
bool firstLineIsContactName = (lines.length > 1 && lines.first.length < 20);
|
||||||
int startIndex = 0;
|
int startIndex = 0;
|
||||||
if (firstLineIsContactName) {
|
if (firstLineIsContactName) {
|
||||||
|
|
|
@ -24,6 +24,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
|
||||||
final String amountValue;
|
final String amountValue;
|
||||||
final String fiatAmountValue;
|
final String fiatAmountValue;
|
||||||
final String fee;
|
final String fee;
|
||||||
|
final String? feeRate;
|
||||||
final String feeValue;
|
final String feeValue;
|
||||||
final String feeFiatAmount;
|
final String feeFiatAmount;
|
||||||
final List<Output> outputs;
|
final List<Output> outputs;
|
||||||
|
@ -44,6 +45,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
|
||||||
required this.amountValue,
|
required this.amountValue,
|
||||||
required this.fiatAmountValue,
|
required this.fiatAmountValue,
|
||||||
required this.fee,
|
required this.fee,
|
||||||
|
this.feeRate,
|
||||||
required this.feeValue,
|
required this.feeValue,
|
||||||
required this.feeFiatAmount,
|
required this.feeFiatAmount,
|
||||||
required this.outputs,
|
required this.outputs,
|
||||||
|
@ -119,6 +121,16 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
|
||||||
itemSubTitleTextStyle: itemSubTitleTextStyle,
|
itemSubTitleTextStyle: itemSubTitleTextStyle,
|
||||||
tileBackgroundColor: tileBackgroundColor,
|
tileBackgroundColor: tileBackgroundColor,
|
||||||
),
|
),
|
||||||
|
if (feeRate != null && feeRate!.isNotEmpty) ...[
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
StandardTile(
|
||||||
|
itemTitle: S.current.send_estimated_fee,
|
||||||
|
itemValue: "$feeRate sat/byte",
|
||||||
|
itemTitleTextStyle: itemTitleTextStyle,
|
||||||
|
itemSubTitleTextStyle: itemSubTitleTextStyle,
|
||||||
|
tileBackgroundColor: tileBackgroundColor,
|
||||||
|
),
|
||||||
|
],
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
|
@ -237,7 +249,7 @@ class StandardTile extends StatelessWidget {
|
||||||
required this.itemValue,
|
required this.itemValue,
|
||||||
required this.itemTitleTextStyle,
|
required this.itemTitleTextStyle,
|
||||||
this.itemSubTitle,
|
this.itemSubTitle,
|
||||||
required this.itemSubTitleTextStyle,
|
this.itemSubTitleTextStyle,
|
||||||
required this.tileBackgroundColor,
|
required this.tileBackgroundColor,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -245,7 +257,7 @@ class StandardTile extends StatelessWidget {
|
||||||
final String itemValue;
|
final String itemValue;
|
||||||
final TextStyle itemTitleTextStyle;
|
final TextStyle itemTitleTextStyle;
|
||||||
final String? itemSubTitle;
|
final String? itemSubTitle;
|
||||||
final TextStyle itemSubTitleTextStyle;
|
final TextStyle? itemSubTitleTextStyle;
|
||||||
final Color tileBackgroundColor;
|
final Color tileBackgroundColor;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -267,7 +279,7 @@ class StandardTile extends StatelessWidget {
|
||||||
Text(itemValue, style: itemTitleTextStyle),
|
Text(itemValue, style: itemTitleTextStyle),
|
||||||
itemSubTitle == null
|
itemSubTitle == null
|
||||||
? Container()
|
? Container()
|
||||||
: Text(itemSubTitle!, style: itemSubTitleTextStyle),
|
: Text(itemSubTitle!, style: itemSubTitleTextStyle!),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -388,31 +400,21 @@ class AddressExpansionTile extends StatelessWidget {
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
AddressFormatter.buildSegmentedAddress(
|
Text(
|
||||||
address: address,
|
isBatchSending ? name : contactType,
|
||||||
walletType: walletType,
|
style: itemTitleTextStyle,
|
||||||
evenTextStyle: TextStyle(
|
softWrap: true,
|
||||||
fontSize: 12,
|
),
|
||||||
|
Text(
|
||||||
|
isBatchSending ? amount : name,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
fontFamily: 'Lato',
|
fontFamily: 'Lato',
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||||
decoration: TextDecoration.none,
|
decoration: TextDecoration.none,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (stealthAddressText(stealthAddress) != null)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
|
||||||
child: AddressFormatter.buildSegmentedAddress(
|
|
||||||
address: stealthAddressText(stealthAddress)!,
|
|
||||||
walletType: walletType,
|
|
||||||
evenTextStyle: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontFamily: 'Lato',
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
|
||||||
decoration: TextDecoration.none),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -422,15 +424,38 @@ class AddressExpansionTile extends StatelessWidget {
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: AddressFormatter.buildSegmentedAddress(
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (stealthAddress != null)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 8.0),
|
||||||
|
child: Text(
|
||||||
|
"Stealth Address:",
|
||||||
|
style: itemSubTitleTextStyle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
AddressFormatter.buildSegmentedAddress(
|
||||||
address: address,
|
address: address,
|
||||||
walletType: walletType,
|
walletType: walletType,
|
||||||
evenTextStyle: TextStyle(
|
evenTextStyle: itemSubTitleTextStyle,
|
||||||
fontSize: 12,
|
),
|
||||||
fontFamily: 'Lato',
|
if (stealthAddress != null) ...[
|
||||||
fontWeight: FontWeight.w600,
|
Padding(
|
||||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
decoration: TextDecoration.none)),
|
child: Text(
|
||||||
|
"Generated Address:",
|
||||||
|
style: itemSubTitleTextStyle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
AddressFormatter.buildSegmentedAddress(
|
||||||
|
address: stealthAddress!,
|
||||||
|
walletType: walletType,
|
||||||
|
evenTextStyle: itemSubTitleTextStyle,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -442,11 +467,3 @@ class AddressExpansionTile extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String? stealthAddressText(String? stealthAddress) {
|
|
||||||
if (stealthAddress == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return stealthAddress.isNotEmpty ? "-> $stealthAddress" : null;
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue