Skip to content

Simple max-sendable estimator#39

Open
amackillop wants to merge 4 commits into
mainfrom
austin_mdk-863_v0-max-withrawable
Open

Simple max-sendable estimator#39
amackillop wants to merge 4 commits into
mainfrom
austin_mdk-863_v0-max-withrawable

Conversation

@amackillop
Copy link
Copy Markdown
Contributor

Why

Consumers of lightning-js want a "max sendable over Lightning right now" number for one-click drain flows (e.g. payouts in moneydevkit.com). Raw getBalance() overstates that because mdk consolidates outbound liquidity on its LSP channel and the payer needs balance - routing_fee(amount).

What

Adds MdkNode#getMaxSendable(), returning either null (no usable LSP channel) or { amountMsat, feeBudgetMsat }. The dust case (channel exists but the fee buffer eats the balance) is { amountMsat: 0, feeBudgetMsat: <buffer> }, distinct from null, so the UI can tell "no channel yet" apart from "fully spent on fees".

v0 is destination-agnostic: subtract a configurable percentage buffer (default 1%, 10-sat floor) from the sum of usable LSP channels' next_outbound_htlc_limit_msat.

Config

Optional maxSendable on MdkNodeOptions:

new MdkNode({
  // ...
  maxSendable: {
    feeBufferBps: 100,        // default
    feeBufferFloorSats: 10,   // default
  },
})

Both fields are optional. Bad input from JS (negative floor, bps above u16::MAX) clamps silently rather than throwing in the constructor, mirroring how SpliceConfig handles the same thing.

Ported from mdkd PR #20 (MDK-863). Consumers like the
moneydevkit.com payouts page need a "max sendable over Lightning
right now" number to drive one-click drains, and raw getBalance()
overstates that by the routing-fee buffer because mdk consolidates
outbound liquidity onto its LSP channel.

The precise version of this would call Router::find_route and
invert per-hop fees, but ldk-node keeps the router private. v0 is
destination-agnostic: subtract a percentage buffer (default 1%,
10-sat floor) from the sum of next_outbound_htlc_limit_msat across
usable LSP channels. v1 will swap the body of compute_estimate for
real fee inversion once the ldk-node fork lands; the accessor
above this stays put across that change.

NoUsableChannel only fires when the filter matches zero channels.
A balance fully consumed by the buffer returns Ok(amount_msat: 0)
so the UI can render "0 sats" instead of "Unavailable" once a
channel exists.
Adds an optional `maxSendable` field on MdkNodeOptions so JS callers
can override the routing-fee buffer used by the estimator.

Defaults are 100 bps and 10 sats, matching mdkd PR #20.
Exposes the v0 max-sendable estimator to JS. Builds a ChannelSnapshot
projection from list_channels, resolves the napi config to native
types, and calls max_sendable::compute_estimate.

NoUsableChannel maps to `null`; `Some(amountMsat: 0)` is the dust
case. That distinction lets the UI tell "no channel yet" apart from
"channel exists but the fee buffer ate the balance". No
`_while_running` sibling because the accessor neither starts nor
stops the node, same as listChannels.

Regenerates index.d.ts for the new symbols.
@amackillop amackillop requested a review from martinsaposnic May 15, 2026 13:21
Copy link
Copy Markdown

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

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: 6087bbf664

ℹ️ 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 src/max_sendable.rs
Some(
acc
.unwrap_or(0)
.saturating_add(c.next_outbound_htlc_limit_msat),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Enforce per-HTLC minimum in max-sendable estimate

compute_estimate sums only next_outbound_htlc_limit_msat, so it can return a positive amount_msat that is still unsendable when the channel’s next_outbound_htlc_minimum_msat is higher than that amount. In that case callers will surface a “max sendable” value that immediately fails at payment time, which defeats the estimator’s purpose for low-liquidity/high-min-HTLC channels. Include the per-channel minimum constraint (or clamp to zero when estimated amount falls below it) when computing the result.

Useful? React with 👍 / 👎.

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