-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathSendQuickpay.swift
More file actions
140 lines (118 loc) · 4.66 KB
/
SendQuickpay.swift
File metadata and controls
140 lines (118 loc) · 4.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import LDKNode
import SwiftUI
struct LoadingView: View {
@State private var outerRotation: Double = 0
@State private var innerRotation: Double = 0
@State private var imageRotation: Double = 0
var body: some View {
ZStack(alignment: .center) {
// Outer ellipse
Image("ellipse-outer-purple")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 311, height: 311)
.rotationEffect(.degrees(outerRotation))
// Inner ellipse
Image("ellipse-inner-purple")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 207, height: 207)
.rotationEffect(.degrees(innerRotation))
// Image
Image("coin-stack-4")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 330, height: 330)
.rotationEffect(.degrees(imageRotation))
}
.frame(width: 320, height: 320)
.clipped()
.frame(maxWidth: .infinity)
.onAppear {
withAnimation(.easeInOut(duration: 2).repeatForever(autoreverses: true)) {
outerRotation = -180
}
withAnimation(.easeInOut(duration: 2).repeatForever(autoreverses: true)) {
innerRotation = 180
}
withAnimation(.easeInOut(duration: 3).repeatForever(autoreverses: true)) {
imageRotation = 20
}
}
}
}
struct SendQuickpay: View {
@EnvironmentObject var app: AppViewModel
@EnvironmentObject var sheets: SheetViewModel
@EnvironmentObject var wallet: WalletViewModel
@Binding var navigationPath: [SendRoute]
var body: some View {
VStack {
SheetHeader(title: t("wallet__send_quickpay__nav_title"))
if let invoice = app.scannedLightningInvoice {
MoneyStack(sats: Int(invoice.amountSatoshis), showSymbol: true)
}
Spacer()
LoadingView()
Spacer()
DisplayText(t("wallet__send_quickpay__title"), accentColor: .purpleAccent)
}
.navigationBarHidden(true)
.allowSwipeBack(false)
.padding(.horizontal, 16)
.sheetBackground()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.onAppear {
Task {
try await performPayment()
}
}
}
private func performPayment() async throws {
var bolt11Invoice: String?
// Handle LNURL Pay
if let lnurlPayData = app.lnurlPayData {
// Set the amount in sats for the success screen
wallet.sendAmountSats = LightningAmountConversion.satsCeil(fromMsats: lnurlPayData.minSendable)
bolt11Invoice = try await LnurlHelper.fetchLnurlInvoice(
callbackUrl: lnurlPayData.callback,
amountMsats: lnurlPayData.minSendable
)
} else if let scannedInvoice = app.scannedLightningInvoice {
wallet.sendAmountSats = scannedInvoice.amountSatoshis
bolt11Invoice = scannedInvoice.bolt11
}
guard let bolt11 = bolt11Invoice else {
throw NSError(
domain: "Payment", code: -1, userInfo: [NSLocalizedDescriptionKey: "No Lightning invoice found"]
)
}
let parsedInvoice = try Bolt11Invoice.fromStr(invoiceStr: bolt11)
let paymentHash = String(describing: parsedInvoice.paymentHash())
do {
// Quickpay only triggers for invoices with built-in amounts, so pass sats: nil
// to let LDK use the invoice's native millisatoshi precision.
try await wallet.sendWithTimeout(
bolt11: bolt11,
sats: nil,
onTimeout: {
app.addPendingPaymentHash(paymentHash)
navigationPath.append(.pending(paymentHash: paymentHash))
}
)
Logger.info("Quickpay payment successful: \(paymentHash)")
navigationPath.append(.success(paymentId: paymentHash))
} catch is PaymentTimeoutError {
// onTimeout callback already navigated to .pending; suppress throw
return
} catch {
Logger.error("Quickpay payment failed: \(error)")
// TODO: remove toast and use failure screen instead
app.toast(error)
// TODO: this is a hack to make sure the navigation binding is ready
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
navigationPath.append(.failure)
}
}
}
}