@@ -274,6 +274,50 @@ final class TransferViewModelHwTests: XCTestCase {
274274 if case . generic = vm. hwTransferError { } else { XCTFail ( " expected .generic error " ) }
275275 }
276276
277+ func testElectrumBroadcastRejectionClearsPendingState( ) async {
278+ let funding = MockHwFunding ( )
279+ funding. broadcastError = BroadcastError . ElectrumError (
280+ errorDetails: " Broadcast failed: bad-txns-inputs-missingorspent "
281+ )
282+ let vm = makeViewModel ( funding: funding, connecting: MockHwConnecting ( ) )
283+
284+ vm. onTransferToSpendingHwConfirm ( order: . mock( ) , deviceId: " dev1 " )
285+ await awaitSigningComplete ( vm)
286+
287+ XCTAssertFalse ( vm. hwSpending. hasPendingBroadcast)
288+ if case . generic = vm. hwTransferError { } else { XCTFail ( " expected .generic error " ) }
289+ XCTAssertEqual ( funding. signCalls, 1 )
290+ }
291+
292+ func testUpdateHwLimitsClearsDesyncedPendingBroadcast( ) async {
293+ let funding = MockHwFunding ( )
294+ let vm = makeViewModel ( funding: funding, connecting: MockHwConnecting ( ) )
295+ funding. broadcastError = BroadcastError . ElectrumError ( errorDetails: " offline " )
296+ let order = IBtOrder . mock ( )
297+
298+ vm. onTransferToSpendingHwConfirm ( order: order, deviceId: " dev1 " )
299+ await awaitSigningComplete ( vm)
300+ XCTAssertTrue ( vm. hwSpending. hasPendingBroadcast)
301+
302+ await vm. updateHwLimits ( deviceId: " dev1 " , blocktankInfo: nil , estimateOrderFee: { _, _ in ( 0 , 0 ) } )
303+
304+ XCTAssertFalse ( vm. hwSpending. hasPendingBroadcast)
305+ vm. cancelHwSigning ( )
306+ XCTAssertEqual ( funding. signCalls, 1 , " cancel after limits reload must not be blocked by stale pending tx " )
307+ }
308+
309+ func testUpdateHwFundingFeeEstimateSetsMiningFeeBeforeSigning( ) async {
310+ let funding = MockHwFunding ( )
311+ let vm = makeViewModel ( funding: funding, connecting: MockHwConnecting ( ) )
312+ let order = IBtOrder . mock ( )
313+
314+ await vm. updateHwFundingFeeEstimate ( order: order, deviceId: " dev1 " )
315+
316+ XCTAssertEqual ( vm. hwSpending. miningFeeSats, funding. funding. miningFeeSats)
317+ XCTAssertEqual ( funding. estimateCalls. count, 1 )
318+ XCTAssertTrue ( funding. composeCalls. isEmpty)
319+ }
320+
277321 func testReentrancyGuardIgnoresConcurrentConfirm( ) async {
278322 let funding = MockHwFunding ( )
279323 funding. composeError = MockHwFunding . TestError ( ) // fail before the network-bound funding tail
0 commit comments