Merge branch 'main' into zano-pr

This commit is contained in:
cyan 2024-11-07 18:04:17 +01:00 committed by GitHub
commit 4081c5109f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
247 changed files with 4725 additions and 1266 deletions

BIN
assets/images/flags/abw.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 607 B

BIN
assets/images/flags/afg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
assets/images/flags/ago.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/images/flags/aia.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
assets/images/flags/and.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
assets/images/flags/asm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 B

BIN
assets/images/flags/atf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
assets/images/flags/atg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 963 B

BIN
assets/images/flags/aut.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 B

BIN
assets/images/flags/aze.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 B

BIN
assets/images/flags/bel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 B

BIN
assets/images/flags/bes.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

BIN
assets/images/flags/bhr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 B

BIN
assets/images/flags/blz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
assets/images/flags/bmu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
assets/images/flags/bol.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 668 B

BIN
assets/images/flags/brn.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
assets/images/flags/btn.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
assets/images/flags/bvt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

BIN
assets/images/flags/bwa.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 B

BIN
assets/images/flags/cck.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/images/flags/cmr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 B

BIN
assets/images/flags/cok.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
assets/images/flags/cpv.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 B

BIN
assets/images/flags/cri.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 B

BIN
assets/images/flags/cuw.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 B

BIN
assets/images/flags/cxr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
assets/images/flags/cyp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/images/flags/dji.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 B

BIN
assets/images/flags/dma.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
assets/images/flags/dza.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B

BIN
assets/images/flags/ecu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
assets/images/flags/est.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 B

BIN
assets/images/flags/eth.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
assets/images/flags/fin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 B

BIN
assets/images/flags/fji.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
assets/images/flags/flk.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
assets/images/flags/fro.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

BIN
assets/images/flags/fsm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

BIN
assets/images/flags/gab.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 B

BIN
assets/images/flags/geo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

BIN
assets/images/flags/ggi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 892 B

BIN
assets/images/flags/ggy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 B

BIN
assets/images/flags/glp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
assets/images/flags/gmb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 B

BIN
assets/images/flags/grc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 B

BIN
assets/images/flags/grd.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 892 B

BIN
assets/images/flags/grl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 B

BIN
assets/images/flags/guf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 581 B

BIN
assets/images/flags/gum.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
assets/images/flags/guy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 635 B

BIN
assets/images/flags/hmd.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 B

BIN
assets/images/flags/iot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
assets/images/flags/irl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 B

BIN
assets/images/flags/jam.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

BIN
assets/images/flags/jey.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 914 B

BIN
assets/images/flags/jor.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
assets/images/flags/kaz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
assets/images/flags/ken.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
assets/images/flags/kir.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
assets/images/flags/kwt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B

BIN
assets/images/flags/lbn.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 960 B

BIN
assets/images/flags/lie.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 844 B

BIN
assets/images/flags/lka.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
assets/images/flags/ltu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 B

BIN
assets/images/flags/lux.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 B

BIN
assets/images/flags/lva.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 B

BIN
assets/images/flags/mco.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

BIN
assets/images/flags/mlt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 B

BIN
assets/images/flags/mnp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
assets/images/flags/mrt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

BIN
assets/images/flags/msr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
assets/images/flags/mtq.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
assets/images/flags/mwi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 754 B

BIN
assets/images/flags/myt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
assets/images/flags/ner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 B

BIN
assets/images/flags/nfk.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
assets/images/flags/niu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/images/flags/omn.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 711 B

BIN
assets/images/flags/per.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 B

BIN
assets/images/flags/plw.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 608 B

BIN
assets/images/flags/pri.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
assets/images/flags/pyf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
assets/images/flags/qat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
assets/images/flags/slb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 B

BIN
assets/images/flags/slv.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 642 B

BIN
assets/images/flags/svk.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 521 B

BIN
assets/images/flags/svn.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
assets/images/flags/tkm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
assets/images/flags/ton.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 B

BIN
assets/images/flags/tuv.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
assets/images/flags/ury.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,017 B

BIN
assets/images/flags/vat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 B

BIN
assets/images/flags/vir.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
assets/images/flags/vut.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -124,6 +124,7 @@ class ElectrumClient {
final errorMsg = error.toString(); final errorMsg = error.toString();
print(errorMsg); print(errorMsg);
unterminatedString = ''; unterminatedString = '';
socket = null;
}, },
onDone: () { onDone: () {
print("SOCKET CLOSED!!!!!"); print("SOCKET CLOSED!!!!!");
@ -132,6 +133,7 @@ class ElectrumClient {
if (host == socket?.address.host || socket == null) { if (host == socket?.address.host || socket == null) {
_setConnectionStatus(ConnectionStatus.disconnected); _setConnectionStatus(ConnectionStatus.disconnected);
socket?.destroy(); socket?.destroy();
socket = null;
} }
} catch (e) { } catch (e) {
print("onDone: $e"); print("onDone: $e");

View file

@ -24,9 +24,12 @@ class ElectrumBalance extends Balance {
final decoded = json.decode(jsonSource) as Map; final decoded = json.decode(jsonSource) as Map;
return ElectrumBalance( return ElectrumBalance(
confirmed: decoded['confirmed'] as int? ?? 0, confirmed: decoded['confirmed'] as int? ?? 0,
unconfirmed: decoded['unconfirmed'] as int? ?? 0, unconfirmed: decoded['unconfirmed'] as int? ?? 0,
frozen: decoded['frozen'] as int? ?? 0); frozen: decoded['frozen'] as int? ?? 0,
secondConfirmed: decoded['secondConfirmed'] as int? ?? 0,
secondUnconfirmed: decoded['secondUnconfirmed'] as int? ?? 0,
);
} }
int confirmed; int confirmed;
@ -36,8 +39,7 @@ class ElectrumBalance extends Balance {
int secondUnconfirmed = 0; int secondUnconfirmed = 0;
@override @override
String get formattedAvailableBalance => String get formattedAvailableBalance => bitcoinAmountToString(amount: confirmed - frozen);
bitcoinAmountToString(amount: confirmed - frozen);
@override @override
String get formattedAdditionalBalance => bitcoinAmountToString(amount: unconfirmed); String get formattedAdditionalBalance => bitcoinAmountToString(amount: unconfirmed);

View file

@ -41,6 +41,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
String? to, String? to,
this.unspents, this.unspents,
this.isReceivedSilentPayment = false, this.isReceivedSilentPayment = false,
Map<String, dynamic>? additionalInfo,
}) { }) {
this.id = id; this.id = id;
this.height = height; this.height = height;
@ -54,6 +55,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
this.isReplaced = isReplaced; this.isReplaced = isReplaced;
this.confirmations = confirmations; this.confirmations = confirmations;
this.to = to; this.to = to;
this.additionalInfo = additionalInfo ?? {};
} }
factory ElectrumTransactionInfo.fromElectrumVerbose(Map<String, Object> obj, WalletType type, factory ElectrumTransactionInfo.fromElectrumVerbose(Map<String, Object> obj, WalletType type,
@ -212,6 +214,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
BitcoinSilentPaymentsUnspent.fromJSON(null, unspent as Map<String, dynamic>)) BitcoinSilentPaymentsUnspent.fromJSON(null, unspent as Map<String, dynamic>))
.toList(), .toList(),
isReceivedSilentPayment: data['isReceivedSilentPayment'] as bool? ?? false, isReceivedSilentPayment: data['isReceivedSilentPayment'] as bool? ?? false,
additionalInfo: data['additionalInfo'] as Map<String, dynamic>?,
); );
} }
@ -246,7 +249,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
isReplaced: isReplaced ?? false, isReplaced: isReplaced ?? false,
inputAddresses: inputAddresses, inputAddresses: inputAddresses,
outputAddresses: outputAddresses, outputAddresses: outputAddresses,
confirmations: info.confirmations); confirmations: info.confirmations,
additionalInfo: additionalInfo);
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
@ -265,10 +269,11 @@ class ElectrumTransactionInfo extends TransactionInfo {
m['inputAddresses'] = inputAddresses; m['inputAddresses'] = inputAddresses;
m['outputAddresses'] = outputAddresses; m['outputAddresses'] = outputAddresses;
m['isReceivedSilentPayment'] = isReceivedSilentPayment; m['isReceivedSilentPayment'] = isReceivedSilentPayment;
m['additionalInfo'] = additionalInfo;
return m; return m;
} }
String toString() { String toString() {
return 'ElectrumTransactionInfo(id: $id, height: $height, amount: $amount, fee: $fee, direction: $direction, date: $date, isPending: $isPending, isReplaced: $isReplaced, confirmations: $confirmations, to: $to, unspent: $unspents, inputAddresses: $inputAddresses, outputAddresses: $outputAddresses)'; return 'ElectrumTransactionInfo(id: $id, height: $height, amount: $amount, fee: $fee, direction: $direction, date: $date, isPending: $isPending, isReplaced: $isReplaced, confirmations: $confirmations, to: $to, unspent: $unspents, inputAddresses: $inputAddresses, outputAddresses: $outputAddresses, additionalInfo: $additionalInfo)';
} }
} }

View file

@ -5,6 +5,7 @@ import 'dart:isolate';
import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_wallet.dart'; import 'package:cw_bitcoin/bitcoin_wallet.dart';
import 'package:cw_bitcoin/litecoin_wallet.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
@ -52,10 +53,9 @@ part 'electrum_wallet.g.dart';
class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet; class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet;
abstract class ElectrumWalletBase extends WalletBase< abstract class ElectrumWalletBase
ElectrumBalance, extends WalletBase<ElectrumBalance, ElectrumTransactionHistory, ElectrumTransactionInfo>
ElectrumTransactionHistory, with Store, WalletKeysFile {
ElectrumTransactionInfo> with Store, WalletKeysFile {
ElectrumWalletBase({ ElectrumWalletBase({
required String password, required String password,
required WalletInfo walletInfo, required WalletInfo walletInfo,
@ -71,8 +71,8 @@ abstract class ElectrumWalletBase extends WalletBase<
ElectrumBalance? initialBalance, ElectrumBalance? initialBalance,
CryptoCurrency? currency, CryptoCurrency? currency,
this.alwaysScan, this.alwaysScan,
}) : accountHD = getAccountHDWallet( }) : accountHD =
currency, network, seedBytes, xpub, walletInfo.derivationInfo), getAccountHDWallet(currency, network, seedBytes, xpub, walletInfo.derivationInfo),
syncStatus = NotConnectedSyncStatus(), syncStatus = NotConnectedSyncStatus(),
_password = password, _password = password,
_feeRates = <int>[], _feeRates = <int>[],
@ -107,12 +107,8 @@ abstract class ElectrumWalletBase extends WalletBase<
sharedPrefs.complete(SharedPreferences.getInstance()); sharedPrefs.complete(SharedPreferences.getInstance());
} }
static Bip32Slip10Secp256k1 getAccountHDWallet( static Bip32Slip10Secp256k1 getAccountHDWallet(CryptoCurrency? currency, BasedUtxoNetwork network,
CryptoCurrency? currency, Uint8List? seedBytes, String? xpub, DerivationInfo? derivationInfo) {
BasedUtxoNetwork network,
Uint8List? seedBytes,
String? xpub,
DerivationInfo? derivationInfo) {
if (seedBytes == null && xpub == null) { if (seedBytes == null && xpub == null) {
throw Exception( throw Exception(
"To create a Wallet you need either a seed or an xpub. This should not happen"); "To create a Wallet you need either a seed or an xpub. This should not happen");
@ -123,9 +119,8 @@ abstract class ElectrumWalletBase extends WalletBase<
case CryptoCurrency.btc: case CryptoCurrency.btc:
case CryptoCurrency.ltc: case CryptoCurrency.ltc:
case CryptoCurrency.tbtc: case CryptoCurrency.tbtc:
return Bip32Slip10Secp256k1.fromSeed(seedBytes, getKeyNetVersion(network)) return Bip32Slip10Secp256k1.fromSeed(seedBytes, getKeyNetVersion(network)).derivePath(
.derivePath(_hardenedDerivationPath( _hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path))
derivationInfo?.derivationPath ?? electrum_path))
as Bip32Slip10Secp256k1; as Bip32Slip10Secp256k1;
case CryptoCurrency.bch: case CryptoCurrency.bch:
return bitcoinCashHDWallet(seedBytes); return bitcoinCashHDWallet(seedBytes);
@ -134,13 +129,11 @@ abstract class ElectrumWalletBase extends WalletBase<
} }
} }
return Bip32Slip10Secp256k1.fromExtendedKey( return Bip32Slip10Secp256k1.fromExtendedKey(xpub!, getKeyNetVersion(network));
xpub!, getKeyNetVersion(network));
} }
static Bip32Slip10Secp256k1 bitcoinCashHDWallet(Uint8List seedBytes) => static Bip32Slip10Secp256k1 bitcoinCashHDWallet(Uint8List seedBytes) =>
Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/44'/145'/0'") Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/44'/145'/0'") as Bip32Slip10Secp256k1;
as Bip32Slip10Secp256k1;
static int estimatedTransactionSize(int inputsCount, int outputsCounts) => static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
inputsCount * 68 + outputsCounts * 34 + 10; inputsCount * 68 + outputsCounts * 34 + 10;
@ -250,7 +243,7 @@ abstract class ElectrumWalletBase extends WalletBase<
} }
if (tip > walletInfo.restoreHeight) { if (tip > walletInfo.restoreHeight) {
_setListeners(walletInfo.restoreHeight, chainTipParam: _currentChainTip); _setListeners(walletInfo.restoreHeight, chainTipParam: currentChainTip);
} }
} else { } else {
alwaysScan = false; alwaysScan = false;
@ -265,23 +258,23 @@ abstract class ElectrumWalletBase extends WalletBase<
} }
} }
int? _currentChainTip; int? currentChainTip;
Future<int> getCurrentChainTip() async { Future<int> getCurrentChainTip() async {
if ((_currentChainTip ?? 0) > 0) { if ((currentChainTip ?? 0) > 0) {
return _currentChainTip!; return currentChainTip!;
} }
_currentChainTip = await electrumClient.getCurrentBlockChainTip() ?? 0; currentChainTip = await electrumClient.getCurrentBlockChainTip() ?? 0;
return _currentChainTip!; return currentChainTip!;
} }
Future<int> getUpdatedChainTip() async { Future<int> getUpdatedChainTip() async {
final newTip = await electrumClient.getCurrentBlockChainTip(); final newTip = await electrumClient.getCurrentBlockChainTip();
if (newTip != null && newTip > (_currentChainTip ?? 0)) { if (newTip != null && newTip > (currentChainTip ?? 0)) {
_currentChainTip = newTip; currentChainTip = newTip;
} }
return _currentChainTip ?? 0; return currentChainTip ?? 0;
} }
@override @override
@ -357,7 +350,7 @@ abstract class ElectrumWalletBase extends WalletBase<
isSingleScan: doSingleScan ?? false, isSingleScan: doSingleScan ?? false,
)); ));
_receiveStream?.cancel(); await _receiveStream?.cancel();
_receiveStream = receivePort.listen((var message) async { _receiveStream = receivePort.listen((var message) async {
if (message is Map<String, ElectrumTransactionInfo>) { if (message is Map<String, ElectrumTransactionInfo>) {
for (final map in message.entries) { for (final map in message.entries) {
@ -618,7 +611,7 @@ abstract class ElectrumWalletBase extends WalletBase<
bool spendsUnconfirmedTX = false; bool spendsUnconfirmedTX = false;
int leftAmount = credentialsAmount; int leftAmount = credentialsAmount;
final availableInputs = unspentCoins.where((utx) { var availableInputs = unspentCoins.where((utx) {
if (!utx.isSending || utx.isFrozen) { if (!utx.isSending || utx.isFrozen) {
return false; return false;
} }
@ -634,6 +627,9 @@ abstract class ElectrumWalletBase extends WalletBase<
}).toList(); }).toList();
final unconfirmedCoins = availableInputs.where((utx) => utx.confirmations == 0).toList(); final unconfirmedCoins = availableInputs.where((utx) => utx.confirmations == 0).toList();
// sort the unconfirmed coins so that mweb coins are first:
availableInputs.sort((a, b) => a.bitcoinAddressRecord.type == SegwitAddresType.mweb ? -1 : 1);
for (int i = 0; i < availableInputs.length; i++) { for (int i = 0; i < availableInputs.length; i++) {
final utx = availableInputs[i]; final utx = availableInputs[i];
if (!spendsUnconfirmedTX) spendsUnconfirmedTX = utx.confirmations == 0; if (!spendsUnconfirmedTX) spendsUnconfirmedTX = utx.confirmations == 0;
@ -652,9 +648,8 @@ abstract class ElectrumWalletBase extends WalletBase<
ECPrivate? privkey; ECPrivate? privkey;
bool? isSilentPayment = false; bool? isSilentPayment = false;
final hd = utx.bitcoinAddressRecord.isHidden final hd =
? walletAddresses.sideHd utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd;
: walletAddresses.mainHd;
if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) { if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) {
final unspentAddress = utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord; final unspentAddress = utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord;
@ -1233,8 +1228,7 @@ abstract class ElectrumWalletBase extends WalletBase<
} }
} }
void setLedgerConnection(ledger.LedgerConnection connection) => void setLedgerConnection(ledger.LedgerConnection connection) => throw UnimplementedError();
throw UnimplementedError();
Future<BtcTransaction> buildHardwareWalletTransaction({ Future<BtcTransaction> buildHardwareWalletTransaction({
required List<BitcoinBaseOutput> outputs, required List<BitcoinBaseOutput> outputs,
@ -1593,9 +1587,7 @@ abstract class ElectrumWalletBase extends WalletBase<
final btcAddress = RegexUtils.addressTypeFromStr(addressRecord.address, network); final btcAddress = RegexUtils.addressTypeFromStr(addressRecord.address, network);
final privkey = generateECPrivate( final privkey = generateECPrivate(
hd: addressRecord.isHidden hd: addressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
? walletAddresses.sideHd
: walletAddresses.mainHd,
index: addressRecord.index, index: addressRecord.index,
network: network); network: network);
@ -1777,8 +1769,7 @@ abstract class ElectrumWalletBase extends WalletBase<
if (height != null) { if (height != null) {
if (time == null && height > 0) { if (time == null && height > 0) {
time = (getDateByBitcoinHeight(height).millisecondsSinceEpoch / 1000) time = (getDateByBitcoinHeight(height).millisecondsSinceEpoch / 1000).round();
.round();
} }
if (confirmations == null) { if (confirmations == null) {
@ -1847,6 +1838,7 @@ abstract class ElectrumWalletBase extends WalletBase<
.map((type) => fetchTransactionsForAddressType(historiesWithDetails, type))); .map((type) => fetchTransactionsForAddressType(historiesWithDetails, type)));
} else if (type == WalletType.litecoin) { } else if (type == WalletType.litecoin) {
await Future.wait(LITECOIN_ADDRESS_TYPES await Future.wait(LITECOIN_ADDRESS_TYPES
.where((type) => type != SegwitAddresType.mweb)
.map((type) => fetchTransactionsForAddressType(historiesWithDetails, type))); .map((type) => fetchTransactionsForAddressType(historiesWithDetails, type)));
} }
@ -1958,6 +1950,20 @@ abstract class ElectrumWalletBase extends WalletBase<
// Got a new transaction fetched, add it to the transaction history // Got a new transaction fetched, add it to the transaction history
// instead of waiting all to finish, and next time it will be faster // instead of waiting all to finish, and next time it will be faster
if (this is LitecoinWallet) {
// if we have a peg out transaction with the same value
// that matches this received transaction, mark it as being from a peg out:
for (final tx2 in transactionHistory.transactions.values) {
final heightDiff = ((tx2.height ?? 0) - (tx.height ?? 0)).abs();
// this isn't a perfect matching algorithm since we don't have the right input/output information from these transaction models (the addresses are in different formats), but this should be more than good enough for now as it's extremely unlikely a user receives the EXACT same amount from 2 different sources and one of them is a peg out and the other isn't WITHIN 5 blocks of each other
if (tx2.additionalInfo["isPegOut"] == true &&
tx2.amount == tx.amount &&
heightDiff <= 5) {
tx.additionalInfo["fromPegOut"] = true;
}
}
}
transactionHistory.addOne(tx); transactionHistory.addOne(tx);
await transactionHistory.save(); await transactionHistory.save();
} }
@ -1984,18 +1990,28 @@ abstract class ElectrumWalletBase extends WalletBase<
if (_isTransactionUpdating) { if (_isTransactionUpdating) {
return; return;
} }
await getCurrentChainTip(); currentChainTip = await getUpdatedChainTip();
bool updated = false;
transactionHistory.transactions.values.forEach((tx) { transactionHistory.transactions.values.forEach((tx) {
if (tx.unspents != null && if ((tx.height ?? 0) > 0 && (currentChainTip ?? 0) > 0) {
tx.unspents!.isNotEmpty && var confirmations = currentChainTip! - tx.height! + 1;
tx.height != null && if (confirmations < 0) {
tx.height! > 0 && // if our chain tip is outdated then it could lead to negative confirmations so this is just a failsafe:
(_currentChainTip ?? 0) > 0) { confirmations = 0;
tx.confirmations = _currentChainTip! - tx.height! + 1; }
if (confirmations != tx.confirmations) {
updated = true;
tx.confirmations = confirmations;
transactionHistory.addOne(tx);
}
} }
}); });
if (updated) {
await transactionHistory.save();
}
_isTransactionUpdating = true; _isTransactionUpdating = true;
await fetchTransactions(); await fetchTransactions();
walletAddresses.updateReceiveAddresses(); walletAddresses.updateReceiveAddresses();
@ -2043,6 +2059,8 @@ abstract class ElectrumWalletBase extends WalletBase<
library: this.runtimeType.toString(), library: this.runtimeType.toString(),
)); ));
} }
}, onError: (e, s) {
print("sub_listen error: $e $s");
}); });
})); }));
} }
@ -2092,6 +2110,13 @@ abstract class ElectrumWalletBase extends WalletBase<
final balances = await Future.wait(balanceFutures); final balances = await Future.wait(balanceFutures);
if (balances.isNotEmpty && balances.first['confirmed'] == null) {
// if we got null balance responses from the server, set our connection status to lost and return our last known balance:
print("got null balance responses from the server, setting connection status to lost");
syncStatus = LostConnectionSyncStatus();
return balance[currency] ?? ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
}
for (var i = 0; i < balances.length; i++) { for (var i = 0; i < balances.length; i++) {
final addressRecord = addresses[i]; final addressRecord = addresses[i];
final balance = balances[i]; final balance = balances[i];
@ -2197,10 +2222,10 @@ abstract class ElectrumWalletBase extends WalletBase<
Future<void> _setInitialHeight() async { Future<void> _setInitialHeight() async {
if (_chainTipUpdateSubject != null) return; if (_chainTipUpdateSubject != null) return;
_currentChainTip = await getUpdatedChainTip(); currentChainTip = await getUpdatedChainTip();
if ((_currentChainTip == null || _currentChainTip! == 0) && walletInfo.restoreHeight == 0) { if ((currentChainTip == null || currentChainTip! == 0) && walletInfo.restoreHeight == 0) {
await walletInfo.updateRestoreHeight(_currentChainTip!); await walletInfo.updateRestoreHeight(currentChainTip!);
} }
_chainTipUpdateSubject = electrumClient.chainTipSubscribe(); _chainTipUpdateSubject = electrumClient.chainTipSubscribe();
@ -2209,7 +2234,7 @@ abstract class ElectrumWalletBase extends WalletBase<
final height = int.tryParse(event['height'].toString()); final height = int.tryParse(event['height'].toString());
if (height != null) { if (height != null) {
_currentChainTip = height; currentChainTip = height;
if (alwaysScan == true && syncStatus is SyncedSyncStatus) { if (alwaysScan == true && syncStatus is SyncedSyncStatus) {
_setListeners(walletInfo.restoreHeight); _setListeners(walletInfo.restoreHeight);
@ -2223,7 +2248,6 @@ abstract class ElectrumWalletBase extends WalletBase<
@action @action
void _onConnectionStatusChange(ConnectionStatus status) { void _onConnectionStatusChange(ConnectionStatus status) {
switch (status) { switch (status) {
case ConnectionStatus.connected: case ConnectionStatus.connected:
if (syncStatus is NotConnectedSyncStatus || if (syncStatus is NotConnectedSyncStatus ||
@ -2270,8 +2294,6 @@ abstract class ElectrumWalletBase extends WalletBase<
Timer(Duration(seconds: 5), () { Timer(Duration(seconds: 5), () {
if (this.syncStatus is NotConnectedSyncStatus || if (this.syncStatus is NotConnectedSyncStatus ||
this.syncStatus is LostConnectionSyncStatus) { this.syncStatus is LostConnectionSyncStatus) {
if (node == null) return;
this.electrumClient.connectToUri( this.electrumClient.connectToUri(
node!.uri, node!.uri,
useSSL: node!.useSSL ?? false, useSSL: node!.useSSL ?? false,

View file

@ -9,6 +9,7 @@ import 'package:crypto/crypto.dart';
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
import 'package:cw_core/cake_hive.dart'; import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/mweb_utxo.dart'; import 'package:cw_core/mweb_utxo.dart';
import 'package:cw_core/node.dart';
import 'package:cw_mweb/mwebd.pbgrpc.dart'; import 'package:cw_mweb/mwebd.pbgrpc.dart';
import 'package:fixnum/fixnum.dart'; import 'package:fixnum/fixnum.dart';
import 'package:bip39/bip39.dart' as bip39; import 'package:bip39/bip39.dart' as bip39;
@ -47,6 +48,7 @@ import 'package:cw_mweb/cw_mweb.dart';
import 'package:bitcoin_base/src/crypto/keypair/sign_utils.dart'; import 'package:bitcoin_base/src/crypto/keypair/sign_utils.dart';
import 'package:pointycastle/ecc/api.dart'; import 'package:pointycastle/ecc/api.dart';
import 'package:pointycastle/ecc/curves/secp256k1.dart'; import 'package:pointycastle/ecc/curves/secp256k1.dart';
import 'package:shared_preferences/shared_preferences.dart';
part 'litecoin_wallet.g.dart'; part 'litecoin_wallet.g.dart';
@ -85,8 +87,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
alwaysScan: alwaysScan, alwaysScan: alwaysScan,
) { ) {
if (seedBytes != null) { if (seedBytes != null) {
mwebHd = Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath( mwebHd =
"m/1000'") as Bip32Slip10Secp256k1; Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/1000'") as Bip32Slip10Secp256k1;
mwebEnabled = alwaysScan ?? false; mwebEnabled = alwaysScan ?? false;
} else { } else {
mwebHd = null; mwebHd = null;
@ -287,6 +289,16 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
await (walletAddresses as LitecoinWalletAddresses).ensureMwebAddressUpToIndexExists(1020); await (walletAddresses as LitecoinWalletAddresses).ensureMwebAddressUpToIndexExists(1020);
} }
@action
@override
Future<void> connectToNode({required Node node}) async {
await super.connectToNode(node: node);
final prefs = await SharedPreferences.getInstance();
final mwebNodeUri = prefs.getString("mwebNodeUri") ?? "ltc-electrum.cakewallet.com:9333";
await CwMweb.setNodeUriOverride(mwebNodeUri);
}
@action @action
@override @override
Future<void> startSync() async { Future<void> startSync() async {
@ -349,6 +361,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
return; return;
} }
// update the current chain tip so that confirmation calculations are accurate:
currentChainTip = nodeHeight;
final resp = await CwMweb.status(StatusRequest()); final resp = await CwMweb.status(StatusRequest());
try { try {
@ -361,22 +376,46 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
} else if (resp.mwebUtxosHeight < nodeHeight) { } else if (resp.mwebUtxosHeight < nodeHeight) {
mwebSyncStatus = SyncingSyncStatus(1, 0.999); mwebSyncStatus = SyncingSyncStatus(1, 0.999);
} else { } else {
bool confirmationsUpdated = false;
if (resp.mwebUtxosHeight > walletInfo.restoreHeight) { if (resp.mwebUtxosHeight > walletInfo.restoreHeight) {
await walletInfo.updateRestoreHeight(resp.mwebUtxosHeight); await walletInfo.updateRestoreHeight(resp.mwebUtxosHeight);
await checkMwebUtxosSpent(); await checkMwebUtxosSpent();
// update the confirmations for each transaction: // update the confirmations for each transaction:
for (final transaction in transactionHistory.transactions.values) { for (final tx in transactionHistory.transactions.values) {
if (transaction.isPending) continue; if (tx.height == null || tx.height == 0) {
int txHeight = transaction.height ?? resp.mwebUtxosHeight; // update with first confirmation on next block since it hasn't been confirmed yet:
final confirmations = (resp.mwebUtxosHeight - txHeight) + 1; tx.height = resp.mwebUtxosHeight;
if (transaction.confirmations == confirmations) continue; continue;
if (transaction.confirmations == 0) {
updateBalance();
} }
transaction.confirmations = confirmations;
transactionHistory.addOne(transaction); final confirmations = (resp.mwebUtxosHeight - tx.height!) + 1;
// if the confirmations haven't changed, skip updating:
if (tx.confirmations == confirmations) continue;
// if an outgoing tx is now confirmed, delete the utxo from the box (delete the unspent coin):
if (confirmations >= 2 &&
tx.direction == TransactionDirection.outgoing &&
tx.unspents != null) {
for (var coin in tx.unspents!) {
final utxo = mwebUtxosBox.get(coin.address);
if (utxo != null) {
print("deleting utxo ${coin.address} @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
await mwebUtxosBox.delete(coin.address);
}
}
}
tx.confirmations = confirmations;
tx.isPending = false;
transactionHistory.addOne(tx);
confirmationsUpdated = true;
}
if (confirmationsUpdated) {
await transactionHistory.save();
await updateTransactions();
} }
await transactionHistory.save();
} }
// prevent unnecessary reaction triggers: // prevent unnecessary reaction triggers:
@ -501,13 +540,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
outputAddresses: [utxo.outputId], outputAddresses: [utxo.outputId],
isReplaced: false, isReplaced: false,
); );
} } else {
if (tx.confirmations != confirmations || tx.height != utxo.height) {
// don't update the confirmations if the tx is updated by electrum: tx.height = utxo.height;
if (tx.confirmations == 0 || utxo.height != 0) { tx.confirmations = confirmations;
tx.height = utxo.height; tx.isPending = utxo.height == 0;
tx.isPending = utxo.height == 0; }
tx.confirmations = confirmations;
} }
bool isNew = transactionHistory.transactions[tx.id] == null; bool isNew = transactionHistory.transactions[tx.id] == null;
@ -557,56 +595,88 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
if (responseStream == null) { if (responseStream == null) {
throw Exception("failed to get utxos stream!"); throw Exception("failed to get utxos stream!");
} }
_utxoStream = responseStream.listen((Utxo sUtxo) async { _utxoStream = responseStream.listen(
// we're processing utxos, so our balance could still be innacurate: (Utxo sUtxo) async {
if (mwebSyncStatus is! SyncronizingSyncStatus && mwebSyncStatus is! SyncingSyncStatus) { // we're processing utxos, so our balance could still be innacurate:
mwebSyncStatus = SyncronizingSyncStatus(); if (mwebSyncStatus is! SyncronizingSyncStatus && mwebSyncStatus is! SyncingSyncStatus) {
processingUtxos = true; mwebSyncStatus = SyncronizingSyncStatus();
_processingTimer?.cancel(); processingUtxos = true;
_processingTimer = Timer.periodic(const Duration(seconds: 2), (timer) async { _processingTimer?.cancel();
processingUtxos = false; _processingTimer = Timer.periodic(const Duration(seconds: 2), (timer) async {
timer.cancel(); processingUtxos = false;
}); timer.cancel();
} });
final utxo = MwebUtxo(
address: sUtxo.address,
blockTime: sUtxo.blockTime,
height: sUtxo.height,
outputId: sUtxo.outputId,
value: sUtxo.value.toInt(),
);
if (mwebUtxosBox.containsKey(utxo.outputId)) {
// we've already stored this utxo, skip it:
// but do update the utxo height if it's somehow different:
final existingUtxo = mwebUtxosBox.get(utxo.outputId);
if (existingUtxo!.height != utxo.height) {
print(
"updating utxo height for $utxo.outputId: ${existingUtxo.height} -> ${utxo.height}");
existingUtxo.height = utxo.height;
await mwebUtxosBox.put(utxo.outputId, existingUtxo);
} }
return;
}
await updateUnspent(); final utxo = MwebUtxo(
await updateBalance(); address: sUtxo.address,
blockTime: sUtxo.blockTime,
height: sUtxo.height,
outputId: sUtxo.outputId,
value: sUtxo.value.toInt(),
);
final mwebAddrs = (walletAddresses as LitecoinWalletAddresses).mwebAddrs; if (mwebUtxosBox.containsKey(utxo.outputId)) {
// we've already stored this utxo, skip it:
// but do update the utxo height if it's somehow different:
final existingUtxo = mwebUtxosBox.get(utxo.outputId);
if (existingUtxo!.height != utxo.height) {
print(
"updating utxo height for $utxo.outputId: ${existingUtxo.height} -> ${utxo.height}");
existingUtxo.height = utxo.height;
await mwebUtxosBox.put(utxo.outputId, existingUtxo);
}
return;
}
// don't process utxos with addresses that are not in the mwebAddrs list: await updateUnspent();
if (utxo.address.isNotEmpty && !mwebAddrs.contains(utxo.address)) { await updateBalance();
return;
}
await mwebUtxosBox.put(utxo.outputId, utxo); final mwebAddrs = (walletAddresses as LitecoinWalletAddresses).mwebAddrs;
await handleIncoming(utxo); // don't process utxos with addresses that are not in the mwebAddrs list:
}); if (utxo.address.isNotEmpty && !mwebAddrs.contains(utxo.address)) {
return;
}
await mwebUtxosBox.put(utxo.outputId, utxo);
await handleIncoming(utxo);
},
onError: (error) {
print("error in utxo stream: $error");
mwebSyncStatus = FailedSyncStatus(error: error.toString());
},
cancelOnError: true,
);
}
Future<void> deleteSpentUtxos() async {
print("deleteSpentUtxos() called!");
final chainHeight = await electrumClient.getCurrentBlockChainTip();
final status = await CwMweb.status(StatusRequest());
if (chainHeight == null || status.blockHeaderHeight != chainHeight) return;
if (status.mwebUtxosHeight != chainHeight) return; // we aren't synced
// delete any spent utxos with >= 2 confirmations:
final spentOutputIds = mwebUtxosBox.values
.where((utxo) => utxo.spent && (chainHeight - utxo.height) >= 2)
.map((utxo) => utxo.outputId)
.toList();
if (spentOutputIds.isEmpty) return;
final resp = await CwMweb.spent(SpentRequest(outputId: spentOutputIds));
final spent = resp.outputId;
if (spent.isEmpty) return;
for (final outputId in spent) {
await mwebUtxosBox.delete(outputId);
}
} }
Future<void> checkMwebUtxosSpent() async { Future<void> checkMwebUtxosSpent() async {
print("checkMwebUtxosSpent() called!");
if (!mwebEnabled) { if (!mwebEnabled) {
return; return;
} }
@ -620,15 +690,17 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
updatedAny = await isConfirmed(tx) || updatedAny; updatedAny = await isConfirmed(tx) || updatedAny;
} }
await deleteSpentUtxos();
// get output ids of all the mweb utxos that have > 0 height: // get output ids of all the mweb utxos that have > 0 height:
final outputIds = final outputIds = mwebUtxosBox.values
mwebUtxosBox.values.where((utxo) => utxo.height > 0).map((utxo) => utxo.outputId).toList(); .where((utxo) => utxo.height > 0 && !utxo.spent)
.map((utxo) => utxo.outputId)
.toList();
final resp = await CwMweb.spent(SpentRequest(outputId: outputIds)); final resp = await CwMweb.spent(SpentRequest(outputId: outputIds));
final spent = resp.outputId; final spent = resp.outputId;
if (spent.isEmpty) { if (spent.isEmpty) return;
return;
}
final status = await CwMweb.status(StatusRequest()); final status = await CwMweb.status(StatusRequest());
final height = await electrumClient.getCurrentBlockChainTip(); final height = await electrumClient.getCurrentBlockChainTip();
@ -739,7 +811,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
mwebUtxosBox.keys.forEach((dynamic oId) { mwebUtxosBox.keys.forEach((dynamic oId) {
final String outputId = oId as String; final String outputId = oId as String;
final utxo = mwebUtxosBox.get(outputId); final utxo = mwebUtxosBox.get(outputId);
if (utxo == null) { if (utxo == null || utxo.spent) {
return; return;
} }
if (utxo.address.isEmpty) { if (utxo.address.isEmpty) {
@ -789,15 +861,23 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
int unconfirmedMweb = 0; int unconfirmedMweb = 0;
try { try {
mwebUtxosBox.values.forEach((utxo) { mwebUtxosBox.values.forEach((utxo) {
if (utxo.height > 0) { bool isConfirmed = utxo.height > 0;
print(
"utxo: ${isConfirmed ? "confirmed" : "unconfirmed"} ${utxo.spent ? "spent" : "unspent"} ${utxo.outputId} ${utxo.height} ${utxo.value}");
if (isConfirmed) {
confirmedMweb += utxo.value.toInt(); confirmedMweb += utxo.value.toInt();
} else { }
if (isConfirmed && utxo.spent) {
unconfirmedMweb -= utxo.value.toInt();
}
if (!isConfirmed && !utxo.spent) {
unconfirmedMweb += utxo.value.toInt(); unconfirmedMweb += utxo.value.toInt();
} }
}); });
if (unconfirmedMweb > 0) {
unconfirmedMweb = -1 * (confirmedMweb - unconfirmedMweb);
}
} catch (_) {} } catch (_) {}
for (var addressRecord in walletAddresses.allAddresses) { for (var addressRecord in walletAddresses.allAddresses) {
@ -829,7 +909,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
// update the txCount for each address using the tx history, since we can't rely on mwebd // update the txCount for each address using the tx history, since we can't rely on mwebd
// to have an accurate count, we should just keep it in sync with what we know from the tx history: // to have an accurate count, we should just keep it in sync with what we know from the tx history:
for (final tx in transactionHistory.transactions.values) { for (final tx in transactionHistory.transactions.values) {
// if (tx.isPending) continue;
if (tx.inputAddresses == null || tx.outputAddresses == null) { if (tx.inputAddresses == null || tx.outputAddresses == null) {
continue; continue;
} }
@ -908,7 +987,26 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
// https://github.com/ltcmweb/mwebd?tab=readme-ov-file#fee-estimation // https://github.com/ltcmweb/mwebd?tab=readme-ov-file#fee-estimation
final preOutputSum = final preOutputSum =
outputs.fold<BigInt>(BigInt.zero, (acc, output) => acc + output.toOutput.amount); outputs.fold<BigInt>(BigInt.zero, (acc, output) => acc + output.toOutput.amount);
final fee = utxos.sumOfUtxosValue() - preOutputSum; var fee = utxos.sumOfUtxosValue() - preOutputSum;
// determines if the fee is correct:
BigInt _sumOutputAmounts(List<TxOutput> outputs) {
BigInt sum = BigInt.zero;
for (final e in outputs) {
sum += e.amount;
}
return sum;
}
final sum1 = _sumOutputAmounts(outputs.map((e) => e.toOutput).toList()) + fee;
final sum2 = utxos.sumOfUtxosValue();
if (sum1 != sum2) {
print("@@@@@ WE HAD TO ADJUST THE FEE! @@@@@@@@");
final diff = sum2 - sum1;
// add the difference to the fee (abs value):
fee += diff.abs();
}
final txb = final txb =
BitcoinTransactionBuilder(utxos: utxos, outputs: outputs, fee: fee, network: network); BitcoinTransactionBuilder(utxos: utxos, outputs: outputs, fee: fee, network: network);
final resp = await CwMweb.create(CreateRequest( final resp = await CwMweb.create(CreateRequest(
@ -949,8 +1047,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
if (!mwebEnabled) { if (!mwebEnabled) {
tx.changeAddressOverride = tx.changeAddressOverride =
(await (walletAddresses as LitecoinWalletAddresses) (await (walletAddresses as LitecoinWalletAddresses).getChangeAddress(isPegIn: false))
.getChangeAddress(isPegIn: false))
.address; .address;
return tx; return tx;
} }
@ -969,15 +1066,25 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
bool hasMwebInput = false; bool hasMwebInput = false;
bool hasMwebOutput = false; bool hasMwebOutput = false;
bool hasRegularOutput = false;
for (final output in transactionCredentials.outputs) { for (final output in transactionCredentials.outputs) {
if (output.extractedAddress?.toLowerCase().contains("mweb") ?? false) { final address = output.address.toLowerCase();
final extractedAddress = output.extractedAddress?.toLowerCase();
if (address.contains("mweb")) {
hasMwebOutput = true; hasMwebOutput = true;
break;
} }
if (output.address.toLowerCase().contains("mweb")) { if (!address.contains("mweb")) {
hasMwebOutput = true; hasRegularOutput = true;
break; }
if (extractedAddress != null && extractedAddress.isNotEmpty) {
if (extractedAddress.contains("mweb")) {
hasMwebOutput = true;
}
if (!extractedAddress.contains("mweb")) {
hasRegularOutput = true;
}
} }
} }
@ -989,11 +1096,11 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
} }
bool isPegIn = !hasMwebInput && hasMwebOutput; bool isPegIn = !hasMwebInput && hasMwebOutput;
bool isPegOut = hasMwebInput && hasRegularOutput;
bool isRegular = !hasMwebInput && !hasMwebOutput; bool isRegular = !hasMwebInput && !hasMwebOutput;
tx.changeAddressOverride = tx.changeAddressOverride = (await (walletAddresses as LitecoinWalletAddresses)
(await (walletAddresses as LitecoinWalletAddresses) .getChangeAddress(isPegIn: isPegIn || isRegular))
.getChangeAddress(isPegIn: isPegIn || isRegular)) .address;
.address;
if (!hasMwebInput && !hasMwebOutput) { if (!hasMwebInput && !hasMwebOutput) {
tx.isMweb = false; tx.isMweb = false;
return tx; return tx;
@ -1046,8 +1153,11 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
final addresses = <String>{}; final addresses = <String>{};
transaction.inputAddresses?.forEach((id) async { transaction.inputAddresses?.forEach((id) async {
final utxo = mwebUtxosBox.get(id); final utxo = mwebUtxosBox.get(id);
await mwebUtxosBox.delete(id); // gets deleted in checkMwebUtxosSpent // await mwebUtxosBox.delete(id); // gets deleted in checkMwebUtxosSpent
if (utxo == null) return; if (utxo == null) return;
// mark utxo as spent so we add it to the unconfirmed balance (as negative):
utxo.spent = true;
await mwebUtxosBox.put(id, utxo);
final addressRecord = walletAddresses.allAddresses final addressRecord = walletAddresses.allAddresses
.firstWhere((addressRecord) => addressRecord.address == utxo.address); .firstWhere((addressRecord) => addressRecord.address == utxo.address);
if (!addresses.contains(utxo.address)) { if (!addresses.contains(utxo.address)) {
@ -1056,7 +1166,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
addressRecord.balance -= utxo.value.toInt(); addressRecord.balance -= utxo.value.toInt();
}); });
transaction.inputAddresses?.addAll(addresses); transaction.inputAddresses?.addAll(addresses);
print("isPegIn: $isPegIn, isPegOut: $isPegOut");
transaction.additionalInfo["isPegIn"] = isPegIn;
transaction.additionalInfo["isPegOut"] = isPegOut;
transactionHistory.addOne(transaction); transactionHistory.addOne(transaction);
await updateUnspent(); await updateUnspent();
await updateBalance(); await updateBalance();
@ -1240,8 +1352,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
@override @override
void setLedgerConnection(LedgerConnection connection) { void setLedgerConnection(LedgerConnection connection) {
_ledgerConnection = connection; _ledgerConnection = connection;
_litecoinLedgerApp = _litecoinLedgerApp = LitecoinLedgerApp(_ledgerConnection!,
LitecoinLedgerApp(_ledgerConnection!, derivationPath: walletInfo.derivationInfo!.derivationPath!); derivationPath: walletInfo.derivationInfo!.derivationPath!);
} }
@override @override
@ -1277,19 +1389,17 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
if (maybeChangePath != null) changePath ??= maybeChangePath.derivationPath; if (maybeChangePath != null) changePath ??= maybeChangePath.derivationPath;
} }
final rawHex = await _litecoinLedgerApp!.createTransaction( final rawHex = await _litecoinLedgerApp!.createTransaction(
inputs: readyInputs, inputs: readyInputs,
outputs: outputs outputs: outputs
.map((e) => TransactionOutput.fromBigInt( .map((e) => TransactionOutput.fromBigInt((e as BitcoinOutput).value,
(e as BitcoinOutput).value, Uint8List.fromList(e.address.toScriptPubKey().toBytes()))) Uint8List.fromList(e.address.toScriptPubKey().toBytes())))
.toList(), .toList(),
changePath: changePath, changePath: changePath,
sigHashType: 0x01, sigHashType: 0x01,
additionals: ["bech32"], additionals: ["bech32"],
isSegWit: true, isSegWit: true,
useTrustedInputForSegwit: true useTrustedInputForSegwit: true);
);
return BtcTransaction.fromRaw(rawHex); return BtcTransaction.fromRaw(rawHex);
} }

Some files were not shown because too many files have changed in this diff Show more