Skip to content

nostr: tolerate missing and null amounts in NIP-47 responses - #1450

Merged
yukibtc merged 1 commit into
nostrdevkit:masterfrom
grunch:nip47-tolerate-missing-numeric-fields
Aug 18, 2026
Merged

nostr: tolerate missing and null amounts in NIP-47 responses#1450
yukibtc merged 1 commit into
nostrdevkit:masterfrom
grunch:nip47-tolerate-missing-numeric-fields

Conversation

@grunch

@grunch grunch commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Fixes #1449.

LookupInvoiceResponse declares amount and fees_paid as required with no serde
default, so a response that omits amount or sends fees_paid: null fails to
deserialize and the whole object is lost — including state, which is what callers
poll a payment for. Alby does both while a payment is in flight, so nwc's
lookup_invoice returns Err and a perfectly healthy wallet looks like one that
answers nothing. It breaks hold-invoice flows in particular, where polling
lookup_invoice until settlement is the only way to observe the invoice resolving.

Same class of problem as the empty-string handling already present on the optional
string fields of these structs; the numeric fields never got equivalent treatment.

What changed

A deserialize_null_as_default helper, alongside the existing
deserialize_empty_string_as_none, applied to the amount/fees_paid fields of
inbound types only:

  • LookupInvoiceResponse
  • MakeHoldInvoiceResponse
  • PaymentNotification
  • HoldInvoiceAcceptedNotification

The request types deliberately keep amount required — those describe what the
caller sends, where a defaulted amount would silently mean "zero-amount".

created_at is left as-is: relaxing it needs Option<Timestamp> or a sentinel,
which felt like a separate decision for you to make. Happy to include it either way.

Serialization is unchanged, so round-trips keep emitting both fields.

Testing

Added test_lookup_invoice_response_tolerates_missing_and_null_amounts, which uses
the exact response shape observed from Alby. cargo test -p nostr --lib --features nip47 nip47 passes (15 tests), and clippy is clean on the crate.

Note: I could not complete a full just precommit — it exhausted the disk on my
machine while building the whole workspace, unrelated to this change. Targeted tests
and clippy on nostr are green.

Found while debugging a Mostro protocol test harness, where this presented for days
as "the wallet is dead server-side".

@codecov

codecov Bot commented Aug 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@TheAwiteb TheAwiteb left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you add a changelog?

LookupInvoiceResponse declares `amount` and `fees_paid` as required with
no serde default, so a response that omits `amount` or sends
`fees_paid: null` fails to deserialize and the whole object is lost —
including `state`, which is what callers poll a payment for. Alby does
both while a payment is in flight, which makes a working wallet look
like one that answers nothing.

This is the same class of problem as the empty-string handling added
earlier for the optional string fields; the numeric fields never got
equivalent treatment.

Relaxed on inbound types only — LookupInvoiceResponse,
MakeHoldInvoiceResponse, PaymentNotification and
HoldInvoiceAcceptedNotification. The request types keep `amount`
required on purpose: those describe what the caller sends, where a
defaulted amount would silently mean zero.

`created_at` is left alone: it would need Option or a sentinel, which is
a separate decision.

Closes nostrdevkit#1449

Signed-off-by: Yuki Kishimoto <yukikishimoto@protonmail.com>
@yukibtc
yukibtc force-pushed the nip47-tolerate-missing-numeric-fields branch from 13f1744 to efe044f Compare August 18, 2026 05:32

@yukibtc yukibtc left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

utACK efe044f

@yukibtc
yukibtc merged commit efe044f into nostrdevkit:master Aug 18, 2026
42 checks passed
@yukibtc

yukibtc commented Aug 18, 2026

Copy link
Copy Markdown
Member

Can you add a changelog?

I've updated the changelog at efe044f

@grunch
grunch deleted the nip47-tolerate-missing-numeric-fields branch August 18, 2026 13:05
@yukibtc

yukibtc commented Aug 19, 2026

Copy link
Copy Markdown
Member

@grunch, this has been included in nostr v0.45.3

@grunch

grunch commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

awesome thanks @yukibtc

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.

nip47: LookupInvoiceResponse requires amount/fees_paid, so Alby responses fail to parse

3 participants