Add LND test wallet
This commit is contained in:
104
wallet/Lightning/Utils/EventBus.swift
Normal file
104
wallet/Lightning/Utils/EventBus.swift
Normal file
@ -0,0 +1,104 @@
|
||||
//
|
||||
// EventBus.swift
|
||||
// wallet
|
||||
//
|
||||
// Created by Jason van den Berg on 2020/08/18.
|
||||
// Copyright © 2020 Jason. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum EventTypes: String {
|
||||
case lndStateChange = "lnd-state-change"
|
||||
case lndStarted = "lnd-started"
|
||||
case lndStopped = "lnd-stopped"
|
||||
case lndRpcReady = "lnd-rpc-ready"
|
||||
case lndWalletUnlocked = "lnd-wallet-unlocked"
|
||||
case lndWalletLocked = "lnd-wallet-locked"
|
||||
case lndChannelUpdate = "lnd-channel-update"
|
||||
}
|
||||
|
||||
private let identifier = "app.lndtest.wallet.eventbus"
|
||||
|
||||
open class EventBus {
|
||||
static let shared = EventBus()
|
||||
static let queue = DispatchQueue(label: identifier, attributes: [])
|
||||
|
||||
struct NamedObserver {
|
||||
let observer: NSObjectProtocol
|
||||
let eventType: EventTypes
|
||||
}
|
||||
|
||||
var cache = [UInt: [NamedObserver]]()
|
||||
|
||||
// MARK: Publish
|
||||
|
||||
open class func postToMainThread(_ eventType: EventTypes, sender: Any? = nil) {
|
||||
DispatchQueue.main.async {
|
||||
NotificationCenter.default.post(name: Notification.Name(rawValue: eventType.rawValue), object: sender)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Subscribe
|
||||
|
||||
@discardableResult
|
||||
open class func on(_ target: AnyObject, eventType: EventTypes, sender: Any? = nil, queue: OperationQueue?, handler: @escaping ((Notification?) -> Void)) -> NSObjectProtocol {
|
||||
let id = UInt(bitPattern: ObjectIdentifier(target))
|
||||
let observer = NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: eventType.rawValue), object: sender, queue: queue, using: handler)
|
||||
let namedObserver = NamedObserver(observer: observer, eventType: eventType)
|
||||
|
||||
EventBus.queue.sync {
|
||||
if let namedObservers = EventBus.shared.cache[id] {
|
||||
EventBus.shared.cache[id] = namedObservers + [namedObserver]
|
||||
} else {
|
||||
EventBus.shared.cache[id] = [namedObserver]
|
||||
}
|
||||
}
|
||||
|
||||
return observer
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open class func onMainThread(_ target: AnyObject, eventType: EventTypes, sender: Any? = nil, handler: @escaping ((Notification?) -> Void)) -> NSObjectProtocol {
|
||||
return EventBus.on(target, eventType: eventType, sender: sender, queue: OperationQueue.main, handler: handler)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open class func onBackgroundThread(_ target: AnyObject, eventType: EventTypes, sender: Any? = nil, handler: @escaping ((Notification?) -> Void)) -> NSObjectProtocol {
|
||||
return EventBus.on(target, eventType: eventType, sender: sender, queue: OperationQueue(), handler: handler)
|
||||
}
|
||||
|
||||
// MARK: Unregister
|
||||
|
||||
open class func unregister(_ target: AnyObject) {
|
||||
let id = UInt(bitPattern: ObjectIdentifier(target))
|
||||
let center = NotificationCenter.default
|
||||
|
||||
EventBus.queue.sync {
|
||||
if let namedObservers = EventBus.shared.cache.removeValue(forKey: id) {
|
||||
for namedObserver in namedObservers {
|
||||
center.removeObserver(namedObserver.observer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open class func unregister(_ target: AnyObject, eventType: EventTypes) {
|
||||
let id = UInt(bitPattern: ObjectIdentifier(target))
|
||||
let center = NotificationCenter.default
|
||||
|
||||
EventBus.queue.sync {
|
||||
if let namedObservers = EventBus.shared.cache[id] {
|
||||
EventBus.shared.cache[id] = namedObservers.filter({ (namedObserver: NamedObserver) -> Bool in
|
||||
if namedObserver.eventType == eventType {
|
||||
center.removeObserver(namedObserver.observer)
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
32
wallet/Lightning/Utils/LightningError.swift
Normal file
32
wallet/Lightning/Utils/LightningError.swift
Normal file
@ -0,0 +1,32 @@
|
||||
//
|
||||
// LightningError.swift
|
||||
// wallet
|
||||
//
|
||||
// Created by Jason van den Berg on 2020/09/12.
|
||||
// Copyright © 2020 Jason. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum LightningError: Error {
|
||||
case unknown
|
||||
case mapping
|
||||
case invalidPassword
|
||||
case paymentError(String)
|
||||
}
|
||||
|
||||
extension LightningError: LocalizedError {
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
case .unknown:
|
||||
return NSLocalizedString("LND_ERROR_UNKNOWN", comment: "LND error")
|
||||
case .mapping:
|
||||
return NSLocalizedString("LND_ERROR_MAPPING", comment: "LND error")
|
||||
case .invalidPassword:
|
||||
return NSLocalizedString("LND_ERROR_INVALID_PASSWORD", comment: "LND error")
|
||||
case .paymentError(let lndKey):
|
||||
//TODO get all possible error keys and create custom messages for them
|
||||
return String(format: NSLocalizedString("LND_ERROR_PAYMENT", comment: "LND error"), lndKey)
|
||||
}
|
||||
}
|
||||
}
|
76
wallet/Lightning/Utils/LightningStateMonitor.swift
Normal file
76
wallet/Lightning/Utils/LightningStateMonitor.swift
Normal file
@ -0,0 +1,76 @@
|
||||
//
|
||||
// LightningStateMonitor.swift
|
||||
// wallet
|
||||
//
|
||||
// Created by Jason van den Berg on 2020/08/18.
|
||||
// Copyright © 2020 Jason. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class LightningMonitorState {
|
||||
var lndRunning = false { didSet { onUpdate() } }
|
||||
var rpcReady = false { didSet { onUpdate() } }
|
||||
var walletUnlocked = false { didSet { onUpdate() } }
|
||||
var walletInfo = Lnrpc_GetInfoResponse() { didSet { onUpdate() } }
|
||||
|
||||
var debuggingStatus: [String] {
|
||||
var entries: [String] = []
|
||||
entries.append("LND running: \(lndRunning ? "✅" : "❌")")
|
||||
entries.append("RPC ready: \(rpcReady ? "✅" : "❌")")
|
||||
entries.append("Wallet unlocked: \(walletUnlocked ? "✅" : "❌")")
|
||||
|
||||
if walletUnlocked {
|
||||
entries.append("Synced to chain: \(walletInfo.syncedToChain ? "✅" : "❌")")
|
||||
entries.append("Block height: \(walletInfo.blockHeight)")
|
||||
entries.append("Peers: \(walletInfo.numPeers)")
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
//ALlow other components to subscribe to state changes from one place
|
||||
private func onUpdate() {
|
||||
EventBus.postToMainThread(.lndStateChange, sender: self)
|
||||
}
|
||||
}
|
||||
|
||||
class LightningStateMonitor {
|
||||
public static let shared = LightningStateMonitor()
|
||||
|
||||
var state = LightningMonitorState()
|
||||
|
||||
private init() {
|
||||
EventBus.onMainThread(self, eventType: .lndStarted) { [weak self] (_) in
|
||||
self?.state.lndRunning = true
|
||||
}
|
||||
|
||||
EventBus.onMainThread(self, eventType: .lndStopped) { [weak self] (_) in
|
||||
self?.state.lndRunning = false
|
||||
self?.state.walletUnlocked = false
|
||||
self?.state.rpcReady = false
|
||||
}
|
||||
|
||||
EventBus.onMainThread(self, eventType: .lndRpcReady) { [weak self] (_) in
|
||||
self?.state.rpcReady = true
|
||||
}
|
||||
|
||||
EventBus.onMainThread(self, eventType: .lndWalletUnlocked) { [weak self] (_) in
|
||||
self?.state.walletUnlocked = true
|
||||
}
|
||||
|
||||
//TODO find better way to subscribe to LND events than this
|
||||
_ = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateInfo), userInfo: nil, repeats: true)
|
||||
}
|
||||
|
||||
@objc private func updateInfo() {
|
||||
Lightning.shared.info { [weak self] (response, error) in
|
||||
guard let self = self else { return }
|
||||
guard error == nil else {
|
||||
return self.state.walletInfo = .init()
|
||||
}
|
||||
|
||||
self.state.walletInfo = response
|
||||
}
|
||||
}
|
||||
}
|
59
wallet/Lightning/Utils/NodePublicKey.swift
Normal file
59
wallet/Lightning/Utils/NodePublicKey.swift
Normal file
@ -0,0 +1,59 @@
|
||||
//
|
||||
// NodePublicKey.swift
|
||||
// wallet
|
||||
//
|
||||
// Created by Jason van den Berg on 2020/08/20.
|
||||
// Copyright © 2020 Jason. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class NodePublicKey {
|
||||
enum NodePublicKeyErrors: Error {
|
||||
case invalidHexString
|
||||
case invalidByte
|
||||
case invalidByteLength
|
||||
}
|
||||
|
||||
private let bytes: [UInt8]
|
||||
let hexString: String
|
||||
|
||||
var data: Data {
|
||||
return Data(bytes)
|
||||
}
|
||||
|
||||
init(_ hexString: String) throws {
|
||||
let length = hexString.count
|
||||
|
||||
// Must start with 02 or 03 as according to SECP256K1
|
||||
guard hexString.hasPrefix("02") || hexString.hasPrefix("03") else {
|
||||
throw NodePublicKeyErrors.invalidHexString
|
||||
}
|
||||
|
||||
// Must be even characters
|
||||
guard length & 1 == 0 else {
|
||||
throw NodePublicKeyErrors.invalidHexString
|
||||
}
|
||||
|
||||
var bytes = [UInt8]()
|
||||
bytes.reserveCapacity(length / 2)
|
||||
|
||||
var index = hexString.startIndex
|
||||
for _ in 0..<length / 2 {
|
||||
let nextIndex = hexString.index(index, offsetBy: 2)
|
||||
guard let byte = UInt8(hexString[index..<nextIndex], radix: 16) else {
|
||||
throw NodePublicKeyErrors.invalidByte
|
||||
}
|
||||
bytes.append(byte)
|
||||
index = nextIndex
|
||||
}
|
||||
|
||||
// Must be 33 bytes in length for compressed bitcoin public key
|
||||
guard bytes.count == 33 else {
|
||||
throw NodePublicKeyErrors.invalidByteLength
|
||||
}
|
||||
|
||||
self.bytes = bytes
|
||||
self.hexString = hexString
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user