Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
02abb4c
[wip] coin control refactor
malik1004x Sep 9, 2026
df940d8
Merge branch 'dev' into cw-1637-coin-control-refactor
malik1004x Sep 10, 2026
39a6fae
[wip] coin control refactor
malik1004x Sep 10, 2026
43c2d49
fix migration
malik1004x Sep 10, 2026
646b35e
more tests
malik1004x Sep 10, 2026
6f8a455
remove tests from different pr
malik1004x Sep 11, 2026
538f4f7
Merge branch 'dev' into cw-1637-coin-control-refactor
malik1004x Sep 14, 2026
992adb8
bugfixes
malik1004x Sep 16, 2026
9e37b95
key on internalId instead of name-based id
malik1004x Sep 16, 2026
b4aa565
Merge branch 'dev' into cw-1637-coin-control-refactor
malik1004x Sep 16, 2026
b546b02
remove print
malik1004x Sep 16, 2026
d802c92
Merge branch 'dev' into cw-1637-coin-control-refactor
malik1004x Sep 30, 2026
aaeb475
fix de/select all
malik1004x Sep 30, 2026
8392635
Merge branch 'dev' into cw-1637-coin-control-refactor
malik1004x Oct 5, 2026
8fd946f
fix freeze order
malik1004x Oct 5, 2026
7e4628a
add test for coin control on accounts
malik1004x Oct 5, 2026
fa8cae8
fixes
malik1004x Oct 8, 2026
97511d1
fix merge mishap
malik1004x Oct 8, 2026
7032e84
Merge branch 'dev' into cw-1637-coin-control-refactor
malik1004x Oct 9, 2026
d25dc34
fix sqlite
malik1004x Oct 9, 2026
a4ea4aa
fix sqlite
malik1004x Oct 9, 2026
16d05d2
fixes
malik1004x Oct 9, 2026
af019a5
guess who forgot the safearea...
malik1004x Oct 13, 2026
a2a63bf
Merge branch 'dev' into cw-1637-coin-control-refactor
malik1004x Oct 13, 2026
706c11f
fix url
malik1004x Oct 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions cw_bitcoin/lib/bitcoin_transaction_credentials.dart
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
import 'package:cw_core/output_info.dart';
import 'package:cw_core/unspent_coin_type.dart';
import "package:cw_bitcoin/bitcoin_transaction_priority.dart";
import "package:cw_core/coin_control/coin_selection.dart";
import "package:cw_core/output_info.dart";
import "package:cw_core/unspent_coin_type.dart";

class BitcoinTransactionCredentials {
BitcoinTransactionCredentials(
this.outputs, {
required this.priority,
this.feeRate,
this.coinTypeToSpendFrom = UnspentCoinType.any,
this.coinSelection = const AllCoinSelection(),
this.payjoinUri,
});

final List<OutputInfo> outputs;
final BitcoinTransactionPriority? priority;
final int? feeRate;
final CoinSelection coinSelection;
final UnspentCoinType coinTypeToSpendFrom;
final String? payjoinUri;
}
11 changes: 9 additions & 2 deletions cw_bitcoin/lib/bitcoin_unspent.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_core/unspent_transaction_output.dart';
import "package:bitcoin_base/bitcoin_base.dart";
import "package:cw_bitcoin/bitcoin_address_record.dart";
import "package:cw_core/unspent_transaction_output.dart";

class BitcoinUnspent extends Unspent {
BitcoinUnspent(BaseBitcoinAddressRecord addressRecord, String hash, int value, int vout)
Expand All @@ -26,6 +27,9 @@ class BitcoinUnspent extends Unspent {

final BaseBitcoinAddressRecord bitcoinAddressRecord;
bool? isPegOut;

@override
String get id => bitcoinAddressRecord.type == SegwitAddresType.mweb ? hash : "$hash:$vout";
}

class BitcoinSilentPaymentsUnspent extends BitcoinUnspent {
Expand Down Expand Up @@ -63,6 +67,9 @@ class BitcoinSilentPaymentsUnspent extends BitcoinUnspent {
return json;
}

@override
bool get isSilentPayment => true;

String? silentPaymentTweak;
String? silentPaymentLabel;
}
26 changes: 11 additions & 15 deletions cw_bitcoin/lib/bitcoin_wallet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/sync_status.dart';
import "package:cw_core/receive_page_option.dart";
import 'package:cw_core/unspent_coin_type.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_bitcoin/bitcoin_unspent.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/utils/zpub.dart';
import 'package:cw_core/wallet_info.dart';
Expand All @@ -59,7 +59,6 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
required String password,
required WalletInfo walletInfo,
required DerivationInfo derivationInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required Box<PayjoinSession> payjoinBox,
required EncryptionFileUtils encryptionFileUtils,
Uint8List? seedBytes,
Expand All @@ -85,7 +84,6 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
password: password,
walletInfo: walletInfo,
derivationInfo: derivationInfo,
unspentCoinsInfo: unspentCoinsInfo,
network: networkParam == null
? BitcoinNetwork.mainnet
: networkParam == BitcoinNetwork.mainnet
Expand Down Expand Up @@ -183,7 +181,6 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
required String mnemonic,
required String password,
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required Box<PayjoinSession> payjoinBox,
required EncryptionFileUtils encryptionFileUtils,
String? passphrase,
Expand Down Expand Up @@ -219,7 +216,6 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
password: password,
walletInfo: walletInfo,
derivationInfo: derivationInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: initialAddresses,
initialSilentAddresses: initialSilentAddresses,
initialSilentAddressIndex: initialSilentAddressIndex,
Expand All @@ -238,7 +234,6 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
static Future<BitcoinWallet> open({
required String name,
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required Box<PayjoinSession> payjoinBox,
required String password,
required EncryptionFileUtils encryptionFileUtils,
Expand Down Expand Up @@ -322,7 +317,6 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
passphrase: passphrase,
walletInfo: walletInfo,
derivationInfo: derivationInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: snp?.addresses,
initialSilentAddresses: snp?.silentAddresses,
initialSilentAddressIndex: snp?.silentAddressIndex ?? 0,
Expand Down Expand Up @@ -430,9 +424,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
@override
bool get hasLightningSupport => seed != null && LightningWallet.isAvailable;

bool get isPayjoinAvailable => unspentCoinsInfo.values
.where((element) => element.walletId == id && element.isSending && !element.isFrozen)
.isNotEmpty;
Future<bool> get isPayjoinAvailable async =>
(await spendableCoins()).isNotEmpty;

Future<PsbtV2> buildPsbt({
required List<BitcoinOutput> outputs,
Expand Down Expand Up @@ -606,7 +599,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
}

final originalPsbt =
await signPsbt(base64.encode(transaction.asPsbtV0()), getUtxoWithPrivateKeys());
await signPsbt(base64.encode(transaction.asPsbtV0()), await getUtxoWithPrivateKeys());

tx.commitOverride = () async {
final sender =
Expand All @@ -617,10 +610,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
return tx;
}

List<UtxoWithPrivateKey> getUtxoWithPrivateKeys({bool confirmedOnly = false}) =>
unspentCoinsForCurrentAccount
.where(
(e) => e.isSending && !e.isFrozen && (!confirmedOnly || (e.confirmations ?? 0) > 0))
Future<List<UtxoWithPrivateKey>> getUtxoWithPrivateKeys({bool confirmedOnly = false}) async =>
(await spendableCoins())
.cast<BitcoinUnspent>()
.where((e) => !confirmedOnly || (e.confirmations ?? 0) > 0)
.map((unspent) => UtxoWithPrivateKey.fromUnspent(unspent, this))
.toList();

Expand Down Expand Up @@ -747,4 +740,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {

return true;
}

@override
Uri coinControlUrl(String txId) => Uri.https("ordinals.com", "/tx/${txId}");
}
23 changes: 5 additions & 18 deletions cw_bitcoin/lib/bitcoin_wallet_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart';
import 'package:cw_bitcoin/mnemonic_is_incorrect_exception.dart';
import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart';
import "package:cw_core/coin_control/coin_notes_store.dart";
import "package:cw_core/coin_control/frozen_coins_store.dart";
import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_core/payjoin_session.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/utils/zpub.dart';
import 'package:cw_core/wallet_service.dart';
import 'package:cw_bitcoin/bitcoin_wallet.dart';
Expand All @@ -21,9 +22,8 @@ class BitcoinWalletService extends WalletService<
BitcoinRestoreWalletFromSeedCredentials,
BitcoinWalletFromKeysCredentials,
BitcoinRestoreWalletFromHardware> {
BitcoinWalletService(this.unspentCoinsInfoSource, this.payjoinSessionSource, this.isDirect);
BitcoinWalletService(this.payjoinSessionSource, this.isDirect);

final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
final Box<PayjoinSession> payjoinSessionSource;
final bool isDirect;

Expand Down Expand Up @@ -63,7 +63,6 @@ class BitcoinWalletService extends WalletService<
password: credentials.password!,
passphrase: credentials.passphrase,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource,
payjoinBox: payjoinSessionSource,
network: network,
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
Expand All @@ -90,7 +89,6 @@ class BitcoinWalletService extends WalletService<
password: password,
name: name,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource,
payjoinBox: payjoinSessionSource,
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
);
Expand All @@ -103,7 +101,6 @@ class BitcoinWalletService extends WalletService<
password: password,
name: name,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource,
payjoinBox: payjoinSessionSource,
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
);
Expand All @@ -121,15 +118,8 @@ class BitcoinWalletService extends WalletService<
}
await WalletInfo.delete(walletInfo);

final unspentCoinsToDelete = unspentCoinsInfoSource.values
.where((unspentCoin) => unspentCoin.walletId == walletInfo.id)
.toList();

final keysToDelete = unspentCoinsToDelete.map((unspentCoin) => unspentCoin.key).toList();

if (keysToDelete.isNotEmpty) {
await unspentCoinsInfoSource.deleteAll(keysToDelete);
}
await FrozenCoinsStore.instance.deleteWallet(walletInfo.internalId);
await CoinNotesStore.instance.deleteWallet(walletInfo.internalId);
}

@override
Expand All @@ -148,7 +138,6 @@ class BitcoinWalletService extends WalletService<
xpub: xpub,
walletInfo: credentials.walletInfo!,
derivationInfo: derivationInfo,
unspentCoinsInfo: unspentCoinsInfoSource,
networkParam: network,
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
payjoinBox: payjoinSessionSource,
Expand All @@ -172,7 +161,6 @@ class BitcoinWalletService extends WalletService<
xpub: xpub,
walletInfo: credentials.walletInfo!,
derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
unspentCoinsInfo: unspentCoinsInfoSource,
networkParam: network,
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
payjoinBox: payjoinSessionSource,
Expand All @@ -199,7 +187,6 @@ class BitcoinWalletService extends WalletService<
passphrase: credentials.passphrase,
mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource,
payjoinBox: payjoinSessionSource,
network: network,
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
Expand Down
8 changes: 8 additions & 0 deletions cw_bitcoin/lib/electrum_balance.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ class ElectrumBalance extends Balance {
@override
Money frozen;

ElectrumBalance copyWith({Money? frozen}) => ElectrumBalance(
confirmed: confirmed,
unconfirmed: unconfirmed,
frozen: frozen ?? this.frozen,
secondConfirmed: secondConfirmed,
secondUnconfirmed: secondUnconfirmed,
);

ElectrumBalance copy() => ElectrumBalance(
confirmed: confirmed,
unconfirmed: unconfirmed,
Expand Down
Loading
Loading