Skip to content

Commit 974de6e

Browse files
authored
Merge pull request #317 from synonymdev/fix/blocktank-orders-migration
fix: migrate blocktank orders
2 parents 2f9e26b + 44719d4 commit 974de6e

3 files changed

Lines changed: 138 additions & 11 deletions

File tree

Bitkit.xcodeproj/project.pbxproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,15 @@
8888
Models/BlocktankNotificationType.swift,
8989
Models/LnPeer.swift,
9090
Models/Toast.swift,
91+
Models/Transfer.swift,
92+
Models/TransferType.swift,
9193
Services/CoreService.swift,
9294
Services/GeoService.swift,
9395
Services/LightningService.swift,
9496
Services/MigrationsService.swift,
9597
Services/RNBackupClient.swift,
9698
Services/ServiceQueue.swift,
99+
Services/TransferStorage.swift,
97100
Services/VssStoreIdProvider.swift,
98101
Utilities/Crypto.swift,
99102
Utilities/Errors.swift,

Bitkit/Services/MigrationsService.swift

Lines changed: 134 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

Bitkit/ViewModels/ChannelDetailsViewModel.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ class ChannelDetailsViewModel: ObservableObject {
143143
}
144144

145145
// Create fake channels from pending orders
146-
guard let orders = try? await coreService.blocktank.orders(refresh: false) else {
146+
guard let orders = try? await coreService.blocktank.orders(refresh: true) else {
147147
return connections
148148
}
149149

0 commit comments

Comments
 (0)