From 2212575c23eb46f3f33d0edaed8a8d512b90afe5 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 29 Aug 2024 15:05:37 +0200 Subject: [PATCH 1/7] feat: ios create pending channel --- example/ios/Podfile.lock | 4 +- .../Classes/LdkChannelManagerPersister.swift | 2 +- lib/ios/Ldk.m | 5 + lib/ios/Ldk.swift | 450 ++++++++++-------- lib/package.json | 2 +- lib/src/ldk.ts | 20 + lib/src/lightning-manager.ts | 56 ++- lib/src/utils/types.ts | 6 + 8 files changed, 325 insertions(+), 220 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index eb5623e8..0576babc 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -316,7 +316,7 @@ PODS: - React-jsinspector (0.72.4) - React-logger (0.72.4): - glog - - react-native-ldk (0.0.148): + - react-native-ldk (0.0.150): - React - react-native-randombytes (3.6.1): - React-Core @@ -621,7 +621,7 @@ SPEC CHECKSUMS: React-jsiexecutor: c7f826e40fa9cab5d37cab6130b1af237332b594 React-jsinspector: aaed4cf551c4a1c98092436518c2d267b13a673f React-logger: da1ebe05ae06eb6db4b162202faeafac4b435e77 - react-native-ldk: fda4d4381d40401bdc5c3a9965937d19b232ed08 + react-native-ldk: 2b19de9eb94dcfd46f3f2a7191502292b75a5d7a react-native-randombytes: 421f1c7d48c0af8dbcd471b0324393ebf8fe7846 react-native-tcp-socket: c1b7297619616b4c9caae6889bcb0aba78086989 React-NativeModulesApple: edb5ace14f73f4969df6e7b1f3e41bef0012740f diff --git a/lib/ios/Classes/LdkChannelManagerPersister.swift b/lib/ios/Classes/LdkChannelManagerPersister.swift index 8b0d4bb9..b7ff3765 100644 --- a/lib/ios/Classes/LdkChannelManagerPersister.swift +++ b/lib/ios/Classes/LdkChannelManagerPersister.swift @@ -25,7 +25,7 @@ class LdkChannelManagerPersister: Persister, ExtendedChannelManagerPersister { guard let fundingGeneration = event.getValueAsFundingGenerationReady() else { return handleEventError(event) } - + LdkEventEmitter.shared.send( withEvent: .channel_manager_funding_generation_ready, body: [ diff --git a/lib/ios/Ldk.m b/lib/ios/Ldk.m index fcc64328..3ed185cb 100644 --- a/lib/ios/Ldk.m +++ b/lib/ios/Ldk.m @@ -86,6 +86,11 @@ @interface RCT_EXTERN_MODULE(Ldk, NSObject) force:(BOOL *)force resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(createChannel:(NSString *)counterPartyNodeId + channelValueSats:(NSInteger *)channelValueSats + pushSats:(NSInteger *)pushSats + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) RCT_EXTERN_METHOD(forceCloseAllChannels:(BOOL *)broadcastLatestTx resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) diff --git a/lib/ios/Ldk.swift b/lib/ios/Ldk.swift index 1afd4843..d3e3a590 100644 --- a/lib/ios/Ldk.swift +++ b/lib/ios/Ldk.swift @@ -1,114 +1,117 @@ -import LightningDevKit import Darwin +import LightningDevKit -//MARK: ************Replicate in typescript and kotlin************ +// MARK: ************Replicate in typescript and kotlin************ enum EventTypes: String, CaseIterable { - case ldk_log = "ldk_log" - case native_log = "native_log" - case register_tx = "register_tx" - case register_output = "register_output" - case broadcast_transaction = "broadcast_transaction" - case channel_manager_funding_generation_ready = "channel_manager_funding_generation_ready" - case channel_manager_payment_claimable = "channel_manager_payment_claimable" - case channel_manager_payment_sent = "channel_manager_payment_sent" - case channel_manager_open_channel_request = "channel_manager_open_channel_request" - case channel_manager_payment_path_successful = "channel_manager_payment_path_successful" - case channel_manager_payment_path_failed = "channel_manager_payment_path_failed" - case channel_manager_payment_failed = "channel_manager_payment_failed" - case channel_manager_pending_htlcs_forwardable = "channel_manager_pending_htlcs_forwardable" - case channel_manager_spendable_outputs = "channel_manager_spendable_outputs" - case channel_manager_channel_closed = "channel_manager_channel_closed" - case channel_manager_discard_funding = "channel_manager_discard_funding" - case channel_manager_payment_claimed = "channel_manager_payment_claimed" - case emergency_force_close_channel = "emergency_force_close_channel" - case new_channel = "new_channel" - case network_graph_updated = "network_graph_updated" - case channel_manager_restarted = "channel_manager_restarted" - case backup_state_update = "backup_state_update" - case lsp_log = "lsp_log" - case used_close_address = "used_close_address" + case ldk_log + case native_log + case register_tx + case register_output + case broadcast_transaction + case channel_manager_funding_generation_ready + case channel_manager_payment_claimable + case channel_manager_payment_sent + case channel_manager_open_channel_request + case channel_manager_payment_path_successful + case channel_manager_payment_path_failed + case channel_manager_payment_failed + case channel_manager_pending_htlcs_forwardable + case channel_manager_spendable_outputs + case channel_manager_channel_closed + case channel_manager_discard_funding + case channel_manager_payment_claimed + case emergency_force_close_channel + case new_channel + case network_graph_updated + case channel_manager_restarted + case backup_state_update + case lsp_log + case used_close_address } -//***************************************************************** + +// ***************************************************************** enum LdkErrors: String { - case unknown_error = "unknown_error" - case already_init = "already_init" - case create_storage_dir_fail = "create_storage_dir_fail" - case init_storage_path = "init_storage_path" - case invalid_seed_hex = "invalid_seed_hex" - case init_chain_monitor = "init_chain_monitor" - case init_keys_manager = "init_keys_manager" - case init_user_config = "init_user_config" - case init_peer_manager = "init_peer_manager" - case invalid_network = "invalid_network" - case init_network_graph = "init_network_graph" - case init_peer_handler = "init_peer_handler" - case add_peer_fail = "add_peer_fail" - case init_channel_manager = "init_channel_manager" - case decode_invoice_fail = "decode_invoice_fail" - case invoice_payment_fail_unknown = "invoice_payment_fail_unknown" - case invoice_payment_fail_must_specify_amount = "invoice_payment_fail_must_specify_amount" - case invoice_payment_fail_must_not_specify_amount = "invoice_payment_fail_must_not_specify_amount" - case invoice_payment_fail_duplicate_payment = "invoice_payment_fail_duplicate_payment" - case invoice_payment_fail_payment_expired = "invoice_payment_fail_payment_expired" - case invoice_payment_fail_route_not_found = "invoice_payment_fail_route_not_found" - case init_ldk_currency = "init_ldk_currency" - case invoice_create_failed = "invoice_create_failed" - case claim_funds_failed = "claim_funds_failed" - case channel_close_fail = "channel_close_fail" - case channel_accept_fail = "channel_accept_fail" - case spend_outputs_fail = "spend_outputs_fail" - case failed_signing_request = "failed_signing_request" - case write_fail = "write_fail" - case read_fail = "read_fail" - case file_does_not_exist = "file_does_not_exist" - case init_network_graph_fail = "init_network_graph_fail" - case data_too_large_for_rn = "data_too_large_for_rn" - case backup_setup_required = "backup_setup_required" - case backup_setup_check_failed = "backup_setup_check_failed" - case backup_setup_failed = "backup_setup_failed" - case backup_check_failed = "backup_check_failed" - case backup_restore_failed = "backup_restore_failed" - case backup_restore_failed_existing_files = "backup_restore_failed_existing_files" - case backup_list_files_failed = "backup_list_files_failed" - case backup_fetch_file_failed = "backup_fetch_file_failed" - case backup_file_failed = "backup_file_failed" - case scorer_download_fail = "scorer_download_fail" + case unknown_error + case already_init + case create_storage_dir_fail + case init_storage_path + case invalid_seed_hex + case init_chain_monitor + case init_keys_manager + case init_user_config + case init_peer_manager + case invalid_network + case init_network_graph + case init_peer_handler + case add_peer_fail + case init_channel_manager + case decode_invoice_fail + case invoice_payment_fail_unknown + case invoice_payment_fail_must_specify_amount + case invoice_payment_fail_must_not_specify_amount + case invoice_payment_fail_duplicate_payment + case invoice_payment_fail_payment_expired + case invoice_payment_fail_route_not_found + case init_ldk_currency + case invoice_create_failed + case claim_funds_failed + case channel_close_fail + case channel_accept_fail + case start_create_channel_fail + case spend_outputs_fail + case failed_signing_request + case write_fail + case read_fail + case file_does_not_exist + case init_network_graph_fail + case data_too_large_for_rn + case backup_setup_required + case backup_setup_check_failed + case backup_setup_failed + case backup_check_failed + case backup_restore_failed + case backup_restore_failed_existing_files + case backup_list_files_failed + case backup_fetch_file_failed + case backup_file_failed + case scorer_download_fail } enum LdkCallbackResponses: String { - case storage_path_set = "storage_path_set" - case fees_updated = "fees_updated" - case log_level_updated = "log_level_updated" - case log_path_updated = "log_path_updated" - case log_write_success = "log_write_success" - case keys_manager_init_success = "keys_manager_init_success" - case channel_manager_init_success = "channel_manager_init_success" - case config_init_success = "config_init_success" - case network_graph_init_success = "network_graph_init_success" - case add_peer_success = "add_peer_success" - case add_peer_skipped = "add_peer_skipped" - case peer_already_connected = "peer_already_connected" - case peer_currently_connecting = "peer_currently_connecting" - case chain_sync_success = "chain_sync_success" - case invoice_payment_success = "invoice_payment_success" - case tx_set_confirmed = "tx_set_confirmed" - case tx_set_unconfirmed = "tx_set_unconfirmed" - case process_pending_htlc_forwards_success = "process_pending_htlc_forwards_success" - case claim_funds_success = "claim_funds_success" - case fail_htlc_backwards_success = "fail_htlc_backwards_success" - case ldk_stop = "ldk_stop" - case ldk_restart = "ldk_restart" - case accept_channel_success = "accept_channel_success" - case close_channel_success = "close_channel_success" - case file_write_success = "file_write_success" - case abandon_payment_success = "abandon_payment_success" - case backup_client_setup_success = "backup_client_setup_success" - case backup_restore_success = "backup_restore_success" - case backup_client_check_success = "backup_client_check_success" - case backup_file_success = "backup_file_success" - case scorer_download_success = "scorer_download_success" - case scorer_download_skip = "scorer_download_skip" + case storage_path_set + case fees_updated + case log_level_updated + case log_path_updated + case log_write_success + case keys_manager_init_success + case channel_manager_init_success + case config_init_success + case network_graph_init_success + case add_peer_success + case add_peer_skipped + case peer_already_connected + case peer_currently_connecting + case chain_sync_success + case invoice_payment_success + case tx_set_confirmed + case tx_set_unconfirmed + case process_pending_htlc_forwards_success + case claim_funds_success + case fail_htlc_backwards_success + case ldk_stop + case ldk_restart + case accept_channel_success + case close_channel_success + case start_create_channel_success + case file_write_success + case abandon_payment_success + case backup_client_setup_success + case backup_restore_success + case backup_client_check_success + case backup_file_success + case scorer_download_success + case scorer_download_skip } enum LdkFileNames: String, CaseIterable { @@ -121,15 +124,15 @@ enum LdkFileNames: String, CaseIterable { @objc(Ldk) class Ldk: NSObject { - //Zero config objects lazy loaded into memory when required - lazy var feeEstimator = {LdkFeeEstimator()}() - lazy var logger = {LdkLogger()}() - lazy var broadcaster = {LdkBroadcaster()}() - lazy var persister = {LdkPersister()}() - lazy var filter = {LdkFilter()}() - lazy var channelManagerPersister = {LdkChannelManagerPersister()}() - - //Config required to setup below objects + // Zero config objects lazy loaded into memory when required + lazy var feeEstimator = LdkFeeEstimator() + lazy var logger = LdkLogger() + lazy var broadcaster = LdkBroadcaster() + lazy var persister = LdkPersister() + lazy var filter = LdkFilter() + lazy var channelManagerPersister = LdkChannelManagerPersister() + + // Config required to setup below objects static var chainMonitor: ChainMonitor? var keysManager: CustomKeysManager? var channelManager: ChannelManager? @@ -142,28 +145,28 @@ class Ldk: NSObject { var ldkNetwork: Network? var ldkCurrency: Currency? - //Keep these in memory for restarting the channel manager constructor + // Keep these in memory for restarting the channel manager constructor var currentNetwork: NSString? var currentBlockchainTipHash: NSString? var currentBlockchainHeight: NSInteger? - //Peer connection checks + // Peer connection checks var backgroundedAt: Date? = nil var addedPeers: [(String, Int, String)] = [] var currentlyConnectingPeers: [String] = [] var droppedPeerTimer: Timer? = nil - //Static to be accessed from other classes + // Static to be accessed from other classes static var accountStoragePath: URL? static var channelStoragePath: URL? - //Uncomment for sending LDK team debugging output + // Uncomment for sending LDK team debugging output // override init() { // Bindings.setLogThreshold(severity: .DEBUG) // super.init() // } - //MARK: Startup methods + // MARK: Startup methods @objc func setAccountStoragePath(_ storagePath: NSString, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { @@ -213,12 +216,12 @@ class Ldk: NSObject { @objc func initKeysManager(_ seed: NSString, address: NSString, destinationScriptPublicKey: NSString, witnessProgram: NSString, witnessProgramVersion: NSInteger, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { if keysManager != nil { - //If previously started with the same key (by backup client) return success. + // If previously started with the same key (by backup client) return success. return handleResolve(resolve, .keys_manager_init_success) } let seconds = UInt64(NSDate().timeIntervalSince1970) - let nanoSeconds = UInt32.init(truncating: NSNumber(value: seconds * 1000 * 1000)) + let nanoSeconds = UInt32(truncating: NSNumber(value: seconds * 1000 * 1000)) let seedBytes = String(seed).hexaBytes guard seedBytes.count == 32 else { @@ -257,7 +260,7 @@ class Ldk: NSObject { let destinationFile = accountStoragePath.appendingPathComponent(LdkFileNames.scorer.rawValue) - //If old one is still recent, skip download. Else delete it. + // If old one is still recent, skip download. Else delete it. if FileManager().fileExists(atPath: destinationFile.path) { let fileAttributes = try? FileManager().attributesOfItem(atPath: destinationFile.path) if let creationDate = fileAttributes?[.creationDate] as? Date { @@ -301,7 +304,7 @@ class Ldk: NSObject { print("accountStoragePath: \(accountStoragePath)") do { - let read = NetworkGraph.read(ser: [UInt8](try Data(contentsOf: networkGraphStoragePath)), arg: logger) + let read = try NetworkGraph.read(ser: [UInt8](Data(contentsOf: networkGraphStoragePath)), arg: logger) if read.isOk() { networkGraph = read.getValue() LdkEventEmitter.shared.send(withEvent: .native_log, body: "Loaded network graph from file") @@ -315,12 +318,12 @@ class Ldk: NSObject { LdkEventEmitter.shared.send(withEvent: .native_log, body: "Failed to load cached network graph from disk. Will sync from scratch. \(error.localizedDescription)") } - //Normal p2p gossip sync + // Normal p2p gossip sync guard rapidGossipSyncUrl != "" else { return handleResolve(resolve, .network_graph_init_success) } - //Download url passed, enable rapid gossip sync + // Download url passed, enable rapid gossip sync do { let rapidGossipSyncStoragePath = accountStoragePath.appendingPathComponent("rapid_gossip_sync") if !FileManager().fileExists(atPath: rapidGossipSyncStoragePath.path) { @@ -329,16 +332,16 @@ class Ldk: NSObject { rapidGossipSync = RapidGossipSync(networkGraph: networkGraph!, logger: logger) - //If it's been more than 24 hours then we need to update RGS + // If it's been more than 24 hours then we need to update RGS let timestamp = networkGraph?.getLastRapidGossipSyncTimestamp() ?? 0 - let minutesDiffSinceLastRGS = (Calendar.current.dateComponents([.minute], from: Date.init(timeIntervalSince1970: TimeInterval(timestamp)), to: Date()).minute)! + let minutesDiffSinceLastRGS = (Calendar.current.dateComponents([.minute], from: Date(timeIntervalSince1970: TimeInterval(timestamp)), to: Date()).minute)! guard minutesDiffSinceLastRGS > 60 * skipHoursThreshold else { - LdkEventEmitter.shared.send(withEvent: .native_log, body: "Skipping rapid gossip sync. Last updated \(minutesDiffSinceLastRGS/60) hours ago.") + LdkEventEmitter.shared.send(withEvent: .native_log, body: "Skipping rapid gossip sync. Last updated \(minutesDiffSinceLastRGS / 60) hours ago.") return handleResolve(resolve, .network_graph_init_success) } - LdkEventEmitter.shared.send(withEvent: .native_log, body: "Rapid gossip sync applying update. Last updated \(minutesDiffSinceLastRGS/60) hours ago.") + LdkEventEmitter.shared.send(withEvent: .native_log, body: "Rapid gossip sync applying update. Last updated \(minutesDiffSinceLastRGS / 60) hours ago.") rapidGossipSync!.downloadAndUpdateGraph(downloadUrl: String(rapidGossipSyncUrl), tempStoragePath: rapidGossipSyncStoragePath, timestamp: timestamp) { [weak self] error in guard let self = self else { return } @@ -346,13 +349,13 @@ class Ldk: NSObject { if let error = error { LdkEventEmitter.shared.send(withEvent: .native_log, body: "Rapid gossip sync fail. \(error.localizedDescription).") - //Temp fix for when a RGS server is changed or reset + // Temp fix for when a RGS server is changed or reset if error.localizedDescription.contains("LightningError") { try? FileManager().removeItem(atPath: networkGraphStoragePath.path) LdkEventEmitter.shared.send(withEvent: .native_log, body: "Deleting persisted graph. Will sync from scratch on next startup.") } - return handleResolve(resolve, .network_graph_init_success) //Continue like normal, likely fine if we don't have the absolute latest state + return handleResolve(resolve, .network_graph_init_success) // Continue like normal, likely fine if we don't have the absolute latest state } LdkEventEmitter.shared.send(withEvent: .native_log, body: "Rapid gossip sync completed.") @@ -415,7 +418,7 @@ class Ldk: NSObject { let storedChannelManager = try? Data(contentsOf: accountStoragePath.appendingPathComponent(LdkFileNames.channel_manager.rawValue).standardizedFileURL) - var channelMonitorsSerialized: Array<[UInt8]> = [] + var channelMonitorsSerialized: [[UInt8]] = [] let channelFiles = try! FileManager.default.contentsOfDirectory(at: channelStoragePath, includingPropertiesForKeys: nil) LdkEventEmitter.shared.send(withEvent: .native_log, body: "Channel files \(channelFiles.count)") for channelFile in channelFiles { @@ -423,7 +426,7 @@ class Ldk: NSObject { channelMonitorsSerialized.append([UInt8](try! Data(contentsOf: channelFile.standardizedFileURL))) } - //Scorer setup + // Scorer setup let probabilisticScorer = getProbabilisticScorer(path: accountStoragePath, networkGraph: networkGraph, logger: logger) let score = probabilisticScorer.asScore() let scorer = MultiThreadedLockableScore(score: score) @@ -441,7 +444,7 @@ class Ldk: NSObject { // print("\(String(cString: strerror(22)))") let scoreParams = ProbabilisticScoringFeeParameters.initWithDefault() - scoreParams.setBasePenaltyMsat(val: 500*1000) + scoreParams.setBasePenaltyMsat(val: 500 * 1000) LdkEventEmitter.shared.send(withEvent: .native_log, body: "Overriding basePenaltyMsat: \(scoreParams.getBasePenaltyMsat())") @@ -457,12 +460,12 @@ class Ldk: NSObject { enableP2PGossip: enableP2PGossip, scorer: scorer, scoreParams: scoreParams - //TODO set payerRetries + // TODO: set payerRetries ) do { if let channelManagerSerialized = storedChannelManager { - //Restoring node + // Restoring node LdkEventEmitter.shared.send(withEvent: .native_log, body: "Restoring node from disk") channelManagerConstructor = try ChannelManagerConstructor( @@ -474,7 +477,7 @@ class Ldk: NSObject { logger: logger ) } else { - //New node + // New node LdkEventEmitter.shared.send(withEvent: .native_log, body: "Creating new channel manager") channelManagerConstructor = ChannelManagerConstructor( network: ldkNetwork!, @@ -501,7 +504,7 @@ class Ldk: NSObject { self.networkGraph = channelManagerConstructor!.netGraph } - //Listen for when the app comes into the foreground so we can restart CMC to get a new tcp peer handler + // Listen for when the app comes into the foreground so we can restart CMC to get a new tcp peer handler currentNetwork = network currentBlockchainTipHash = blockHash currentBlockchainHeight = blockHeight @@ -535,7 +538,7 @@ class Ldk: NSObject { LdkEventEmitter.shared.send(withEvent: .native_log, body: "Restarting LDK on move to foreground. App was backgrounded \(Int(secondsSinceBackgrounded))s ago") backgroundedAt = nil - restart { res in } reject: { code, message, error in } + restart { _ in } reject: { _, _, _ in } } @objc @@ -549,20 +552,21 @@ class Ldk: NSObject { @objc func restart(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { guard channelManagerConstructor != nil else { - //Wasn't yet started + // Wasn't yet started return handleReject(reject, .init_channel_manager) } - guard let currentNetwork = self.currentNetwork, - let currentBlockchainTipHash = self.currentBlockchainTipHash, - let currentBlockchainHeight = self.currentBlockchainHeight else { - //Node was never started + guard let currentNetwork = currentNetwork, + let currentBlockchainTipHash = currentBlockchainTipHash, + let currentBlockchainHeight = currentBlockchainHeight + else { + // Node was never started return handleReject(reject, .init_channel_manager) } LdkEventEmitter.shared.send(withEvent: .native_log, body: "Stopping LDK background tasks") - //Reset only objects created by initChannelManager + // Reset only objects created by initChannelManager channelManagerConstructor?.interrupt() channelManagerConstructor = nil Self.chainMonitor = nil @@ -571,13 +575,13 @@ class Ldk: NSObject { peerHandler = nil LdkEventEmitter.shared.send(withEvent: .native_log, body: "Starting LDK background tasks again") - initChannelManager(currentNetwork, blockHash: currentBlockchainTipHash, blockHeight: currentBlockchainHeight) { success in - //Notify JS that a sync is required + initChannelManager(currentNetwork, blockHash: currentBlockchainTipHash, blockHeight: currentBlockchainHeight) { _ in + // Notify JS that a sync is required LdkEventEmitter.shared.send(withEvent: .channel_manager_restarted, body: "") LdkEventEmitter.shared.send(withEvent: .native_log, body: "LDK restarted successfully") return handleResolve(resolve, .ldk_restart) - } reject: { errorCode, errorMessage, error in + } reject: { errorCode, errorMessage, _ in LdkEventEmitter.shared.send(withEvent: .native_log, body: "Error restarting LDK. \(String(describing: errorCode)) \(String(describing: errorMessage))") handleReject(reject, .unknown_error) } @@ -586,12 +590,12 @@ class Ldk: NSObject { @objc func stop(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { guard let cm = channelManagerConstructor else { - //Wasn't yet started + // Wasn't yet started return handleResolve(resolve, .ldk_stop) } stopStartDroppedPeerTimer() - removeForegroundObserver() //LDK was intentionally stopped and we shouldn't attempt a restart + removeForegroundObserver() // LDK was intentionally stopped and we shouldn't attempt a restart cm.interrupt() channelManagerConstructor = nil Self.chainMonitor = nil @@ -608,7 +612,7 @@ class Ldk: NSObject { return handleResolve(resolve, .ldk_stop) } - //MARK: Update methods + // MARK: Update methods @objc func updateFees(_ anchorChannelFee: NSInteger, nonAnchorChannelFee: NSInteger, channelCloseMinimum: NSInteger, minAllowedAnchorChannelRemoteFee: NSInteger, onChainSweep: NSInteger, minAllowedNonAnchorChannelRemoteFee: NSInteger, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { @@ -631,7 +635,7 @@ class Ldk: NSObject { @objc func syncToTip(_ header: NSString, blockHash: NSString, height: NSInteger, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { - //Sync ChannelMonitors and ChannelManager to chain tip + // Sync ChannelMonitors and ChannelManager to chain tip guard let channelManager = channelManager else { return handleReject(reject, .init_channel_manager) } @@ -643,7 +647,7 @@ class Ldk: NSObject { channelManager.asConfirm().bestBlockUpdated(header: String(header).hexaBytes, height: UInt32(height)) chainMonitor.asConfirm().bestBlockUpdated(header: String(header).hexaBytes, height: UInt32(height)) - //Used for quick restarts + // Used for quick restarts currentBlockchainTipHash = blockHash currentBlockchainHeight = height @@ -702,7 +706,7 @@ class Ldk: NSObject { .listPeers() .map { Data($0.getCounterpartyNodeId()).hexEncodedString() } - addedPeers.forEach { (address, port, pubKey) in + addedPeers.forEach { address, port, pubKey in guard !currentList.contains(pubKey) else { return } @@ -721,7 +725,7 @@ class Ldk: NSObject { @objc func addPeer(_ address: NSString, port: NSInteger, pubKey: NSString, timeout: NSInteger, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { - //timeout param not used. Only for android. + // timeout param not used. Only for android. guard backgroundedAt == nil else { LdkEventEmitter.shared.send(withEvent: .native_log, body: "App was backgrounded, skipping addPeer()") @@ -736,7 +740,7 @@ class Ldk: NSObject { return handleReject(reject, .init_peer_manager) } - //If peer is already connected don't add again + // If peer is already connected don't add again let currentList = peerManager .listPeers() .map { Data($0.getCounterpartyNodeId()).hexEncodedString() } @@ -751,13 +755,13 @@ class Ldk: NSObject { return handleResolve(resolve, .peer_currently_connecting) } - //Add to retry list if peers are dropped + // Add to retry list if peers are dropped currentlyConnectingPeers.append(String(pubKey)) let res = peerHandler.connect(address: String(address), port: UInt16(port), theirNodeId: String(pubKey).hexaBytes) currentlyConnectingPeers.removeAll { $0 == String(pubKey) } - if !addedPeers.contains(where: { (_, _, pk) in + if !addedPeers.contains(where: { _, _, pk in pk == String(pubKey) }) { addedPeers.append((String(address), Int(port), String(pubKey))) @@ -832,8 +836,8 @@ class Ldk: NSObject { } let res = trustedPeer0Conf ? - channelManager.acceptInboundChannelFromTrustedPeer0conf(temporaryChannelId: temporaryChannelId, counterpartyNodeId: counterpartyNodeId, userChannelId: [UInt8](userChannelId)) : - channelManager.acceptInboundChannel(temporaryChannelId: temporaryChannelId, counterpartyNodeId: counterpartyNodeId, userChannelId: [UInt8](userChannelId)) + channelManager.acceptInboundChannelFromTrustedPeer0conf(temporaryChannelId: temporaryChannelId, counterpartyNodeId: counterpartyNodeId, userChannelId: [UInt8](userChannelId)) : + channelManager.acceptInboundChannel(temporaryChannelId: temporaryChannelId, counterpartyNodeId: counterpartyNodeId, userChannelId: [UInt8](userChannelId)) guard res.isOk() else { guard let error = res.getError() else { @@ -869,8 +873,8 @@ class Ldk: NSObject { let counterpartyNodeId = String(counterPartyNodeId).hexaBytes let res = force ? - channelManager.forceCloseBroadcastingLatestTxn(channelId: channelId, counterpartyNodeId: counterpartyNodeId) : - channelManager.closeChannel(channelId: channelId, counterpartyNodeId: counterpartyNodeId) + channelManager.forceCloseBroadcastingLatestTxn(channelId: channelId, counterpartyNodeId: counterpartyNodeId) : + channelManager.closeChannel(channelId: channelId, counterpartyNodeId: counterpartyNodeId) guard res.isOk() else { guard let error = res.getError() else { return handleReject(reject, .channel_close_fail) @@ -880,7 +884,7 @@ class Ldk: NSObject { case .APIMisuseError: return handleReject(reject, .channel_close_fail, nil, error.getValueAsApiMisuseError()?.getErr()) case .ChannelUnavailable: - return handleReject(reject, .channel_close_fail, nil, "Channel unavailable for closing") //Crashes when returning error.getValueAsChannelUnavailable()?.getErr() + return handleReject(reject, .channel_close_fail, nil, "Channel unavailable for closing") // Crashes when returning error.getValueAsChannelUnavailable()?.getErr() case .FeeRateTooHigh: return handleReject(reject, .channel_close_fail, nil, error.getValueAsFeeRateTooHigh()?.getErr()) case .IncompatibleShutdownScript: @@ -895,6 +899,42 @@ class Ldk: NSObject { return handleResolve(resolve, .close_channel_success) } + @objc + func createChannel(_ counterPartyNodeId: NSString, channelValueSats: NSInteger, pushSats: NSInteger, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { + guard let channelManager = channelManager else { + return handleReject(reject, .init_channel_manager) + } + + guard let keysManager else { + return handleReject(reject, .init_keys_manager) + } + + let theirNetworkKey = String(counterPartyNodeId).hexaBytes + // TODO: check peer is connected first + + let channelValueSatoshis = UInt64(channelValueSats) + let pushMsat = UInt64(pushSats) * 1000 + + guard let channelId = Bindings.ChannelId.initWithTemporaryFromEntropySource(entropySource: keysManager.inner.asEntropySource()).getA() else { + return handleReject(reject, .start_create_channel_fail) + } + + let res = channelManager.createChannel( + theirNetworkKey: theirNetworkKey, + channelValueSatoshis: channelValueSatoshis, + pushMsat: pushMsat, + userChannelId: channelId, + temporaryChannelId: .initWithTemporaryFromEntropySource(entropySource: keysManager.inner.asEntropySource()), + overrideConfig: .initWithDefault() + ) + + if res.isOk() { + return resolve(Data(res.getValue()?.getA() ?? []).hexEncodedString()) + } + + handleReject(reject, .start_create_channel_fail) + } + @objc func forceCloseAllChannels(_ broadcastLatestTx: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { guard let channelManager = channelManager else { @@ -916,17 +956,17 @@ class Ldk: NSObject { return handleReject(reject, .init_keys_manager) } - var ldkDescriptors: Array = [] + var ldkDescriptors: [SpendableOutputDescriptor] = [] for descriptor in descriptorsSerialized { let read = SpendableOutputDescriptor.read(ser: (descriptor as! String).hexaBytes) - guard read.isOk() else { + guard read.isOk() else { return handleReject(reject, .spend_outputs_fail, nil, read.getError().debugDescription) } ldkDescriptors.append(read.getValue()!) } - var ldkOutputs: Array = [] + var ldkOutputs: [TxOut] = [] for output in outputs { let d = output as! NSDictionary ldkOutputs.append(TxOut(scriptPubkey: (d["script_pubkey"] as! String).hexaBytes, value: d["value"] as! UInt64)) @@ -949,16 +989,16 @@ class Ldk: NSObject { return resolve(Data(res.getValue()!).hexEncodedString()) } - //MARK: Payments + // MARK: Payments @objc func decode(_ paymentRequest: NSString, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { let parsedInvoice = Bolt11Invoice.fromStr(s: String(paymentRequest)) - guard parsedInvoice.isOk(), let invoice = parsedInvoice.getValue() else { + guard parsedInvoice.isOk(), let invoice = parsedInvoice.getValue() else { let error = parsedInvoice.getError()?.getValueAsParseError() return handleReject(reject, .decode_invoice_fail, nil, error?.toStr()) } - return resolve(invoice.asJson) //Invoice class extended in Helpers file + return resolve(invoice.asJson) // Invoice class extended in Helpers file } @objc @@ -973,18 +1013,18 @@ class Ldk: NSObject { let isZeroValueInvoice = invoice.amountMilliSatoshis() == nil - //If it's a zero invoice and we don't have an amount then don't proceed + // If it's a zero invoice and we don't have an amount then don't proceed guard !(isZeroValueInvoice && amountSats == 0) else { return handleReject(reject, .invoice_payment_fail_must_specify_amount) } - //Amount was set but not allowed to set own amount + // Amount was set but not allowed to set own amount guard !(amountSats > 0 && !isZeroValueInvoice) else { return handleReject(reject, .invoice_payment_fail_must_not_specify_amount) } let paymentId = invoice.paymentHash()! - let (paymentHash, recipientOnion, routeParameters) = isZeroValueInvoice ? Bindings.paymentParametersFromZeroAmountInvoice(invoice: invoice, amountMsat: UInt64(amountSats*1000)).getValue()! : Bindings.paymentParametersFromInvoice(invoice: invoice).getValue()! + let (paymentHash, recipientOnion, routeParameters) = isZeroValueInvoice ? Bindings.paymentParametersFromZeroAmountInvoice(invoice: invoice, amountMsat: UInt64(amountSats * 1000)).getValue()! : Bindings.paymentParametersFromInvoice(invoice: invoice).getValue()! let res = channelManager.sendPayment(paymentHash: paymentHash, recipientOnion: recipientOnion, paymentId: paymentId, routeParams: routeParameters, retryStrategy: .initWithTimeout(a: UInt64(timeoutSeconds))) @@ -995,7 +1035,7 @@ class Ldk: NSObject { "payment_hash": Data(invoice.paymentHash() ?? []).hexEncodedString(), "amount_sat": isZeroValueInvoice ? amountSats : (invoice.amountMilliSatoshis() ?? 0) / 1000, "unix_timestamp": Int(Date().timeIntervalSince1970), - "state": res.isOk() ? "pending" : "failed" + "state": res.isOk() ? "pending" : "failed", ]) if res.isOk() { @@ -1048,7 +1088,7 @@ class Ldk: NSObject { logger: logger, network: ldkCurrency, amtMsat: amountSats == 0 ? nil : UInt64(amountSats) * 1000, - description: String(description).withoutEmojis, //TODO remove to allow emojis when fixed in ldk + description: String(description).withoutEmojis, // TODO: remove to allow emojis when fixed in ldk invoiceExpiryDeltaSecs: UInt32(expiryDelta), minFinalCltvExpiryDelta: nil ) @@ -1058,10 +1098,10 @@ class Ldk: NSObject { return handleReject(reject, .invoice_create_failed) } - return resolve(invoice.asJson) //Invoice class extended in Helpers file + return resolve(invoice.asJson) // Invoice class extended in Helpers file } - guard let error = res.getError(), let creationError = error.getValueAsCreationError() else { + guard let error = res.getError(), let creationError = error.getValueAsCreationError() else { return handleReject(reject, .invoice_create_failed) } @@ -1103,7 +1143,7 @@ class Ldk: NSObject { return handleResolve(resolve, .fail_htlc_backwards_success) } - //MARK: Fetch methods + // MARK: Fetch methods @objc func version(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { let res: [String: String] = [ @@ -1256,11 +1296,11 @@ class Ldk: NSObject { } guard let shortIdUint64 = UInt64(String(shortChannelId)) else { - return handleReject(reject, .init_network_graph) //TODO add new error code + return handleReject(reject, .init_network_graph) // TODO: add new error code } guard let channelInfo = networkGraph.channel(shortChannelId: shortIdUint64) else { - return handleReject(reject, .init_network_graph) //TODO add new error code + return handleReject(reject, .init_network_graph) // TODO: add new error code } return resolve(channelInfo.asJson) @@ -1281,14 +1321,14 @@ class Ldk: NSObject { return resolve(chainMonitor.getClaimableBalancesAsJson(ignoredChannels: ignoredChannels)) } - //MARK: Misc functions + // MARK: Misc functions @objc func writeToFile(_ fileName: NSString, path: NSString, content: NSString, format: NSString, remotePersist: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { let fileUrl: URL do { if path != "" { - //Make sure custom path exists by creating if missing + // Make sure custom path exists by creating if missing let pathUrl = URL(fileURLWithPath: String(path), isDirectory: true) if !FileManager().fileExists(atPath: pathUrl.path) { @@ -1297,7 +1337,7 @@ class Ldk: NSObject { fileUrl = URL(fileURLWithPath: String(path)).appendingPathComponent(String(fileName)) } else { - //Assume default directory if no path was set + // Assume default directory if no path was set guard let accountStoragePath = Ldk.accountStoragePath else { return handleReject(reject, .init_storage_path) } @@ -1321,9 +1361,9 @@ class Ldk: NSObject { try fileData.write(to: fileUrl) - //Save to remote server if required + // Save to remote server if required if remotePersist { - //Continue to retry remote persist in background + // Continue to retry remote persist in background BackupClient.addToPersistQueue(.misc(fileName: String(fileName)), [UInt8](fileData)) } @@ -1340,7 +1380,7 @@ class Ldk: NSObject { if path != "" { fileUrl = URL(fileURLWithPath: String(path)).appendingPathComponent(String(fileName)) } else { - //Assume default directory if no path was set + // Assume default directory if no path was set guard let accountStoragePath = Ldk.accountStoragePath else { return handleReject(reject, .init_storage_path) } @@ -1357,9 +1397,9 @@ class Ldk: NSObject { let timestamp = ((attr[FileAttributeKey.modificationDate] as? Date)?.timeIntervalSince1970 ?? 0).rounded() if format == "hex" { - resolve(["content": try Data(contentsOf: fileUrl).hexEncodedString(), "timestamp": timestamp] as [String : Any]) + try resolve(["content": Data(contentsOf: fileUrl).hexEncodedString(), "timestamp": timestamp] as [String: Any]) } else { - resolve(["content": try String(contentsOf: fileUrl, encoding: .utf8), "timestamp": timestamp] as [String : Any]) + try resolve(["content": String(contentsOf: fileUrl, encoding: .utf8), "timestamp": timestamp] as [String: Any]) } } catch { return handleReject(reject, .read_fail, error, "Failed to read \(format) content from file \(fileUrl.path)") @@ -1394,7 +1434,6 @@ class Ldk: NSObject { @objc func spendRecoveredForceCloseOutputs(_ transaction: NSString, confirmationHeight: NSInteger, changeDestinationScript: NSString, useInner: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { - guard let channelStoragePath = Ldk.channelStoragePath, let keysManager, let channelManager else { return handleReject(reject, .init_storage_path) } @@ -1411,7 +1450,7 @@ class Ldk: NSObject { for channelFile in channelFiles { let channelId = channelFile.lastPathComponent.replacingOccurrences(of: ".bin", with: "") - //Ignore open channels + // Ignore open channels guard !openChannelIds.contains(channelId) else { continue } @@ -1440,19 +1479,20 @@ class Ldk: NSObject { } let res = useInner ? keysManager.inner.asOutputSpender().spendSpendableOutputs( - descriptors: descriptors, - outputs: [], - changeDestinationScript: String(changeDestinationScript).hexaBytes, - feerateSatPer1000Weight: feeEstimator.getEstSatPer1000Weight(confirmationTarget: .OnChainSweep), - locktime: nil) - : - keysManager.spendSpendableOutputs( descriptors: descriptors, outputs: [], changeDestinationScript: String(changeDestinationScript).hexaBytes, feerateSatPer1000Weight: feeEstimator.getEstSatPer1000Weight(confirmationTarget: .OnChainSweep), locktime: nil ) + : + keysManager.spendSpendableOutputs( + descriptors: descriptors, + outputs: [], + changeDestinationScript: String(changeDestinationScript).hexaBytes, + feerateSatPer1000Weight: feeEstimator.getEstSatPer1000Weight(confirmationTarget: .OnChainSweep), + locktime: nil + ) guard res.isOk() else { LdkEventEmitter.shared.send(withEvent: .native_log, body: "Failed to spend output from closed channel: \(channelId).") @@ -1497,7 +1537,7 @@ class Ldk: NSObject { logDump.append("Ready: \(channel.getIsChannelReady() ? "YES" : "NO")") logDump.append("Usable: \(channel.getIsUsable() ? "YES" : "NO")") - logDump.append("Balance: \(channel.getBalanceMsat()/1000) sats") + logDump.append("Balance: \(channel.getBalanceMsat() / 1000) sats") } } @@ -1518,9 +1558,9 @@ class Ldk: NSObject { logDump.append("RGS last sync time unavailable.") } - if let peers = peerManager?.listPeers() { + if let peers = peerManager?.listPeers() { if peers.count > 0 { - peers.forEach { (peer) in + peers.forEach { peer in logDump.append("Connected peer: \(Data(peer.getCounterpartyNodeId()).hexEncodedString())") } } else { @@ -1543,7 +1583,7 @@ class Ldk: NSObject { resolve(logString) } - //MARK: Backup methods + // MARK: Backup methods @objc func backupSetup(_ seed: NSString, network: NSString, server: NSString, serverPubKey: NSString, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { let seedBytes = String(seed).hexaBytes @@ -1553,7 +1593,7 @@ class Ldk: NSObject { } let seconds = UInt64(NSDate().timeIntervalSince1970) - let nanoSeconds = UInt32.init(truncating: NSNumber(value: seconds * 1000 * 1000)) + let nanoSeconds = UInt32(truncating: NSNumber(value: seconds * 1000 * 1000)) let keysManager = KeysManager( seed: String(seed).hexaBytes, startingTimeSecs: seconds, @@ -1598,13 +1638,13 @@ class Ldk: NSObject { if !overwrite { let fileManager = FileManager.default - //Make sure channel manager and channel monitors don't exist + // Make sure channel manager and channel monitors don't exist if fileManager.fileExists(atPath: accountStoragePath.appendingPathComponent(LdkFileNames.channel_manager.rawValue).path) { handleReject(reject, .backup_restore_failed_existing_files) return } - if !(try fileManager.contentsOfDirectory(atPath: channelStoragePath.path).isEmpty) { + if try !(fileManager.contentsOfDirectory(atPath: channelStoragePath.path).isEmpty) { handleReject(reject, .backup_restore_failed_existing_files) return } @@ -1613,7 +1653,7 @@ class Ldk: NSObject { let completeBackup = try BackupClient.retrieveCompleteBackup() for file in completeBackup.files { - //Decide if .json or .bin by checking enum + // Decide if .json or .bin by checking enum let key = file.key.replacingOccurrences(of: ".bin", with: "") var fileName = "\(key).json" @@ -1658,7 +1698,7 @@ class Ldk: NSObject { let result = try BackupClient.listFiles() resolve([ "list": result.list, - "channel_monitors": result.channel_monitors + "channel_monitors": result.channel_monitors, ]) } catch { handleReject(reject, .backup_list_files_failed, error, error.localizedDescription) @@ -1694,14 +1734,14 @@ class Ldk: NSObject { do { let data = try BackupClient.retrieve(.misc(fileName: String(fileName))) - resolve(String.init(data: data, encoding: .utf8)) + resolve(String(data: data, encoding: .utf8)) } catch { handleReject(reject, .backup_fetch_file_failed, error, error.localizedDescription) } } } -//MARK: Singleton react native event emitter +// MARK: Singleton react native event emitter @objc(LdkEventEmitter) class LdkEventEmitter: RCTEventEmitter { public static var shared: LdkEventEmitter! @@ -1712,7 +1752,7 @@ class LdkEventEmitter: RCTEventEmitter { } public func send(withEvent eventType: EventTypes, body: Any) { - //TODO convert all bytes to hex here + // TODO: convert all bytes to hex here sendEvent(withName: eventType.rawValue, body: body) if eventType == .native_log { diff --git a/lib/package.json b/lib/package.json index 0aa9006d..cc9cb518 100644 --- a/lib/package.json +++ b/lib/package.json @@ -1,7 +1,7 @@ { "name": "@synonymdev/react-native-ldk", "title": "React Native LDK", - "version": "0.0.148", + "version": "0.0.150", "description": "React Native wrapper for LDK", "main": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/lib/src/ldk.ts b/lib/src/ldk.ts index 4d294357..4c67a7d5 100644 --- a/lib/src/ldk.ts +++ b/lib/src/ldk.ts @@ -39,6 +39,7 @@ import { TInitKeysManager, TSpendRecoveredForceCloseOutputsReq, TChannelMonitor, + TCreateChannelReq, } from './utils/types'; import { extractPaymentRequest } from './utils/helpers'; @@ -480,6 +481,25 @@ class LDK { } } + /** + * Start create channel process and returns channel ID that can be used when funding the channel. + * https://docs.rs/lightning/latest/lightning/ln/channelmanager/struct.ChannelManager.html#method.create_channel + * @param counterPartyNodeId + * @param channelValueSats + * @param pushSats + * @returns {Promise | Ok | Err>>} + */ + async createChannel({counterPartyNodeId, channelValueSats, pushSats}: TCreateChannelReq): Promise> { + try { + const res = await NativeLDK.createChannel(counterPartyNodeId, channelValueSats, pushSats); + this.writeDebugToLog('createChannel'); + return ok(res); + } catch (e) { + this.writeErrorToLog('createChannel', e); + return err(e); + } + } + /** * Force close all channels * @param broadcastLatestTx diff --git a/lib/src/lightning-manager.ts b/lib/src/lightning-manager.ts index a977a221..1cb724e8 100644 --- a/lib/src/lightning-manager.ts +++ b/lib/src/lightning-manager.ts @@ -58,6 +58,7 @@ import { TLspLogPayload, TLspLogEvent, TChannelMonitor, + TCreateChannelReq, } from './utils/types'; import { appendPath, @@ -170,10 +171,10 @@ class LightningManager { this.onBroadcastTransaction.bind(this), ); //Channel manager handle events: - ldk.onEvent( - EEventTypes.channel_manager_funding_generation_ready, - this.onChannelManagerFundingGenerationReady.bind(this), - ); + // ldk.onEvent( + // EEventTypes.channel_manager_funding_generation_ready, + // this.onChannelManagerFundingGenerationReady.bind(this), + // ); ldk.onEvent( EEventTypes.channel_manager_payment_claimable, this.onChannelManagerPaymentClaimable.bind(this), @@ -2050,13 +2051,13 @@ class LightningManager { //LDK channel manager events //All events and their values: https://docs.rs/lightning/latest/lightning/util/events/enum.Event.html //Sample node for examples on how to handle events: https://github.com/lightningdevkit/ldk-sample/blob/c0a722430b8fbcb30310d64487a32aae839da3e8/src/main.rs#L600 - private onChannelManagerFundingGenerationReady( - res: TChannelManagerFundingGenerationReady, - ): void { - console.log( - `onChannelManagerFundingGenerationReady: ${JSON.stringify(res)}`, - ); //TODO - } + // private onChannelManagerFundingGenerationReady( + // res: TChannelManagerFundingGenerationReady, + // ): void { + // console.log( + // `onChannelManagerFundingGenerationReady: ${JSON.stringify(res)}`, + // ); //TODO + // } private async onChannelManagerPaymentClaimable( res: TChannelManagerClaim, @@ -2437,6 +2438,39 @@ class LightningManager { remotePersist: true, }); } + + /** + * Creates a new channel with the provided peer and then listens for the channel manager funding generation ready event. + * Once event is triggered, those details can be used to create the funding transaction. + */ + async createChannel(req: TCreateChannelReq): Promise> { + const res = await ldk.createChannel(req); + if (res.isErr()) { + return err(res.error); + } + + return new Promise((resolve, reject) => { + // Channel funding ready event should be instant but if it fails and we don't get the event, we should reject. + const timeout = setTimeout(() => { + reject(err(new Error("Event not triggered within 5 seconds"))); + }, 5000); + + // Listen for the event for the channel funding details + ldk.onEvent(EEventTypes.channel_manager_funding_generation_ready, (eventRes: TChannelManagerFundingGenerationReady) => { + clearTimeout(timeout); + resolve(ok(eventRes)); + }); + + ldk.onEvent(EEventTypes.channel_manager_channel_closed, (eventRes: TChannelManagerChannelClosed) => { + if (eventRes.channel_id === res.value) { + clearTimeout(timeout); + reject(err("Channel closed before funding")); + } + }); + }); + } + + //TODO: fund channel } export default new LightningManager(); diff --git a/lib/src/utils/types.ts b/lib/src/utils/types.ts index 8cb617b1..d9d2c098 100644 --- a/lib/src/utils/types.ts +++ b/lib/src/utils/types.ts @@ -281,6 +281,12 @@ export type TCloseChannelReq = { force?: boolean; }; +export type TCreateChannelReq = { + counterPartyNodeId: string; + channelValueSats: number; + pushSats: number; +}; + export type TAcceptChannelReq = { temporaryChannelId: string; counterPartyNodeId: string; From d1ee7034e2914a5ab237d8f759d348d08dac2740 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 29 Aug 2024 15:57:08 +0200 Subject: [PATCH 2/7] feat: ios fund created channel --- example/utils/helpers.ts | 15 +++++++++++++++ lib/ios/Ldk.m | 5 +++++ lib/ios/Ldk.swift | 21 +++++++++++++++++++++ lib/src/ldk.ts | 20 ++++++++++++++++++++ lib/src/utils/types.ts | 6 ++++++ 5 files changed, 67 insertions(+) diff --git a/example/utils/helpers.ts b/example/utils/helpers.ts index eace4214..2a0dd5a1 100644 --- a/example/utils/helpers.ts +++ b/example/utils/helpers.ts @@ -240,3 +240,18 @@ export const ldkNetwork = (network: TAvailableNetworks): ENetworks => { return ENetworks.signet; } }; + + +export const scriptToAddress = (outputScript: string): string => { + if (!outputScript.startsWith('0020')) { + throw new Error('Invalid output script, currently example app only supports P2WSH'); + } + + outputScript = outputScript.slice(4); + + const scriptPubKey = Buffer.from(outputScript, 'hex'); + const network = getNetwork(selectedNetwork); + const address = bitcoin.payments.p2wsh({ hash: scriptPubKey, network }).address; + + return address ?? ""; +} \ No newline at end of file diff --git a/lib/ios/Ldk.m b/lib/ios/Ldk.m index 3ed185cb..56114719 100644 --- a/lib/ios/Ldk.m +++ b/lib/ios/Ldk.m @@ -91,6 +91,11 @@ @interface RCT_EXTERN_MODULE(Ldk, NSObject) pushSats:(NSInteger *)pushSats resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(fundChannel:(NSString *)temporaryChannelId + counterpartyNodeId:(NSString *)counterpartyNodeId + fundingTransaction:(NSString *)fundingTransaction + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) RCT_EXTERN_METHOD(forceCloseAllChannels:(BOOL *)broadcastLatestTx resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) diff --git a/lib/ios/Ldk.swift b/lib/ios/Ldk.swift index d3e3a590..f1148f67 100644 --- a/lib/ios/Ldk.swift +++ b/lib/ios/Ldk.swift @@ -59,6 +59,7 @@ enum LdkErrors: String { case channel_close_fail case channel_accept_fail case start_create_channel_fail + case fund_channel_fail case spend_outputs_fail case failed_signing_request case write_fail @@ -104,6 +105,7 @@ enum LdkCallbackResponses: String { case accept_channel_success case close_channel_success case start_create_channel_success + case fund_channel_success case file_write_success case abandon_payment_success case backup_client_setup_success @@ -935,6 +937,25 @@ class Ldk: NSObject { handleReject(reject, .start_create_channel_fail) } + @objc + func fundChannel(_ temporaryChannelId: NSString, counterpartyNodeId: NSString, fundingTransaction: NSString, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { + guard let channelManager = channelManager else { + return handleReject(reject, .init_channel_manager) + } + + let res = channelManager.fundingTransactionGenerated( + temporaryChannelId: .initWith(aArg: String(temporaryChannelId).hexaBytes), + counterpartyNodeId: String(counterpartyNodeId).hexaBytes, + fundingTransaction: String(fundingTransaction).hexaBytes + ) + + if res.isOk() { + return handleResolve(resolve, .fund_channel_success) + } + + handleReject(reject, .fund_channel_fail) + } + @objc func forceCloseAllChannels(_ broadcastLatestTx: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { guard let channelManager = channelManager else { diff --git a/lib/src/ldk.ts b/lib/src/ldk.ts index 4c67a7d5..58717c39 100644 --- a/lib/src/ldk.ts +++ b/lib/src/ldk.ts @@ -40,6 +40,7 @@ import { TSpendRecoveredForceCloseOutputsReq, TChannelMonitor, TCreateChannelReq, + TFundChannelReq, } from './utils/types'; import { extractPaymentRequest } from './utils/helpers'; @@ -500,6 +501,25 @@ class LDK { } } + /** + * Funds an already created channel with a signed transaction. + * https://docs.rs/lightning/latest/lightning/ln/channelmanager/struct.ChannelManager.html#method.funding_transaction_generated + * @param temporaryChannelId + * @param counterPartyNodeId + * @param fundingTransaction + * @returns {Promise | Ok | Err>>} + */ + async fundChannel({temporaryChannelId, counterPartyNodeId, fundingTransaction}: TFundChannelReq): Promise> { + try { + const res = await NativeLDK.fundChannel(temporaryChannelId, counterPartyNodeId, fundingTransaction); + this.writeDebugToLog('fundChannel'); + return ok(res); + } catch (e) { + this.writeErrorToLog('fundChannel', e); + return err(e); + } + } + /** * Force close all channels * @param broadcastLatestTx diff --git a/lib/src/utils/types.ts b/lib/src/utils/types.ts index d9d2c098..a011cd4d 100644 --- a/lib/src/utils/types.ts +++ b/lib/src/utils/types.ts @@ -287,6 +287,12 @@ export type TCreateChannelReq = { pushSats: number; }; +export type TFundChannelReq = { + temporaryChannelId: string; + counterPartyNodeId: string; + fundingTransaction: string; +}; + export type TAcceptChannelReq = { temporaryChannelId: string; counterPartyNodeId: string; From 31e22d34c90f3ae1fe422f81adf17f5df2043dfc Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 30 Aug 2024 12:38:18 +0200 Subject: [PATCH 3/7] feat: android open and fund channels --- example/utils/helpers.ts | 17 +----- .../main/java/com/reactnativeldk/LdkModule.kt | 52 +++++++++++++++++++ lib/ios/Ldk.swift | 11 ++-- 3 files changed, 60 insertions(+), 20 deletions(-) diff --git a/example/utils/helpers.ts b/example/utils/helpers.ts index 2a0dd5a1..c219a535 100644 --- a/example/utils/helpers.ts +++ b/example/utils/helpers.ts @@ -239,19 +239,4 @@ export const ldkNetwork = (network: TAvailableNetworks): ENetworks => { case 'bitcoinSignet': return ENetworks.signet; } -}; - - -export const scriptToAddress = (outputScript: string): string => { - if (!outputScript.startsWith('0020')) { - throw new Error('Invalid output script, currently example app only supports P2WSH'); - } - - outputScript = outputScript.slice(4); - - const scriptPubKey = Buffer.from(outputScript, 'hex'); - const network = getNetwork(selectedNetwork); - const address = bitcoin.payments.p2wsh({ hash: scriptPubKey, network }).address; - - return address ?? ""; -} \ No newline at end of file +}; \ No newline at end of file diff --git a/lib/android/src/main/java/com/reactnativeldk/LdkModule.kt b/lib/android/src/main/java/com/reactnativeldk/LdkModule.kt index 1f1a8b79..a771c388 100644 --- a/lib/android/src/main/java/com/reactnativeldk/LdkModule.kt +++ b/lib/android/src/main/java/com/reactnativeldk/LdkModule.kt @@ -86,6 +86,8 @@ enum class LdkErrors { invoice_create_failed, init_scorer_failed, channel_close_fail, + start_create_channel_fail, + fund_channel_fail, channel_accept_fail, spend_outputs_fail, failed_signing_request, @@ -126,6 +128,8 @@ enum class LdkCallbackResponses { ldk_restart, accept_channel_success, close_channel_success, + start_create_channel_fail, + fund_channel_success, file_write_success, backup_client_setup_success, backup_restore_success, @@ -764,6 +768,54 @@ class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMod handleResolve(promise, LdkCallbackResponses.close_channel_success) } + @ReactMethod + fun createChannel(counterPartyNodeId: String, channelValueSats: Double, pushSats: Double, promise: Promise) { + channelManager ?: return handleReject(promise, LdkErrors.init_channel_manager) + keysManager ?: return handleReject(promise, LdkErrors.init_keys_manager) + + val theirNetworkKey = counterPartyNodeId.hexa() + val channelValueSatoshis = channelValueSats.toLong() + val pushMsat = pushSats.toLong() * 1000 + val userChannelIdBytes = ByteArray(16) + Random().nextBytes(userChannelIdBytes) + val userChannelId = UInt128(userChannelIdBytes) + + val tempChannelId = ChannelId.temporary_from_entropy_source(keysManager!!.inner.as_EntropySource()) + + val res = channelManager!!.create_channel( + theirNetworkKey, + channelValueSatoshis, + pushMsat, + userChannelId, + tempChannelId, + UserConfig.with_default() + ) + + if (!res.is_ok) { + return handleReject(promise, LdkErrors.start_create_channel_fail) + } + + handleResolve(promise, LdkCallbackResponses.start_create_channel_fail) + } + + @ReactMethod + fun fundChannel(temporaryChannelId: String, counterPartyNodeId: String, fundingTransaction: String, promise: Promise) { + channelManager ?: return handleReject(promise, LdkErrors.init_channel_manager) + + val res = channelManager!!.funding_transaction_generated( + ChannelId.of(temporaryChannelId.hexa()), + counterPartyNodeId.hexa(), + fundingTransaction.hexa() + ) + + if (res.is_ok) { + handleResolve(promise, LdkCallbackResponses.fund_channel_success) + return + } + + handleReject(promise, LdkErrors.fund_channel_fail) + } + @ReactMethod fun forceCloseAllChannels(broadcastLatestTx: Boolean, promise: Promise) { channelManager ?: return handleReject(promise, LdkErrors.init_channel_manager) diff --git a/lib/ios/Ldk.swift b/lib/ios/Ldk.swift index f1148f67..7f16ff7d 100644 --- a/lib/ios/Ldk.swift +++ b/lib/ios/Ldk.swift @@ -917,16 +917,19 @@ class Ldk: NSObject { let channelValueSatoshis = UInt64(channelValueSats) let pushMsat = UInt64(pushSats) * 1000 - guard let channelId = Bindings.ChannelId.initWithTemporaryFromEntropySource(entropySource: keysManager.inner.asEntropySource()).getA() else { - return handleReject(reject, .start_create_channel_fail) + var userChannelId = Data(count: 32) + userChannelId.withUnsafeMutableBytes { mutableBytes in + arc4random_buf(mutableBytes.baseAddress, 32) } + let temporaryChannelId = Bindings.ChannelId.initWithTemporaryFromEntropySource(entropySource: keysManager.inner.asEntropySource()) + let res = channelManager.createChannel( theirNetworkKey: theirNetworkKey, channelValueSatoshis: channelValueSatoshis, pushMsat: pushMsat, - userChannelId: channelId, - temporaryChannelId: .initWithTemporaryFromEntropySource(entropySource: keysManager.inner.asEntropySource()), + userChannelId: [UInt8](userChannelId), + temporaryChannelId: temporaryChannelId, overrideConfig: .initWithDefault() ) From 3ea51d39c11806c4d5e4877e82c5912868e30573 Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 30 Aug 2024 14:20:37 +0200 Subject: [PATCH 4/7] feat: example app channel funding --- example/Dev.tsx | 100 ++++++++++++++---- .../main/java/com/reactnativeldk/LdkModule.kt | 12 +++ .../classes/LdkChannelManagerPersister.kt | 2 +- lib/src/lightning-manager.ts | 7 -- 4 files changed, 93 insertions(+), 28 deletions(-) diff --git a/example/Dev.tsx b/example/Dev.tsx index ab2086f6..3a848a18 100644 --- a/example/Dev.tsx +++ b/example/Dev.tsx @@ -25,7 +25,12 @@ import lm, { TChannelUpdate, } from '@synonymdev/react-native-ldk'; import { backupServerDetails, peers } from './utils/constants'; -import { createNewAccount, getAccount, getAddress } from './utils/helpers'; +import { + createNewAccount, + getAccount, + getAddress, + getAddressFromScriptPubKey, +} from './utils/helpers'; import RNFS from 'react-native-fs'; let logSubscription: EmitterSubscription | undefined; @@ -41,6 +46,7 @@ const Dev = (): ReactElement => { const [nodeStarted, setNodeStarted] = useState(false); const [showLogs, setShowLogs] = useState(false); const [logContent, setLogContent] = useState(''); + const [temporaryChannelId, setTemporaryChannelId] = useState(''); //For funding channels locally useEffect(() => { //Restarting LDK on each code update causes constant errors. @@ -231,7 +237,6 @@ const Dev = (): ReactElement => { } }} /> -