lnlink

iOS app for connecting to lightning nodes
git clone git://jb55.com/lnlink
Log | Files | Refs | Submodules | README

commit 1f5750ca7cdd7184d68f101b715cb210be042dcc
parent bc5d5198a156fe7d94e4ba219ebd933a5595275c
Author: William Casarin <jb55@jb55.com>
Date:   Sun, 30 Jan 2022 11:34:03 -0800

rpc working

Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
A.gitignore | 1+
A.gitmodules | 3+++
Alightninglink-c/lightninglink-Bridging-Header.h | 5+++++
Alightninglink-c/lightninglink.c | 18++++++++++++++++++
Alightninglink-c/lightninglink.h | 17+++++++++++++++++
Mlightninglink.xcodeproj/project.pbxproj | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mlightninglink/ContentView.swift | 18+++++++++++++++---
Alightninglink/LNSocket.swift | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alightninglink/RPC.swift | 209+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mlightninglink/lightninglinkApp.swift | 9++++++++-
Alnsocket | 1+
11 files changed, 447 insertions(+), 6 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1 @@ +*xcuserdata* diff --git a/.gitmodules b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lnsocket"] + path = lnsocket + url = git://jb55.com/lnsocket diff --git a/lightninglink-c/lightninglink-Bridging-Header.h b/lightninglink-c/lightninglink-Bridging-Header.h @@ -0,0 +1,5 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#include "lightninglink.h" diff --git a/lightninglink-c/lightninglink.c b/lightninglink-c/lightninglink.c @@ -0,0 +1,18 @@ +// +// lightninglink.c +// lightninglink +// +// Created by William Casarin on 2022-01-07. +// + +#include "lightninglink.h" +#include <sys/select.h> + + +void fd_do_set(int socket, fd_set *set) { + FD_SET(socket, set); +} + +void fd_do_zero(fd_set *set) { + FD_ZERO(set); +} diff --git a/lightninglink-c/lightninglink.h b/lightninglink-c/lightninglink.h @@ -0,0 +1,17 @@ +// +// lightninglink.h +// lightninglink +// +// Created by William Casarin on 2022-01-07. +// + +#ifndef lightninglink_h +#define lightninglink_h + +#include "lnsocket.h" +#include "commando.h" + +void fd_do_zero(fd_set *); +void fd_do_set(int, fd_set *); + +#endif /* lightninglink_h */ diff --git a/lightninglink.xcodeproj/project.pbxproj b/lightninglink.xcodeproj/project.pbxproj @@ -14,6 +14,13 @@ 4C641D2A2788FF30002A36C9 /* lightninglinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C641D292788FF30002A36C9 /* lightninglinkTests.swift */; }; 4C641D342788FF31002A36C9 /* lightninglinkUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C641D332788FF31002A36C9 /* lightninglinkUITests.swift */; }; 4C641D362788FF31002A36C9 /* lightninglinkUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C641D352788FF31002A36C9 /* lightninglinkUITestsLaunchTests.swift */; }; + 4C641D492789083E002A36C9 /* lightninglink.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C641D482789083E002A36C9 /* lightninglink.c */; }; + 4C641D4B279CFA32002A36C9 /* lnsocket in Resources */ = {isa = PBXBuildFile; fileRef = 4C641D4A279CFA32002A36C9 /* lnsocket */; }; + 4C873FCF27A62DC1008C972C /* lnsocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C873FCE27A62DC1008C972C /* lnsocket.a */; }; + 4C873FD127A62DE7008C972C /* libsodium.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C873FD027A62DE7008C972C /* libsodium.a */; }; + 4C873FD327A62DF5008C972C /* libsecp256k1.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C873FD227A62DF5008C972C /* libsecp256k1.a */; }; + 4C873FD527A6EF3F008C972C /* LNSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C873FD427A6EF3F008C972C /* LNSocket.swift */; }; + 4C873FD727A6F1F5008C972C /* RPC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C873FD627A6F1F5008C972C /* RPC.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -44,6 +51,16 @@ 4C641D2F2788FF31002A36C9 /* lightninglinkUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = lightninglinkUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 4C641D332788FF31002A36C9 /* lightninglinkUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = lightninglinkUITests.swift; sourceTree = "<group>"; }; 4C641D352788FF31002A36C9 /* lightninglinkUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = lightninglinkUITestsLaunchTests.swift; sourceTree = "<group>"; }; + 4C641D462789083E002A36C9 /* lightninglink-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "lightninglink-Bridging-Header.h"; sourceTree = "<group>"; }; + 4C641D472789083E002A36C9 /* lightninglink.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = lightninglink.h; sourceTree = "<group>"; }; + 4C641D482789083E002A36C9 /* lightninglink.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = lightninglink.c; sourceTree = "<group>"; }; + 4C641D4A279CFA32002A36C9 /* lnsocket */ = {isa = PBXFileReference; lastKnownFileType = folder; path = lnsocket; sourceTree = "<group>"; }; + 4C873FCC27A62DA8008C972C /* ios */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ios; path = lnsocket/target/ios; sourceTree = "<group>"; }; + 4C873FCE27A62DC1008C972C /* lnsocket.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = lnsocket.a; path = lnsocket/target/ios/lnsocket.a; sourceTree = "<group>"; }; + 4C873FD027A62DE7008C972C /* libsodium.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libsodium.a; path = lnsocket/target/ios/libsodium.a; sourceTree = "<group>"; }; + 4C873FD227A62DF5008C972C /* libsecp256k1.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libsecp256k1.a; path = lnsocket/target/ios/libsecp256k1.a; sourceTree = "<group>"; }; + 4C873FD427A6EF3F008C972C /* LNSocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LNSocket.swift; sourceTree = "<group>"; }; + 4C873FD627A6F1F5008C972C /* RPC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RPC.swift; sourceTree = "<group>"; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -51,6 +68,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 4C873FD327A62DF5008C972C /* libsecp256k1.a in Frameworks */, + 4C873FD127A62DE7008C972C /* libsodium.a in Frameworks */, + 4C873FCF27A62DC1008C972C /* lnsocket.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -74,10 +94,13 @@ 4C641D0C2788FF2F002A36C9 = { isa = PBXGroup; children = ( + 4C641D4A279CFA32002A36C9 /* lnsocket */, + 4C641D4227890715002A36C9 /* lightninglink-c */, 4C641D172788FF2F002A36C9 /* lightninglink */, 4C641D282788FF30002A36C9 /* lightninglinkTests */, 4C641D322788FF31002A36C9 /* lightninglinkUITests */, 4C641D162788FF2F002A36C9 /* Products */, + 4C873FCB27A62DA8008C972C /* Frameworks */, ); sourceTree = "<group>"; }; @@ -94,10 +117,12 @@ 4C641D172788FF2F002A36C9 /* lightninglink */ = { isa = PBXGroup; children = ( + 4C873FD427A6EF3F008C972C /* LNSocket.swift */, 4C641D182788FF2F002A36C9 /* lightninglinkApp.swift */, 4C641D1A2788FF2F002A36C9 /* ContentView.swift */, 4C641D1C2788FF30002A36C9 /* Assets.xcassets */, 4C641D1E2788FF30002A36C9 /* Preview Content */, + 4C873FD627A6F1F5008C972C /* RPC.swift */, ); path = lightninglink; sourceTree = "<group>"; @@ -127,6 +152,27 @@ path = lightninglinkUITests; sourceTree = "<group>"; }; + 4C641D4227890715002A36C9 /* lightninglink-c */ = { + isa = PBXGroup; + children = ( + 4C641D472789083E002A36C9 /* lightninglink.h */, + 4C641D482789083E002A36C9 /* lightninglink.c */, + 4C641D462789083E002A36C9 /* lightninglink-Bridging-Header.h */, + ); + path = "lightninglink-c"; + sourceTree = "<group>"; + }; + 4C873FCB27A62DA8008C972C /* Frameworks */ = { + isa = PBXGroup; + children = ( + 4C873FD227A62DF5008C972C /* libsecp256k1.a */, + 4C873FD027A62DE7008C972C /* libsodium.a */, + 4C873FCE27A62DC1008C972C /* lnsocket.a */, + 4C873FCC27A62DA8008C972C /* ios */, + ); + name = Frameworks; + sourceTree = "<group>"; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -195,6 +241,7 @@ TargetAttributes = { 4C641D142788FF2F002A36C9 = { CreatedOnToolsVersion = 13.1; + LastSwiftMigration = 1310; }; 4C641D242788FF30002A36C9 = { CreatedOnToolsVersion = 13.1; @@ -233,6 +280,7 @@ files = ( 4C641D202788FF30002A36C9 /* Preview Assets.xcassets in Resources */, 4C641D1D2788FF30002A36C9 /* Assets.xcassets in Resources */, + 4C641D4B279CFA32002A36C9 /* lnsocket in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -257,8 +305,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4C873FD527A6EF3F008C972C /* LNSocket.swift in Sources */, 4C641D1B2788FF2F002A36C9 /* ContentView.swift in Sources */, + 4C641D492789083E002A36C9 /* lightninglink.c in Sources */, 4C641D192788FF2F002A36C9 /* lightninglinkApp.swift in Sources */, + 4C873FD727A6F1F5008C972C /* RPC.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -346,7 +397,14 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + lnsocket, + lnsocket/deps/secp256k1/include, + lnsocket/deps/libsodium/src/libsodium/include, + ); IPHONEOS_DEPLOYMENT_TARGET = 15.0; + "LD_RUNPATH_SEARCH_PATHS[arch=*]" = lnsocket/target/ios; + LIBRARY_SEARCH_PATHS = lnsocket/target/ios; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -401,7 +459,14 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + lnsocket, + lnsocket/deps/secp256k1/include, + lnsocket/deps/libsodium/src/libsodium/include, + ); IPHONEOS_DEPLOYMENT_TARGET = 15.0; + "LD_RUNPATH_SEARCH_PATHS[arch=*]" = lnsocket/target/ios; + LIBRARY_SEARCH_PATHS = lnsocket/target/ios; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -416,10 +481,11 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"lightninglink/Preview Content\""; - DEVELOPMENT_TEAM = 5VWAH67C65; + DEVELOPMENT_TEAM = XK7H4JAB3D; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; @@ -431,10 +497,16 @@ "$(inherited)", "@executable_path/Frameworks", ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/lnsocket/target/ios", + ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.jb55.lightninglink; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "lightninglink-c/lightninglink-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -445,10 +517,11 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"lightninglink/Preview Content\""; - DEVELOPMENT_TEAM = 5VWAH67C65; + DEVELOPMENT_TEAM = XK7H4JAB3D; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; @@ -460,10 +533,15 @@ "$(inherited)", "@executable_path/Frameworks", ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/lnsocket/target/ios", + ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.jb55.lightninglink; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "lightninglink-c/lightninglink-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; diff --git a/lightninglink/ContentView.swift b/lightninglink/ContentView.swift @@ -8,14 +8,26 @@ import SwiftUI struct ContentView: View { + @State private var info: GetInfo + + init(info: GetInfo) { + self.info = info + } + var body: some View { - Text("Hello, world!") - .padding() + let _self = self + VStack { + Text(self.info.alias) + Text("\(self.info.num_active_channels) active channels") + Text("\(self.info.msatoshi_fees_collected / 1000) sats collected in fees") + } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { - ContentView() + Group { + ContentView(info: .empty) + } } } diff --git a/lightninglink/LNSocket.swift b/lightninglink/LNSocket.swift @@ -0,0 +1,90 @@ + +import Foundation + +public class LNSocket { + var ln: OpaquePointer! + + init() { + self.ln = lnsocket_create() + } + + func genkey() { + lnsocket_genkey(self.ln) + } + + func testrun() -> GetInfo? { + let node_id = "03f3c108ccd536b8526841f0a5c58212bb9e6584a1eb493080e7c1cc34f82dad71" + let host = "24.84.152.187" + + self.genkey() + + guard self.connect(node_id: node_id, host: host) else { + return nil + } + + guard self.perform_init() else { + return nil + } + + let res = rpc_getinfo(ln: self, token: "") + + switch res { + case .success(let getinfo): + return getinfo + + case .failure(let err): + print("\(err)") + return nil + } + } + + func connect(node_id: String, host: String) -> Bool { + node_id.withCString { p_node_id in + host.withCString { p_host in + return lnsocket_connect(self.ln, p_node_id, p_host) != 0 + } + } + } + + func write(_ data: Data) -> Bool { + data.withUnsafeBytes { msg in + return lnsocket_write(self.ln, msg, UInt16(data.count)) != 0 + } + } + + func fd() -> Int32 { + var sock: Int32 = 0 + lnsocket_fd(self.ln, &sock) + return sock + } + + func recv() -> (UInt16, Data)? { + var msgtype: UInt16 = 0 + var mpayload = UnsafeMutablePointer<UInt8>(nil) + var payload_len: UInt16 = 0 + + guard lnsocket_recv(self.ln, &msgtype, &mpayload, &payload_len) != 0 else { + return nil + } + + guard let payload = mpayload else { + return nil + } + + let data = Data(bytes: payload, count: Int(payload_len)) + + return (msgtype, data) + } + + func perform_init() -> Bool { + return lnsocket_perform_init(self.ln) != 0 + } + + func print_errors() { + lnsocket_print_errors(self.ln) + } + + deinit { + lnsocket_destroy(self.ln) + } +} diff --git a/lightninglink/RPC.swift b/lightninglink/RPC.swift @@ -0,0 +1,209 @@ +// +// RPC.swift +// lightninglink +// +// Created by William Casarin on 2022-01-30. +// + +import Foundation + + +public typealias RequestRes<T> = Result<T, RequestError> + +public struct ResultWrapper<T: Decodable>: Decodable { + public var result: T +} + +public struct GetInfo: Decodable { + public var alias: String + public var id: String + public var color: String + public var network: String + public var num_peers: Int + public var msatoshi_fees_collected: Int + public var num_active_channels: Int + + public static var empty = GetInfo(alias: "", id: "", color: "", network: "", num_peers: 0, msatoshi_fees_collected: 0, num_active_channels: 0) +} + +public enum RequestErrorType: Error { + case decoding(DecodingError) + case connectionFailed + case initFailed + case writeFailed + case timeout + case selectFailed + case recvFailed + case badCommandoMsgType(Int) + case badConnectionString + case outOfMemory + case encoding(EncodingError) + case status(Int) + case unknown(String) +} + +public struct RequestError: Error, CustomStringConvertible { + public var response: HTTPURLResponse? + public var respData: Data = Data() + public var errorType: RequestErrorType + + init(errorType: RequestErrorType) { + self.errorType = errorType + } + + init(respData: Data, errorType: RequestErrorType) { + self.respData = respData + self.errorType = errorType + } + + public var description: String { + let strData = String(decoding: respData, as: UTF8.self) + + guard let resp = response else { + return "respData: \(strData)\nerrorType: \(errorType)\n" + } + + return "response: \(resp)\nrespData: \(strData)\nerrorType: \(errorType)\n" + } +} + + +func parse_connection_string(_ cs: String) -> (String, String)? { + let arr = cs.components(separatedBy: "@") + if arr.count != 2 { + return nil + } + return (arr[0], arr[1]) +} + +public func performRpcOnce<IN: Encodable, OUT: Decodable>( + connectionString: String, operation: String, authToken: String, + params: IN +) -> RequestRes<OUT> { + guard let parts = parse_connection_string(connectionString) else { + return .failure(RequestError(errorType: .badConnectionString)) + } + + let node_id = parts.0 + let host = parts.1 + + let ln = LNSocket() + ln.genkey() + + guard ln.connect(node_id: node_id, host: host) else { + return .failure(RequestError(errorType: .connectionFailed)) + } + + guard ln.perform_init() else { + return .failure(RequestError(errorType: .initFailed)) + } + + return performRpc(ln: ln, operation: operation, authToken: authToken, params: params) +} + +public func performRpc<IN: Encodable, OUT: Decodable>( + ln: LNSocket, operation: String, authToken: String, params: IN) -> RequestRes<OUT> +{ + + guard let msg = make_commando_msg(authToken: authToken, operation: operation, params: params) else { + return .failure(RequestError(errorType: .outOfMemory)) + } + + guard ln.write(msg) else { + return .failure(RequestError(errorType: .writeFailed)) + } + + switch commando_read_all(ln: ln) { + case .failure(let req_err): + return .failure(req_err) + + case .success(let data): + return decodeJSON(data) + } +} + +func decodeJSON<T: Decodable>(_ dat: Data) -> RequestRes<T> { + do { + let dat = try JSONDecoder().decode(ResultWrapper<T>.self, from: dat) + return .success(dat.result) + } + catch let decode_err as DecodingError { + return .failure(RequestError(respData: dat, errorType: .decoding(decode_err))) + } + catch let err { + return .failure(RequestError(respData: dat, errorType: .unknown("\(err)"))) + } +} + + +func make_commando_msg<IN: Encodable>(authToken: String, operation: String, params: IN) -> Data? { + let encoder = JSONEncoder() + let json_data = try! encoder.encode(params) + guard let params_json = String(data: json_data, encoding: String.Encoding.utf8) else { + return nil + } + var buf = [UInt8](repeating: 0, count: 65536) + var outlen: UInt16 = 0 + var ok: Bool = false + + authToken.withCString { token in + operation.withCString { op in + params_json.withCString { ps in + ok = commando_make_rpc_msg(op, ps, token, 1, &buf, Int32(buf.capacity), &outlen) != 0 + }}} + + guard ok else { + return nil + } + + return Data(buf[..<Int(outlen)]) +} + + +func commando_read_all(ln: LNSocket, timeout_ms: Int32 = 2000) -> RequestRes<Data> { + var rv: Int32 = 0 + var set = fd_set() + var timeout = timeval() + + timeout.tv_sec = __darwin_time_t(timeout_ms / 1000); + timeout.tv_usec = (timeout_ms % 1000) * 1000; + + fd_do_zero(&set) + let fd = ln.fd() + fd_do_set(fd, &set) + + var all_data = Data() + + while(true) { + rv = select(fd + 1, &set, nil, nil, &timeout) + + if rv == -1 { + return .failure(RequestError(errorType: .selectFailed)) + } else if rv == 0 { + return .failure(RequestError(errorType: .timeout)) + } + + guard let (msgtype, data) = ln.recv() else { + return .failure(RequestError(errorType: .recvFailed)) + } + + all_data.append(data[8...]) + + if msgtype == COMMANDO_REPLY_TERM { + break + } else if msgtype == COMMANDO_REPLY_CONTINUES { + continue + } else { + return .failure(RequestError(errorType: .badCommandoMsgType(Int(msgtype)))) + } + } + + return .success(all_data) +} + + +public func rpc_getinfo(ln: LNSocket, token: String) -> RequestRes<GetInfo> +{ + let params: Array<String> = [] + return performRpc(ln: ln, operation: "getinfo", authToken: token, params: params) +} diff --git a/lightninglink/lightninglinkApp.swift b/lightninglink/lightninglinkApp.swift @@ -9,9 +9,16 @@ import SwiftUI @main struct lightninglinkApp: App { + var info: GetInfo = .empty + + init() { + let ln = LNSocket() + self.info = ln.testrun() ?? .empty + } + var body: some Scene { WindowGroup { - ContentView() + ContentView(info: self.info) } } } diff --git a/lnsocket b/lnsocket @@ -0,0 +1 @@ +Subproject commit 3bc7497de143644c24ead95e516833c51e7c6ee8