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