feat: Generic unit on amount #1470
No reviewers
Labels
No labels
DB & Storage
Deployment
Error Handling & Logging
Maintenance
Payment Backend
backport
backport v0.13.x
backport v0.14.x
backport v0.15.x
bindings
blocked
bug
cdk-sql
ci
cli
deps
documentation
duplicate
enhancement
good first issue
help wanted
invalid
keep-open
ldk-node-ui
migrations
mint
mutation-testing
needs rebase
needs review
new nut
nut change
question
ready
rust-version
rustfmt
stacked hold
stale
testing
wallet
weekly-report
wontfix
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
cashubtc/cdk!1470
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "generic_unit_on_amount"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Description
The Cashu protocol supports multiple currency units (Sat, Msat, Usd, Eur, etc.), but the previous Amount type was a simple u64 wrapper with no awareness of units. This created a subtle but dangerous class of bugs: nothing prevented code from accidentally adding 1000 satoshis to 500 millisatoshis, producing a nonsensical result of 1500 in an undefined unit. These bugs are especially insidious because the code compiles and runs without error—the incorrect math only manifests as wrong balances or failed payments in production.
This change makes unit mismatches impossible by encoding the currency unit into the type system. Amount now carries its unit at the type level, and arithmetic operations verify unit compatibility before proceeding. The compiler catches many mistakes statically, and runtime checks catch the rest with a clear UnitMismatch error rather than silent corruption.
The refactor maintains backwards compatibility by using Amount<()> (untyped) at serialization boundaries where the wire protocol expects a plain integer, while internal mint logic now uses Amount to get the safety guarantees. The with_unit() method provides a clear conversion point at the boundary between protocol parsing and application logic, making it explicit where unit context is being added.
This is particularly important for the mint's quote handling, where amounts flow between payment backends (which may use different units internally) and the core mint logic. By making MintQuote.amount_paid, MeltQuote.fee_reserve, and payment response types carry their units, we ensure that fee calculations, balance checks, and unit conversions are always performed correctly.
closes: https://github.com/cashubtc/cdk/issues/1463
Notes to the reviewers
Suggested CHANGELOG Updates
CHANGED
ADDED
REMOVED
FIXED
Checklist
just final-checkbefore committing@ -277,0 +447,4 @@/// # use cashu::{Amount, nuts::CurrencyUnit};/// let amount = Amount::new(1000, CurrencyUnit::Sat);/// let (value, unit) = amount.into_parts();/// assert_eq!(value, 1000);@thesimplekid Pardon my ignorance, but wouldn't it be better if the other amount had the same generic, and we refuse to do the math if the currency units are different? Or the implementor can implement a way to unify the units if possible (from Sat to MSat, for instance).
This is a great chance in my opinion, to reduce confusions when doing math with different units
@ -277,0 +447,4 @@/// # use cashu::{Amount, nuts::CurrencyUnit};/// let amount = Amount::new(1000, CurrencyUnit::Sat);/// let (value, unit) = amount.into_parts();/// assert_eq!(value, 1000);If the other Amount had a unit there would be no need for a generic or a second unit type. This allows us to ensure our units match in the mint where it matters without losing copy and having to introduce clone in even more places throughout the wallet making this a much harder refactor.
@ -277,0 +447,4 @@/// # use cashu::{Amount, nuts::CurrencyUnit};/// let amount = Amount::new(1000, CurrencyUnit::Sat);/// let (value, unit) = amount.into_parts();/// assert_eq!(value, 1000);There is already a convert to fn for this.