diff --git a/lib/android/src/main/java/com/reactnativeldk/LdkModule.kt b/lib/android/src/main/java/com/reactnativeldk/LdkModule.kt index 5be5315c..9f94e873 100644 --- a/lib/android/src/main/java/com/reactnativeldk/LdkModule.kt +++ b/lib/android/src/main/java/com/reactnativeldk/LdkModule.kt @@ -118,6 +118,7 @@ enum class LdkCallbackResponses { tx_set_unconfirmed, process_pending_htlc_forwards_success, claim_funds_success, + fail_htlc_backwards_success, ldk_stop, ldk_restart, accept_channel_success, @@ -506,6 +507,7 @@ class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMod handleResolve(promise, LdkCallbackResponses.channel_manager_init_success) } + @ReactMethod fun restart(promise: Promise) { if (channelManagerConstructor == null) { @@ -875,6 +877,15 @@ class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMod return handleResolve(promise, LdkCallbackResponses.claim_funds_success) } + @ReactMethod + fun failHtlcBackwards(paymentHash: String, promise: Promise) { + channelManager ?: return handleReject(promise, LdkErrors.init_channel_manager) + + channelManager!!.fail_htlc_backwards(paymentHash.hexa()) + + return handleResolve(promise, LdkCallbackResponses.fail_htlc_backwards_success) + } + //MARK: Fetch methods @ReactMethod diff --git a/lib/android/src/main/java/com/reactnativeldk/classes/LdkChannelManagerPersister.kt b/lib/android/src/main/java/com/reactnativeldk/classes/LdkChannelManagerPersister.kt index fcd44b85..1deb5336 100644 --- a/lib/android/src/main/java/com/reactnativeldk/classes/LdkChannelManagerPersister.kt +++ b/lib/android/src/main/java/com/reactnativeldk/classes/LdkChannelManagerPersister.kt @@ -254,6 +254,11 @@ class LdkChannelManagerPersister: ChannelManagerConstructor.EventHandler { val existingPayment = existingPayments.getJSONObject(i) //Replace entry if payment hash exists (Confirmed payment replacing pending) if (existingPayment.getString("payment_hash") == payment.getString("payment_hash")) { + //Don't replace a successful payment. If a 2nd wallet tries to pay an invoice the first successful one should not be overwritten. + if (existingPayment.getString("state") == "successful") { + return + } + payments[i] = mergeObj(existingPayment, payment.toHashMap()) paymentReplaced = true continue diff --git a/lib/ios/Classes/LdkChannelManagerPersister.swift b/lib/ios/Classes/LdkChannelManagerPersister.swift index b4158c40..3d96d11f 100644 --- a/lib/ios/Classes/LdkChannelManagerPersister.swift +++ b/lib/ios/Classes/LdkChannelManagerPersister.swift @@ -412,6 +412,11 @@ class LdkChannelManagerPersister: Persister, ExtendedChannelManagerPersister { for (index, existingPayment) in payments.enumerated() { if let existingPaymentHash = existingPayment["payment_hash"] as? String, let newPaymentHash = payment["payment_hash"] as? String { if existingPaymentHash == newPaymentHash { + //Don't replace a successful payment. If a 2nd wallet tries to pay an invoice the first successful one should not be overwritten. + if existingPayment["state"] as? String == "successful" { + return + } + payments[index] = mergeObj(payments[index], payment) //Merges update into orginal entry paymentReplaced = true } diff --git a/lib/ios/Ldk.m b/lib/ios/Ldk.m index 0decfc4b..cd41ceca 100644 --- a/lib/ios/Ldk.m +++ b/lib/ios/Ldk.m @@ -147,6 +147,9 @@ @interface RCT_EXTERN_MODULE(Ldk, NSObject) RCT_EXTERN_METHOD(claimFunds:(NSString *)paymentPreimage resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(failHtlcBackwards:(NSString *)paymentHash + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) //MARK: Misc methods RCT_EXTERN_METHOD(writeToFile:(NSString *)fileName diff --git a/lib/ios/Ldk.swift b/lib/ios/Ldk.swift index 9fd7d62b..0708ca6a 100644 --- a/lib/ios/Ldk.swift +++ b/lib/ios/Ldk.swift @@ -92,6 +92,7 @@ enum LdkCallbackResponses: String { 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" @@ -285,6 +286,9 @@ class Ldk: NSObject { let networkGraphStoragePath = accountStoragePath.appendingPathComponent(LdkFileNames.network_graph.rawValue).standardizedFileURL + print("rapidGossipSyncUrl: \(rapidGossipSyncUrl)") + print("accountStoragePath: \(accountStoragePath)") + do { let read = NetworkGraph.read(ser: [UInt8](try Data(contentsOf: networkGraphStoragePath)), arg: logger) if read.isOk() { @@ -305,9 +309,6 @@ class Ldk: NSObject { return handleResolve(resolve, .network_graph_init_success) } - print("rapidGossipSyncUrl: \(rapidGossipSyncUrl)") - print("accountStoragePath: \(accountStoragePath)") - //Download url passed, enable rapid gossip sync do { let rapidGossipSyncStoragePath = accountStoragePath.appendingPathComponent("rapid_gossip_sync") @@ -953,6 +954,19 @@ class Ldk: NSObject { return handleResolve(resolve, .claim_funds_success) } + @objc + func failHtlcBackwards(_ paymentHash: NSString, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { + guard let channelManager = channelManager else { + return handleReject(reject, .init_channel_manager) + } + + LdkEventEmitter.shared.send(withEvent: .native_log, body: "Rejecting payment with failHtlcBackwards") + + channelManager.failHtlcBackwards(paymentHash: String(paymentHash).hexaBytes) + + return handleResolve(resolve, .fail_htlc_backwards_success) + } + //MARK: Fetch methods @objc func version(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { diff --git a/lib/package.json b/lib/package.json index c2e092d5..683b41a3 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.137", + "version": "0.0.138", "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 8331bec4..5ec2312b 100644 --- a/lib/src/ldk.ts +++ b/lib/src/ldk.ts @@ -629,6 +629,22 @@ class LDK { } } + /** + * https://docs.rs/lightning/latest/lightning/ln/channelmanager/struct.ChannelManager.html#method.fail_htlc_backwards + * @returns {Promise | Ok | Err>>} + * @param paymentHash + */ + async failHtlcBackwards(paymentHash: string): Promise> { + try { + const res = await NativeLDK.failHtlcBackwards(paymentHash); + this.writeDebugToLog('failHtlcBackwards'); + return ok(res); + } catch (e) { + this.writeErrorToLog('failHtlcBackwards', e); + return err(e); + } + } + /** * Pays a bolt11 payment request and returns paymentId * @param paymentRequest diff --git a/lib/src/lightning-manager.ts b/lib/src/lightning-manager.ts index b0811281..51ecc1f1 100644 --- a/lib/src/lightning-manager.ts +++ b/lib/src/lightning-manager.ts @@ -1815,12 +1815,39 @@ class LightningManager { ); //TODO } - private onChannelManagerPaymentClaimable(res: TChannelManagerClaim): void { + private async onChannelManagerPaymentClaimable( + res: TChannelManagerClaim, + ): Promise { + const claimedPayments = await this.getLdkPaymentsClaimed(); + + const existingClaimedPayment = claimedPayments.find( + (p) => p.payment_hash === res.payment_hash, + ); + + console.error( + `Existing claimed payments: ${JSON.stringify(existingClaimedPayment)}`, + ); + + if ( + existingClaimedPayment && + existingClaimedPayment.state === 'successful' + ) { + ldk + .writeToLogFile( + 'error', + `Failing payment as it was already claimed: ${res.payment_hash}`, + ) + .catch(console.error); + + await ldk.failHtlcBackwards(res.payment_hash); + return; + } + if (res.spontaneous_payment_preimage) { //https://docs.rs/lightning/latest/lightning/util/events/enum.PaymentPurpose.html#variant.SpontaneousPayment - ldk.claimFunds(res.spontaneous_payment_preimage).catch(console.error); + await ldk.claimFunds(res.spontaneous_payment_preimage); } else { - ldk.claimFunds(res.payment_preimage).catch(console.error); + await ldk.claimFunds(res.payment_preimage); } }