commit 5b9f38d440859b9527085598b4b2cf984aca5e3e
parent f7e45486455d19268db41f4b12fb45c6fa8c47ad
Author: William Casarin <jb55@jb55.com>
Date: Mon, 21 Mar 2022 19:46:32 -0700
lnurlp metadata parsing
Signed-off-by: William Casarin <jb55@jb55.com>
Diffstat:
3 files changed, 134 insertions(+), 41 deletions(-)
diff --git a/lightninglink/LNUrl.swift b/lightninglink/LNUrl.swift
@@ -6,6 +6,7 @@
//
import Foundation
+import SwiftUI
public struct LNUrlDecode {
let encoded: Bech32
@@ -155,3 +156,39 @@ func handle_lnurl(_ url: URL, completion: @escaping (LNUrl?) -> ()) {
task.resume()
}
+
+func decode_lnurlp_metadata(_ lnurlp: LNUrlPay) -> LNUrlPayDecode {
+ var metadata = Array<Array<String>>()
+ do {
+ metadata = try JSONDecoder().decode(Array<Array<String>>.self, from: Data(lnurlp.metadata.utf8))
+ } catch {
+
+ }
+
+ var description: String? = nil
+ var longDescription: String? = nil
+ var thumbnail: Image? = nil
+ var vendor: String = lnurlp.callback.host ?? ""
+
+ for entry in metadata {
+ if entry.count == 2 {
+ if entry[0] == "text/plain" {
+ description = entry[1]
+ } else if entry[0] == "text/identifier" {
+ vendor = entry[1]
+ } else if entry[0] == "text/long-desc" {
+ longDescription = entry[1]
+ } else if entry[0] == "image/png;base64" || entry[0] == "image/jpg;base64" {
+ guard let dat = Data(base64Encoded: entry[1]) else {
+ continue
+ }
+ guard let ui_img = UIImage(data: dat) else {
+ continue
+ }
+ thumbnail = Image(uiImage: ui_img)
+ }
+ }
+ }
+
+ return LNUrlPayDecode(description: description, longDescription: longDescription, thumbnail: thumbnail, vendor: vendor)
+}
diff --git a/lightninglink/RPC.swift b/lightninglink/RPC.swift
@@ -6,6 +6,7 @@
//
import Foundation
+import SwiftUI
public typealias RequestRes<T> = Result<T, RequestError<RpcErrorData>>
@@ -46,7 +47,55 @@ public struct Channel: Decodable {
public var channel_total_sat: Int64
}
-public struct Decode: Decodable {
+public struct LNUrlPayDecode {
+ public let description: String?
+ public let longDescription: String?
+ public let thumbnail: Image?
+ public let vendor: String
+}
+
+public enum Decode {
+ case invoice(InvoiceDecode)
+ case lnurlp(LNUrlPayDecode)
+
+ func thumbnail() -> Image? {
+ switch self {
+ case .lnurlp(let decode):
+ return decode.thumbnail
+ case .invoice:
+ return nil
+ }
+ }
+
+ func description() -> String? {
+ switch self {
+ case .invoice(let inv):
+ return inv.description
+ case .lnurlp(let lnurl):
+ return lnurl.description
+ }
+ }
+
+ func vendor() -> String? {
+ switch self {
+ case .invoice(let inv):
+ return inv.vendor
+ case .lnurlp(let lnurl):
+ return lnurl.vendor
+ }
+ }
+
+ func amount_msat() -> String? {
+ switch self {
+ case .invoice(let inv):
+ return inv.amount_msat
+ case .lnurlp:
+ return nil
+ }
+ }
+}
+
+public struct InvoiceDecode: Decodable {
public var type: String
public var currency: String?
public var valid: Bool
@@ -62,7 +111,7 @@ public struct Decode: Decodable {
public var vendor: String?
}
-func get_decode_expiry(_ decode: Decode) -> Int64? {
+func get_decode_expiry(_ decode: InvoiceDecode) -> Int64? {
// bolt11
if decode.expiry != nil {
return decode.expiry
@@ -316,7 +365,7 @@ public func rpc_listfunds(ln: LNSocket, token: String) -> RequestRes<ListFunds>
return performRpc(ln: ln, operation: "listfunds", authToken: token, timeout_ms: default_timeout, params: params)
}
-public func rpc_decode(ln: LNSocket, token: String, inv: String) -> RequestRes<Decode>
+public func rpc_decode(ln: LNSocket, token: String, inv: String) -> RequestRes<InvoiceDecode>
{
let params = [ inv ];
return performRpc(ln: ln, operation: "decode", authToken: token, timeout_ms: 1000, params: params)
diff --git a/lightninglink/Views/PayView.swift b/lightninglink/Views/PayView.swift
@@ -11,7 +11,7 @@ import Combine
public struct Offer {
let offer: String
let amount: InvoiceAmount
- let decoded: Decode
+ let decoded: InvoiceDecode
}
public struct Invoice {
@@ -149,19 +149,23 @@ struct PayView: View {
if self.invoice != nil {
let invoice = self.invoice!
- if invoice.description != nil {
- Text(invoice.description!)
+ if invoice.description() != nil {
+ Text(invoice.description()!)
.multilineTextAlignment(.center)
.padding()
}
- if invoice.vendor != nil {
- Text(invoice.vendor!)
+ if invoice.vendor() != nil {
+ Text(invoice.vendor()!)
.font(.callout)
.foregroundColor(.gray)
}
}
+ if self.invoice != nil && self.invoice!.thumbnail() != nil {
+ self.invoice!.thumbnail()!
+ }
+
Spacer()
// Middle area
@@ -256,7 +260,7 @@ struct PayView: View {
guard let invoice = self.invoice else {
return
}
- guard let amount_msat_str = invoice.amount_msat else {
+ guard let amount_msat_str = invoice.amount_msat() else {
return
}
guard let amount_msat = parse_msat(amount_msat_str) else {
@@ -523,10 +527,10 @@ struct PayView: View {
}
- func handle_offer(ln: LNSocket, decoded: Decode, inv: String) {
+ func handle_offer(ln: LNSocket, decoded: InvoiceDecode, inv: String) {
switch handle_bolt12_offer(ln: ln, decoded: decoded, inv: inv) {
case .right(let state):
- self.invoice = decoded
+ self.invoice = .invoice(decoded)
switch_state(state)
case .left(let err):
self.error = err
@@ -534,6 +538,9 @@ struct PayView: View {
}
func handle_lnurl_payview(ln: LNSocket?, lnurlp: LNUrlPay) {
+ let decode = decode_lnurlp_metadata(lnurlp)
+ self.invoice = .lnurlp(decode)
+
switch_state(.invoice_request(.lnurl(lnurlp)))
}
@@ -583,7 +590,7 @@ struct PayView: View {
}
self.state = .ready(Invoice(invstr: inv, amount: amount))
- self.invoice = decoded
+ self.invoice = .invoice(decoded)
update_expiry_percent()
} else {
self.error = "unknown decoded type: \(decoded.type)"
@@ -593,46 +600,45 @@ struct PayView: View {
}
func update_expiry_percent() {
- guard let invoice = self.invoice else {
- return
- }
+ if case let .invoice(invoice) = self.invoice {
+ guard let expiry = get_decode_expiry(invoice) else {
+ self.expiry_percent = nil
+ return
+ }
- guard let expiry = get_decode_expiry(invoice) else {
- self.expiry_percent = nil
- return
- }
+ guard let created_at = invoice.created_at else {
+ self.expiry_percent = nil
+ return
+ }
- guard let created_at = invoice.created_at else {
- self.expiry_percent = nil
- return
- }
+ let now = Int64(Date().timeIntervalSince1970)
+ let expires_at = created_at + expiry
- let now = Int64(Date().timeIntervalSince1970)
- let expires_at = created_at + expiry
+ guard expiry > 0 else {
+ self.expiry_percent = nil
+ return
+ }
- guard expiry > 0 else {
- self.expiry_percent = nil
- return
- }
+ guard now < expires_at else {
+ self.error = "Invoice expired"
+ self.expiry_percent = nil
+ return
+ }
- guard now < expires_at else {
- self.error = "Invoice expired"
- self.expiry_percent = nil
- return
- }
+ guard now >= created_at else {
+ self.expiry_percent = 1
+ return
+ }
- guard now >= created_at else {
- self.expiry_percent = 1
- return
+ let prog = now - created_at
+ self.expiry_percent = 1.0 - (Double(prog) / Double(expiry))
}
- let prog = now - created_at
- self.expiry_percent = 1.0 - (Double(prog) / Double(expiry))
}
}
-func fetchinvoice_req_from_offer(offer: Decode, offer_str: String, pay_amt: PayAmount) -> Either<String, FetchInvoiceReq> {
+func fetchinvoice_req_from_offer(offer: InvoiceDecode, offer_str: String, pay_amt: PayAmount) -> Either<String, FetchInvoiceReq> {
var qty: Int? = nil
if offer.quantity_min != nil {
@@ -747,7 +753,7 @@ struct PayView_Previews: PreviewProvider {
}
*/
-func handle_bolt12_offer(ln: LNSocket, decoded: Decode, inv: String) -> Either<String, PayState> {
+func handle_bolt12_offer(ln: LNSocket, decoded: InvoiceDecode, inv: String) -> Either<String, PayState> {
if decoded.amount_msat != nil {
guard let min_amt = parse_msat(decoded.amount_msat!) else {
return .left("Error parsing amount_msat: '\(decoded.amount_msat!)'")
@@ -844,3 +850,4 @@ func pay_amount_matches(pay_amt: PayAmount, invoice_amount: InvoiceAmount) -> Bo
return false
}
+