Skip to content

Commit 4b9083d

Browse files
authored
Merge pull request #249 from synonymdev/feat/cpfp-boost-fee-ui
Show CPFP txs as boost fee
2 parents ab36651 + 1b49bd8 commit 4b9083d

File tree

5 files changed

+46
-7
lines changed

5 files changed

+46
-7
lines changed

Bitkit/Resources/Localization/en.lproj/Localizable.strings

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,6 +1054,8 @@
10541054
"wallet__activity_output" = "{count, plural, one {OUTPUT} other {OUTPUTS (#)}}";
10551055
"wallet__activity_boosted_cpfp" = "BOOSTED TRANSACTION {num} (CPFP)";
10561056
"wallet__activity_boosted_rbf" = "BOOSTED TRANSACTION {num} (RBF)";
1057+
"wallet__activity_boost_fee" = "Boost Fee";
1058+
"wallet__activity_boost_fee_description" = "Boosted incoming transaction";
10571059
"wallet__activity_explorer" = "Open Block Explorer";
10581060
"wallet__activity_successful" = "Successful";
10591061
"wallet__activity_invoice_note" = "Invoice note";

Bitkit/Services/CoreService.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,15 @@ class ActivityService {
137137
return doesExistMap
138138
}
139139

140+
func isCpfpChildTransaction(txId: String) async -> Bool {
141+
guard await getTxIdsInBoostTxIds().contains(txId),
142+
let activity = try? await getOnchainActivityByTxId(txid: txId)
143+
else {
144+
return false
145+
}
146+
return activity.doesExist
147+
}
148+
140149
init(coreService: CoreService) {
141150
self.coreService = coreService
142151
}

Bitkit/Views/Wallets/Activity/ActivityIcon.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ struct ActivityIcon: View {
1010
let isBoosted: Bool
1111
let isTransfer: Bool
1212
let doesExist: Bool
13+
let isCpfpChild: Bool
1314

14-
init(activity: Activity, size: CGFloat = 32) {
15+
init(activity: Activity, size: CGFloat = 32, isCpfpChild: Bool = false) {
1516
self.size = size
17+
self.isCpfpChild = isCpfpChild
1618
switch activity {
1719
case let .lightning(ln):
1820
isLightning = true
@@ -65,7 +67,7 @@ struct ActivityIcon: View {
6567
backgroundColor: .red16,
6668
size: size
6769
)
68-
} else if isBoosted && !(confirmed ?? false) {
70+
} else if isCpfpChild || (isBoosted && !(confirmed ?? false)) {
6971
CircularIcon(
7072
icon: "timer-alt",
7173
iconColor: .yellow,

Bitkit/Views/Wallets/Activity/ActivityItemView.swift

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ struct ActivityItemView: View {
1414
@EnvironmentObject var channelDetails: ChannelDetailsViewModel
1515
@StateObject private var viewModel: ActivityItemViewModel
1616
@State private var boostTxDoesExist: [String: Bool] = [:] // Maps boostTxId -> doesExist
17+
@State private var isCpfpChild: Bool = false
1718

1819
init(item: Activity) {
1920
self.item = item
@@ -100,6 +101,10 @@ struct ActivityItemView: View {
100101
: t("wallet__activity_transfer_savings_done")
101102
}
102103

104+
if isCpfpChild {
105+
return t("wallet__activity_boost_fee")
106+
}
107+
103108
return isSent
104109
? t("wallet__activity_bitcoin_sent")
105110
: t("wallet__activity_bitcoin_received")
@@ -112,9 +117,11 @@ struct ActivityItemView: View {
112117
private var shouldDisableBoostButton: Bool {
113118
switch viewModel.activity {
114119
case .lightning:
115-
// Lightning transactions can never be boosted
116120
return true
117121
case let .onchain(activity):
122+
if isCpfpChild {
123+
return true
124+
}
118125
if !activity.doesExist {
119126
return true
120127
}
@@ -186,7 +193,7 @@ struct ActivityItemView: View {
186193
HStack(alignment: .bottom) {
187194
MoneyStack(sats: amount, prefix: amountPrefix, showSymbol: false)
188195
Spacer()
189-
ActivityIcon(activity: viewModel.activity, size: 48)
196+
ActivityIcon(activity: viewModel.activity, size: 48, isCpfpChild: isCpfpChild)
190197
}
191198
.padding(.bottom, 16)
192199

@@ -221,6 +228,11 @@ struct ActivityItemView: View {
221228
}
222229
}
223230
.task {
231+
// Check if this is a CPFP child transaction
232+
if case let .onchain(activity) = viewModel.activity {
233+
isCpfpChild = await CoreService.shared.activity.isCpfpChildTransaction(txId: activity.txId)
234+
}
235+
224236
// Load boostTxIds doesExist status to determine RBF vs CPFP
225237
if case let .onchain(activity) = viewModel.activity,
226238
!activity.boostTxIds.isEmpty

Bitkit/Views/Wallets/Activity/ActivityRowOnchain.swift

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@ private struct ActivityStatus: View {
55
let txType: PaymentType
66
let confirmed: Bool
77
let isTransfer: Bool
8+
let isCpfpChild: Bool
89

910
var body: some View {
1011
if isTransfer {
1112
BodyMSBText(t("wallet__activity_transfer"))
1213
} else {
13-
if txType == .sent {
14+
if isCpfpChild {
15+
BodyMSBText(t("wallet__activity_boost_fee"))
16+
} else if txType == .sent {
1417
BodyMSBText(t("wallet__activity_sent"))
1518
} else {
1619
BodyMSBText(t("wallet__activity_received"))
@@ -22,6 +25,7 @@ private struct ActivityStatus: View {
2225
struct ActivityRowOnchain: View {
2326
let item: OnchainActivity
2427
let feeEstimates: FeeRates?
28+
@State private var isCpfpChild: Bool = false
2529

2630
private var amountPrefix: String {
2731
return item.txType == .sent ? "-" : "+"
@@ -53,6 +57,10 @@ struct ActivityRowOnchain: View {
5357
return t("wallet__activity_removed")
5458
}
5559

60+
if isCpfpChild {
61+
return t("wallet__activity_boost_fee_description")
62+
}
63+
5664
if item.isTransfer {
5765
switch item.txType {
5866
case .sent:
@@ -75,16 +83,22 @@ struct ActivityRowOnchain: View {
7583

7684
var body: some View {
7785
HStack(spacing: 16) {
78-
ActivityIcon(activity: .onchain(item), size: 40)
86+
ActivityIcon(activity: .onchain(item), size: 40, isCpfpChild: isCpfpChild)
7987

8088
VStack(alignment: .leading, spacing: 2) {
81-
ActivityStatus(txType: item.txType, confirmed: item.confirmed, isTransfer: item.isTransfer)
89+
ActivityStatus(txType: item.txType, confirmed: item.confirmed, isTransfer: item.isTransfer, isCpfpChild: isCpfpChild)
90+
.lineLimit(1)
8291
CaptionBText(description)
92+
.lineLimit(1)
8393
}
94+
.fixedSize(horizontal: false, vertical: true)
8495

8596
Spacer()
8697

8798
MoneyCell(sats: amount, prefix: amountPrefix)
8899
}
100+
.task {
101+
isCpfpChild = await CoreService.shared.activity.isCpfpChildTransaction(txId: item.txId)
102+
}
89103
}
90104
}

0 commit comments

Comments
 (0)