Skip to content

Commit e251850

Browse files
committed
phoenix and cln
1 parent 9bb264a commit e251850

File tree

14 files changed

+299
-20
lines changed

14 files changed

+299
-20
lines changed

bindings/lni_nodejs/index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ export declare class PhoenixdNode {
246246
lookupInvoice(paymentHash: string): Transaction
247247
payOffer(offer: string, amountMsats: number, payerNote?: string | undefined | null): PayInvoiceResponse
248248
listTransactions(params: ListTransactionsParams): Array<Transaction>
249+
onInvoiceEvents(params: OnInvoiceEventParams, callback: (arg0: string, arg1?: Transaction | undefined | null) => void): void
249250
}
250251
export declare class ClnNode {
251252
constructor(config: ClnConfig)
@@ -261,6 +262,7 @@ export declare class ClnNode {
261262
lookupInvoice(paymentHash: string): Transaction
262263
listTransactions(params: ListTransactionsParams): Array<Transaction>
263264
decode(str: string): string
265+
onInvoiceEvents(params: OnInvoiceEventParams, callback: (arg0: string, arg1?: Transaction | undefined | null) => void): void
264266
}
265267
export declare class LndNode {
266268
constructor(config: LndConfig)

bindings/lni_nodejs/main.mjs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -132,24 +132,26 @@ 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());
149151

150152
await node.onInvoiceEvents(
151153
{
152-
paymentHash: process.env.LND_TEST_PAYMENT_HASH,
154+
paymentHash: config.test_hash,
153155
pollingDelaySec: 4,
154156
maxPollingSec: 60,
155157
},

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/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)]
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Generated by uniffi-bindgen-react-native
2+
#include "lni-react-native.h"
3+
#include "generated/lni.hpp"
4+
5+
namespace lnireactnative {
6+
using namespace facebook;
7+
8+
uint8_t installRustCrate(jsi::Runtime &runtime, std::shared_ptr<react::CallInvoker> callInvoker) {
9+
NativeLni::registerModule(runtime, callInvoker);
10+
return true;
11+
}
12+
13+
uint8_t cleanupRustCrate(jsi::Runtime &runtime) {
14+
return false;
15+
}
16+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#ifndef LNIREACTNATIVE_H
2+
#define LNIREACTNATIVE_H
3+
// Generated by uniffi-bindgen-react-native
4+
#include <cstdint>
5+
#include <jsi/jsi.h>
6+
#include <ReactCommon/CallInvoker.h>
7+
8+
namespace lnireactnative {
9+
using namespace facebook;
10+
11+
uint8_t installRustCrate(jsi::Runtime &runtime, std::shared_ptr<react::CallInvoker> callInvoker);
12+
uint8_t cleanupRustCrate(jsi::Runtime &runtime);
13+
}
14+
15+
#endif /* LNIREACTNATIVE_H */
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Generated by uniffi-bindgen-react-native
2+
import type { TurboModule } from 'react-native';
3+
import { TurboModuleRegistry } from 'react-native';
4+
5+
export interface Spec extends TurboModule {
6+
installRustCrate(): boolean;
7+
cleanupRustCrate(): boolean;
8+
}
9+
10+
export default TurboModuleRegistry.getEnforcing<Spec>('LniReactNative');
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Generated by uniffi-bindgen-react-native
2+
import installer from './NativeLniReactNative';
3+
4+
// Register the rust crate with Hermes
5+
// - the boolean flag ensures this loads exactly once, even if the JS
6+
// code is reloaded (e.g. during development with metro).
7+
let rustInstalled = false;
8+
if (!rustInstalled) {
9+
installer.installRustCrate();
10+
rustInstalled = true;
11+
}
12+
13+
// Export the generated bindings to the app.
14+
export * from './generated/lni';
15+
16+
// Now import the bindings so we can:
17+
// - intialize them
18+
// - export them as namespaced objects as the default export.
19+
import * as lni from './generated/lni';
20+
21+
// Initialize the generated bindings: mostly checksums, but also callbacks.
22+
// - the boolean flag ensures this loads exactly once, even if the JS code
23+
// is reloaded (e.g. during development with metro).
24+
let initialized = false;
25+
if (!initialized) {
26+
lni.default.initialize();
27+
initialized = true;
28+
}
29+
30+
// Export the crates as individually namespaced objects.
31+
export default {
32+
lni,
33+
};
34+

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+
}

crates/lni/cln/lib.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,14 @@ impl ClnNode {
9393
pub fn decode(&self, str: String) -> Result<String, ApiError> {
9494
crate::cln::api::decode(&self.config, str)
9595
}
96+
97+
pub fn on_invoice_events(
98+
&self,
99+
params: crate::types::OnInvoiceEventParams,
100+
callback: Box<dyn crate::types::OnInvoiceEventCallback>,
101+
) {
102+
crate::cln::api::on_invoice_events(self.config.clone(), params, callback)
103+
}
96104
}
97105

98106
#[cfg(test)]
@@ -326,4 +334,27 @@ mod tests {
326334
}
327335
}
328336
}
337+
338+
#[test]
339+
fn test_on_invoice_events() {
340+
struct OnInvoiceEventCallback {}
341+
impl crate::types::OnInvoiceEventCallback for OnInvoiceEventCallback {
342+
fn success(&self, transaction: Option<Transaction>) {
343+
println!("success");
344+
}
345+
fn pending(&self, transaction: Option<Transaction>) {
346+
println!("pending");
347+
}
348+
fn failure(&self, transaction: Option<Transaction>) {
349+
println!("epic fail");
350+
}
351+
}
352+
let params = crate::types::OnInvoiceEventParams {
353+
payment_hash: TEST_PAYMENT_HASH.to_string(),
354+
polling_delay_sec: 3,
355+
max_polling_sec: 60,
356+
};
357+
let callback = OnInvoiceEventCallback {};
358+
NODE.on_invoice_events(params, Box::new(callback));
359+
}
329360
}

crates/lni/lnd/api.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,4 @@ pub fn on_invoice_events(
514514
"failure" => callback.failure(tx),
515515
_ => {}
516516
});
517-
}
518-
519-
// }
517+
}

crates/lni/phoenixd/api.rs

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ use super::types::{
55
use super::PhoenixdConfig;
66
use crate::{
77
phoenixd::types::GetBalanceResponse, ApiError, InvoiceType, NodeInfo, PayCode,
8-
PayInvoiceParams, PayInvoiceResponse, Transaction,
8+
PayInvoiceParams, PayInvoiceResponse, Transaction, OnInvoiceEventCallback, OnInvoiceEventParams,
99
};
1010
use serde_urlencoded;
11+
use std::thread;
12+
use std::time::Duration;
1113

1214
// TODO
1315
// list_channels
@@ -265,7 +267,7 @@ pub fn lookup_invoice(
265267
fees_paid: inv.fees * 1000,
266268
created_at: inv.created_at,
267269
expires_at: 0, // TODO
268-
settled_at: 0, // TODO
270+
settled_at: inv.completed_at,
269271
description: inv.description.unwrap_or_default(),
270272
description_hash: "".to_string(), // TODO
271273
payer_note: Some(inv.payer_note.unwrap_or("".to_string())),
@@ -409,3 +411,59 @@ pub fn list_transactions(
409411

410412
Ok(transactions)
411413
}
414+
415+
// Core logic shared by both implementations
416+
pub fn poll_invoice_events<F>(config: &PhoenixdConfig, params: OnInvoiceEventParams, mut callback: F)
417+
where
418+
F: FnMut(String, Option<Transaction>),
419+
{
420+
let mut start_time = std::time::Instant::now();
421+
loop {
422+
if start_time.elapsed() > Duration::from_secs(params.max_polling_sec as u64) {
423+
// timeout
424+
callback("failure".to_string(), None);
425+
break;
426+
}
427+
428+
let (status, transaction) = match lookup_invoice(config, params.payment_hash.clone())
429+
{
430+
Ok(transaction) => {
431+
if transaction.settled_at > 0 {
432+
("settled".to_string(), Some(transaction))
433+
} else {
434+
("pending".to_string(), Some(transaction))
435+
}
436+
}
437+
Err(_) => ("error".to_string(), None),
438+
};
439+
440+
match status.as_str() {
441+
"settled" => {
442+
callback("success".to_string(), transaction);
443+
break;
444+
}
445+
"error" => {
446+
callback("failure".to_string(), transaction);
447+
break;
448+
}
449+
_ => {
450+
callback("pending".to_string(), transaction);
451+
}
452+
}
453+
454+
thread::sleep(Duration::from_secs(params.polling_delay_sec as u64));
455+
}
456+
}
457+
458+
pub fn on_invoice_events(
459+
config: PhoenixdConfig,
460+
params: OnInvoiceEventParams,
461+
callback: Box<dyn OnInvoiceEventCallback>,
462+
) {
463+
poll_invoice_events(&config, params, move |status, tx| match status.as_str() {
464+
"success" => callback.success(tx),
465+
"pending" => callback.pending(tx),
466+
"failure" => callback.failure(tx),
467+
_ => {}
468+
});
469+
}

0 commit comments

Comments
 (0)