Skip to content

lexe#50

Open
ntheile wants to merge 1 commit into
masterfrom
lexe
Open

lexe#50
ntheile wants to merge 1 commit into
masterfrom
lexe

Conversation

@ntheile

@ntheile ntheile commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features
    • Added Lexe wallet integration with support for node information, invoice creation and payment, offers, transaction history, invoice lookup, decoding, and payment event notifications.
    • Added configurable Lexe networks, credentials, and local data storage.
    • Added Lexe node creation through the public interface.
    • Added validation for payment inputs and secure preimage handling.
  • Documentation
    • Updated feature documentation and examples to include Lexe integration.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a Lexe-backed LightningNode with wallet initialization, invoice and offer operations, transaction lookup, event polling, public factory wiring, tests, and README usage documentation.

Changes

Lexe backend integration

Layer / File(s) Summary
Wallet configuration and initialization
crates/lni/Cargo.toml, crates/lni/lexe/lib.rs
Adds Lexe and test dependencies, defines LexeConfig, validates credentials and networks, and initializes LexeWallet.
Lexe payment API
crates/lni/lexe/api.rs
Implements amount and timestamp conversion, invoice and offer operations, payment filtering and pagination, invoice lookup, event polling, permissions, and validation tests.
LightningNode exposure and validation
crates/lni/lexe/lib.rs, crates/lni/lib.rs, crates/lni/types.rs
Delegates Lexe operations through LexeNode, adds decoding and callbacks, registers the enum variant and factory, and updates UniFFI export wiring.
Lexe usage documentation
readme.md
Adds Lexe initialization examples and updates the supported node list.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant LexeNode
  participant LexeAPI
  participant LexeWallet
  Client->>LexeNode: create_lexe_node(config)
  LexeNode->>LexeAPI: create_invoice(params)
  LexeAPI->>LexeWallet: create invoice
  LexeWallet-->>LexeAPI: invoice payment data
  LexeAPI-->>LexeNode: Transaction
  LexeNode-->>Client: LightningNode result
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is too vague and does not describe the change beyond naming the new module. Use a concise descriptive title, such as adding Lexe node support or the Lexe backend API.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch lexe

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 83f81bd130

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/lni/lexe/api.rs
Comment thread crates/lni/lexe/lib.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/lni/lexe/api.rs`:
- Around line 256-276: Bound the caller-controlled limit in matching_payments
and the surrounding API parameter validation to the public API’s maximum page
size, rejecting params.limit values above that maximum. Avoid
Vec::with_capacity(limit) based on the untrusted request; use a bounded capacity
or no preallocation while preserving pagination and result limits.
- Around line 402-408: Update the timeout handling in the payment flow around
wallet.pay_invoice to reconcile the remote payment state using the invoice
payment hash before returning. If reconciliation cannot determine whether
payment completed, return an explicit indeterminate result that requires lookup
before retrying instead of the generic NetworkError; preserve the existing
successful payment path.
- Around line 519-543: Update on_invoice_events to reject requests where both
params.payment_hash and params.search are empty, using the same selector
validation behavior as lookup_invoice before entering the polling loop. Preserve
the existing callback.failure(None) response and avoid calling matching_payments
without at least one event selector.
- Around line 99-104: Update the routing-options validation condition to reject
only enabled unsupported options, allowing optional boolean fields set to
Some(false) while continuing to reject Some(true). Preserve the existing
handling for max_parts and hop public keys, and adjust the is_amp and
allow_self_payment checks in the surrounding validation logic.

In `@crates/lni/lexe/lib.rs`:
- Around line 306-307: Remove the payment preimage output from the affected
diagnostic paths, including the println! and corresponding dbg! calls around
payment handling. Ensure the locations also covered at the referenced lines do
not log payment.preimage, while retaining only non-sensitive payment identifiers
if diagnostics are still required.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0aa055ad-db06-4084-a514-2233e5cb96f8

📥 Commits

Reviewing files that changed from the base of the PR and between 84943f4 and 83f81bd.

📒 Files selected for processing (6)
  • crates/lni/Cargo.toml
  • crates/lni/lexe/api.rs
  • crates/lni/lexe/lib.rs
  • crates/lni/lib.rs
  • crates/lni/types.rs
  • readme.md

Comment thread crates/lni/lexe/api.rs
Comment on lines +99 to +104
if params.max_parts.is_some()
|| params.first_hop_pubkey.is_some()
|| params.last_hop_pubkey.is_some()
|| params.allow_self_payment.is_some()
|| params.is_amp.is_some()
{

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Accept explicitly disabled routing options.

Some(false) requests no unsupported behavior, but this rejects it merely because the field is present. This can break clients that serialize optional booleans explicitly.

Proposed fix
     if params.max_parts.is_some()
         || params.first_hop_pubkey.is_some()
         || params.last_hop_pubkey.is_some()
-        || params.allow_self_payment.is_some()
-        || params.is_amp.is_some()
+        || params.allow_self_payment.unwrap_or(false)
+        || params.is_amp.unwrap_or(false)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if params.max_parts.is_some()
|| params.first_hop_pubkey.is_some()
|| params.last_hop_pubkey.is_some()
|| params.allow_self_payment.is_some()
|| params.is_amp.is_some()
{
if params.max_parts.is_some()
|| params.first_hop_pubkey.is_some()
|| params.last_hop_pubkey.is_some()
|| params.allow_self_payment.unwrap_or(false)
|| params.is_amp.unwrap_or(false)
{
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/lni/lexe/api.rs` around lines 99 - 104, Update the routing-options
validation condition to reject only enabled unsupported options, allowing
optional boolean fields set to Some(false) while continuing to reject
Some(true). Preserve the existing handling for max_parts and hop public keys,
and adjust the is_amp and allow_self_payment checks in the surrounding
validation logic.

Comment thread crates/lni/lexe/api.rs
Comment on lines +256 to +276
async fn matching_payments(
wallet: &LexeWallet,
payment_hash: Option<&str>,
search: Option<&str>,
created_after: Option<i64>,
created_before: Option<i64>,
skip: usize,
limit: usize,
) -> Result<Vec<Payment>, ApiError> {
if limit == 0 {
return Ok(Vec::new());
}

wallet
.sync_payments()
.await
.map_err(|error| api_error("Failed to sync Lexe payments", error))?;

let mut after = None;
let mut skipped = 0usize;
let mut matches = Vec::with_capacity(limit);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Bound the caller-controlled transaction limit.

An i64::MAX limit is accepted and passed to Vec::with_capacity(limit), which can panic or abort on allocation; large values also trigger unbounded pagination. Enforce a reasonable maximum and avoid preallocating the full requested size.

Proposed defensive allocation
-    let mut matches = Vec::with_capacity(limit);
+    let mut matches = Vec::with_capacity(limit.min(PAYMENT_PAGE_SIZE));

Also reject params.limit above the public API’s maximum page size.

Also applies to: 466-474

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/lni/lexe/api.rs` around lines 256 - 276, Bound the caller-controlled
limit in matching_payments and the surrounding API parameter validation to the
public API’s maximum page size, rejecting params.limit values above that
maximum. Avoid Vec::with_capacity(limit) based on the untrusted request; use a
bounded capacity or no preallocation while preserving pagination and result
limits.

Comment thread crates/lni/lexe/api.rs
Comment on lines +402 to +408
let payment = if let Some(timeout_secs) = params.timeout_seconds {
tokio::time::timeout(
Duration::from_secs(timeout_secs as u64),
wallet.pay_invoice(request),
)
.await
.map_err(|_| ApiError::NetworkError("Lexe payment timed out".to_owned()))?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Reconcile payment state after a timeout.

The timeout only stops the local future; the remote payment may still complete. Returning a generic network error can cause callers to retry an indeterminate payment. Reconcile using the invoice payment hash, or return an explicit indeterminate result requiring lookup before retry.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/lni/lexe/api.rs` around lines 402 - 408, Update the timeout handling
in the payment flow around wallet.pay_invoice to reconcile the remote payment
state using the invoice payment hash before returning. If reconciliation cannot
determine whether payment completed, return an explicit indeterminate result
that requires lookup before retrying instead of the generic NetworkError;
preserve the existing successful payment path.

Comment thread crates/lni/lexe/api.rs
Comment on lines +519 to +543
pub async fn on_invoice_events(
wallet: Arc<LexeWallet>,
params: OnInvoiceEventParams,
callback: Arc<dyn OnInvoiceEventCallback>,
) {
if params.polling_delay_sec <= 0 || params.max_polling_sec <= 0 {
callback.failure(None);
return;
}

let started = tokio::time::Instant::now();
let max_polling = Duration::from_secs(params.max_polling_sec as u64);
let polling_delay = Duration::from_secs(params.polling_delay_sec as u64);
let mut last_transaction = None;

while started.elapsed() < max_polling {
match matching_payments(
&wallet,
params.payment_hash.as_deref(),
params.search.as_deref(),
None,
None,
0,
1,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Require an event selector before polling.

With both payment_hash and search empty, matching_payments matches the newest arbitrary wallet payment. The callback can therefore report an unrelated transaction immediately. Apply the same selector validation used by lookup_invoice.

Proposed fix
 ) {
+    if params.payment_hash.as_deref().is_none_or(str::is_empty)
+        && params.search.as_deref().is_none_or(str::is_empty)
+    {
+        callback.failure(None);
+        return;
+    }
+
     if params.polling_delay_sec <= 0 || params.max_polling_sec <= 0 {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub async fn on_invoice_events(
wallet: Arc<LexeWallet>,
params: OnInvoiceEventParams,
callback: Arc<dyn OnInvoiceEventCallback>,
) {
if params.polling_delay_sec <= 0 || params.max_polling_sec <= 0 {
callback.failure(None);
return;
}
let started = tokio::time::Instant::now();
let max_polling = Duration::from_secs(params.max_polling_sec as u64);
let polling_delay = Duration::from_secs(params.polling_delay_sec as u64);
let mut last_transaction = None;
while started.elapsed() < max_polling {
match matching_payments(
&wallet,
params.payment_hash.as_deref(),
params.search.as_deref(),
None,
None,
0,
1,
)
pub async fn on_invoice_events(
wallet: Arc<LexeWallet>,
params: OnInvoiceEventParams,
callback: Arc<dyn OnInvoiceEventCallback>,
) {
if params.payment_hash.as_deref().is_none_or(str::is_empty)
&& params.search.as_deref().is_none_or(str::is_empty)
{
callback.failure(None);
return;
}
if params.polling_delay_sec <= 0 || params.max_polling_sec <= 0 {
callback.failure(None);
return;
}
let started = tokio::time::Instant::now();
let max_polling = Duration::from_secs(params.max_polling_sec as u64);
let polling_delay = Duration::from_secs(params.polling_delay_sec as u64);
let mut last_transaction = None;
while started.elapsed() < max_polling {
match matching_payments(
&wallet,
params.payment_hash.as_deref(),
params.search.as_deref(),
None,
None,
0,
1,
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/lni/lexe/api.rs` around lines 519 - 543, Update on_invoice_events to
reject requests where both params.payment_hash and params.search are empty,
using the same selector validation behavior as lookup_invoice before entering
the polling loop. Preserve the existing callback.failure(None) response and
avoid calling matching_payments without at least one event selector.

Comment thread crates/lni/lexe/lib.rs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant