Skip to content

Commit e3e3b86

Browse files
authored
On Invoice Events (#9)
* on invoice events * fix impl * pending success failure callbacks * cleanup polling * docs * napi nodejs callback * fix rn * fix for rn * phoenix and cln
1 parent cdf253d commit e3e3b86

File tree

17 files changed

+494
-42
lines changed

17 files changed

+494
-42
lines changed

bindings/lni_nodejs/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ serde = { version = "1", features=["derive"] }
1717
serde_json = "1"
1818
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls", "blocking", "socks"] }
1919
tokio = { version = "1", features = ["full"] }
20-
napi = { version = "2.12.2", default-features = false, features = ["napi4", "tokio_rt", "async"] }
21-
napi-derive = "2.12.2"
20+
napi = { version = "2.16.17", default-features = false, features = ["napi4", "tokio_rt", "async"] }
21+
napi-derive = "2.16.13"
2222
dotenv = "0.15.0"
2323
lazy_static = "1.4.0"
2424

bindings/lni_nodejs/index.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,11 @@ export interface PayInvoiceParams {
221221
allowSelfPayment?: boolean
222222
isAmp?: boolean
223223
}
224+
export interface OnInvoiceEventParams {
225+
paymentHash: string
226+
pollingDelaySec: number
227+
maxPollingSec: number
228+
}
224229
export interface Payment {
225230
paymentId: string
226231
circId: string
@@ -241,6 +246,7 @@ export declare class PhoenixdNode {
241246
lookupInvoice(paymentHash: string): Transaction
242247
payOffer(offer: string, amountMsats: number, payerNote?: string | undefined | null): PayInvoiceResponse
243248
listTransactions(params: ListTransactionsParams): Array<Transaction>
249+
onInvoiceEvents(params: OnInvoiceEventParams, callback: (arg0: string, arg1?: Transaction | undefined | null) => void): void
244250
}
245251
export declare class ClnNode {
246252
constructor(config: ClnConfig)
@@ -256,6 +262,7 @@ export declare class ClnNode {
256262
lookupInvoice(paymentHash: string): Transaction
257263
listTransactions(params: ListTransactionsParams): Array<Transaction>
258264
decode(str: string): string
265+
onInvoiceEvents(params: OnInvoiceEventParams, callback: (arg0: string, arg1?: Transaction | undefined | null) => void): void
259266
}
260267
export declare class LndNode {
261268
constructor(config: LndConfig)
@@ -271,4 +278,5 @@ export declare class LndNode {
271278
lookupInvoice(paymentHash: string): Transaction
272279
listTransactions(params: ListTransactionsParams): Array<Transaction>
273280
decode(str: string): string
281+
onInvoiceEvents(params: OnInvoiceEventParams, callback: (arg0: string, arg1?: Transaction | undefined | null) => void): void
274282
}

bindings/lni_nodejs/main.mjs

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -132,20 +132,33 @@ async function lnd() {
132132
}
133133

134134
async function test() {
135-
// const config = {
136-
// url: process.env.PHOENIXD_URL,
137-
// password: process.env.PHOENIXD_PASSWORD,
138-
// test_hash: process.env.PHOENIXD_TEST_PAYMENT_HASH,
139-
// };
140-
// const node = new PhoenixdNode(config);
141135
const config = {
142-
url: process.env.LND_URL,
143-
macaroon: process.env.LND_MACAROON,
144-
socks5Proxy: "socks5h://127.0.0.1:9150",
145-
acceptInvalidCerts: true,
136+
url: process.env.PHOENIXD_URL,
137+
password: process.env.PHOENIXD_PASSWORD,
138+
test_hash: process.env.PHOENIXD_TEST_PAYMENT_HASH,
146139
};
147-
const node = new LndNode(config);
140+
const node = new PhoenixdNode(config);
141+
// const config = {
142+
// url: process.env.LND_URL,
143+
// macaroon: process.env.LND_MACAROON,
144+
// // socks5Proxy: "socks5h://127.0.0.1:9150",
145+
// acceptInvalidCerts: true,
146+
// };
147+
// const node = new LndNode(config);
148+
149+
148150
console.log("Node info:", await node.getInfo());
151+
152+
await node.onInvoiceEvents(
153+
{
154+
paymentHash: config.test_hash,
155+
pollingDelaySec: 4,
156+
maxPollingSec: 60,
157+
},
158+
(status, tx) => {
159+
console.log("Invoice event:", status, tx);
160+
}
161+
);
149162
}
150163

151164
async function main() {

bindings/lni_nodejs/src/cln.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,18 @@ impl ClnNode {
106106
.map_err(|e| napi::Error::from_reason(e.to_string()))?;
107107
Ok(decoded)
108108
}
109+
110+
#[napi]
111+
pub fn on_invoice_events<T: Fn(String, Option<lni::Transaction>) -> Result<()>>(
112+
&self,
113+
params: lni::types::OnInvoiceEventParams,
114+
callback: T,
115+
) -> Result<()> {
116+
lni::cln::api::poll_invoice_events(&self.inner, params, move |status, tx| {
117+
callback(status.clone(), tx.clone()).map_err(|err| napi::Error::from_reason(err.to_string()));
118+
});
119+
Ok(())
120+
}
109121
}
110122

111123
// #[cfg(test)]

bindings/lni_nodejs/src/lnd.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,16 @@ impl LndNode {
104104
.map_err(|e| napi::Error::from_reason(e.to_string()))?;
105105
Ok(decoded)
106106
}
107+
108+
#[napi]
109+
pub fn on_invoice_events<T: Fn(String, Option<lni::Transaction>) -> Result<()>>(
110+
&self,
111+
params: lni::types::OnInvoiceEventParams,
112+
callback: T,
113+
) -> Result<()> {
114+
lni::lnd::api::poll_invoice_events(&self.inner, params, move |status, tx| {
115+
callback(status.clone(), tx.clone()).map_err(|err| napi::Error::from_reason(err.to_string()));
116+
});
117+
Ok(())
118+
}
107119
}

bindings/lni_nodejs/src/phoenixd.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,18 @@ impl PhoenixdNode {
9292
.map_err(|e| napi::Error::from_reason(e.to_string()))?;
9393
Ok(txns)
9494
}
95+
96+
#[napi]
97+
pub fn on_invoice_events<T: Fn(String, Option<lni::Transaction>) -> Result<()>>(
98+
&self,
99+
params: lni::types::OnInvoiceEventParams,
100+
callback: T,
101+
) -> Result<()> {
102+
lni::phoenixd::api::poll_invoice_events(&self.inner, params, move |status, tx| {
103+
callback(status.clone(), tx.clone()).map_err(|err| napi::Error::from_reason(err.to_string()));
104+
});
105+
Ok(())
106+
}
95107
}
96108

97109
#[cfg(test)]

bindings/lni_react_native/example/src/App.tsx

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import { useEffect, useState } from 'react';
22
import { Text, View, StyleSheet } from 'react-native';
3-
import { LndNode, LndConfig, PhoenixdNode, PhoenixdConfig } from 'lni_react_native';
3+
import {
4+
LndNode,
5+
LndConfig,
6+
PhoenixdNode,
7+
PhoenixdConfig,
8+
type OnInvoiceEventCallback,
9+
Transaction,
10+
} from 'lni_react_native';
411
import { LND_URL, LND_MACAROON } from '@env';
512

613
export default function App() {
@@ -11,12 +18,32 @@ export default function App() {
1118
try {
1219
const node = new LndNode(
1320
LndConfig.create({
14-
url: '', //LND_URL,
15-
macaroon:
16-
'', // LND_MACAROON,
17-
socks5Proxy: 'socks5h://127.0.0.1:9050',
21+
url: '',
22+
macaroon: '',
23+
socks5Proxy: undefined, // 'socks5h://127.0.0.1:9050',
1824
})
1925
);
26+
27+
await node.onInvoiceEvents(
28+
{
29+
paymentHash: '',
30+
pollingDelaySec: BigInt(3), // poll every 3 seconds
31+
maxPollingSec: BigInt(60), // for up to 60 seconds
32+
},
33+
{
34+
success(transaction: Transaction | undefined): void {
35+
console.log('Received success invoice event:', transaction);
36+
setResult('Success');
37+
},
38+
pending(transaction: Transaction | undefined): void {
39+
console.log('Received pending event:', transaction);
40+
},
41+
failure(transaction: Transaction | undefined): void {
42+
console.log('Received failure event:', transaction);
43+
},
44+
}
45+
);
46+
2047
const info = await node.listTransactions({
2148
from: BigInt(0),
2249
limit: BigInt(10),
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
22
rust:
33
repo: https://github.com/lightning-node-interface/lni
4-
branch: fix-react-native
4+
branch: on-invoice-events
55
manifestPath: crates/lni/Cargo.toml

crates/lni/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ thiserror = "1.0"
2020
serde = { version = "1", features = ["derive"] }
2121
serde_json = "1"
2222
uniffi = { version = "0.29.0", features = ["tokio", "cli"] }
23-
napi = { version = "2.12.2", default-features = false, features = ["napi4"] }
24-
napi-derive = "2.12.2"
23+
napi = { version = "2.16.17", default-features = false, features = ["napi4"] }
24+
napi-derive = "2.16.13"
2525
async-std = "1.10.0"
2626
tokio = { version = "1", features = ["full"] }
2727
dotenv = "0.15.0"

crates/lni/cln/api.rs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ use super::ClnConfig;
66
use crate::types::NodeInfo;
77
use crate::{
88
calculate_fee_msats, ApiError, InvoiceType, PayCode, PayInvoiceParams, PayInvoiceResponse,
9-
Transaction,
9+
Transaction, OnInvoiceEventCallback, OnInvoiceEventParams,
1010
};
1111
use reqwest::header;
12+
use std::thread;
13+
use std::time::Duration;
1214

1315
// https://docs.corelightning.org/reference/get_list_methods_resource
1416

@@ -561,3 +563,59 @@ pub fn list_transactions(
561563
Err(e) => Err(e),
562564
}
563565
}
566+
567+
568+
// Core logic shared by both implementations
569+
pub fn poll_invoice_events<F>(config: &ClnConfig, params: OnInvoiceEventParams, mut callback: F)
570+
where
571+
F: FnMut(String, Option<Transaction>),
572+
{
573+
let mut start_time = std::time::Instant::now();
574+
loop {
575+
if start_time.elapsed() > Duration::from_secs(params.max_polling_sec as u64) {
576+
// timeout
577+
callback("failure".to_string(), None);
578+
break;
579+
}
580+
581+
let (status, transaction) = match lookup_invoice(config, Some(params.payment_hash.clone()), None, None ) {
582+
Ok(transaction) => {
583+
if transaction.settled_at > 0 {
584+
("settled".to_string(), Some(transaction))
585+
} else {
586+
("pending".to_string(), Some(transaction))
587+
}
588+
}
589+
Err(_) => ("error".to_string(), None),
590+
};
591+
592+
match status.as_str() {
593+
"settled" => {
594+
callback("success".to_string(), transaction);
595+
break;
596+
}
597+
"error" => {
598+
callback("failure".to_string(), transaction);
599+
break;
600+
}
601+
_ => {
602+
callback("pending".to_string(), transaction);
603+
}
604+
}
605+
606+
thread::sleep(Duration::from_secs(params.polling_delay_sec as u64));
607+
}
608+
}
609+
610+
pub fn on_invoice_events(
611+
config: ClnConfig,
612+
params: OnInvoiceEventParams,
613+
callback: Box<dyn OnInvoiceEventCallback>,
614+
) {
615+
poll_invoice_events(&config, params, move |status, tx| match status.as_str() {
616+
"success" => callback.success(tx),
617+
"pending" => callback.pending(tx),
618+
"failure" => callback.failure(tx),
619+
_ => {}
620+
});
621+
}

0 commit comments

Comments
 (0)