Skip to content

Commit

Permalink
feat: bolt 12 zero
Browse files Browse the repository at this point in the history
  • Loading branch information
reez authored Jun 19, 2024
1 parent ecf1b0f commit e1343c5
Show file tree
Hide file tree
Showing 12 changed files with 482 additions and 132 deletions.
8 changes: 8 additions & 0 deletions LDKNodeMonday.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@
AEA057E92B912C3C00DB1096 /* ZeroInvoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEA057E82B912C3C00DB1096 /* ZeroInvoiceView.swift */; };
AEA057EB2B912E1800DB1096 /* AmountInvoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEA057EA2B912E1800DB1096 /* AmountInvoiceView.swift */; };
AEA057ED2B912FEA00DB1096 /* JITInvoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEA057EC2B912FEA00DB1096 /* JITInvoiceView.swift */; };
AEAAD9D12C23394D00765F5B /* Bolt12ZeroInvoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEAAD9D02C23394D00765F5B /* Bolt12ZeroInvoiceView.swift */; };
AEAAD9D32C23399700765F5B /* Bolt12ZeroInvoiceViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEAAD9D22C23399700765F5B /* Bolt12ZeroInvoiceViewModel.swift */; };
AEBAA4832A01A80A0042EA82 /* PeersListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEBAA4822A01A80A0042EA82 /* PeersListView.swift */; };
AEBAA48D2A01A93B0042EA82 /* PeerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEBAA48C2A01A93B0042EA82 /* PeerView.swift */; };
AEBAA4922A01C34A0042EA82 /* ChannelDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEBAA4912A01C34A0042EA82 /* ChannelDetailView.swift */; };
Expand Down Expand Up @@ -156,6 +158,8 @@
AEA057E82B912C3C00DB1096 /* ZeroInvoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZeroInvoiceView.swift; sourceTree = "<group>"; };
AEA057EA2B912E1800DB1096 /* AmountInvoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmountInvoiceView.swift; sourceTree = "<group>"; };
AEA057EC2B912FEA00DB1096 /* JITInvoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JITInvoiceView.swift; sourceTree = "<group>"; };
AEAAD9D02C23394D00765F5B /* Bolt12ZeroInvoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bolt12ZeroInvoiceView.swift; sourceTree = "<group>"; };
AEAAD9D22C23399700765F5B /* Bolt12ZeroInvoiceViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bolt12ZeroInvoiceViewModel.swift; sourceTree = "<group>"; };
AEBAA4822A01A80A0042EA82 /* PeersListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeersListView.swift; sourceTree = "<group>"; };
AEBAA48C2A01A93B0042EA82 /* PeerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerView.swift; sourceTree = "<group>"; };
AEBAA4912A01C34A0042EA82 /* ChannelDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelDetailView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -274,6 +278,7 @@
isa = PBXGroup;
children = (
AE028A252B9631F100B336E7 /* ZeroAmountViewModel.swift */,
AEAAD9D22C23399700765F5B /* Bolt12ZeroInvoiceViewModel.swift */,
AE5BFE852C0B9D7B003B467C /* Bolt12InvoiceViewModel.swift */,
AE028A272B96325700B336E7 /* AmountInvoiceViewModel.swift */,
AE028A292B96328600B336E7 /* JITInvoiceViewModel.swift */,
Expand Down Expand Up @@ -369,6 +374,7 @@
AEBFD8B52B8BABC00024E3F7 /* ReceiveView.swift */,
AEA057E82B912C3C00DB1096 /* ZeroInvoiceView.swift */,
AE5BFE832C0B9D06003B467C /* Bolt12InvoiceView.swift */,
AEAAD9D02C23394D00765F5B /* Bolt12ZeroInvoiceView.swift */,
AEA057EA2B912E1800DB1096 /* AmountInvoiceView.swift */,
AEA057EC2B912FEA00DB1096 /* JITInvoiceView.swift */,
AE17E8DB29A402E30058C9C9 /* AddressView.swift */,
Expand Down Expand Up @@ -638,6 +644,7 @@
AE1D9C0B2B2A251500620748 /* ChannelDetails+Extensions.swift in Sources */,
AEBAA4832A01A80A0042EA82 /* PeersListView.swift in Sources */,
AE7096332B5C20480038BE56 /* PriceService.swift in Sources */,
AEAAD9D12C23394D00765F5B /* Bolt12ZeroInvoiceView.swift in Sources */,
AEDF76022B5C6863002DDEE1 /* Optional+Extensions.swift in Sources */,
AE5BFE842C0B9D06003B467C /* Bolt12InvoiceView.swift in Sources */,
AEA057E92B912C3C00DB1096 /* ZeroInvoiceView.swift in Sources */,
Expand Down Expand Up @@ -669,6 +676,7 @@
AE7D3FAD2A4263C100EAE730 /* PaymentsViewModel.swift in Sources */,
AEE8FDDD29F855E700406DD9 /* String+Extensions.swift in Sources */,
AE028A2A2B96328600B336E7 /* JITInvoiceViewModel.swift in Sources */,
AEAAD9D32C23399700765F5B /* Bolt12ZeroInvoiceViewModel.swift in Sources */,
AE00550E2B479EF000100797 /* OnboardingView.swift in Sources */,
AEBFD8B02B8A97DB0024E3F7 /* LightningServiceProvider.swift in Sources */,
AE6BB56F2A008CBA009E16E3 /* UInt64+Extensions.swift in Sources */,
Expand Down
40 changes: 26 additions & 14 deletions LDKNodeMonday/Extensions/String+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -164,29 +164,41 @@ extension String {
let queryParams = self.queryParameters()

if let lightningAddress = queryParams["lightning"], !lightningAddress.isEmpty {
let address = lightningAddress
let newAddress = address.lowercased()
let amount = newAddress.bolt11amount() ?? "0"
return (newAddress, amount, .isLightning)
return processLightningAddress(lightningAddress)
} else if self.isLightningAddress && !self.starts(with: "lnurl") {
let address = self
let amount = address.bolt11amount() ?? "0"
return (address, amount, .isLightning)
return processLightningAddress(self)
} else if self.isBitcoinAddress {
let address = self.extractBitcoinAddress()
let amount = queryParams["amount"] ?? "0"
if let amountValue = UInt64(amount), amountValue <= spendableBalance {
return (address, amount, .isBitcoin)
} else {
return (address, "0", .isBitcoin)
}
return processBitcoinAddress(spendableBalance)
} else if self.starts(with: "lnurl") {
return ("LNURL not supported yet", "0", .isLightningURL)
} else {
return ("", "0", .isNone)
}
}

private func processBitcoinAddress(_ spendableBalance: UInt64) -> (String, String, Payment) {
let address = self.extractBitcoinAddress()
let queryParams = self.queryParameters()
let amount = queryParams["amount"] ?? "0"

if let amountValue = UInt64(amount), amountValue <= spendableBalance {
return (address, amount, .isBitcoin)
} else {
return (address, "0", .isBitcoin)
}
}

private func processLightningAddress(_ address: String) -> (String, String, Payment) {
let sanitizedAddress = address.replacingOccurrences(of: "lightning:", with: "")

if sanitizedAddress.starts(with: "lno") {
return (sanitizedAddress, "0", .isLightning)
} else {
let amount = sanitizedAddress.bolt11amount() ?? "0"
return (sanitizedAddress, amount, .isLightning)
}
}

private func extractBitcoinAddress() -> String {
if self.lowercased().hasPrefix("bitcoin:") {
let address = self.replacingOccurrences(of: "bitcoin:", with: "")
Expand Down
7 changes: 4 additions & 3 deletions LDKNodeMonday/Model/ReceiveOption.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
import Foundation

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

Expand Down
39 changes: 39 additions & 0 deletions LDKNodeMonday/Service/Lightning Service/LightningNodeService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class LightningNodeService {
config.trustedPeers0conf = [
Constants.Config.LiquiditySourceLsps2.Signet.mutiny.nodeId
]
config.logLevel = .trace

let nodeBuilder = Builder.fromConfig(config: config)
nodeBuilder.setEsploraServer(esploraServerUrl: storedEsploraURL)
Expand Down Expand Up @@ -166,6 +167,8 @@ class LightningNodeService {
)
}

/// Send - Bolt 11

func sendPayment(invoice: Bolt11Invoice) async throws -> PaymentHash {
let paymentHash = try ldkNode.bolt11Payment().send(invoice: invoice)
return paymentHash
Expand All @@ -181,6 +184,28 @@ class LightningNodeService {
return paymentHash
}

/// Send - Bolt 12

func sendPaymentBolt12(invoice: Bolt12Invoice) async throws -> PaymentId {
let payerNote = "BOLT 12 payment payer note"
let paymentId = try ldkNode.bolt12Payment().send(offer: invoice, payerNote: payerNote)
return paymentId
}

func sendPaymentUsingAmountBolt12(invoice: Bolt12Invoice, amountMsat: UInt64) async throws
-> PaymentId
{
let payerNote = "BOLT 12 payment payer note"
let paymentId = try ldkNode.bolt12Payment().sendUsingAmount(
offer: invoice,
payerNote: payerNote,
amountMsat: amountMsat
)
return paymentId
}

/// Receive - Bolt 11

func receivePayment(amountMsat: UInt64, description: String, expirySecs: UInt32) async throws
-> Bolt11Invoice
{
Expand All @@ -202,11 +227,25 @@ class LightningNodeService {
return invoice
}

/// Receive - Bolt 12

// name these like receive(with amountMSat: ...)
func receivePaymentBolt12(amountMsat: UInt64, description: String) async throws -> Bolt12Invoice
{
let offer = try ldkNode.bolt12Payment().receive(
amountMsat: amountMsat,
description: description
)
return offer
}

func receiveVariableAmountBolt12(description: String) async throws -> Bolt12Invoice {
let offer = try ldkNode.bolt12Payment().receiveVariableAmount(description: description)
return offer
}

/// Receive - JIT

func receivePaymentViaJitChannel(
amountMsat: UInt64,
description: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ func handleNodeError(_ error: NodeError) -> MondayError {
case .UnsupportedCurrency(let message):
return .init(title: "UnsupportedCurrency", detail: message)

case .InvalidNodeId(let message):
return .init(title: "InvalidNodeId", detail: message)

}

}
64 changes: 64 additions & 0 deletions LDKNodeMonday/View Model/Home/AmountViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,74 @@ class AmountViewModel {
}
}

func sendPaymentBolt12(invoice: Bolt12Invoice) async {
do {
try await LightningNodeService.shared.sendPaymentBolt12(invoice: invoice)
} catch let error as NodeError {
NotificationCenter.default.post(name: .ldkErrorReceived, object: error)
let errorString = handleNodeError(error)
DispatchQueue.main.async {
self.amountConfirmationViewError = .init(
title: errorString.title,
detail: errorString.detail
)
}
} catch {
DispatchQueue.main.async {
self.amountConfirmationViewError = .init(
title: "Unexpected error",
detail: error.localizedDescription
)
}
}
}

func getColor() {
let color = LightningNodeService.shared.networkColor
DispatchQueue.main.async {
self.networkColor = color
}
}

func handleLightningPayment(address: String, numpadAmount: String) async {
if address.starts(with: "lno") {
await sendPaymentBolt12(invoice: address)
} else if address.bolt11amount() == "0" {
if let amountSats = UInt64(numpadAmount) {
let amountMsat = amountSats * 1000
await sendPaymentUsingAmount(invoice: address, amountMsat: amountMsat)
} else {
self.amountConfirmationViewError = .init(
title: "Unexpected error",
detail: "Invalid amount entered"
)
}
} else {
await sendPayment(invoice: address)
}
}

func handleBitcoinPayment(address: String, numpadAmount: String) async {
if numpadAmount == "0" {
self.amountConfirmationViewError = .init(
title: "Unexpected error",
detail: "Invalid amount entered"
)
} else if let amount = UInt64(numpadAmount) {
await sendToOnchain(address: address, amountMsat: amount)
} else {
self.amountConfirmationViewError = .init(
title: "Unexpected error",
detail: "Unknown error occurred"
)
}
}

func handleLightningURLPayment() {
self.amountConfirmationViewError = .init(
title: "LNURL Error",
detail: "LNURL not supported yet"
)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ class Bolt12InvoiceViewModel: ObservableObject {
@Published var invoice: Bolt12Invoice = ""
@Published var receiveViewError: MondayError?
@Published var networkColor = Color.gray
@Published var amountMsat: String = ""

func receiveVariableAmountPayment(description: String, expirySecs: UInt32) async {
func receivePayment(amountMsat: UInt64, description: String) async {
do {
let invoice = try await LightningNodeService.shared.receiveVariableAmountBolt12(
let invoice = try await LightningNodeService.shared.receivePaymentBolt12(
amountMsat: amountMsat,
description: description
)
DispatchQueue.main.async {
Expand All @@ -37,6 +39,10 @@ class Bolt12InvoiceViewModel: ObservableObject {
}
}

func clearInvoice() {
self.invoice = ""
}

func getColor() {
let color = LightningNodeService.shared.networkColor
DispatchQueue.main.async {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// Bolt12ZeroInvoiceViewModel.swift
// LDKNodeMonday
//
// Created by Matthew Ramsden on 6/19/24.
//

import Foundation
import LDKNode
import SwiftUI

class Bolt12ZeroInvoiceViewModel: ObservableObject {
@Published var invoice: Bolt12Invoice = ""
@Published var receiveViewError: MondayError?
@Published var networkColor = Color.gray

func receivePayment(description: String) async {
do {
let invoice = try await LightningNodeService.shared.receiveVariableAmountBolt12(
description: description
)
DispatchQueue.main.async {
self.invoice = invoice
}
} catch let error as NodeError {
let errorString = handleNodeError(error)
DispatchQueue.main.async {
self.receiveViewError = .init(title: errorString.title, detail: errorString.detail)
}
} catch {
DispatchQueue.main.async {
self.receiveViewError = .init(
title: "Unexpected error",
detail: error.localizedDescription
)
}
}
}

func getColor() {
let color = LightningNodeService.shared.networkColor
DispatchQueue.main.async {
self.networkColor = color
}
}

}
Loading

0 comments on commit e1343c5

Please sign in to comment.