Skip to content

On Invoice Events #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Apr 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions bindings/lni_nodejs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ serde = { version = "1", features=["derive"] }
serde_json = "1"
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls", "blocking", "socks"] }
tokio = { version = "1", features = ["full"] }
napi = { version = "2.12.2", default-features = false, features = ["napi4", "tokio_rt", "async"] }
napi-derive = "2.12.2"
napi = { version = "2.16.17", default-features = false, features = ["napi4", "tokio_rt", "async"] }
napi-derive = "2.16.13"
dotenv = "0.15.0"
lazy_static = "1.4.0"

Expand Down
8 changes: 8 additions & 0 deletions bindings/lni_nodejs/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,11 @@ export interface PayInvoiceParams {
allowSelfPayment?: boolean
isAmp?: boolean
}
export interface OnInvoiceEventParams {
paymentHash: string
pollingDelaySec: number
maxPollingSec: number
}
export interface Payment {
paymentId: string
circId: string
Expand All @@ -241,6 +246,7 @@ export declare class PhoenixdNode {
lookupInvoice(paymentHash: string): Transaction
payOffer(offer: string, amountMsats: number, payerNote?: string | undefined | null): PayInvoiceResponse
listTransactions(params: ListTransactionsParams): Array<Transaction>
onInvoiceEvents(params: OnInvoiceEventParams, callback: (arg0: string, arg1?: Transaction | undefined | null) => void): void
}
export declare class ClnNode {
constructor(config: ClnConfig)
Expand All @@ -256,6 +262,7 @@ export declare class ClnNode {
lookupInvoice(paymentHash: string): Transaction
listTransactions(params: ListTransactionsParams): Array<Transaction>
decode(str: string): string
onInvoiceEvents(params: OnInvoiceEventParams, callback: (arg0: string, arg1?: Transaction | undefined | null) => void): void
}
export declare class LndNode {
constructor(config: LndConfig)
Expand All @@ -271,4 +278,5 @@ export declare class LndNode {
lookupInvoice(paymentHash: string): Transaction
listTransactions(params: ListTransactionsParams): Array<Transaction>
decode(str: string): string
onInvoiceEvents(params: OnInvoiceEventParams, callback: (arg0: string, arg1?: Transaction | undefined | null) => void): void
}
35 changes: 24 additions & 11 deletions bindings/lni_nodejs/main.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -132,20 +132,33 @@ async function lnd() {
}

async function test() {
// const config = {
// url: process.env.PHOENIXD_URL,
// password: process.env.PHOENIXD_PASSWORD,
// test_hash: process.env.PHOENIXD_TEST_PAYMENT_HASH,
// };
// const node = new PhoenixdNode(config);
const config = {
url: process.env.LND_URL,
macaroon: process.env.LND_MACAROON,
socks5Proxy: "socks5h://127.0.0.1:9150",
acceptInvalidCerts: true,
url: process.env.PHOENIXD_URL,
password: process.env.PHOENIXD_PASSWORD,
test_hash: process.env.PHOENIXD_TEST_PAYMENT_HASH,
};
const node = new LndNode(config);
const node = new PhoenixdNode(config);
// const config = {
// url: process.env.LND_URL,
// macaroon: process.env.LND_MACAROON,
// // socks5Proxy: "socks5h://127.0.0.1:9150",
// acceptInvalidCerts: true,
// };
// const node = new LndNode(config);


console.log("Node info:", await node.getInfo());

await node.onInvoiceEvents(
{
paymentHash: config.test_hash,
pollingDelaySec: 4,
maxPollingSec: 60,
},
(status, tx) => {
console.log("Invoice event:", status, tx);
}
);
}

async function main() {
Expand Down
12 changes: 12 additions & 0 deletions bindings/lni_nodejs/src/cln.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,18 @@ impl ClnNode {
.map_err(|e| napi::Error::from_reason(e.to_string()))?;
Ok(decoded)
}

#[napi]
pub fn on_invoice_events<T: Fn(String, Option<lni::Transaction>) -> Result<()>>(
&self,
params: lni::types::OnInvoiceEventParams,
callback: T,
) -> Result<()> {
lni::cln::api::poll_invoice_events(&self.inner, params, move |status, tx| {
callback(status.clone(), tx.clone()).map_err(|err| napi::Error::from_reason(err.to_string()));
});
Ok(())
}
}

// #[cfg(test)]
Expand Down
12 changes: 12 additions & 0 deletions bindings/lni_nodejs/src/lnd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,16 @@ impl LndNode {
.map_err(|e| napi::Error::from_reason(e.to_string()))?;
Ok(decoded)
}

#[napi]
pub fn on_invoice_events<T: Fn(String, Option<lni::Transaction>) -> Result<()>>(
&self,
params: lni::types::OnInvoiceEventParams,
callback: T,
) -> Result<()> {
lni::lnd::api::poll_invoice_events(&self.inner, params, move |status, tx| {
callback(status.clone(), tx.clone()).map_err(|err| napi::Error::from_reason(err.to_string()));
});
Ok(())
}
}
12 changes: 12 additions & 0 deletions bindings/lni_nodejs/src/phoenixd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,18 @@ impl PhoenixdNode {
.map_err(|e| napi::Error::from_reason(e.to_string()))?;
Ok(txns)
}

#[napi]
pub fn on_invoice_events<T: Fn(String, Option<lni::Transaction>) -> Result<()>>(
&self,
params: lni::types::OnInvoiceEventParams,
callback: T,
) -> Result<()> {
lni::phoenixd::api::poll_invoice_events(&self.inner, params, move |status, tx| {
callback(status.clone(), tx.clone()).map_err(|err| napi::Error::from_reason(err.to_string()));
});
Ok(())
}
}

#[cfg(test)]
Expand Down
37 changes: 32 additions & 5 deletions bindings/lni_react_native/example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { useEffect, useState } from 'react';
import { Text, View, StyleSheet } from 'react-native';
import { LndNode, LndConfig, PhoenixdNode, PhoenixdConfig } from 'lni_react_native';
import {
LndNode,
LndConfig,
PhoenixdNode,
PhoenixdConfig,
type OnInvoiceEventCallback,
Transaction,
} from 'lni_react_native';
import { LND_URL, LND_MACAROON } from '@env';

export default function App() {
Expand All @@ -11,12 +18,32 @@ export default function App() {
try {
const node = new LndNode(
LndConfig.create({
url: '', //LND_URL,
macaroon:
'', // LND_MACAROON,
socks5Proxy: 'socks5h://127.0.0.1:9050',
url: '',
macaroon: '',
socks5Proxy: undefined, // 'socks5h://127.0.0.1:9050',
})
);

await node.onInvoiceEvents(
{
paymentHash: '',
pollingDelaySec: BigInt(3), // poll every 3 seconds
maxPollingSec: BigInt(60), // for up to 60 seconds
},
{
success(transaction: Transaction | undefined): void {
console.log('Received success invoice event:', transaction);
setResult('Success');
},
pending(transaction: Transaction | undefined): void {
console.log('Received pending event:', transaction);
},
failure(transaction: Transaction | undefined): void {
console.log('Received failure event:', transaction);
},
}
);

const info = await node.listTransactions({
from: BigInt(0),
limit: BigInt(10),
Expand Down
2 changes: 1 addition & 1 deletion bindings/lni_react_native/ubrn.config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
rust:
repo: https://github.com/lightning-node-interface/lni
branch: fix-react-native
branch: on-invoice-events
manifestPath: crates/lni/Cargo.toml
4 changes: 2 additions & 2 deletions crates/lni/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ thiserror = "1.0"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
uniffi = { version = "0.29.0", features = ["tokio", "cli"] }
napi = { version = "2.12.2", default-features = false, features = ["napi4"] }
napi-derive = "2.12.2"
napi = { version = "2.16.17", default-features = false, features = ["napi4"] }
napi-derive = "2.16.13"
async-std = "1.10.0"
tokio = { version = "1", features = ["full"] }
dotenv = "0.15.0"
Expand Down
60 changes: 59 additions & 1 deletion crates/lni/cln/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ use super::ClnConfig;
use crate::types::NodeInfo;
use crate::{
calculate_fee_msats, ApiError, InvoiceType, PayCode, PayInvoiceParams, PayInvoiceResponse,
Transaction,
Transaction, OnInvoiceEventCallback, OnInvoiceEventParams,
};
use reqwest::header;
use std::thread;
use std::time::Duration;

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

Expand Down Expand Up @@ -561,3 +563,59 @@ pub fn list_transactions(
Err(e) => Err(e),
}
}


// Core logic shared by both implementations
pub fn poll_invoice_events<F>(config: &ClnConfig, params: OnInvoiceEventParams, mut callback: F)
where
F: FnMut(String, Option<Transaction>),
{
let mut start_time = std::time::Instant::now();
loop {
if start_time.elapsed() > Duration::from_secs(params.max_polling_sec as u64) {
// timeout
callback("failure".to_string(), None);
break;
}

let (status, transaction) = match lookup_invoice(config, Some(params.payment_hash.clone()), None, None ) {
Ok(transaction) => {
if transaction.settled_at > 0 {
("settled".to_string(), Some(transaction))
} else {
("pending".to_string(), Some(transaction))
}
}
Err(_) => ("error".to_string(), None),
};

match status.as_str() {
"settled" => {
callback("success".to_string(), transaction);
break;
}
"error" => {
callback("failure".to_string(), transaction);
break;
}
_ => {
callback("pending".to_string(), transaction);
}
}

thread::sleep(Duration::from_secs(params.polling_delay_sec as u64));
}
}

pub fn on_invoice_events(
config: ClnConfig,
params: OnInvoiceEventParams,
callback: Box<dyn OnInvoiceEventCallback>,
) {
poll_invoice_events(&config, params, move |status, tx| match status.as_str() {
"success" => callback.success(tx),
"pending" => callback.pending(tx),
"failure" => callback.failure(tx),
_ => {}
});
}
31 changes: 31 additions & 0 deletions crates/lni/cln/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ impl ClnNode {
pub fn decode(&self, str: String) -> Result<String, ApiError> {
crate::cln::api::decode(&self.config, str)
}

pub fn on_invoice_events(
&self,
params: crate::types::OnInvoiceEventParams,
callback: Box<dyn crate::types::OnInvoiceEventCallback>,
) {
crate::cln::api::on_invoice_events(self.config.clone(), params, callback)
}
}

#[cfg(test)]
Expand Down Expand Up @@ -326,4 +334,27 @@ mod tests {
}
}
}

#[test]
fn test_on_invoice_events() {
struct OnInvoiceEventCallback {}
impl crate::types::OnInvoiceEventCallback for OnInvoiceEventCallback {
fn success(&self, transaction: Option<Transaction>) {
println!("success");
}
fn pending(&self, transaction: Option<Transaction>) {
println!("pending");
}
fn failure(&self, transaction: Option<Transaction>) {
println!("epic fail");
}
}
let params = crate::types::OnInvoiceEventParams {
payment_hash: TEST_PAYMENT_HASH.to_string(),
polling_delay_sec: 3,
max_polling_sec: 60,
};
let callback = OnInvoiceEventCallback {};
NODE.on_invoice_events(params, Box::new(callback));
}
}
Loading