CW-994 mweb enhancements (#2204)

* add mweb block explorer * potential fix for #2 * fix spending when mweb is disabled * Update cw_bitcoin/lib/litecoin_wallet.dart * fix resolving conflict missing brackets --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

Matthew Fosse committed Dec 1, 2025 at 18:16 UTC 20022c03930d6764279babcfcc433ddf219ab759
4 files changed +54 -7
cw_bitcoin/lib/electrum_wallet.dart
+7 -1
@@ -11,7 +11,6 @@ import 'package:cw_bitcoin/bitcoin_amount_format.dart';
11 import 'package:cw_core/utils/print_verbose.dart';
12 import 'package:cw_bitcoin/bitcoin_wallet.dart';
13 import 'package:cw_bitcoin/litecoin_wallet.dart';
14 -import 'package:path_provider/path_provider.dart';
14 import 'package:shared_preferences/shared_preferences.dart';
15 import 'package:blockchain_utils/blockchain_utils.dart';
16 import 'package:collection/collection.dart';
@@ -899,6 +898,13 @@ abstract class ElectrumWalletBase
898 throw BitcoinTransactionNoDustException();
899 }
900
901 + // if mweb isn't enabled, don't consider spending mweb coins:
902 + if (this is LitecoinWallet) {
903 + var mwebEnabled = (this as LitecoinWallet).mwebEnabled;
904 + if (!mwebEnabled) {
905 + coinTypeToSpendFrom = UnspentCoinType.nonMweb;
906 + }
907 + }
908
909 // If there is only one output, and the amount to send is more than the max spendable amount
910 // then it is actually a send all transaction
cw_bitcoin/lib/litecoin_wallet.dart
+21 -5
@@ -886,6 +886,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
886 if (unspent.vout == 0) {
887 unspent.isChange = true;
888 }
889 +
890 + // printV("unspent: $unspent ${unspent.vout} ${utxo.value}");
891 mwebUnspentCoins.add(unspent);
892 });
893
@@ -1140,13 +1142,27 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
1142 @override
1143 Future<PendingTransaction> createTransaction(Object credentials) async {
1144 try {
1143 - var tx = await super.createTransaction(credentials) as PendingBitcoinTransaction;
1145 + var creds;
1146 + if (!mwebEnabled) {
1147 + BitcoinTransactionCredentials btcCreds = (credentials as BitcoinTransactionCredentials);
1148 + // sets unspent coin type to nonMweb:
1149 + creds = BitcoinTransactionCredentials(
1150 + btcCreds.outputs,
1151 + priority: btcCreds.priority,
1152 + feeRate: btcCreds.feeRate,
1153 + coinTypeToSpendFrom: UnspentCoinType.nonMweb,
1154 + );
1155 + } else {
1156 + creds = credentials;
1157 + }
1158 + var tx = await super.createTransaction(creds as Object) as PendingBitcoinTransaction;
1159 tx.isMweb = mwebEnabled;
1160
1161 if (!mwebEnabled) {
1162 tx.changeAddressOverride = (await (walletAddresses as LitecoinWalletAddresses)
1163 .getChangeAddress(coinTypeToSpendFrom: UnspentCoinType.nonMweb))
1164 .address;
1165 +
1166 if (tx.shouldCommitUR()) {
1167 tx.unsignedPsbt = await buildPsbt(tx, false);
1168 }
@@ -1165,17 +1181,17 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
1181 final address = output.address.toLowerCase();
1182 final extractedAddress = output.extractedAddress?.toLowerCase();
1183
1168 - if (address.contains("mweb")) {
1184 + if (address.startsWith("ltcmweb")) {
1185 hasMwebOutput = true;
1186 }
1171 - if (!address.contains("mweb")) {
1187 + if (!address.startsWith("ltcmweb")) {
1188 hasRegularOutput = true;
1189 }
1190 if (extractedAddress != null && extractedAddress.isNotEmpty) {
1175 - if (extractedAddress.contains("mweb")) {
1191 + if (extractedAddress.startsWith("ltcmweb")) {
1192 hasMwebOutput = true;
1193 }
1178 - if (!extractedAddress.contains("mweb")) {
1194 + if (!extractedAddress.startsWith("ltcmweb")) {
1195 hasRegularOutput = true;
1196 }
1197 }
cw_bitcoin/lib/litecoin_wallet_addresses.dart
+1 -1
@@ -188,7 +188,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
188 if (!element.isSending || element.isFrozen) {
189 return;
190 }
191 - if (element.address.contains("mweb")) {
191 + if (element.address.startsWith("ltcmweb")) {
192 comesFromMweb = true;
193 }
194 });
lib/view_model/transaction_details_view_model.dart
+25
@@ -59,6 +59,7 @@ abstract class TransactionDetailsViewModelBase with Store {
59 if (!canReplaceByFee) _checkForRBF(tx);
60 break;
61 case WalletType.litecoin:
62 + _addLitecoinListItems(tx, dateFormat);
63 case WalletType.bitcoinCash:
64 _addElectrumListItems(tx, dateFormat);
65 break;
@@ -388,6 +389,30 @@ abstract class TransactionDetailsViewModelBase with Store {
389 items.addAll(_items);
390 }
391
392 + void _addLitecoinListItems(TransactionInfo tx, DateFormat dateFormat) {
393 + _addElectrumListItems(tx, dateFormat);
394 +
395 + bool isMweb = bitcoin!.txIsMweb(tx);
396 +
397 + final _items = [
398 + if (isMweb)
399 + BlockExplorerListItem(
400 + title: S.current.view_in_block_explorer,
401 + value: S.current.view_transaction_on + 'mwebexplorer.com',
402 + onTap: () async {
403 + try {
404 + final uri = Uri.parse('https://www.mwebexplorer.com/blocks/block/${tx.height}');
405 + if (await canLaunchUrl(uri))
406 + await launchUrl(uri, mode: LaunchMode.externalApplication);
407 + } catch (e) {}
408 + },
409 + key: ValueKey('block_explorer_list_item_mweb_wallet_type_key'),
410 + ),
411 + ];
412 +
413 + items.addAll(_items);
414 + }
415 +
416 void _addHavenListItems(TransactionInfo tx, DateFormat dateFormat) {
417 items.addAll([
418 StandartListItem(