commit 5a289cddd0a761cfc11c906c379a901d20ee2f0e
parent 150f18324114ba56cf4cadf80ba15a5f29bfb4af
Author: William Casarin <jb55@jb55.com>
Date: Sat, 26 Feb 2022 15:45:56 -0800
setup screen working
Signed-off-by: William Casarin <jb55@jb55.com>
Diffstat:
7 files changed, 502 insertions(+), 238 deletions(-)
diff --git a/lightninglink.xcodeproj/project.pbxproj b/lightninglink.xcodeproj/project.pbxproj
@@ -26,6 +26,7 @@
4CCB0E2627C979F30026461C /* CodeScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCB0E2227C979F30026461C /* CodeScanner.swift */; };
4CCB0E2727C979F30026461C /* ScannerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCB0E2327C979F30026461C /* ScannerCoordinator.swift */; };
4CCB0E2827C979F30026461C /* ScannerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCB0E2427C979F30026461C /* ScannerViewController.swift */; };
+ 4CCB0E2B27CA71CA0026461C /* SetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCB0E2A27CA71CA0026461C /* SetupView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -73,6 +74,7 @@
4CCB0E2227C979F30026461C /* CodeScanner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodeScanner.swift; sourceTree = "<group>"; };
4CCB0E2327C979F30026461C /* ScannerCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScannerCoordinator.swift; sourceTree = "<group>"; };
4CCB0E2427C979F30026461C /* ScannerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScannerViewController.swift; sourceTree = "<group>"; };
+ 4CCB0E2A27CA71CA0026461C /* SetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -129,15 +131,14 @@
4C641D172788FF2F002A36C9 /* lightninglink */ = {
isa = PBXGroup;
children = (
+ 4CCB0E2927CA71AA0026461C /* Views */,
4CCB0E1F27C979F30026461C /* CodeScanner */,
4C0359FE27AEEE8500FF92CE /* Info.plist */,
4C873FD427A6EF3F008C972C /* LNSocket.swift */,
4C641D182788FF2F002A36C9 /* lightninglinkApp.swift */,
- 4C641D1A2788FF2F002A36C9 /* ContentView.swift */,
4C641D1C2788FF30002A36C9 /* Assets.xcassets */,
4C641D1E2788FF30002A36C9 /* Preview Content */,
4C873FD627A6F1F5008C972C /* RPC.swift */,
- 4C0359FF27AEF90000FF92CE /* PayView.swift */,
4C035A0327AEFD2F00FF92CE /* Invoice.swift */,
4C8B289227B44EAF00DF3372 /* LNLink.swift */,
);
@@ -200,6 +201,16 @@
path = CodeScanner;
sourceTree = "<group>";
};
+ 4CCB0E2927CA71AA0026461C /* Views */ = {
+ isa = PBXGroup;
+ children = (
+ 4C0359FF27AEF90000FF92CE /* PayView.swift */,
+ 4C641D1A2788FF2F002A36C9 /* ContentView.swift */,
+ 4CCB0E2A27CA71CA0026461C /* SetupView.swift */,
+ );
+ path = Views;
+ sourceTree = "<group>";
+ };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -341,6 +352,7 @@
4CCB0E2827C979F30026461C /* ScannerViewController.swift in Sources */,
4C035A0427AEFD2F00FF92CE /* Invoice.swift in Sources */,
4C641D192788FF2F002A36C9 /* lightninglinkApp.swift in Sources */,
+ 4CCB0E2B27CA71CA0026461C /* SetupView.swift in Sources */,
4C873FD727A6F1F5008C972C /* RPC.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
diff --git a/lightninglink/ContentView.swift b/lightninglink/ContentView.swift
@@ -1,215 +0,0 @@
-//
-// ContentView.swift
-// lightninglink
-//
-// Created by William Casarin on 2022-01-07.
-//
-
-import SwiftUI
-import AVFoundation
-
-extension Notification.Name {
- static var sentPayment: Notification.Name {
- return Notification.Name("did send payment")
- }
-}
-
-enum ActiveAlert: Identifiable {
- var id: String {
- switch self {
- case .pay:
- return "pay"
- }
- }
-
- case pay(InvoiceAmount, String)
-}
-
-enum ActiveSheet: Identifiable {
- var id: String {
- switch self {
- case .qr:
- return "qrcode"
- case .pay:
- return "paysheet"
- }
- }
-
- case qr
- case pay(InvoiceAmount, String)
-}
-
-struct Funds {
- public var onchain_sats: Int64
- public var channel_sats: Int64
-
- public static var empty = Funds(onchain_sats: 0, channel_sats: 0)
-
- public static func from_listfunds(fs: ListFunds) -> Funds {
- var onchain_sats: Int64 = 0
- var channel_sats: Int64 = 0
-
- for channel in fs.channels {
- channel_sats += channel.channel_sat
- }
-
- for output in fs.outputs {
- onchain_sats += output.value
- }
-
- return Funds(onchain_sats: onchain_sats, channel_sats: channel_sats)
- }
-}
-
-let SCAN_TYPES: [AVMetadataObject.ObjectType] = [.qr]
-
-struct ContentView: View {
- @State private var info: GetInfo
- @State private var active_sheet: ActiveSheet?
- @State private var active_alert: ActiveAlert?
- @State private var has_alert: Bool
- @State private var last_pay: Pay?
- @State private var funds: Funds
-
- private var lnlink: LNLink
-
- init(info: GetInfo, lnlink: LNLink, funds: ListFunds) {
- self.info = info
- self.lnlink = lnlink
- self.has_alert = false
- self.funds = Funds.from_listfunds(fs: funds)
- }
-
- func refresh_funds() {
- let ln = LNSocket()
- guard ln.connect_and_init(node_id: self.lnlink.node_id, host: self.lnlink.host) else {
- return
- }
- let funds = fetch_funds(ln: ln, token: lnlink.token)
- self.funds = Funds.from_listfunds(fs: funds)
- }
-
- func format_last_pay() -> String {
- guard let pay = last_pay else {
- return ""
- }
-
- if (pay.msatoshi >= 1000) {
- let sats = pay.msatoshi / 1000
- let fee = (pay.msatoshi_sent - pay.msatoshi) / 1000
- return "-\(sats) sats (\(fee) sats fee)"
- }
-
- return "-\(pay.msatoshi) msats (\(pay.msatoshi_sent) msats sent)"
- }
-
- func check_pay() {
- guard let (amt, inv) = get_clipboard_invoice() else {
- self.active_sheet = .qr
- self.has_alert = false
- return
- }
-
- self.active_sheet = nil
- self.active_alert = .pay(amt, inv)
- self.has_alert = true
- }
-
- var body: some View {
- VStack {
- Group {
- Text(self.info.alias)
- .font(.largeTitle)
- .padding()
- Text("\(self.info.num_active_channels) active channels")
- Text("\(self.info.msatoshi_fees_collected / 1000) sats collected in fees")
- }
- Spacer()
- Text("\(format_last_pay())")
- .foregroundColor(Color.red)
-
- Text("\(self.funds.channel_sats) sats")
- .font(.title)
- .padding()
- Text("\(self.funds.onchain_sats) onchain")
- Spacer()
- HStack {
- Spacer()
- Button("Pay", action: check_pay)
- .font(.title)
- .padding()
- }
- }
- .alert("Use invoice in clipboard?", isPresented: $has_alert, presenting: active_alert) { alert in
- Button("Use QR") {
- self.has_alert = false
- self.active_sheet = .qr
- }
- Button("Yes") {
- self.has_alert = false
- self.active_alert = nil
- switch alert {
- case .pay(let amt, let inv):
- self.active_sheet = .pay(amt, inv)
- }
- }
- }
- .sheet(item: $active_sheet) { sheet in
- switch sheet {
- case .qr:
- CodeScannerView(codeTypes: SCAN_TYPES) { res in
- switch res {
- case .success(let scan_res):
- let code = scan_res.string
- var invstr: String = code
- if code.starts(with: "lightning:") {
- let index = code.index(code.startIndex, offsetBy: 10)
- invstr = String(code[index...])
- }
- let m_parsed = parseInvoiceAmount(invstr)
- guard let parsed = m_parsed else {
- return
- }
- self.active_sheet = .pay(parsed, invstr)
-
- case .failure:
- self.active_sheet = nil
- return
- }
-
- }
-
- case .pay(let amt, let raw):
- PayView(invoice_str: raw, amount: amt, lnlink: self.lnlink)
- }
- }
- .onReceive(NotificationCenter.default.publisher(for: .sentPayment)) { payment in
- last_pay = payment.object as! Pay
- self.active_sheet = nil
- refresh_funds()
- }
- }
-}
-
-/*
-struct ContentView_Previews: PreviewProvider {
- static var previews: some View {
- Group {
- ContentView(info: .empty, lnlink: ln, token: "", funds: .empty)
- }
- }
-}
- */
-
-
-func get_clipboard_invoice() -> (InvoiceAmount, String)? {
- guard let inv = UIPasteboard.general.string else {
- return nil
- }
-
- guard let amt = parseInvoiceAmount(inv) else {
- return nil
- }
-
- return (amt, inv)
-}
diff --git a/lightninglink/RPC.swift b/lightninglink/RPC.swift
@@ -256,7 +256,7 @@ func commando_read_all(ln: LNSocket, timeout_ms: Int32 = 2000) -> RequestRes<Dat
public let default_timeout: Int32 = 8000
-public func rpc_getinfo(ln: LNSocket, token: String) -> RequestRes<GetInfo>
+public func rpc_getinfo(ln: LNSocket, token: String, timeout: Int32 = default_timeout) -> RequestRes<GetInfo>
{
let params: Array<String> = []
return performRpc(ln: ln, operation: "getinfo", authToken: token, timeout_ms: default_timeout, params: params)
diff --git a/lightninglink/Views/ContentView.swift b/lightninglink/Views/ContentView.swift
@@ -0,0 +1,215 @@
+//
+// ContentView.swift
+// lightninglink
+//
+// Created by William Casarin on 2022-01-07.
+//
+
+import SwiftUI
+import AVFoundation
+
+extension Notification.Name {
+ static var sentPayment: Notification.Name {
+ return Notification.Name("did send payment")
+ }
+}
+
+enum ActiveAlert: Identifiable {
+ var id: String {
+ switch self {
+ case .pay:
+ return "pay"
+ }
+ }
+
+ case pay(InvoiceAmount, String)
+}
+
+public enum ActiveSheet: Identifiable {
+ public var id: String {
+ switch self {
+ case .qr:
+ return "qrcode"
+ case .pay:
+ return "paysheet"
+ }
+ }
+
+ case qr
+ case pay(InvoiceAmount, String)
+}
+
+struct Funds {
+ public var onchain_sats: Int64
+ public var channel_sats: Int64
+
+ public static var empty = Funds(onchain_sats: 0, channel_sats: 0)
+
+ public static func from_listfunds(fs: ListFunds) -> Funds {
+ var onchain_sats: Int64 = 0
+ var channel_sats: Int64 = 0
+
+ for channel in fs.channels {
+ channel_sats += channel.channel_sat
+ }
+
+ for output in fs.outputs {
+ onchain_sats += output.value
+ }
+
+ return Funds(onchain_sats: onchain_sats, channel_sats: channel_sats)
+ }
+}
+
+let SCAN_TYPES: [AVMetadataObject.ObjectType] = [.qr]
+
+struct ContentView: View {
+ @State private var active_sheet: ActiveSheet?
+ @State private var active_alert: ActiveAlert?
+ @State private var has_alert: Bool
+ @State private var last_pay: Pay?
+ @State private var dashboard: Dashboard
+ @State private var funds: Funds
+
+ private var lnlink: LNLink
+
+ init(dashboard: Dashboard, lnlink: LNLink) {
+ self.dashboard = dashboard
+ self.lnlink = lnlink
+ self.has_alert = false
+ self.funds = Funds.from_listfunds(fs: dashboard.funds)
+ }
+
+ func refresh_funds() {
+ let ln = LNSocket()
+ guard ln.connect_and_init(node_id: self.lnlink.node_id, host: self.lnlink.host) else {
+ return
+ }
+ let funds = fetch_funds(ln: ln, token: lnlink.token)
+ self.funds = Funds.from_listfunds(fs: funds)
+ }
+
+ func format_last_pay() -> String {
+ guard let pay = last_pay else {
+ return ""
+ }
+
+ if (pay.msatoshi >= 1000) {
+ let sats = pay.msatoshi / 1000
+ let fee = (pay.msatoshi_sent - pay.msatoshi) / 1000
+ return "-\(sats) sats (\(fee) sats fee)"
+ }
+
+ return "-\(pay.msatoshi) msats (\(pay.msatoshi_sent) msats sent)"
+ }
+
+ func check_pay() {
+ guard let (amt, inv) = get_clipboard_invoice() else {
+ self.active_sheet = .qr
+ self.has_alert = false
+ return
+ }
+
+ self.active_sheet = nil
+ self.active_alert = .pay(amt, inv)
+ self.has_alert = true
+ }
+
+ var body: some View {
+ VStack {
+ Group {
+ Text(self.dashboard.info.alias)
+ .font(.largeTitle)
+ .padding()
+ Text("\(self.dashboard.info.num_active_channels) active channels")
+ Text("\(self.dashboard.info.msatoshi_fees_collected / 1000) sats collected in fees")
+ }
+ Spacer()
+ Text("\(format_last_pay())")
+ .foregroundColor(Color.red)
+
+ Text("\(self.funds.channel_sats) sats")
+ .font(.title)
+ .padding()
+ Text("\(self.funds.onchain_sats) onchain")
+ Spacer()
+ HStack {
+ Spacer()
+ Button("Pay", action: check_pay)
+ .font(.title)
+ .padding()
+ }
+ }
+ .alert("Use invoice in clipboard?", isPresented: $has_alert, presenting: active_alert) { alert in
+ Button("Use QR") {
+ self.has_alert = false
+ self.active_sheet = .qr
+ }
+ Button("Yes") {
+ self.has_alert = false
+ self.active_alert = nil
+ switch alert {
+ case .pay(let amt, let inv):
+ self.active_sheet = .pay(amt, inv)
+ }
+ }
+ }
+ .sheet(item: $active_sheet) { sheet in
+ switch sheet {
+ case .qr:
+ CodeScannerView(codeTypes: SCAN_TYPES) { res in
+ switch res {
+ case .success(let scan_res):
+ let code = scan_res.string
+ var invstr: String = code
+ if code.starts(with: "lightning:") {
+ let index = code.index(code.startIndex, offsetBy: 10)
+ invstr = String(code[index...])
+ }
+ let m_parsed = parseInvoiceAmount(invstr)
+ guard let parsed = m_parsed else {
+ return
+ }
+ self.active_sheet = .pay(parsed, invstr)
+
+ case .failure:
+ self.active_sheet = nil
+ return
+ }
+
+ }
+
+ case .pay(let amt, let raw):
+ PayView(invoice_str: raw, amount: amt, lnlink: self.lnlink)
+ }
+ }
+ .onReceive(NotificationCenter.default.publisher(for: .sentPayment)) { payment in
+ last_pay = payment.object as! Pay
+ self.active_sheet = nil
+ refresh_funds()
+ }
+ }
+}
+
+/*
+struct ContentView_Previews: PreviewProvider {
+ static var previews: some View {
+ Group {
+ ContentView(info: .empty, lnlink: ln, token: "", funds: .empty)
+ }
+ }
+}
+ */
+
+
+func get_clipboard_invoice() -> (InvoiceAmount, String)? {
+ guard let inv = UIPasteboard.general.string else {
+ return nil
+ }
+
+ guard let amt = parseInvoiceAmount(inv) else {
+ return nil
+ }
+
+ return (amt, inv)
+}
diff --git a/lightninglink/PayView.swift b/lightninglink/Views/PayView.swift
diff --git a/lightninglink/Views/SetupView.swift b/lightninglink/Views/SetupView.swift
@@ -0,0 +1,198 @@
+//
+// SetupView.swift
+// lightninglink
+//
+// Created by William Casarin on 2022-02-26.
+//
+
+import SwiftUI
+import Foundation
+
+public enum ActiveAuthSheet: Identifiable {
+ public var id: String {
+ switch self {
+ case .qr:
+ return "qrcode"
+ }
+ }
+
+ case qr
+}
+
+public enum SetupResult {
+ case connection_failed
+ case plugin_missing
+ case auth_invalid(String)
+ case success(GetInfo, ListFunds)
+}
+
+public enum SetupViewState {
+ case initial
+ case validating
+ case validated
+}
+
+struct SetupView: View {
+ @State var active_sheet: ActiveAuthSheet? = nil
+ @State var state: SetupViewState = .initial
+ @State var error: String? = nil
+ @State var dashboard: Dashboard = .empty
+ @State var lnlink: LNLink? = nil
+
+ func perform_validation(_ lnlink: LNLink) {
+ validate_connection(lnlink: lnlink) { res in
+ switch res {
+ case .connection_failed:
+ self.state = .initial
+ self.error = "Connection failed"
+ case .plugin_missing:
+ self.state = .initial
+ self.error = "Connected but could not retrieve data, plugin missing?"
+ case .auth_invalid(let str):
+ self.state = .initial
+ self.error = "Auth issue: \(str)"
+ case .success(let info, let funds):
+ save_lnlink(lnlink: lnlink)
+ self.lnlink = lnlink
+ self.dashboard = Dashboard(info: info, funds: funds)
+ self.state = .validated
+ self.error = nil
+ }
+ }
+ }
+
+ func setup_view() -> some View {
+ VStack {
+ Button("Scan auth QR") {
+ self.active_sheet = .qr
+ }
+ if self.error != nil {
+ Text("\(self.error!)")
+ }
+ }
+ .sheet(item: $active_sheet) { active_sheet in
+ switch active_sheet {
+ case .qr:
+ CodeScannerView(codeTypes: SCAN_TYPES) { code_res in
+ switch code_res {
+ case .success(let scan_res):
+ let auth_qr = scan_res.string
+ // auth_qr ~ lnlink:host:port?nodeid=nodeid&token=rune
+ let m_lnlink = parse_auth_qr(auth_qr)
+
+ switch m_lnlink {
+ case .left(let err):
+ self.error = err
+ case .right(let lnlink):
+ self.state = .validating
+ self.perform_validation(lnlink)
+ }
+
+ case .failure(let scan_err):
+ self.error = scan_err.localizedDescription
+ }
+ }
+ }
+ }
+
+ }
+
+ func validating_view() -> some View {
+ Text("Checking connection...")
+ }
+
+ var body: some View {
+ Group {
+ switch self.state {
+ case .initial:
+ setup_view()
+ case .validating:
+ validating_view()
+ case .validated:
+ ContentView(dashboard: self.dashboard, lnlink: self.lnlink!)
+ }
+ }
+ }
+}
+
+struct SetupView_Previews: PreviewProvider {
+ static var previews: some View {
+ SetupView()
+ }
+}
+
+
+func get_qs_param(qs: URLComponents, param: String) -> String? {
+ return qs.queryItems?.first(where: { $0.name == param })?.value
+}
+
+
+func parse_auth_qr(_ qr: String) -> Either<String, LNLink> {
+ var auth_qr = qr
+ if auth_qr.hasPrefix("lnlink:") && !auth_qr.hasPrefix("lnlink://") {
+ auth_qr = qr.replacingOccurrences(of: "lnlink:", with: "lnlink://")
+ }
+
+ guard let url = URL(string: auth_qr) else {
+ return .left("Invalid url")
+ }
+
+ guard let host = url.host else {
+ return .left("No hostname found in auth qr")
+ }
+
+ guard let qs = URLComponents(string: auth_qr) else {
+ return .left("Invalid url querystring")
+ }
+
+ guard let nodeid = get_qs_param(qs: qs, param: "nodeid") else {
+ return .left("No nodeid found in auth qr")
+ }
+
+ guard let token = get_qs_param(qs: qs, param: "token") else {
+ return .left("No token found in auth qr")
+ }
+
+ let lnlink = LNLink(token: token, host: host, node_id: nodeid)
+ return .right(lnlink)
+}
+
+
+func validate_connection(lnlink: LNLink, completion: @escaping (SetupResult) -> Void) {
+ let ln = LNSocket()
+
+ guard ln.connect_and_init(node_id: lnlink.node_id, host: lnlink.host) else {
+ completion(.connection_failed)
+ return
+ }
+
+ let res = rpc_getinfo(ln: ln, token: lnlink.token, timeout: 5000)
+
+ switch res {
+ case .failure(let rpc_err):
+ switch rpc_err.errorType {
+ case .timeout:
+ completion(.plugin_missing)
+ return
+ default:
+ break
+ }
+
+ guard let decoded = rpc_err.decoded else {
+ completion(.auth_invalid(rpc_err.description))
+ return
+ }
+
+ completion(.auth_invalid(decoded.message))
+
+ case .success(let getinfo):
+ let funds_res = rpc_listfunds(ln: ln, token: lnlink.token)
+
+ switch funds_res {
+ case .failure:
+ completion(.success(getinfo, .empty))
+ case .success(let listfunds):
+ completion(.success(getinfo, listfunds))
+ }
+ }
+}
diff --git a/lightninglink/lightninglinkApp.swift b/lightninglink/lightninglinkApp.swift
@@ -7,31 +7,67 @@
import SwiftUI
-@main
-struct lightninglinkApp: App {
- var info: GetInfo = .empty
- var funds: ListFunds = .empty
- var lnlink: LNLink
-
- init() {
- self.ln = LNSocket()
- self.token = ""
- let node_id = "03f3c108ccd536b8526841f0a5c58212bb9e6584a1eb493080e7c1cc34f82dad71"
- let host = "24.84.152.187"
- let lnlink = LNLink(token: token, host: host, node_id: node_id)
- self.lnlink = lnlink
-
- guard ln.connect_and_init(node_id: node_id, host: host) else {
- return
- }
+public struct Dashboard {
+ public let info: GetInfo
+ public let funds: ListFunds
+
+ public static var empty: Dashboard = Dashboard(info: .empty, funds: .empty)
+}
- self.info = fetch_info(ln: ln, token: token)
- self.funds = fetch_funds(ln: ln, token: token)
+func fetch_dashboard(lnlink: LNLink) -> Either<String, Dashboard> {
+ let ln = LNSocket()
+
+ guard ln.connect_and_init(node_id: lnlink.node_id, host: lnlink.host) else {
+ return .left("Connect failed :(")
}
+ let res = rpc_getinfo(ln: ln, token: lnlink.token)
+ switch res {
+ case .failure(let res_err):
+ return .left(res_err.decoded?.message ?? res_err.description)
+ case .success(let info):
+ let res2 = rpc_listfunds(ln: ln, token: lnlink.token)
+ switch res2 {
+ case .failure(let err):
+ return .left(err.decoded?.message ?? err.description)
+ case .success(let funds):
+ return .right(Dashboard(info: info, funds: funds))
+ }
+ }
+}
+
+@main
+struct lightninglinkApp: App {
+ @State var dashboard: Dashboard?
+ @State var lnlink: LNLink? = load_lnlink()
+ @State var error: String?
+
var body: some Scene {
WindowGroup {
- ContentView(info: self.info, lnlink: self.lnlink, funds: self.funds)
+ if self.error != nil {
+ Text("Error: \(self.error!)")
+ } else {
+ if self.lnlink != nil {
+ if self.dashboard != nil {
+ ContentView(dashboard: self.dashboard!, lnlink: self.lnlink!)
+ } else {
+ VStack {
+ Text("Connecting...")
+ .onAppear() {
+ let res = fetch_dashboard(lnlink: self.lnlink!)
+ switch res {
+ case .left(let err):
+ self.error = err
+ case .right(let dash):
+ self.dashboard = dash
+ }
+ }
+ }
+ }
+ } else {
+ SetupView()
+ }
+ }
}
}
}
@@ -56,3 +92,21 @@ func fetch_funds(ln: LNSocket, token: String) -> ListFunds {
return funds
}
}
+
+func save_lnlink(lnlink: LNLink) {
+ UserDefaults.standard.set(lnlink.token, forKey: "lnlink_token")
+ UserDefaults.standard.set(lnlink.node_id, forKey: "lnlink_nodeid")
+ UserDefaults.standard.set(lnlink.host, forKey: "lnlink_host")
+}
+
+func load_lnlink() -> LNLink? {
+ let m_token = UserDefaults.standard.string(forKey: "lnlink_token")
+ let m_nodeid = UserDefaults.standard.string(forKey: "lnlink_nodeid")
+ let m_host = UserDefaults.standard.string(forKey: "lnlink_host")
+
+ guard let token = m_token else { return nil }
+ guard let node_id = m_nodeid else { return nil }
+ guard let host = m_host else { return nil }
+
+ return LNLink(token: token, host: host, node_id: node_id)
+}