Skip to content
This repository was archived by the owner on Feb 9, 2026. It is now read-only.

Commit 9652689

Browse files
authored
Merge pull request #136 from synonymdev/native-tx-writes
Native transaction persistence
2 parents f0320ae + 253959a commit 9652689

10 files changed

Lines changed: 388 additions & 57 deletions

File tree

example/App.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,11 +413,19 @@ const App = (): ReactElement => {
413413
<Button
414414
title={'Get claimed payments'}
415415
onPress={async (): Promise<void> => {
416-
setMessage('Getting all LDK payments...');
416+
setMessage('Getting all claimed payments...');
417417
const res = await lm.getLdkPaymentsClaimed();
418418
setMessage(JSON.stringify(res));
419419
}}
420420
/>
421+
<Button
422+
title={'Get sent payments'}
423+
onPress={async (): Promise<void> => {
424+
setMessage('Getting all sent payments...');
425+
const res = await lm.getLdkPaymentsSent();
426+
setMessage(JSON.stringify(res));
427+
}}
428+
/>
421429

422430
<Button
423431
title={'Create invoice'}

lib/android/src/main/java/com/reactnativeldk/Helpers.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.reactnativeldk
22
import com.facebook.react.bridge.*
3+
import org.json.JSONObject
34
import org.ldk.enums.Currency
45
import org.ldk.enums.Network
56
import org.ldk.structs.*
@@ -381,4 +382,18 @@ fun getNetwork(chain: String): Pair<Network, Currency> {
381382
"mainnet" -> Pair(Network.LDKNetwork_Bitcoin, Currency.LDKCurrency_Bitcoin)
382383
else -> Pair(Network.LDKNetwork_Bitcoin, Currency.LDKCurrency_Bitcoin)
383384
}
385+
}
386+
387+
fun mergeObj(obj1: JSONObject, obj2: HashMap<String, Any>): HashMap<String, Any> {
388+
val newObj = HashMap<String, Any>()
389+
390+
obj1.keys().forEach { key ->
391+
newObj[key] = obj1[key]
392+
}
393+
394+
obj2.keys.forEach { key ->
395+
newObj[key] = obj2[key]!!
396+
}
397+
398+
return newObj
384399
}

lib/android/src/main/java/com/reactnativeldk/LdkModule.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import org.ldk.impl.bindings.get_ldk_version
1313
import org.ldk.structs.*
1414
import org.ldk.structs.Result_InvoiceParseOrSemanticErrorZ.Result_InvoiceParseOrSemanticErrorZ_OK
1515
import org.ldk.structs.Result_InvoiceSignOrCreationErrorZ.Result_InvoiceSignOrCreationErrorZ_OK
16+
import org.ldk.structs.Result_PaymentIdPaymentErrorZ.Result_PaymentIdPaymentErrorZ_OK
1617
import java.io.File
1718
import java.net.InetSocketAddress
1819
import java.nio.file.Files
@@ -111,7 +112,9 @@ enum class LdkCallbackResponses {
111112
enum class LdkFileNames(val fileName: String) {
112113
network_graph("network_graph.bin"),
113114
channel_manager("channel_manager.bin"),
114-
scorer("scorer.bin")
115+
scorer("scorer.bin"),
116+
paymentsClaimed("payments_claimed.json"),
117+
paymentsSent("payments_sent.json"),
115118
}
116119

117120
class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
@@ -684,6 +687,14 @@ class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMod
684687
UtilMethods.pay_zero_value_invoice(invoice, amountSats.toLong() * 1000, Retry.timeout(timeoutSeconds.toLong()), channelManager) else
685688
UtilMethods.pay_invoice(invoice, Retry.timeout(timeoutSeconds.toLong()), channelManager)
686689
if (res.is_ok) {
690+
channelManagerPersister.persistPaymentSent(hashMapOf(
691+
"payment_id" to (res as Result_PaymentIdPaymentErrorZ_OK).res.hexEncodedString(),
692+
"payment_hash" to invoice.payment_hash().hexEncodedString(),
693+
"amount_sat" to if (isZeroValueInvoice) amountSats.toLong() else ((invoice.amount_milli_satoshis() as Option_u64Z.Some).some.toInt() / 1000),
694+
"unix_timestamp" to (System.currentTimeMillis() / 1000).toInt(),
695+
"state" to "pending"
696+
))
697+
687698
return handleResolve(promise, LdkCallbackResponses.invoice_payment_success)
688699
}
689700

lib/android/src/main/java/com/reactnativeldk/classes/LdkChannelManagerPersister.kt

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.reactnativeldk.classes
22

33
import com.facebook.react.bridge.Arguments
4+
import com.facebook.react.bridge.WritableMap
45
import com.reactnativeldk.*
6+
import org.json.JSONArray
57
import org.ldk.batteries.ChannelManagerConstructor
68
import org.ldk.structs.Event
79
import org.ldk.structs.Option_u64Z
@@ -30,6 +32,10 @@ class LdkChannelManagerPersister: ChannelManagerConstructor.EventHandler {
3032
(paymentClaimable.purpose as? PaymentPurpose.SpontaneousPayment)?.let {
3133
body.putHexString("spontaneous_payment_preimage", it.spontaneous_payment)
3234
}
35+
body.putInt("unix_timestamp", (System.currentTimeMillis() / 1000).toInt())
36+
body.putString("state", "pending")
37+
38+
persistPaymentClaimed(body)
3339
return LdkEventEmitter.send(EventTypes.channel_manager_payment_claimable, body)
3440
}
3541

@@ -39,6 +45,11 @@ class LdkChannelManagerPersister: ChannelManagerConstructor.EventHandler {
3945
body.putHexString("payment_preimage", paymentSent.payment_preimage)
4046
body.putHexString("payment_hash", paymentSent.payment_hash)
4147
body.putInt("fee_paid_sat", (paymentSent.fee_paid_msat as Option_u64Z.Some).some.toInt() / 1000)
48+
body.putInt("unix_timestamp", (System.currentTimeMillis() / 1000).toInt())
49+
body.putString("state", "successful")
50+
51+
persistPaymentSent(body.toHashMap())
52+
4253
return LdkEventEmitter.send(EventTypes.channel_manager_payment_sent, body)
4354
}
4455

@@ -76,13 +87,30 @@ class LdkChannelManagerPersister: ChannelManagerConstructor.EventHandler {
7687
// paymentPathFailed.path.iterator().forEach { path.pushMap(it.asJson) }
7788
// body.putArray("path_hops", path)
7889

90+
if (paymentPathFailed.payment_id != null) {
91+
persistPaymentSent(hashMapOf(
92+
"payment_id" to paymentPathFailed.payment_id!!.hexEncodedString(),
93+
"payment_hash" to paymentPathFailed.payment_hash.hexEncodedString(),
94+
"unix_timestamp" to (System.currentTimeMillis() / 1000).toInt(),
95+
"state" to if (paymentPathFailed.payment_failed_permanently) "failed" else "pending"
96+
))
97+
}
98+
7999
return LdkEventEmitter.send(EventTypes.channel_manager_payment_path_failed, body)
80100
}
81101

82102
(event as? Event.PaymentFailed)?.let { paymentFailed ->
83103
val body = Arguments.createMap()
84104
body.putHexString("payment_id", paymentFailed.payment_id)
85105
body.putHexString("payment_hash", paymentFailed.payment_hash)
106+
107+
persistPaymentSent(hashMapOf(
108+
"payment_id" to paymentFailed.payment_id!!.hexEncodedString(),
109+
"payment_hash" to paymentFailed.payment_hash.hexEncodedString(),
110+
"unix_timestamp" to (System.currentTimeMillis() / 1000).toInt(),
111+
"state" to "failed"
112+
))
113+
86114
return LdkEventEmitter.send(EventTypes.channel_manager_payment_failed, body)
87115
}
88116

@@ -132,6 +160,10 @@ class LdkChannelManagerPersister: ChannelManagerConstructor.EventHandler {
132160
(paymentClaimed.purpose as? PaymentPurpose.SpontaneousPayment)?.let {
133161
body.putHexString("spontaneous_payment_preimage", it.spontaneous_payment)
134162
}
163+
body.putInt("unix_timestamp", (System.currentTimeMillis() / 1000).toInt())
164+
body.putString("state", "successful")
165+
166+
persistPaymentClaimed(body)
135167
return LdkEventEmitter.send(EventTypes.channel_manager_payment_claimed, body)
136168
}
137169
}
@@ -159,4 +191,89 @@ class LdkChannelManagerPersister: ChannelManagerConstructor.EventHandler {
159191
LdkEventEmitter.send(EventTypes.native_log, "Persisted scorer to disk")
160192
}
161193
}
194+
195+
private fun persistPaymentClaimed(payment: WritableMap) {
196+
if (LdkModule.accountStoragePath == "") {
197+
LdkEventEmitter.send(EventTypes.native_log, "Error. Failed to persist claimed payment to disk (No set storage)")
198+
return
199+
}
200+
201+
var payments: Array<HashMap<String, Any>> = arrayOf()
202+
var paymentReplaced = false
203+
204+
try {
205+
if (File(LdkModule.accountStoragePath + "/" + LdkFileNames.paymentsClaimed.fileName).exists()) {
206+
val data = File(LdkModule.accountStoragePath + "/" + LdkFileNames.paymentsClaimed.fileName).readBytes()
207+
val existingPayments = JSONArray(String(data))
208+
for (i in 0 until existingPayments.length()) {
209+
val existingPayment = existingPayments.getJSONObject(i)
210+
//Replace entry if payment hash exists (Confirmed payment replacing pending)
211+
if (existingPayment.getString("payment_hash") == payment.getString("payment_hash")) {
212+
payments[i] = mergeObj(existingPayment, payment.toHashMap())
213+
paymentReplaced = true
214+
continue
215+
}
216+
217+
val map = HashMap<String, Any>()
218+
for (key in existingPayment.keys()) {
219+
map[key] = existingPayments.getJSONObject(i).get(key)
220+
}
221+
222+
payments = payments.plus(map)
223+
}
224+
}
225+
} catch (e: Exception) {
226+
LdkEventEmitter.send(EventTypes.native_log, "Error could not read existing claimed payments")
227+
}
228+
229+
//No existing payment found, append as new payment
230+
if (!paymentReplaced) {
231+
payments = payments.plus(payment.toHashMap())
232+
}
233+
234+
File(LdkModule.accountStoragePath + "/" + LdkFileNames.paymentsClaimed.fileName).writeText(JSONArray(payments).toString())
235+
}
236+
237+
fun persistPaymentSent(payment: HashMap<String, Any>) {
238+
if (LdkModule.accountStoragePath == "") {
239+
LdkEventEmitter.send(EventTypes.native_log, "Error. Failed to persist sent payment to disk (No set storage)")
240+
return
241+
}
242+
243+
var payments: Array<HashMap<String, Any>> = arrayOf()
244+
var paymentReplaced = false
245+
246+
try {
247+
if (File(LdkModule.accountStoragePath + "/" + LdkFileNames.paymentsSent.fileName).exists()) {
248+
val data = File(LdkModule.accountStoragePath + "/" + LdkFileNames.paymentsSent.fileName).readBytes()
249+
val existingPayments = JSONArray(String(data))
250+
for (i in 0 until existingPayments.length()) {
251+
val existingPayment = existingPayments.getJSONObject(i)
252+
//Replace entry if payment ID exists (Confirmed payment replacing pending)
253+
if (existingPayment.getString("payment_id") == payment["payment_id"]) {
254+
var merged = mergeObj(existingPayment, payment)
255+
payments = payments.plus(merged)
256+
paymentReplaced = true
257+
continue
258+
}
259+
260+
val map = HashMap<String, Any>()
261+
for (key in existingPayment.keys()) {
262+
map[key] = existingPayment.get(key)
263+
}
264+
265+
payments = payments.plus(map)
266+
}
267+
}
268+
} catch (e: Exception) {
269+
LdkEventEmitter.send(EventTypes.native_log, "Error could not read existing sent payments")
270+
}
271+
272+
//No existing payment found, append as new payment
273+
if (!paymentReplaced) {
274+
payments = payments.plus(payment)
275+
}
276+
277+
File(LdkModule.accountStoragePath + "/" + LdkFileNames.paymentsSent.fileName).writeText(JSONArray(payments).toString())
278+
}
162279
}

0 commit comments

Comments
 (0)