commit 8b307f8a359f39bc58b78d874c8a09e8d3460a0c
parent 1d30d0d5662583178de7823d1fe3351a6a101aa4
Author: William Casarin <jb55@jb55.com>
Date: Sun, 20 Feb 2022 17:50:16 -0800
invoices: parse invoice amount
Let's skip parsing the entire invoice for now since that's a bit
complicated
Signed-off-by: William Casarin <jb55@jb55.com>
Diffstat:
8 files changed, 100 insertions(+), 394 deletions(-)
diff --git a/lightninglink.xcodeproj/project.pbxproj b/lightninglink.xcodeproj/project.pbxproj
@@ -9,7 +9,6 @@
/* Begin PBXBuildFile section */
4C0359FB27AEE86600FF92CE /* QRScan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0359FA27AEE86600FF92CE /* QRScan.swift */; };
4C035A0027AEF90000FF92CE /* PayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0359FF27AEF90000FF92CE /* PayView.swift */; };
- 4C035A0227AEFB2400FF92CE /* Bech32.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C035A0127AEFB2400FF92CE /* Bech32.swift */; };
4C035A0427AEFD2F00FF92CE /* Invoice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C035A0327AEFD2F00FF92CE /* Invoice.swift */; };
4C641D192788FF2F002A36C9 /* lightninglinkApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C641D182788FF2F002A36C9 /* lightninglinkApp.swift */; };
4C641D1B2788FF2F002A36C9 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C641D1A2788FF2F002A36C9 /* ContentView.swift */; };
@@ -19,12 +18,12 @@
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 */; };
+ 4C8B289327B44EAF00DF3372 /* LNLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8B289227B44EAF00DF3372 /* LNLink.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -48,7 +47,6 @@
4C0359FA27AEE86600FF92CE /* QRScan.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRScan.swift; sourceTree = "<group>"; };
4C0359FE27AEEE8500FF92CE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
4C0359FF27AEF90000FF92CE /* PayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayView.swift; sourceTree = "<group>"; };
- 4C035A0127AEFB2400FF92CE /* Bech32.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32.swift; sourceTree = "<group>"; };
4C035A0327AEFD2F00FF92CE /* Invoice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Invoice.swift; sourceTree = "<group>"; };
4C641D152788FF2F002A36C9 /* lightninglink.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = lightninglink.app; sourceTree = BUILT_PRODUCTS_DIR; };
4C641D182788FF2F002A36C9 /* lightninglinkApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = lightninglinkApp.swift; sourceTree = "<group>"; };
@@ -70,6 +68,7 @@
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>"; };
+ 4C8B289227B44EAF00DF3372 /* LNLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LNLink.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -135,8 +134,8 @@
4C873FD627A6F1F5008C972C /* RPC.swift */,
4C0359FA27AEE86600FF92CE /* QRScan.swift */,
4C0359FF27AEF90000FF92CE /* PayView.swift */,
- 4C035A0127AEFB2400FF92CE /* Bech32.swift */,
4C035A0327AEFD2F00FF92CE /* Invoice.swift */,
+ 4C8B289227B44EAF00DF3372 /* LNLink.swift */,
);
path = lightninglink;
sourceTree = "<group>";
@@ -294,7 +293,6 @@
files = (
4C641D202788FF30002A36C9 /* Preview Assets.xcassets in Resources */,
4C641D1D2788FF30002A36C9 /* Assets.xcassets in Resources */,
- 4C641D4B279CFA32002A36C9 /* lnsocket in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -322,11 +320,11 @@
4C035A0027AEF90000FF92CE /* PayView.swift in Sources */,
4C873FD527A6EF3F008C972C /* LNSocket.swift in Sources */,
4C641D1B2788FF2F002A36C9 /* ContentView.swift in Sources */,
+ 4C8B289327B44EAF00DF3372 /* LNLink.swift in Sources */,
4C641D492789083E002A36C9 /* lightninglink.c in Sources */,
4C035A0427AEFD2F00FF92CE /* Invoice.swift in Sources */,
4C641D192788FF2F002A36C9 /* lightninglinkApp.swift in Sources */,
4C873FD727A6F1F5008C972C /* RPC.swift in Sources */,
- 4C035A0227AEFB2400FF92CE /* Bech32.swift in Sources */,
4C0359FB27AEE86600FF92CE /* QRScan.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
diff --git a/lightninglink/Bech32.swift b/lightninglink/Bech32.swift
@@ -1,347 +0,0 @@
-// Copyright (c) 2017 Alex Bosworth
-// Copyright (c) 2017 Pieter Wuille
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-//
-
-import Foundation
-
-extension String {
- func lastIndex(of string: String) -> Int? {
- guard let range = self.range(of: string, options: .backwards) else { return nil }
-
- return self.distance(from: startIndex, to: range.lowerBound)
- }
-}
-
-let CHARSET = byteConvert(string: "qpzry9x8gf2tvdw0s3jn54khce6mua7l")
-let GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
-
-func polymod(_ values: [Int]) -> Int {
- return values.reduce(1) { chk, value in
- let top = chk >> 25
-
- return (Int()..<5).reduce((chk & 0x1ffffff) << 5 ^ value) { chk, i in
- guard (top >> i) & 1 > Int() else { return chk }
-
- return chk ^ GENERATOR[i]
- }
- }
-}
-
-func hrpExpand(_ hrp: [UInt8]) -> [UInt8] {
- return (Int()..<hrp.count).map { hrp[$0] >> 5 } + [UInt8()] + (Int()..<hrp.count).map { hrp[$0] & 31 }
-}
-
-func verifyChecksum(hrp: [UInt8], data: [UInt8]) -> Bool {
- return polymod((hrpExpand(hrp) + data).map { Int($0) }) == 1
-}
-
-func createChecksum(hrp: [UInt8], data: [UInt8]) -> [UInt8] {
- let values = (hrpExpand(hrp) + data + Array(repeating: UInt8(), count: 6)).map { Int($0) }
- let mod: Int = polymod(values) ^ 1
-
- return (Int()..<6).map { (mod >> (5 * (5 - $0))) & 31 }.map { UInt8($0) }
-}
-
-func byteConvert(string: String) -> [UInt8] {
- return string.map { String($0).unicodeScalars.first?.value }.flatMap { $0 }.map { UInt8($0) }
-}
-
-func stringConvert(bytes: [UInt8]) -> String {
- return bytes.reduce(String(), { $0 + String(format: "%c", $1)})
-}
-
-func encode(hrp: [UInt8], data: [UInt8]) -> String {
- let checksum = createChecksum(hrp: hrp, data: data)
-
- return stringConvert(bytes: hrp) + "1" + stringConvert(bytes: (data + checksum).map { CHARSET[Int($0)] })
-}
-
-enum DecodeBech32Error: Error {
- case caseMixing
- case inconsistentHrp
- case invalidAddress
- case invalidBits
- case invalidCharacter(String)
- case invalidChecksum
- case invalidPayToHashLength
- case invalidVersion
- case missingSeparator
- case missingVersion
-
- var localizedDescription: String {
- switch self {
- case .caseMixing:
- return "Mixed case characters are not allowed"
-
- case .inconsistentHrp:
- return "Internally inconsistent HRP"
-
- case .invalidAddress:
- return "Address is not a valid type"
-
- case .invalidBits:
- return "Bits are not valid"
-
- case .invalidCharacter(let char):
- return "Character \"\(char)\" is not valid"
-
- case .invalidChecksum:
- return "Checksum failed to verify data"
-
- case .invalidPayToHashLength:
- return "Unknown hash length for encoded output payload hash"
-
- case .invalidVersion:
- return "Invalid version number"
-
- case .missingSeparator:
- return "Missing address data separator"
-
- case .missingVersion:
- return "Missing address version"
- }
- }
-}
-
-public func decodeBech32(bechString: String) throws -> (hrp: [UInt8], data: [UInt8]) {
- let bechBytes = byteConvert(string: bechString)
-
- guard !(bechBytes.contains() { $0 < 33 && $0 > 126 }) else { throw DecodeBech32Error.invalidCharacter(bechString) }
-
- let hasLower = bechBytes.contains() { $0 >= 97 && $0 <= 122 }
- let hasUpper = bechBytes.contains() { $0 >= 65 && $0 <= 90 }
-
- if hasLower && hasUpper { throw DecodeBech32Error.caseMixing }
-
- let bechString = bechString.lowercased()
-
- guard let pos = bechString.lastIndex(of: "1") else { throw DecodeBech32Error.missingSeparator }
-
- if pos < 1 || pos + 7 > bechString.count {
- throw DecodeBech32Error.missingSeparator
- }
-
- let bechStringBytes = byteConvert(string: bechString)
- let hrp = byteConvert(string: bechString.substring(to: bechString.index(bechString.startIndex, offsetBy: pos)))
-
- let data: [UInt8] = try ((pos + 1)..<bechStringBytes.count).map { i in
- guard let d = CHARSET.firstIndex(of: bechStringBytes[i]) else {
- throw DecodeBech32Error.invalidCharacter(stringConvert(bytes: [bechStringBytes[i]]))
- }
-
- return UInt8(d)
- }
-
- guard verifyChecksum(hrp: hrp, data: data) else { throw DecodeBech32Error.invalidChecksum }
-
- return (hrp: hrp, data: Array(data[Int()..<data.count - 6]))
-}
-
-func convertbits(data: [UInt8], fromBits: Int, toBits: Int, pad: Bool) throws -> [UInt8] {
- var acc = Int()
- var bits = UInt8()
- let maxv = (1 << toBits) - 1
-
- let converted: [[Int]] = try data.map { value in
- if (value < 0 || (UInt8(Int(value) >> fromBits)) != 0) {
- throw DecodeBech32Error.invalidCharacter(stringConvert(bytes: [value]))
- }
-
- acc = (acc << fromBits) | Int(value)
- bits += UInt8(fromBits)
-
- var values = [Int]()
-
- while bits >= UInt8(toBits) {
- bits -= UInt8(toBits)
- values += [(acc >> Int(bits)) & maxv]
- }
-
- return values
- }
-
- let padding = pad && bits > UInt8() ? [acc << (toBits - Int(bits)) & maxv] : []
-
- if !pad && (bits >= UInt8(fromBits) || acc << (toBits - Int(bits)) & maxv > Int()) {
- throw DecodeBech32Error.invalidBits
- }
-
- return ((converted.flatMap { $0 }) + padding).map { UInt8($0) }
-}
-
-func encode(hrp: [UInt8], version: UInt8, program: [UInt8]) throws -> String {
- let address = try encode(hrp: hrp, data: [version] + convertbits(data: program, fromBits: 8, toBits: 5, pad: true))
-
- // Confirm encoded address parses without error
- let _ = try decodeAddress(hrp: hrp, address: address)
-
- return address
-}
-
-func decodeAddress(hrp: [UInt8], address: String) throws -> (version: UInt8, program: [UInt8]) {
- let decoded = try decodeBech32(bechString: address)
-
- // Confirm decoded address matches expected type
- guard stringConvert(bytes: decoded.hrp) == stringConvert(bytes: hrp) else { throw DecodeBech32Error.inconsistentHrp }
-
- // Confirm version byte is present
- guard let versionByte = decoded.data.first else { throw DecodeBech32Error.missingVersion }
-
- // Confirm version byte is within the acceptable range
- guard !decoded.data.isEmpty && versionByte <= 16 else { throw DecodeBech32Error.invalidVersion }
-
- let program = try convertbits(data: Array(decoded.data[1..<decoded.data.count]), fromBits: 5, toBits: 8, pad: false)
-
- // Confirm program is a valid length
- guard program.count > 1 && program.count < 41 else { throw DecodeBech32Error.invalidAddress }
-
- if versionByte == UInt8() {
- // Confirm program is a known byte length (20 for pkhash, 32 for scripthash)
- guard program.count == 20 || program.count == 32 else { throw DecodeBech32Error.invalidPayToHashLength }
- }
-
- return (version: versionByte, program: program)
-}
-
-func segwitScriptPubKey(version: UInt8, program: [UInt8]) -> [UInt8] {
- return [version > UInt8() ? version + 0x50 : UInt8(), UInt8(program.count)] + program
-}
-
-
-/*
-class TestBech32: XCTestCase {
- func testInvalidAddresses() {
- let INVALID_ADDRESS = [
- "tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty",
- "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5",
- "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2",
- "bc1rw5uspcuh",
- "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90",
- "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P",
- "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7",
- "tb1pw508d6qejxtdg4y5r3zarqfsj6c3",
- "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv",
- ]
-
- INVALID_ADDRESS.forEach { test in
- ["bc", "tb"].forEach { type in
- do {
- let _ = try decodeAddress(hrp: byteConvert(string: type), address: test)
-
- XCTFail("Expected invalid address: \(test)")
- } catch {
- return
- }
- }
- }
- }
-
- func testChecksums() {
- let VALID_CHECKSUM: [String] = [
- "A12UEL5L",
- "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs",
- "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw",
- "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j",
- "split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w"
- ]
-
- do {
- try VALID_CHECKSUM.forEach { test in
- let _ = try decodeBech32(bechString: test)
- }
- } catch {
- XCTFail(error.localizedDescription)
- }
- }
-
- func testValidAddresses() {
- let VALID_BC_ADDRESSES: [String: (decoded: [UInt8], type: String)] = [
- "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4": (
- decoded: [
- 0x00, 0x14, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54,
- 0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6
- ],
- type: "bc"
- ),
-
- "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7": (
- decoded: [
- 0x00, 0x20, 0x18, 0x63, 0x14, 0x3c, 0x14, 0xc5, 0x16, 0x68, 0x04,
- 0xbd, 0x19, 0x20, 0x33, 0x56, 0xda, 0x13, 0x6c, 0x98, 0x56, 0x78,
- 0xcd, 0x4d, 0x27, 0xa1, 0xb8, 0xc6, 0x32, 0x96, 0x04, 0x90, 0x32,
- 0x62
- ],
- type: "tb"
- ),
-
- "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx": (
- decoded: [
- 0x51, 0x28, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54,
- 0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6,
- 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, 0x1c,
- 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6
- ],
- type: "bc"
- ),
-
- "BC1SW50QA3JX3S": (decoded: [0x60, 0x02, 0x75, 0x1e], type: "bc"),
-
- "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj": (
- decoded: [
- 0x52, 0x10, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54,
- 0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23
- ],
- type: "bc"
- ),
-
- "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy": (
- decoded: [
- 0x00, 0x20, 0x00, 0x00, 0x00, 0xc4, 0xa5, 0xca, 0xd4, 0x62, 0x21,
- 0xb2, 0xa1, 0x87, 0x90, 0x5e, 0x52, 0x66, 0x36, 0x2b, 0x99, 0xd5,
- 0xe9, 0x1c, 0x6c, 0xe2, 0x4d, 0x16, 0x5d, 0xab, 0x93, 0xe8, 0x64,
- 0x33
- ],
- type: "tb"
- )
- ]
-
- do {
- try VALID_BC_ADDRESSES.forEach { address, result in
- let scriptPubKey = result.decoded
- let hrp = byteConvert(string: result.type)
-
- let ret = try decodeAddress(hrp: hrp, address: address)
-
- let output = segwitScriptPubKey(version: ret.version, program: ret.program)
-
- XCTAssertEqual(output, scriptPubKey)
-
- let recreated = try encode(hrp: hrp, version: ret.version, program: ret.program).lowercased()
-
- XCTAssertEqual(recreated, address.lowercased())
- }
- } catch {
- XCTFail(error.localizedDescription)
- }
- }
-}
-
- */
diff --git a/lightninglink/ContentView.swift b/lightninglink/ContentView.swift
@@ -24,7 +24,7 @@ enum ActiveSheet: Identifiable {
}
case qr
- case pay(Invoice, String)
+ case pay(Int64, String)
}
struct Funds {
@@ -55,18 +55,20 @@ struct ContentView: View {
@State private var last_pay: Pay?
@State private var funds: Funds
- private var ln: LNSocket
- private var token: String
+ private var lnlink: LNLink
- init(info: GetInfo, ln: LNSocket, token: String, funds: ListFunds) {
+ init(info: GetInfo, lnlink: LNLink, funds: ListFunds) {
self.info = info
- self.ln = ln
- self.token = token
+ self.lnlink = lnlink
self.funds = Funds.from_listfunds(fs: funds)
}
func refresh_funds() {
- let funds = fetch_funds(ln: self.ln, token: self.token)
+ 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)
}
@@ -119,15 +121,15 @@ struct ContentView: View {
let index = code.index(code.startIndex, offsetBy: 10)
invstr = String(code[index...])
}
- let m_parsed = parseInvoice(invstr)
+ let m_parsed = parseInvoiceAmount(invstr)
guard let parsed = m_parsed else {
return
}
self.activeSheet = .pay(parsed, invstr)
}
- case .pay(let inv, let raw):
- PayView(invoice_str: raw, invoice: inv, ln: self.ln, token: self.token)
+ case .pay(let amt, let raw):
+ PayView(invoice_str: raw, amount: amt, lnlink: self.lnlink)
}
}
.onReceive(NotificationCenter.default.publisher(for: .sentPayment)) { payment in
@@ -137,11 +139,12 @@ struct ContentView: View {
}
}
+/*
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
- let ln = LNSocket()
Group {
- ContentView(info: .empty, ln: ln, token: "", funds: .empty)
+ ContentView(info: .empty, lnlink: ln, token: "", funds: .empty)
}
}
}
+ */
diff --git a/lightninglink/Invoice.swift b/lightninglink/Invoice.swift
@@ -8,29 +8,50 @@
import Foundation
-public struct Bolt11Invoice {
- var msats: Int64
-}
+public func parseInvoiceAmount(_ inv: String) -> Int64?
+{
+ if !inv.starts(with: "lnbc") {
+ return nil
+ }
-public struct Bolt12Invoice {
- var msats: Int64
-}
+ var ind = 4
+ var num: String = ""
+ var scale: Character = Character("p")
-public enum Invoice {
- case bolt11(Bolt11Invoice)
- case bolt12(Bolt12Invoice)
+ // number part
+ while true {
+ let c = inv[inv.index(inv.startIndex, offsetBy: ind)]
+ ind += 1
- func amount() -> Int64 {
- return invoiceAmount(self)
+ if c >= "0" && c <= "9" {
+ continue
+ } else {
+ let start_ind = inv.index(inv.startIndex, offsetBy: 4)
+ let end_ind = inv.index(inv.startIndex, offsetBy: ind - 1)
+ scale = inv[inv.index(inv.startIndex, offsetBy: ind - 1)]
+ num = String(inv[start_ind..<end_ind])
+ break
+ }
+ }
+
+ if !(scale == "m" || scale == "u" || scale == "n" || scale == "p") {
+ return nil
+ }
+
+ guard let n = Int(num) else {
+ return nil
}
- static var empty: Invoice {
- let b11 = Bolt11Invoice(msats: 0)
- let inv: Invoice = .bolt11(b11)
- return inv
+ switch scale {
+ case "m": return Int64(n * 100000000);
+ case "u": return Int64(n * 100000);
+ case "n": return Int64(n * 100);
+ case "p": return Int64(n * 1);
+ default: return nil
}
}
+/*
public func parseInvoice(_ str: String) -> Invoice?
{
// decode bech32
@@ -61,3 +82,4 @@ public func invoiceAmount(_ inv: Invoice) -> Int64
}
}
+ */
diff --git a/lightninglink/LNLink.swift b/lightninglink/LNLink.swift
@@ -0,0 +1,14 @@
+//
+// LNLink.swift
+// lightninglink
+//
+// Created by William Casarin on 2022-02-09.
+//
+
+import Foundation
+
+public struct LNLink {
+ var token: String
+ var host: String
+ var node_id: String
+}
diff --git a/lightninglink/PayView.swift b/lightninglink/PayView.swift
@@ -7,21 +7,27 @@
import SwiftUI
+func render_amount(_ amount: Int64) -> String {
+ if amount < 1000 {
+ return "\(amount) msats"
+ }
+
+ return "\(amount / 1000) sats"
+}
+
struct PayView: View {
var invoice_str: String
- var invoice: Invoice
- var ln: LNSocket
- var token: String
+ var amount: Int64
+ var lnlink: LNLink
@State var pay_result: Pay?
@State var error: String?
@Environment(\.presentationMode) var presentationMode
- init(invoice_str: String, invoice: Invoice, ln: LNSocket, token: String) {
+ init(invoice_str: String, amount: Int64, lnlink: LNLink) {
self.invoice_str = invoice_str
- self.invoice = invoice
- self.ln = ln
- self.token = token
+ self.amount = amount
+ self.lnlink = lnlink
}
var successView: some View {
@@ -47,7 +53,7 @@ struct PayView: View {
.font(.largeTitle)
Spacer()
Text("Pay")
- Text("\(self.invoice.amount()) msats?")
+ Text("\(render_amount(self.amount))?")
.font(.title)
Text("\(self.error ?? "")")
Spacer()
@@ -60,9 +66,17 @@ struct PayView: View {
Spacer()
Button("Confirm") {
+ // do a fresh connection for each payment
+ let ln = LNSocket()
+
+ guard ln.connect_and_init(node_id: self.lnlink.node_id, host: self.lnlink.host) else {
+ self.error = "Failed to connect, please try again!"
+ return
+ }
+
let res = rpc_pay(
- ln: self.ln,
- token: self.token,
+ ln: ln,
+ token: lnlink.token,
bolt11: self.invoice_str,
amount_msat: nil)
diff --git a/lightninglink/RPC.swift b/lightninglink/RPC.swift
@@ -248,7 +248,7 @@ func commando_read_all(ln: LNSocket, timeout_ms: Int32 = 2000) -> RequestRes<Dat
return .success(all_data)
}
-public let default_timeout: Int32 = 3000
+public let default_timeout: Int32 = 8000
public func rpc_getinfo(ln: LNSocket, token: String) -> RequestRes<GetInfo>
{
@@ -258,6 +258,7 @@ public func rpc_getinfo(ln: LNSocket, token: String) -> RequestRes<GetInfo>
public func rpc_pay(ln: LNSocket, token: String, bolt11: String, amount_msat: Int64?) -> RequestRes<Pay>
{
+
var params: Array<String> = [ bolt11 ]
if amount_msat != nil {
params.append("\(amount_msat!)")
diff --git a/lightninglink/lightninglinkApp.swift b/lightninglink/lightninglinkApp.swift
@@ -11,14 +11,15 @@ import SwiftUI
struct lightninglinkApp: App {
var info: GetInfo = .empty
var funds: ListFunds = .empty
- var ln: LNSocket
- var token: String
+ 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
@@ -30,7 +31,7 @@ struct lightninglinkApp: App {
var body: some Scene {
WindowGroup {
- ContentView(info: self.info, ln: self.ln, token: self.token, funds: self.funds)
+ ContentView(info: self.info, lnlink: self.lnlink, funds: self.funds)
}
}
}