Skip to content

Commit

Permalink
feat: bip21
Browse files Browse the repository at this point in the history
  • Loading branch information
reez authored Aug 25, 2024
1 parent 74dceb8 commit 041a10a
Show file tree
Hide file tree
Showing 11 changed files with 520 additions and 96 deletions.
44 changes: 25 additions & 19 deletions LDKNodeMonday.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 56;
objectVersion = 60;
objects = {

/* Begin PBXBuildFile section */
Expand Down Expand Up @@ -32,7 +32,6 @@
AE17E8E129A402E40058C9C9 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AE17E8E029A402E40058C9C9 /* Preview Assets.xcassets */; };
AE17E90D29A42D430058C9C9 /* LightningNodeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE17E90C29A42D430058C9C9 /* LightningNodeService.swift */; };
AE186B8E2A1540B700338463 /* StartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE186B8D2A1540B700338463 /* StartView.swift */; };
AE1AED452C25C94D00B467EF /* LDKNode in Frameworks */ = {isa = PBXBuildFile; productRef = AE1AED442C25C94D00B467EF /* LDKNode */; };
AE1D9BEC2B2A1FFD00620748 /* BitcoinUI in Frameworks */ = {isa = PBXBuildFile; productRef = AE1D9BEB2B2A1FFD00620748 /* BitcoinUI */; };
AE1D9C0B2B2A251500620748 /* ChannelDetails+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE1D9C0A2B2A251500620748 /* ChannelDetails+Extensions.swift */; };
AE3815362B6A9705006B2952 /* LightningNodesService.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE3815352B6A9705006B2952 /* LightningNodesService.swift */; };
Expand Down Expand Up @@ -70,6 +69,9 @@
AE7D3FAD2A4263C100EAE730 /* PaymentsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE7D3FAC2A4263C100EAE730 /* PaymentsViewModel.swift */; };
AE80116B29A59976009B9967 /* NodeIDView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE80116A29A59976009B9967 /* NodeIDView.swift */; };
AE80116D29A59AF4009B9967 /* ChannelAddView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE80116C29A59AF4009B9967 /* ChannelAddView.swift */; };
AE80C2002C4AB360006E7193 /* BIP21View.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE80C1FF2C4AB360006E7193 /* BIP21View.swift */; };
AE80C2022C4AB38D006E7193 /* BIP21ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE80C2012C4AB38D006E7193 /* BIP21ViewModel.swift */; };
AE80C2052C4AB5E4006E7193 /* LDKNode in Frameworks */ = {isa = PBXBuildFile; productRef = AE80C2042C4AB5E4006E7193 /* LDKNode */; };
AE94226A2A007D6C007E4F12 /* ChannelsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE9422692A007D6C007E4F12 /* ChannelsListView.swift */; };
AEA057E92B912C3C00DB1096 /* ZeroInvoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEA057E82B912C3C00DB1096 /* ZeroInvoiceView.swift */; };
AEA057EB2B912E1800DB1096 /* AmountInvoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEA057EA2B912E1800DB1096 /* AmountInvoiceView.swift */; };
Expand Down Expand Up @@ -154,6 +156,8 @@
AE7D3FAC2A4263C100EAE730 /* PaymentsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentsViewModel.swift; sourceTree = "<group>"; };
AE80116A29A59976009B9967 /* NodeIDView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeIDView.swift; sourceTree = "<group>"; };
AE80116C29A59AF4009B9967 /* ChannelAddView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelAddView.swift; sourceTree = "<group>"; };
AE80C1FF2C4AB360006E7193 /* BIP21View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BIP21View.swift; sourceTree = "<group>"; };
AE80C2012C4AB38D006E7193 /* BIP21ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BIP21ViewModel.swift; sourceTree = "<group>"; };
AE8D877429B145F100F2B918 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
AE9422692A007D6C007E4F12 /* ChannelsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelsListView.swift; sourceTree = "<group>"; };
AEA057E82B912C3C00DB1096 /* ZeroInvoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZeroInvoiceView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -189,7 +193,7 @@
AEE5B7652A09B1FC001E5E59 /* CodeScanner in Frameworks */,
AE51BEFD2B37A5DC00BAE452 /* SimpleToast in Frameworks */,
AE7C4A082B406D590061189D /* SimpleToast in Frameworks */,
AE1AED452C25C94D00B467EF /* LDKNode in Frameworks */,
AE80C2052C4AB5E4006E7193 /* LDKNode in Frameworks */,
AE01C5B02AB3BEED00F28C7E /* KeychainAccess in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -283,6 +287,7 @@
AEAAD9D22C23399700765F5B /* Bolt12ZeroInvoiceViewModel.swift */,
AE5BFE852C0B9D7B003B467C /* Bolt12InvoiceViewModel.swift */,
AE028A272B96325700B336E7 /* AmountInvoiceViewModel.swift */,
AE80C2012C4AB38D006E7193 /* BIP21ViewModel.swift */,
AE028A292B96328600B336E7 /* JITInvoiceViewModel.swift */,
AE49E8552A253674002623E8 /* AddressViewModel.swift */,
);
Expand Down Expand Up @@ -378,6 +383,7 @@
AE5BFE832C0B9D06003B467C /* Bolt12InvoiceView.swift */,
AEAAD9D02C23394D00765F5B /* Bolt12ZeroInvoiceView.swift */,
AEA057EA2B912E1800DB1096 /* AmountInvoiceView.swift */,
AE80C1FF2C4AB360006E7193 /* BIP21View.swift */,
AEA057EC2B912FEA00DB1096 /* JITInvoiceView.swift */,
AE17E8DB29A402E30058C9C9 /* AddressView.swift */,
);
Expand Down Expand Up @@ -555,7 +561,7 @@
AE51BEFC2B37A5DC00BAE452 /* SimpleToast */,
AE7C4A072B406D590061189D /* SimpleToast */,
AE060C372C051B59006724F1 /* LDKNode */,
AE1AED442C25C94D00B467EF /* LDKNode */,
AE80C2042C4AB5E4006E7193 /* LDKNode */,
);
productName = LDKNodeMonday;
productReference = AE17E8D629A402E30058C9C9 /* LDKNodeMonday.app */;
Expand Down Expand Up @@ -590,7 +596,7 @@
AE01C5AE2AB3BEED00F28C7E /* XCRemoteSwiftPackageReference "KeychainAccess" */,
AE1D9BEA2B2A1FFD00620748 /* XCRemoteSwiftPackageReference "BitcoinUI" */,
AE7C4A062B406D590061189D /* XCRemoteSwiftPackageReference "SimpleToast" */,
AE1AED432C25C94D00B467EF /* XCRemoteSwiftPackageReference "ldk-node" */,
AE80C2032C4AB5E4006E7193 /* XCLocalSwiftPackageReference "../ldk-node/bindings/swift" */,
);
productRefGroup = AE17E8D729A402E30058C9C9 /* Products */;
projectDirPath = "";
Expand Down Expand Up @@ -626,6 +632,7 @@
AE0055162B4A0E0100100797 /* Logger+Extensions.swift in Sources */,
AEA057EB2B912E1800DB1096 /* AmountInvoiceView.swift in Sources */,
AE70963C2B5C22270038BE56 /* CurrencyCode.swift in Sources */,
AE80C2002C4AB360006E7193 /* BIP21View.swift in Sources */,
AE01C5B22AB3BF3C00F28C7E /* KeyService.swift in Sources */,
AE49E8642A2537B3002623E8 /* PeersListViewModel.swift in Sources */,
AE49E8542A253647002623E8 /* BitcoinViewModel.swift in Sources */,
Expand Down Expand Up @@ -685,6 +692,7 @@
AE6BB56F2A008CBA009E16E3 /* UInt64+Extensions.swift in Sources */,
AE49E85A2A2536D4002623E8 /* ChannelAddViewModel.swift in Sources */,
AE0055142B4895B500100797 /* SeedViewModel.swift in Sources */,
AE80C2022C4AB38D006E7193 /* BIP21ViewModel.swift in Sources */,
AE7D3FAB2A4263AE00EAE730 /* PaymentsView.swift in Sources */,
AE80116D29A59AF4009B9967 /* ChannelAddView.swift in Sources */,
AE551D472B8ECE7D0034B61E /* Payment.swift in Sources */,
Expand Down Expand Up @@ -906,6 +914,13 @@
};
/* End XCConfigurationList section */

/* Begin XCLocalSwiftPackageReference section */
AE80C2032C4AB5E4006E7193 /* XCLocalSwiftPackageReference "../ldk-node/bindings/swift" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = "../ldk-node/bindings/swift";
};
/* End XCLocalSwiftPackageReference section */

/* Begin XCRemoteSwiftPackageReference section */
AE01C5AE2AB3BEED00F28C7E /* XCRemoteSwiftPackageReference "KeychainAccess" */ = {
isa = XCRemoteSwiftPackageReference;
Expand All @@ -915,19 +930,11 @@
version = 4.2.2;
};
};
AE1AED432C25C94D00B467EF /* XCRemoteSwiftPackageReference "ldk-node" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/lightningdevkit/ldk-node.git";
requirement = {
kind = exactVersion;
version = 0.3.0;
};
};
AE1D9BEA2B2A1FFD00620748 /* XCRemoteSwiftPackageReference "BitcoinUI" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/reez/BitcoinUI.git";
requirement = {
branch = 1.0.6;
branch = main;
kind = branch;
};
};
Expand Down Expand Up @@ -959,11 +966,6 @@
isa = XCSwiftPackageProductDependency;
productName = LDKNode;
};
AE1AED442C25C94D00B467EF /* LDKNode */ = {
isa = XCSwiftPackageProductDependency;
package = AE1AED432C25C94D00B467EF /* XCRemoteSwiftPackageReference "ldk-node" */;
productName = LDKNode;
};
AE1D9BEB2B2A1FFD00620748 /* BitcoinUI */ = {
isa = XCSwiftPackageProductDependency;
package = AE1D9BEA2B2A1FFD00620748 /* XCRemoteSwiftPackageReference "BitcoinUI" */;
Expand All @@ -978,6 +980,10 @@
package = AE7C4A062B406D590061189D /* XCRemoteSwiftPackageReference "SimpleToast" */;
productName = SimpleToast;
};
AE80C2042C4AB5E4006E7193 /* LDKNode */ = {
isa = XCSwiftPackageProductDependency;
productName = LDKNode;
};
AEE5B7642A09B1FC001E5E59 /* CodeScanner */ = {
isa = XCSwiftPackageProductDependency;
package = AEE5B7632A09B1FC001E5E59 /* XCRemoteSwiftPackageReference "CodeScanner" */;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"originHash" : "3c4f831e3e2d84e3d749572be35ef446cf04910008de67451a51af6ca58e22b3",
"originHash" : "205ed5286a50854d994301c2ba51d3a38173557dcfa21e1c3da1ccccb0730a15",
"pins" : [
{
"identity" : "bitcoinui",
"kind" : "remoteSourceControl",
"location" : "https://github.com/reez/BitcoinUI.git",
"state" : {
"branch" : "1.0.6",
"revision" : "2f4f1a49a869ebd19c08718ade319ffdf59e6da1"
"branch" : "main",
"revision" : "22ea27e2495aac9035c39a1b09165a802d90bdde"
}
},
{
Expand All @@ -28,15 +28,6 @@
"version" : "4.2.2"
}
},
{
"identity" : "ldk-node",
"kind" : "remoteSourceControl",
"location" : "https://github.com/lightningdevkit/ldk-node.git",
"state" : {
"revision" : "bd9bd683201c1597d8930ac4118fa950cedfd56c",
"version" : "0.3.0"
}
},
{
"identity" : "simpletoast",
"kind" : "remoteSourceControl",
Expand Down
119 changes: 81 additions & 38 deletions LDKNodeMonday/Extensions/String+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,44 +10,47 @@ import Foundation
extension String {

func bolt11amount() -> String? {
let regex = try! NSRegularExpression(pattern: "ln.*?(\\d+)([munp]?)", options: [])
let regex = try! NSRegularExpression(
pattern: "ln(?:bc|tb|tbs)(?<amount>\\d+)(?<multiplier>[munp]?)",
options: [.caseInsensitive]
)

if let match = regex.firstMatch(
in: self,
options: [],
range: NSRange(location: 0, length: self.utf16.count)
) {
let amountRange = match.range(at: 1)
let multiplierRange = match.range(at: 2)

if let amountSwiftRange = Range(amountRange, in: self),
let multiplierSwiftRange = Range(multiplierRange, in: self)
{

let amountString = self[amountSwiftRange]
let multiplierString = self[multiplierSwiftRange]
let numberFormatter = NumberFormatter()

if let amount = numberFormatter.number(from: String(amountString))?.doubleValue {
var conversion = amount

switch multiplierString {
case "m":
conversion *= 0.001
case "u":
conversion *= 0.000001
case "n":
conversion *= 0.000000001
case "p":
conversion *= 0.000000000001
default:
break
}

let convertedAmount = conversion * 100_000_000
let formattedAmount = String(format: "%.0f", convertedAmount)
return formattedAmount
}

guard let amountRange = Range(match.range(withName: "amount"), in: self),
let multiplierRange = Range(match.range(withName: "multiplier"), in: self)
else {
return nil
}

let amountString = String(self[amountRange])
let multiplierString = String(self[multiplierRange])

guard let amount = Int(amountString) else {
return nil
}

var conversion = Double(amount)
switch multiplierString.lowercased() {
case "m":
conversion *= 0.001
case "u":
conversion *= 0.000001
case "n":
conversion *= 0.000000001
case "p":
conversion *= 0.000000000001
default:
break
}

let convertedAmount = conversion * 100_000_000
let formattedAmount = String(format: "%.0f", convertedAmount)
return formattedAmount
}

return nil
Expand Down Expand Up @@ -158,14 +161,51 @@ extension String {
return params
}

func processBIP21(_ input: String, spendableBalance: UInt64) -> (String, String, Payment) {
guard let url = URL(string: input),
let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
else {
return ("", "0", .isNone)
}

let bitcoinAddress = url.path
var amount = "0"
var bolt12Offer: String?
var bolt11Invoice: String?

for item in components.queryItems ?? [] {
switch item.name.lowercased() {
case "amount":
if let value = item.value, let btcAmount = Double(value) {
amount = String(format: "%.0f", btcAmount * 100_000_000)
}
case "lightning":
bolt11Invoice = item.value
case "lno":
bolt12Offer = item.value
default:
break
}
}

if let offer = bolt12Offer {
return processLightningAddress(offer)
}
if let invoice = bolt11Invoice {
return processLightningAddress(invoice)
}
return (bitcoinAddress, amount, .isBitcoin)
}

func extractPaymentInfo(spendableBalance: UInt64) -> (
address: String, amount: String, payment: Payment
) {
let queryParams = self.queryParameters()

if let lightningAddress = queryParams["lightning"], !lightningAddress.isEmpty {
return processLightningAddress(lightningAddress)
} else if self.isLightningAddress && !self.starts(with: "lnurl") {
if self.lowercased().starts(with: "bitcoin:") && self.contains("?") {
return processBIP21(self, spendableBalance: spendableBalance)
} else if self.lowercased().starts(with: "lightning:") {
let invoice = String(self.dropFirst(10)) // Remove "lightning:" prefix
return processLightningAddress(invoice)
} else if self.lowercased().starts(with: "lnbc") || self.lowercased().starts(with: "lntb") {
return processLightningAddress(self)
} else if self.isBitcoinAddress {
return processBitcoinAddress(spendableBalance)
Expand All @@ -191,7 +231,7 @@ extension String {
private func processLightningAddress(_ address: String) -> (String, String, Payment) {
let sanitizedAddress = address.replacingOccurrences(of: "lightning:", with: "")

if sanitizedAddress.starts(with: "lno") {
if sanitizedAddress.lowercased().starts(with: "lno") {
return (sanitizedAddress, "0", .isLightning)
} else {
let amount = sanitizedAddress.bolt11amount() ?? "0"
Expand All @@ -202,6 +242,9 @@ extension String {
private func extractBitcoinAddress() -> String {
if self.lowercased().hasPrefix("bitcoin:") {
let address = self.replacingOccurrences(of: "bitcoin:", with: "")
if let addressEnd = address.range(of: "?")?.lowerBound {
return String(address[..<addressEnd]).uppercased()
}
return address.uppercased()
}
return self.uppercased()
Expand Down
16 changes: 6 additions & 10 deletions LDKNodeMonday/Model/ReceiveOption.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,19 @@
import Foundation

enum ReceiveOption: String, CaseIterable, Identifiable {
case bolt11Zero = "Bolt11 0"
case bolt11 = "Bolt11"
case bolt11JIT = "Bolt11 JIT"
// case bolt12Zero = "Bolt12 0"
case bolt12 = "Bolt12"
case bitcoin = "Address"

var id: Self { self }

case bolt11JIT = "Bolt11 JIT"
case bip21 = "BIP21"
}

extension ReceiveOption {
var systemImageName: String {
switch self {
case .bitcoin:
return "bitcoinsign"
default:
case .bolt11JIT:
return "bolt"
default:
return "qrcode"
}
}
}
Loading

0 comments on commit 041a10a

Please sign in to comment.