@@ -338,6 +338,9 @@ class MigrationsService: ObservableObject {
338338 /// Stored metadata from RN backup for reapplying after on-chain activities are synced
339339 private var pendingRemoteMetadata : RNMetadata ?
340340
341+ /// Stored paid orders from RN backup for creating transfers after wallet starts
342+ private var pendingRemotePaidOrders : [ String : String ] ? // orderId -> txId
343+
341344 private init ( ) { }
342345
343346 private var rnNetworkString : String {
@@ -707,6 +710,40 @@ extension MigrationsService {
707710 }
708711 }
709712
713+ func extractRNBlocktank( from mmkvData: [ String : String ] ) -> ( orders: [ String ] , paidOrders: [ String : String ] ) ? {
714+ guard let rootJson = mmkvData [ " persist:root " ] ,
715+ let jsonStart = rootJson. firstIndex ( of: " { " )
716+ else { return nil }
717+
718+ let jsonString = String ( rootJson [ jsonStart... ] )
719+ guard let data = jsonString. data ( using: . utf8) ,
720+ let root = try ? JSONSerialization . jsonObject ( with: data) as? [ String : Any ] ,
721+ let blocktankJson = root [ " blocktank " ] as? String ,
722+ let blocktankData = blocktankJson. data ( using: . utf8) ,
723+ let blocktankDict = try ? JSONSerialization . jsonObject ( with: blocktankData) as? [ String : Any ]
724+ else {
725+ return nil
726+ }
727+
728+ var orderIds : [ String ] = [ ]
729+ var paidOrdersMap : [ String : String ] = [ : ]
730+
731+ if let orders = blocktankDict [ " orders " ] as? [ [ String : Any ] ] {
732+ orderIds = orders. compactMap { $0 [ " id " ] as? String }
733+ }
734+
735+ if let paidOrders = blocktankDict [ " paidOrders " ] as? [ String : String ] {
736+ paidOrdersMap = paidOrders
737+ }
738+
739+ if orderIds. isEmpty && paidOrdersMap. isEmpty {
740+ return nil
741+ }
742+
743+ Logger . debug ( " Extracted RN blocktank: \( orderIds. count) orders, \( paidOrdersMap. count) paid orders " , context: " Migration " )
744+ return ( orders: orderIds, paidOrders: paidOrdersMap)
745+ }
746+
710747 func extractRNActivities( from mmkvData: [ String : String ] ) -> [ RNActivityItem ] ? {
711748 guard let rootJson = mmkvData [ " persist:root " ] ,
712749 let jsonStart = rootJson. firstIndex ( of: " { " )
@@ -1079,6 +1116,26 @@ extension MigrationsService {
10791116 }
10801117 }
10811118
1119+ func applyRNBlocktank( orderIds: [ String ] , paidOrders: [ String : String ] ) async {
1120+ let allOrderIds = Array ( Set ( orderIds + Array( paidOrders. keys) ) )
1121+
1122+ guard !allOrderIds. isEmpty else { return }
1123+
1124+ do {
1125+ let fetchedOrders = try await CoreService . shared. blocktank. orders ( orderIds: allOrderIds, filter: nil , refresh: true )
1126+ if !fetchedOrders. isEmpty {
1127+ try await CoreService . shared. blocktank. upsertOrdersList ( fetchedOrders)
1128+ Logger . info ( " Upserted \( fetchedOrders. count) Blocktank orders " , context: " Migration " )
1129+ }
1130+
1131+ if !paidOrders. isEmpty {
1132+ await createTransfersForPaidOrders ( paidOrdersMap: paidOrders, orders: fetchedOrders)
1133+ }
1134+ } catch {
1135+ Logger . warn ( " Failed to fetch and upsert Blocktank orders: \( error) " , context: " Migration " )
1136+ }
1137+ }
1138+
10821139 func migrateMMKVData( ) async {
10831140 guard let mmkvData = loadRNMmkvData ( ) else {
10841141 Logger . debug ( " No MMKV data to migrate " , context: " Migration " )
@@ -1128,6 +1185,13 @@ extension MigrationsService {
11281185 Logger . debug ( " No todos found in MMKV " , context: " Migration " )
11291186 }
11301187
1188+ if let blocktank = extractRNBlocktank ( from: mmkvData) {
1189+ Logger . info ( " Migrating blocktank orders " , context: " Migration " )
1190+ await applyRNBlocktank ( orderIds: blocktank. orders, paidOrders: blocktank. paidOrders)
1191+ } else {
1192+ Logger . debug ( " No blocktank data found in MMKV " , context: " Migration " )
1193+ }
1194+
11311195 UserDefaults . standard. set ( " " , forKey: " onchainAddress " )
11321196
11331197 Logger . info ( " MMKV data migration completed " , context: " Migration " )
@@ -1185,6 +1249,13 @@ extension MigrationsService {
11851249 await applyAllMetadata ( metadata)
11861250 pendingRemoteMetadata = nil
11871251 }
1252+
1253+ // Handle remote backup paid orders (create transfers for pending channel orders)
1254+ if let paidOrders = pendingRemotePaidOrders {
1255+ Logger . info ( " Applying \( paidOrders. count) remote paid orders " , context: " Migration " )
1256+ await applyRemotePaidOrders ( paidOrders)
1257+ pendingRemotePaidOrders = nil
1258+ }
11881259 }
11891260
11901261 private func applyRemoteTransfers( _ transfers: [ String : String ] ) async {
@@ -1209,6 +1280,22 @@ extension MigrationsService {
12091280 Logger . info ( " Applied \( applied) / \( transfers. count) transfer markers " , context: " Migration " )
12101281 }
12111282
1283+ private func applyRemotePaidOrders( _ paidOrders: [ String : String ] ) async {
1284+ let orderIds = Array ( paidOrders. keys)
1285+ guard !orderIds. isEmpty else { return }
1286+
1287+ do {
1288+ let fetchedOrders = try await CoreService . shared. blocktank. orders ( orderIds: orderIds, filter: nil , refresh: true )
1289+ if !fetchedOrders. isEmpty {
1290+ try await CoreService . shared. blocktank. upsertOrdersList ( fetchedOrders)
1291+ Logger . info ( " Upserted \( fetchedOrders. count) Blocktank orders from remote backup " , context: " Migration " )
1292+ }
1293+ await createTransfersForPaidOrders ( paidOrdersMap: paidOrders, orders: fetchedOrders)
1294+ } catch {
1295+ Logger . warn ( " Failed to fetch Blocktank orders: \( error) " , context: " Migration " )
1296+ }
1297+ }
1298+
12121299 private func applyBoostTransactions( _ boosts: [ String : String ] ) async {
12131300 var applied = 0
12141301
@@ -1740,7 +1827,6 @@ extension MigrationsService {
17401827
17411828 struct BlocktankBackup : Codable {
17421829 var orders : [ BlocktankOrder ] ?
1743- var paidOrders : [ String ] ?
17441830 }
17451831
17461832 struct BackupEnvelope : Codable {
@@ -1758,19 +1844,57 @@ extension MigrationsService {
17581844 orderIds. append ( contentsOf: orders. map ( \. id) )
17591845 }
17601846
1761- if let paidOrderIds = json. data. paidOrders {
1762- orderIds. append ( contentsOf: paidOrderIds)
1847+ // paidOrders is a map of orderId -> txId
1848+ var paidOrdersMap : [ String : String ] = [ : ]
1849+ if let rawDict = try ? JSONSerialization . jsonObject ( with: data) as? [ String : Any ] ,
1850+ let dataDict = rawDict [ " data " ] as? [ String : Any ] ,
1851+ let paidOrders = dataDict [ " paidOrders " ] as? [ String : String ]
1852+ {
1853+ paidOrdersMap = paidOrders
1854+ orderIds. append ( contentsOf: paidOrders. keys)
1855+ Logger . info ( " Found \( paidOrders. count) paid orders in blocktank backup " , context: " Migration " )
17631856 }
17641857
1765- if !orderIds. isEmpty {
1858+ // Store paid orders for processing after wallet starts (CoreService not ready yet during restore)
1859+ if !paidOrdersMap. isEmpty {
1860+ pendingRemotePaidOrders = paidOrdersMap
1861+ }
1862+ }
1863+
1864+ private func createTransfersForPaidOrders( paidOrdersMap: [ String: String] , orders: [ IBtOrder] ) async {
1865+ let now = UInt64 ( Date ( ) . timeIntervalSince1970)
1866+ var transfers : [ Transfer ] = [ ]
1867+
1868+ for (orderId, txId) in paidOrdersMap {
1869+ guard let order = orders. first ( where: { $0. id == orderId } ) else {
1870+ Logger . warn ( " Paid order \( orderId) not found in fetched orders " , context: " Migration " )
1871+ continue
1872+ }
1873+
1874+ if order. state2 == . executed {
1875+ continue
1876+ }
1877+
1878+ let transfer = Transfer (
1879+ id: txId,
1880+ type: . toSpending,
1881+ amountSats: order. clientBalanceSat + order. feeSat,
1882+ channelId: nil ,
1883+ fundingTxId: nil ,
1884+ lspOrderId: orderId,
1885+ isSettled: false ,
1886+ createdAt: now,
1887+ settledAt: nil
1888+ )
1889+ transfers. append ( transfer)
1890+ }
1891+
1892+ if !transfers. isEmpty {
17661893 do {
1767- let fetchedOrders = try await CoreService . shared. blocktank. orders ( orderIds: orderIds, filter: nil , refresh: true )
1768- if !fetchedOrders. isEmpty {
1769- try await CoreService . shared. blocktank. upsertOrdersList ( fetchedOrders)
1770- Logger . info ( " Upserted \( fetchedOrders. count) Blocktank orders " , context: " Migration " )
1771- }
1894+ try TransferStorage . shared. upsertList ( transfers)
1895+ Logger . info ( " Created \( transfers. count) transfers for paid Blocktank orders " , context: " Migration " )
17721896 } catch {
1773- Logger . warn ( " Failed to fetch and upsert Blocktank orders: \( error) " , context: " Migration " )
1897+ Logger . error ( " Failed to create transfers for paid orders: \( error) " , context: " Migration " )
17741898 }
17751899 }
17761900 }
0 commit comments