@@ -555,6 +555,94 @@ final class BDKService {
555555 try await signAndBroadcast ( psbt: psbt)
556556 }
557557
558+ func sweepWif( wif: String , feeRate: UInt64 ) async throws -> [ Txid ] {
559+ // Keep sweep minimal and predictable across signet/testnet/testnet4 in this example:
560+ // use the Esplora path only.
561+ guard self . clientType == . esplora else {
562+ throw WalletError . sweepEsploraOnly
563+ }
564+
565+ let candidates = [
566+ " pkh( \( wif) ) " ,
567+ " wpkh( \( wif) ) " ,
568+ " sh(wpkh( \( wif) )) " ,
569+ " tr( \( wif) ) " ,
570+ ]
571+
572+ var sweptTxids : [ Txid ] = [ ]
573+ var lastWIFOperationError : Error ?
574+
575+ var destinationScript : Script ?
576+
577+ for descriptorString in candidates {
578+ guard
579+ let descriptor = try ? Descriptor (
580+ descriptor: descriptorString,
581+ network: self . network
582+ )
583+ else {
584+ continue
585+ }
586+
587+ let tempDir = FileManager . default. temporaryDirectory
588+ . appendingPathComponent ( " bdk-sweep- \( UUID ( ) . uuidString) " , isDirectory: true )
589+ try ? FileManager . default. createDirectory ( at: tempDir, withIntermediateDirectories: true )
590+ defer { try ? FileManager . default. removeItem ( at: tempDir) }
591+
592+ let tempPath = tempDir. appendingPathComponent ( " wallet.sqlite " ) . path
593+ let sweepPersister = try Persister . newSqlite ( path: tempPath)
594+ let sweepWallet = try Wallet . createSingle (
595+ descriptor: descriptor,
596+ network: self . network,
597+ persister: sweepPersister
598+ )
599+
600+ let _ = sweepWallet. revealNextAddress ( keychain: . external)
601+ let syncRequest = try sweepWallet. startSyncWithRevealedSpks ( ) . build ( )
602+ let update = try await self . blockchainClient. sync ( syncRequest, UInt64 ( 5 ) )
603+ try sweepWallet. applyUpdate ( update: update)
604+
605+ if sweepWallet. balance ( ) . total. toSat ( ) == 0 {
606+ continue
607+ }
608+
609+ if destinationScript == nil {
610+ let destinationAddress = try getAddress ( )
611+ destinationScript = try Address ( address: destinationAddress, network: self . network)
612+ . scriptPubkey ( )
613+ }
614+
615+ guard let destinationScript else {
616+ throw WalletError . noSweepableFunds
617+ }
618+
619+ do {
620+ let psbt = try TxBuilder ( )
621+ . drainTo ( script: destinationScript)
622+ . drainWallet ( )
623+ . feeRate ( feeRate: FeeRate . fromSatPerVb ( satVb: feeRate) )
624+ . finish ( wallet: sweepWallet)
625+
626+ guard try sweepWallet. sign ( psbt: psbt) else {
627+ continue
628+ }
629+
630+ let tx = try psbt. extractTx ( )
631+ try await self . blockchainClient. broadcast ( tx)
632+ sweptTxids. append ( tx. computeTxid ( ) )
633+ } catch {
634+ lastWIFOperationError = error
635+ continue
636+ }
637+ }
638+
639+ guard !sweptTxids. isEmpty else {
640+ throw lastWIFOperationError ?? WalletError . noSweepableFunds
641+ }
642+
643+ return sweptTxids
644+ }
645+
558646 func buildTransaction( address: String , amount: UInt64 , feeRate: UInt64 ) throws
559647 -> Psbt
560648 {
@@ -768,6 +856,7 @@ struct BDKClient {
768856 let fullScanWithInspector : ( FullScanScriptInspector ) async throws -> Void
769857 let getAddress : ( ) throws -> String
770858 let send : ( String , UInt64 , UInt64 ) throws -> Void
859+ let sweepWif : ( String , UInt64 ) async throws -> [ Txid ]
771860 let calculateFee : ( Transaction ) throws -> Amount
772861 let calculateFeeRate : ( Transaction ) throws -> UInt64
773862 let sentAndReceived : ( Transaction ) throws -> SentAndReceivedValues
@@ -812,6 +901,9 @@ extension BDKClient {
812901 try await BDKService . shared. send ( address: address, amount: amount, feeRate: feeRate)
813902 }
814903 } ,
904+ sweepWif: { ( wif, feeRate) in
905+ try await BDKService . shared. sweepWif ( wif: wif, feeRate: feeRate)
906+ } ,
815907 calculateFee: { tx in try BDKService . shared. calculateFee ( tx: tx) } ,
816908 calculateFeeRate: { tx in try BDKService . shared. calculateFeeRate ( tx: tx) } ,
817909 sentAndReceived: { tx in try BDKService . shared. sentAndReceived ( tx: tx) } ,
@@ -874,6 +966,7 @@ extension BDKClient {
874966 fullScanWithInspector: { _ in } ,
875967 getAddress: { " tb1pd8jmenqpe7rz2mavfdx7uc8pj7vskxv4rl6avxlqsw2u8u7d4gfs97durt " } ,
876968 send: { _, _, _ in } ,
969+ sweepWif: { _, _ in [ ] } ,
877970 calculateFee: { _ in Amount . mock } ,
878971 calculateFeeRate: { _ in UInt64 ( 6.15 ) } ,
879972 sentAndReceived: { _ in
0 commit comments