Skip to content

feat: batch minting models and api#31

Merged
d4rp4t merged 4 commits intoKukks:masterfrom
d4rp4t:feat/batch-mint
Mar 18, 2026
Merged

feat: batch minting models and api#31
d4rp4t merged 4 commits intoKukks:masterfrom
d4rp4t:feat/batch-mint

Conversation

@d4rp4t
Copy link
Collaborator

@d4rp4t d4rp4t commented Jan 27, 2026

cashubtc/nuts#333

Summary by CodeRabbit

  • New Features

    • Added NUT-29 batch mint support and related batch mint API endpoints and request/response models
    • Added a helper to expose blank mint outputs
  • Improvements

    • Extended mint info with richer protocol details (swap, WebSocket, MPP)
    • Interface and client updated to support batch mint workflows
  • Tests

    • Minor test formatting and default session adjustments
  • Chores

    • Code formatting and cleanup changes

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 27, 2026

Warning

Rate limit exceeded

@d4rp4t has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 1 minutes and 47 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 210bfa31-040a-4434-bf96-1b9e3982c5ee

📥 Commits

Reviewing files that changed from the base of the PR and between 0748732 and 6fde555.

📒 Files selected for processing (2)
  • DotNut/Abstractions/MintInfo.cs
  • DotNut/ApiModels/BatchMint/PostBatchedMintRequest.cs
📝 Walkthrough

Walkthrough

Adds NUT‑29 batch mint support: new batch mint/check API surface and DTOs, consolidates several API model types into MintInfo, exposes a MeltHandlerBolt11 accessor, and applies formatting/whitespace changes across tests and a Nostr handler.

Changes

Cohort / File(s) Summary
Batch Mint API client & interface
DotNut/Api/CashuHttpClient.cs, DotNut/Api/ICashuApi.cs
Added BatchCheckMintQuoteState<TResponse>(...) and BatchMint(...) methods to support batched mint quote checks and batch mint execution.
Batch Mint DTOs
DotNut/ApiModels/BatchMint/PostBatchedMintRequest.cs, DotNut/ApiModels/BatchMint/PostBatchedMintResponse.cs, DotNut/ApiModels/BatchMint/PostBatchedMintQuoteStateRequest.cs
New request/response models for batched mint operations (quotes, amounts, outputs, signatures, and returned blind signatures).
MintInfo & model consolidation
DotNut/Abstractions/MintInfo.cs
Introduced NUT‑29 handling (IsSupportedBatchMint/CheckNut29), added BatchMintInfo, SwapInfo, WebSocketSupport, MPPInfo types and simplified ProtectedEndpoint Regex; consolidated previously separate Info models into this file.
Removed legacy Info models
DotNut/ApiModels/Info/MPPInfo.cs, DotNut/ApiModels/Info/SwapInfo.cs, DotNut/ApiModels/Info/WebSocketSupport.cs
Deleted standalone DTO files (types moved/merged into MintInfo.cs).
Handler API addition
DotNut/Abstractions/Handlers/MeltHandlerBolt11.cs
Added public GetBlankOutputs() returning blankOutputs.
Formatting / tests / handler tweaks
DotNut.Tests/Integration.cs, DotNut.Tests/UnitTest1.cs, DotNut.Nostr/NostrNip17PaymentRequestInterfaceHandler.cs, DotNut/NUT14/HTLCProofSecret.cs
Whitespace, line‑wrapping, and attribute string reflow changes; multi‑line conditional/constructor formatting in Nostr handler; no semantic changes.
IDE test session config
DotNut.sln.DotSettings.user
Enabled UnitTesting sessions (IsActive="True") and added TestId entry for Nut29Tests in sessions.

Sequence Diagram(s)

sequenceDiagram
  participant Caller as Client/Service
  participant ClientLib as CashuHttpClient
  participant Server as Cashu API Server
  participant MintSvc as Mint Service

  Caller->>ClientLib: BatchCheckMintQuoteState(method, request)
  ClientLib->>Server: POST /v1/mint/quote/{method}/check (request JSON)
  Server->>MintSvc: validate quotes / quote states
  MintSvc-->>Server: quote states response
  Server-->>ClientLib: 200 + JSON
  ClientLib-->>Caller: deserialized response

  Caller->>ClientLib: BatchMint(method, request)
  ClientLib->>Server: POST /v1/mint/{method}/batch (request JSON)
  Server->>MintSvc: perform batch mint (outputs, signatures)
  MintSvc-->>Server: blind signatures
  Server-->>ClientLib: 200 + signatures JSON
  ClientLib-->>Caller: PostBatchedMintResponse
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Feat/nut26 #29 — Modifies the same NostrNip17PaymentRequestInterfaceHandler.cs with related Nostr transport/tag handling changes.
  • Feat/abstractions #23 — Also updates NostrNip17PaymentRequestInterfaceHandler.cs (reformatting/composition) and includes similar test edits.

Suggested reviewers

  • Kukks

Poem

🐰
A batch of mints in tidy rows,
Quotes checked fast where code now flows,
DTOs hop in, signatures too,
A little method, a rabbit's cue —
Hooray for mints and tests anew! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: batch minting models and api' accurately describes the main changes: new batch minting data models (PostBatchedMintQuoteStateRequest, PostBatchedMintRequest, PostBatchedMintResponse) and API methods (BatchCheckMintQuoteState, BatchMint) added across multiple files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@d4rp4t d4rp4t marked this pull request as ready for review March 18, 2026 00:04
@d4rp4t d4rp4t changed the title batch minting models and api feat: batch minting models and api Mar 18, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
DotNut.Nostr/NostrNip17PaymentRequestInterfaceHandler.cs (1)

21-23: Extract the NIP-17 transport predicate into a shared helper.

The same filter is duplicated in CanHandle and SendPayment; centralizing it reduces drift risk when transport-tag rules change.

♻️ Proposed refactor
 public class NostrNip17PaymentRequestInterfaceHandler : PaymentRequestInterfaceHandler
 {
+    private static bool IsNip17NostrTransport(PaymentRequestTransport transport)
+    {
+        return transport.Type == "nostr"
+            && transport.Tags?.Any(tag => tag.Key == "n" && tag.Value.Any(v => v == "17")) == true;
+    }
+
     public static void Register()
     {
         PaymentRequestTransportInitiator.Handlers.Add(
             new NostrNip17PaymentRequestInterfaceHandler()
         );
@@
     public bool CanHandle(PaymentRequest request)
     {
-        return request.Transports.Any(t =>
-            t.Type == "nostr"
-            && t.Tags?.Any(tag => tag.Key == "n" && tag.Value.Any(v => v == "17")) == true
-        );
+        return request.Transports.Any(IsNip17NostrTransport);
     }
@@
-        var nostrTransport = request.Transports.FirstOrDefault(t =>
-            t.Type == "nostr"
-            && t.Tags?.Any(tag => tag.Key == "n" && tag.Value.Any(v => v == "17")) == true
-        );
+        var nostrTransport = request.Transports.FirstOrDefault(IsNip17NostrTransport);

Also applies to: 33-35

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DotNut.Nostr/NostrNip17PaymentRequestInterfaceHandler.cs` around lines 21 -
23, Extract the duplicated NIP-17 transport predicate into a single shared
helper to avoid drift: create a private static method (e.g., IsNip17Transport or
MatchesNip17Transport) on the NostrNip17PaymentRequestInterfaceHandler class and
replace the inline predicate in both CanHandle and SendPayment with calls to
that helper; the helper should encapsulate the check t.Type == "nostr" &&
t.Tags?.Any(tag => tag.Key == "n" && tag.Value.Any(v => v == "17")) == true so
both methods use the exact same logic.
DotNut.sln.DotSettings.user (1)

18-23: Keep Rider user-session state out of version control.

These entries are personal IDE state (IsActive, session ancestry, generated session IDs). Committing them just adds machine-specific churn and merge noise without affecting the product. Please drop this file from the PR, or ignore *.DotSettings.user.

Also applies to: 41-48

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DotNut.sln.DotSettings.user` around lines 18 - 23, The committed
DotNut.sln.DotSettings.user contains personal Rider session state (entries under
UnitTestSessionStore/Sessions like the SessionState/ TestAncestor blocks) and
should be removed from the PR: remove the file from the commit (git rm --cached
DotNut.sln.DotSettings.user or simply drop it from the change), add a rule to
ignore these IDE user files by adding "*.DotSettings.user" to .gitignore, and
commit the .gitignore update so future personal Rider settings (including
UnitTestSessionStore entries) are not tracked.
DotNut/Api/ICashuApi.cs (1)

61-65: Make the batch quote-check response shape explicit.

This endpoint returns a JSON array of quote objects in request order, so Task<TResponse> leaves a fixed part of the contract implicit. Task<TQuoteResponse[]> or IReadOnlyList<TQuoteResponse> would make the public API harder to misuse. (github.com)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DotNut/Api/ICashuApi.cs` around lines 61 - 65, The BatchCheckMintQuoteState
method in ICashuApi currently returns a generic Task<TResponse> which hides the
fact the endpoint returns an ordered JSON array of quote objects; change the
signature of BatchCheckMintQuoteState to return a concrete collection type such
as Task<TQuoteResponse[]> or Task<IReadOnlyList<TQuoteResponse>> (replace the
generic TResponse), update the method declaration in the ICashuApi interface,
and then update any callers or implementations to deserialize and handle an
array/list of TQuoteResponse in request order to ensure the API contract is
explicit and harder to misuse.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@DotNut/Abstractions/Handlers/MeltHandlerBolt11.cs`:
- Line 15: GetBlankOutputs currently returns the live blankOutputs list which
allows callers to mutate the internal collection and corrupt downstream Melt()
processing; change GetBlankOutputs to return a defensive copy or an
immutable/read-only view (e.g., return new List<OutputData>(blankOutputs) or
IReadOnlyList<OutputData>) so callers cannot add/remove/reorder the internal
list, and update any call sites to accept the new return type if you switch to
IReadOnlyList; keep the internal blankOutputs field private and only mutate it
inside Melt() and other internal methods.

In `@DotNut/Abstractions/MintInfo.cs`:
- Around line 223-247: The CheckNut29 logic incorrectly treats omitted NUT-29
fields as "unsupported"; update CheckNut29 (and the analogous function around
lines 382-390) to: mark Supported = true whenever a valid nut29 payload
deserializes (regardless of Methods being null/empty), preserve the semantics
that a null Methods means "all NUT-04 methods" (i.e., keep Methods null rather
than forcing Supported = false), and make MaxBatchSize optional (change its type
to int? or otherwise detect presence) so you only set MaxBatchSize when nut29
provides it; copy nut29.Methods and nut29.MaxBatchSize when present but do not
treat missing fields as disabling batch support. Ensure JsonException handling
remains.

In `@DotNut/ApiModels/BatchMint/PostBatchedMintRequest.cs`:
- Around line 10-12: The Amounts property currently allows nullable array
entries (ulong?[]?) which permits invalid payloads like [100, null]; change the
property type to an optional array of non-null ulongs (ulong[]?) so
quote_amounts remains optional but, when present, contains only non-null values;
update the Amounts declaration in PostBatchedMintRequest (keeping the existing
attributes JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault) and
JsonPropertyName("quote_amounts")) to use ulong[]? instead of ulong?[]?.

---

Nitpick comments:
In `@DotNut.Nostr/NostrNip17PaymentRequestInterfaceHandler.cs`:
- Around line 21-23: Extract the duplicated NIP-17 transport predicate into a
single shared helper to avoid drift: create a private static method (e.g.,
IsNip17Transport or MatchesNip17Transport) on the
NostrNip17PaymentRequestInterfaceHandler class and replace the inline predicate
in both CanHandle and SendPayment with calls to that helper; the helper should
encapsulate the check t.Type == "nostr" && t.Tags?.Any(tag => tag.Key == "n" &&
tag.Value.Any(v => v == "17")) == true so both methods use the exact same logic.

In `@DotNut.sln.DotSettings.user`:
- Around line 18-23: The committed DotNut.sln.DotSettings.user contains personal
Rider session state (entries under UnitTestSessionStore/Sessions like the
SessionState/ TestAncestor blocks) and should be removed from the PR: remove the
file from the commit (git rm --cached DotNut.sln.DotSettings.user or simply drop
it from the change), add a rule to ignore these IDE user files by adding
"*.DotSettings.user" to .gitignore, and commit the .gitignore update so future
personal Rider settings (including UnitTestSessionStore entries) are not
tracked.

In `@DotNut/Api/ICashuApi.cs`:
- Around line 61-65: The BatchCheckMintQuoteState method in ICashuApi currently
returns a generic Task<TResponse> which hides the fact the endpoint returns an
ordered JSON array of quote objects; change the signature of
BatchCheckMintQuoteState to return a concrete collection type such as
Task<TQuoteResponse[]> or Task<IReadOnlyList<TQuoteResponse>> (replace the
generic TResponse), update the method declaration in the ICashuApi interface,
and then update any callers or implementations to deserialize and handle an
array/list of TQuoteResponse in request order to ensure the API contract is
explicit and harder to misuse.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f4a3df9d-708c-44ef-a089-9f7cfc749ddf

📥 Commits

Reviewing files that changed from the base of the PR and between df39a19 and 1fcafc4.

📒 Files selected for processing (15)
  • DotNut.Nostr/NostrNip17PaymentRequestInterfaceHandler.cs
  • DotNut.Tests/Integration.cs
  • DotNut.Tests/UnitTest1.cs
  • DotNut.sln.DotSettings.user
  • DotNut/Abstractions/Handlers/MeltHandlerBolt11.cs
  • DotNut/Abstractions/MintInfo.cs
  • DotNut/Api/CashuHttpClient.cs
  • DotNut/Api/ICashuApi.cs
  • DotNut/ApiModels/BatchMint/PostBatchedMintQuoteStateRequest.cs
  • DotNut/ApiModels/BatchMint/PostBatchedMintRequest.cs
  • DotNut/ApiModels/BatchMint/PostBatchedMintResponse.cs
  • DotNut/ApiModels/Info/MPPInfo.cs
  • DotNut/ApiModels/Info/SwapInfo.cs
  • DotNut/ApiModels/Info/WebSocketSupport.cs
  • DotNut/NUT14/HTLCProofSecret.cs
💤 Files with no reviewable changes (3)
  • DotNut/ApiModels/Info/WebSocketSupport.cs
  • DotNut/ApiModels/Info/MPPInfo.cs
  • DotNut/ApiModels/Info/SwapInfo.cs

{
public PostMeltQuoteBolt11Response GetQuote() => quote;

public List<OutputData> GetBlankOutputs() => blankOutputs;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't expose the live blankOutputs list.

This hands callers the same collection that Melt() later reuses for NUT-10 processing, request construction, and change reconstruction. A caller can add, remove, or reorder outputs between those steps and corrupt the melt flow.

Possible fix
-    public List<OutputData> GetBlankOutputs() => blankOutputs;
+    public IReadOnlyList<OutputData> GetBlankOutputs() => blankOutputs.AsReadOnly();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DotNut/Abstractions/Handlers/MeltHandlerBolt11.cs` at line 15,
GetBlankOutputs currently returns the live blankOutputs list which allows
callers to mutate the internal collection and corrupt downstream Melt()
processing; change GetBlankOutputs to return a defensive copy or an
immutable/read-only view (e.g., return new List<OutputData>(blankOutputs) or
IReadOnlyList<OutputData>) so callers cannot add/remove/reorder the internal
list, and update any call sites to accept the new return type if you switch to
IReadOnlyList; keep the internal blankOutputs field private and only mutate it
inside Melt() and other internal methods.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
DotNut/Abstractions/MintInfo.cs (1)

223-247: ⚠️ Potential issue | 🟠 Major

Don't collapse the NUT-29 "all methods" case into an empty array.

Support should be based on successfully deserializing the NUT-29 payload. Line 232 still gates it on Methods, while Lines 391-393 initialize Methods to []. Because Line 385 defines AllSupported as Methods is null, valid payloads like {} or {"max_batch_size": 64} lose the "all NUT-04 methods" signal and surface as Supported = true, AllSupported = false.

In System.Text.Json, if a property is declared as `public string[]? Methods { get; set; } = [];`, does omitting `"methods"` during deserialization keep `Methods` as `[]` instead of `null`?
💡 Proposed fix
-                if (nut29?.Methods is not null)
+                if (nut29 is not null)
                 {
                     return new BatchMintInfo
                     {
                         Supported = true,
                         MaxBatchSize = nut29.MaxBatchSize,
                         Methods = nut29.Methods,
                     };
                 }
@@
-    public string[]? Methods { get; set; } = [];
+    public string[]? Methods { get; set; }

Also applies to: 382-394

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DotNut/Abstractions/MintInfo.cs` around lines 223 - 247, The CheckNut29
method incorrectly treats a successfully deserialized NUT-29 payload with
missing "methods" as unsupported because it checks nut29?.Methods is not null;
change the logic to consider the payload supported when
JsonSerializer.Deserialize<BatchMintInfo> returns a non-null nut29 (i.e., test
nut29 != null), and assign Methods = nut29.Methods (preserving a null value if
the JSON omitted the property) so that AllSupported (which depends on Methods
being null) remains correct; also remove any default initializer that sets
Methods = [] in the BatchMintInfo type (and fix the same pattern where
BatchMintInfo is constructed/initialized elsewhere) so deserialization can
produce null for omitted "methods".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@DotNut/Abstractions/MintInfo.cs`:
- Around line 223-247: The CheckNut29 method incorrectly treats a successfully
deserialized NUT-29 payload with missing "methods" as unsupported because it
checks nut29?.Methods is not null; change the logic to consider the payload
supported when JsonSerializer.Deserialize<BatchMintInfo> returns a non-null
nut29 (i.e., test nut29 != null), and assign Methods = nut29.Methods (preserving
a null value if the JSON omitted the property) so that AllSupported (which
depends on Methods being null) remains correct; also remove any default
initializer that sets Methods = [] in the BatchMintInfo type (and fix the same
pattern where BatchMintInfo is constructed/initialized elsewhere) so
deserialization can produce null for omitted "methods".

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ae3c11e9-4dd5-4bd3-8f3e-8af2abe3f3a2

📥 Commits

Reviewing files that changed from the base of the PR and between 1fcafc4 and 0748732.

📒 Files selected for processing (2)
  • DotNut/Abstractions/MintInfo.cs
  • DotNut/ApiModels/BatchMint/PostBatchedMintRequest.cs

@d4rp4t d4rp4t merged commit cf248d9 into Kukks:master Mar 18, 2026
1 check passed
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