Skip to content

Conversation

@tnull
Copy link
Contributor

@tnull tnull commented Feb 10, 2026

Fixes #2076.

The confusion around sats/msat keeps coming up over and over again, most-recently surfaced again over at lightningdevkit/ldk-server#123 for example. We therefore for ages planned to introduce a dedicated LightningAmount type that avoids that confusion by using strong typing. Now, with the power of Grayskul Claude, we can easily make this refactoring.

To this end, we introduce a lightning_types::amount::LightningAmount and use it for all occurences of _msat/_msats in the public API.

As follow-ups I also want to finish migrating _sat/_sats/_satoshis arguments to bitcoin::Amount and fee rates in public APIs to use bitcoin::Amount (cf. #2410). I basically have the changes ready. Reviewers, please let me know if you'd prefer to do it all in one PR rather in ~2-3 PRs.

Note that I'd be in favor of eventually using these strong types everywhere in the codebase instead of the u64s everywhere, as this would considerably reduce the chances of messing up conversions. However, we we choose the less invasive option first: introduce the type, and change the API layer.

Add a new `LightningAmount` struct wrapping `u64` millisatoshis to
provide type-safe handling of Lightning Network amounts. This type
will be used to replace raw `u64` values in public APIs throughout
the codebase.

The type provides:
- Constructors: `from_msat()`, `from_sat()` (saturating on overflow)
- Accessors: `to_msat()`, `to_sat_rounded()`, `to_sat_ceil()`, `to_sat_floor()`
- Conversion: `From<bitcoin::Amount>`
- Arithmetic: `Add`, `Sub`, `AddAssign`, `SubAssign`, `Sum`
- Display: formats as "{value} msat"
- A `ZERO` constant for convenience

Co-Authored-By: HAL 9000
@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Feb 10, 2026

👋 Hi! This PR is now in draft status.
I'll wait to assign reviewers until you mark it as ready for review.
Just convert it out of draft status when you're ready for review!

@tnull
Copy link
Contributor Author

tnull commented Feb 10, 2026

Pinged a few people for conceptual review.

We still might need to figure out some details, especially on the Offer APIs as there we have another amount type. Could rename that to OfferAmount, for example.

@tnull tnull marked this pull request as draft February 10, 2026 11:31
Replace raw `u64` values with the type-safe `LightningAmount` wrapper in
all public method APIs dealing with millisatoshi amounts. This includes
renaming methods like `as_msat()` to `as_amount()`, and
`amount_milli_satoshis()` to `amount()`, ensuring callers use
`LightningAmount::from_msat()` and `.to_msat()` for conversions.

Internal logic, serialized struct fields, and message types remain
unchanged.

Co-Authored-By: HAL 9000
Signed-off-by: Elias Rohrer <dev@tnull.de>

/// Constructs a new [`LightningAmount`] from the given number of satoshis.
///
/// Saturates to [`u64::MAX`] on overflow.
Copy link
Contributor

Choose a reason for hiding this comment

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

It this preferable to panic? I wonder if there could be signed/unsigned conversion bugs on the input, then leading to a silent overflow here.

/// can be denominated in millisatoshis (1/1000 of a satoshi), allowing for finer-grained payment
/// amounts.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct LightningAmount {
Copy link
Contributor

@joostjager joostjager Feb 10, 2026

Choose a reason for hiding this comment

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

Why not call it MilliSatoshi, when that is what it is? Or Msat. If this type is eventually used everywhere, that's quite a few chars saved.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also wondering if a type alias would be sufficient. No compiler checking, but it does help I think. Avoids the custom arithmetic ops etc. An amount is so widely passed around, maybe it is too much to make it a new type.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why not call it MilliSatoshi, when that is what it is? Or Msat. If this type is eventually used everywhere, that's quite a few chars saved.

Because we have the bitcoin::Amount counterpart.

No compiler checking

It's mostly the compiler checking and providing default conversion methods between denominations that we're after.

Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

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

Bleh, can we do this more iteratively? Just introduce the types and use them in a few places that don't touch every test, then do separate PRs that change more code iteratively?

@tnull
Copy link
Contributor Author

tnull commented Feb 10, 2026

Bleh, can we do this more iteratively? Just introduce the types and use them in a few places that don't touch every test, then do separate PRs that change more code iteratively?

I don't expect the overwhelming majority of these changes to influence anything in-flight. I'd rather not make this yet another years-long endeavour. For example, we added fn-level rustfmt::skips ~8 months ago (at the time 948 skips) and still have close to 700 skips to go. Let's not repeat this here please.

FWIW, just doing the API, and doing bitcoin::Amount and bitcoin::FeeRate in follow-ups already is the iterative approach, as far as I'm concerned.

@TheBlueMatt
Copy link
Collaborator

For example, we added fn-level rustfmt::skips ~8 months ago (at the time 948 skips) and still have close to 700 skips to go. Let's not repeat this here please.

Oh come on, that's an inconsistent priority, at best (doubly for things that aren't super actively being modified). Modifying a bunch of interfaces and resulting tests all in a single commit is really not great for review, let alone conflicts. There's ongoing high-priority splicing work that likely conflicts with some of the channel.rs changes, and similar for the custom commitment tx work that is trying to make progress to unblock 0-reserve. Looking at the test changes, there's some API changes that are relatively small code diff and a few that are relatively huge code diff - splitting those up into just three or so PRs doesn't seem crazy and would reduce review complexity and let us sequence PRs better.

@tnull
Copy link
Contributor Author

tnull commented Feb 10, 2026

Oh come on, that's an inconsistent priority, at best (doubly for things that aren't super actively being modified). Modifying a bunch of interfaces and resulting tests all in a single commit is really not great for review, let alone conflicts. There's ongoing high-priority splicing work that likely conflicts with some of the channel.rs changes, and similar for the custom commitment tx work that is trying to make progress to unblock 0-reserve. Looking at the test changes, there's some API changes that are relatively small code diff and a few that are relatively huge code diff - splitting those up into just three or so PRs doesn't seem crazy and would reduce review complexity and let us sequence PRs better.

Well, if you insist this needs to be separate PRs, then we better hold up on this after we ship 0.3 to ensure that all PRs (hopefully including bitcoin::Amount and bitcoin::FeeRate follow-ups) make it into 0.4? I imagine from a user's perspective it would be very annoying/worse to have some of the API converted over to LightingAmount while some is still kept on u64. Not only would that be confusing, but it would have them make multiple rounds of essentially the same API upgrade procedures across different releases. So, IMO we should land all of them at least in one release?

I do however also think a similar approach would be preferable to devs, i.e., one rebase to upgrade all APIs is preferable to do it over and over again as we land 5-6 PRs over time.

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.

Introduce and use an amount type throughout public interface

4 participants