@@ -37,6 +37,7 @@ import 'package:bb_mobile/features/send/domain/usecases/preview_bitcoin_fee_pres
3737import 'package:bb_mobile/features/send/domain/usecases/preview_bitcoin_fee_usecase.dart' ;
3838import 'package:bb_mobile/features/send/domain/usecases/sign_bitcoin_tx_usecase.dart' ;
3939import 'package:bb_mobile/features/send/domain/usecases/sign_liquid_tx_usecase.dart' ;
40+ import 'package:flutter/foundation.dart' ;
4041import 'package:flutter_bloc/flutter_bloc.dart' ;
4142import 'package:freezed_annotation/freezed_annotation.dart' ;
4243
@@ -909,7 +910,16 @@ class TransferBloc extends Bloc<TransferEvent, TransferState>
909910 final utxos = await _getWalletUtxosUsecase.execute (
910911 walletId: state.fromWallet! .id,
911912 );
913+ // A wallet sync can change the available coins; any cached preview PSBT
914+ // was built against the prior set, so drop it — otherwise a sync landing
915+ // mid-flow could leave a stale PSBT staged for broadcast. Guarded so a
916+ // no-op refresh doesn't needlessly re-shimmer. Mirrors SendCubit.loadUtxos.
917+ final utxosChanged = ! setEquals (
918+ (state.utxos ?? const < WalletUtxo > []).toSet (),
919+ utxos.toSet (),
920+ );
912921 emit (state.copyWith (utxos: utxos));
922+ if (utxosChanged) _clearBitcoinFeePreviews (emit);
913923 } catch (e) {
914924 log.severe (
915925 message: 'Error loading UTXOs' ,
@@ -1141,6 +1151,25 @@ class TransferBloc extends Bloc<TransferEvent, TransferState>
11411151 emit (state.copyWith (feePreviewCache: BitcoinFeePreviewCache .empty));
11421152 }
11431153
1154+ /// Belt-and-suspenders relay-floor re-assert, mirroring
1155+ /// `SendCubit.createTransaction` . The pre-build gate in
1156+ /// [_onCustomFeeFinalized] checks an absolute custom fee against the
1157+ /// *previous* `bitcoinTxSize` ; if the real tx is larger, an absolute fee
1158+ /// that cleared that gate can land below the floor at the actual vsize.
1159+ /// Re-checking the freshly built fee against the freshly built vsize closes
1160+ /// the only below-relay-broadcast vector on the swap surface — don't rely on
1161+ /// BDK rejecting sub-minrelay itself.
1162+ bool _builtFeeClearsRelay ({
1163+ required TransferState stateToUse,
1164+ required int builtFeeSat,
1165+ required int txSize,
1166+ }) {
1167+ return NetworkFee .absolute (builtFeeSat).aboveMinRelay (
1168+ txSize: txSize,
1169+ floorSatPerKwu: stateToUse.bitcoinNetworkFees? .minRelay.satPerKwu,
1170+ );
1171+ }
1172+
11441173 Future <void > _rebuildTransactionWithState (
11451174 Emitter <TransferState > emit,
11461175 TransferState stateToUse,
@@ -1203,11 +1232,33 @@ class TransferBloc extends Bloc<TransferEvent, TransferState>
12031232 psbt: signedPsbtAndTxSize.signedPsbt,
12041233 );
12051234
1235+ if (! _builtFeeClearsRelay (
1236+ stateToUse: stateToUse,
1237+ builtFeeSat: bitcoinAbsoluteFeesSat,
1238+ txSize: signedPsbtAndTxSize.txSize,
1239+ )) {
1240+ log.warning (
1241+ 'Rebuild aborted — built fee $bitcoinAbsoluteFeesSat sats at '
1242+ '${signedPsbtAndTxSize .txSize } vbytes is below the relay floor' ,
1243+ );
1244+ emit (
1245+ stateToUse.copyWith (
1246+ signedPsbt: '' ,
1247+ buildTransactionException: BuildTransactionException (
1248+ 'Built fee $bitcoinAbsoluteFeesSat sats at '
1249+ '${signedPsbtAndTxSize .txSize } vbytes is below the relay floor' ,
1250+ ),
1251+ ),
1252+ );
1253+ return ;
1254+ }
1255+
12061256 emit (
12071257 stateToUse.copyWith (
12081258 signedPsbt: signedPsbtAndTxSize.signedPsbt,
12091259 bitcoinAbsoluteFeesSat: bitcoinAbsoluteFeesSat,
12101260 bitcoinTxSize: signedPsbtAndTxSize.txSize,
1261+ buildTransactionException: null ,
12111262 ),
12121263 );
12131264 } else if (stateToUse.swap != null && stateToUse.swap is ChainSwap ) {
@@ -1253,6 +1304,27 @@ class TransferBloc extends Bloc<TransferEvent, TransferState>
12531304 psbt: signedPsbtAndTxSize.signedPsbt,
12541305 );
12551306
1307+ if (! _builtFeeClearsRelay (
1308+ stateToUse: stateToUse,
1309+ builtFeeSat: bitcoinAbsoluteFeesSat,
1310+ txSize: signedPsbtAndTxSize.txSize,
1311+ )) {
1312+ log.warning (
1313+ 'Rebuild aborted — built fee $bitcoinAbsoluteFeesSat sats at '
1314+ '${signedPsbtAndTxSize .txSize } vbytes is below the relay floor' ,
1315+ );
1316+ emit (
1317+ stateToUse.copyWith (
1318+ signedPsbt: '' ,
1319+ buildTransactionException: BuildTransactionException (
1320+ 'Built fee $bitcoinAbsoluteFeesSat sats at '
1321+ '${signedPsbtAndTxSize .txSize } vbytes is below the relay floor' ,
1322+ ),
1323+ ),
1324+ );
1325+ return ;
1326+ }
1327+
12561328 final updatedSwap = await _updateSendSwapLockupFeesUsecase.execute (
12571329 swapId: swap.id,
12581330 lockupFees: bitcoinAbsoluteFeesSat,
@@ -1264,6 +1336,7 @@ class TransferBloc extends Bloc<TransferEvent, TransferState>
12641336 signedPsbt: signedPsbtAndTxSize.signedPsbt,
12651337 bitcoinAbsoluteFeesSat: bitcoinAbsoluteFeesSat,
12661338 bitcoinTxSize: signedPsbtAndTxSize.txSize,
1339+ buildTransactionException: null ,
12671340 ),
12681341 );
12691342 }
0 commit comments