Skip to content

feat: nwc#2150

Open
asmogo wants to merge 2 commits into
cashubtc:mainfrom
asmogo:feat/nwc
Open

feat: nwc#2150
asmogo wants to merge 2 commits into
cashubtc:mainfrom
asmogo:feat/nwc

Conversation

@asmogo

@asmogo asmogo commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

Description


Notes to the reviewers


Suggested CHANGELOG Updates

CHANGED

ADDED

REMOVED

FIXED


Checklist

  • I followed the code style guidelines
  • I ran just quick-check before committing
  • If the Wallet API was modified (added/removed/changed), I have reflected those changes in the FFI bindings (crates/cdk-ffi)

@github-project-automation github-project-automation Bot moved this to Backlog in CDK Jun 24, 2026
@codecov

codecov Bot commented Jun 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 40.00000% with 357 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.65%. Comparing base (d481edc) to head (74d01e5).
⚠️ Report is 13 commits behind head on main.

Files with missing lines Patch % Lines
crates/cdk-nwc/src/service.rs 41.49% 172 Missing ⚠️
crates/cdk-ffi/src/nwc.rs 0.00% 119 Missing ⚠️
crates/cdk/src/wallet/nwc.rs 63.73% 66 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2150      +/-   ##
==========================================
+ Coverage   71.55%   71.65%   +0.10%     
==========================================
  Files         356      359       +3     
  Lines       73999    75095    +1096     
==========================================
+ Hits        52950    53813     +863     
- Misses      21049    21282     +233     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@asmogo

asmogo commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator Author

closes #2106

@asmogo

asmogo commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator Author

I have tested this with https://nwc-playground.vercel.app/

@asmogo asmogo marked this pull request as ready for review June 25, 2026 15:34
@asmogo asmogo mentioned this pull request Jun 25, 2026
1 task
@thesimplekid thesimplekid added this to the 0.17.3 milestone Jun 26, 2026

@thesimplekid thesimplekid left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I see there is a nwc crate https://github.com/rust-nostr/nostr/tree/master/crates/nwc for the client side of this. I think it would be good to use that to add some end to end tests in for this or maybe just an example would suffice.

return Ok(false);
}

handle_request(&service_keys, &client, handler.as_ref(), &event).await;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Right now handle_request(...).await runs inside the relay notification callback, so requests are processed serially. For a pay_invoice request this can take a while: we need to get a melt quote, pay it, and wait for the LN invoice/payment flow to complete. While that is happening, other requests from the client will not be processed.

I wonder if we eventually want to spawn request handling here, probably with a bounded concurrency limit. That said, I think it is fine to leave the implementation serial for now since this only becomes noticeable if a client sends many zaps/requests at once and payments are slow.

/// A [`cdk_nwc::NwcRequestHandler`] backed by a Cashu [`Wallet`].
#[derive(Debug, Clone)]
pub struct WalletNwcHandler {
wallet: Wallet,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
wallet: Wallet,
wallet: Arc<Wallet>,

We should make this an Arc<> as we eventually want to remove clone from the Wallet. And I think the FFI aligns with this already

#[instrument(skip(self))]
async fn make_invoice(
&self,
request: MakeInvoiceRequest,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

MakeInvoiceRequest accepts a description_hash but we can't support this in cashu so I think we should error if this is set.

.mint_quote(
PaymentMethod::Known(KnownMethod::Bolt11),
Some(amount),
request.description.clone(),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Not all mints support setting the description as its optional https://github.com/cashubtc/nuts/blob/main/23.md#mint-quote. So to avoid silently attempting to create an invoice without the correct description I think we should check the mint info before getting the quote if the description is set in the request.

This is important for ZAPs as the invoice description has to be set to the zap receipt I believe.

#[derive(Debug, Clone)]
pub struct WalletNwcHandler {
wallet: Wallet,
budget_msat: Option<u64>,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I would rename this to max_payment_msat. budget made me think it was some sort of cumulatively daily budget, which would maybe something useful we could add later, I think many nostr apps allow you to set things like hourly, daily zap budgets?

}
}

/// Convert millisatoshis to a wallet [`Amount`] for the given unit.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could we use the cdk Amount type we have to_sat and to_msat fns instead of having the logic here as well?

/// Convert to millisatoshis and return the raw u64 value
///
/// Returns an error if the unit cannot be converted to Msat
/// (i.e., the unit is not Sat or Msat).
pub fn to_msat(&self) -> Result<u64, Error> {
self.convert_to(&CurrencyUnit::Msat).map(|a| a.value())
}
/// Convert to satoshis and return the raw u64 value
///
/// Returns an error if the unit cannot be converted to Sat
/// (i.e., the unit is not Sat or Msat).
pub fn to_sat(&self) -> Result<u64, Error> {
self.convert_to(&CurrencyUnit::Sat).map(|a| a.value())
}
.

So I think we could use that internally and convert at the boundary?

Comment on lines +410 to +411
created_at: Timestamp::from(quote.expiry),
expires_at: Some(Timestamp::from(quote.expiry)),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should these both be the expiry?

@github-project-automation github-project-automation Bot moved this from Backlog to In progress in CDK Jun 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

2 participants