Repository: BoltzExchange/boltz-client
Pull request #425: feat: minimal batched
Author: login: jackstar12
Created: 2025-04-24T19:22:05Z | Merged: 2025-04-30T09:04:42Z

closes: #416 

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
	- Added support for a new minimal threshold ("MinimalBatched") for batched swaps, allowing swaps below a certain amount to be handled differently.
- **Improvements**
	- Enhanced swap status handling to better distinguish and process swaps that are batch-only based on the minimal threshold.
	- Updated fee display logic to show fees when a swap is successful, regardless of specific transaction events.
- **Bug Fixes**
	- Improved finalization logic for swaps, ensuring correct processing for swaps below the minimal threshold.
- **Tests**
	- Added new test scenarios to verify swap lifecycle handling at the minimal allowed amount threshold.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/cmd/boltzcli/commands.go b/cmd/boltzcli/commands.go
index 43fc1409..d9792526 100644
--- a/cmd/boltzcli/commands.go
+++ b/cmd/boltzcli/commands.go
@@ -304,7 +304,9 @@ func swapInfoStream(ctx *cli.Context, id string, json bool) error {
 					fmt.Printf("Transaction ID: %s\nAmount: %dsat\n", info.Swap.LockupTransactionId, info.Swap.ExpectedAmount)
 				case boltz.InvoiceSet:
 					fmt.Printf("Invoice: %s\n", info.Swap.Invoice)
-				case boltz.TransactionClaimed:
+				}
+
+				if swap.State == boltzrpc.SwapState_SUCCESSFUL {
 					fmt.Printf("Paid %d sat onchain fee and %d sat boltz fee\n", *info.Swap.OnchainFee, *info.Swap.ServiceFee)
 				}
 			} else if info.ReverseSwap != nil {
diff --git a/internal/nursery/swap.go b/internal/nursery/swap.go
index cb460558..abbb9999 100644
--- a/internal/nursery/swap.go
+++ b/internal/nursery/swap.go
@@ -194,6 +194,10 @@ func (nursery *Nursery) handleSwapStatus(swap *database.Swap, status boltz.SwapS
 		}
 	}
 
+	// batchOnly indicates whether the backend is willing to do a 
+	// cooperative claim transaction or will batch claim
+	batchOnly := false
+
 	switch parsedStatus {
 	case boltz.TransactionMempool:
 		fallthrough
@@ -280,8 +284,28 @@ func (nursery *Nursery) handleSwapStatus(swap *database.Swap, status boltz.SwapS
 		logger.Infof("Swap %s succeeded", swap.Id)
 
 		if parsedStatus == boltz.TransactionClaimPending {
-			if err := nursery.cooperativeSwapClaim(swap, status); err != nil {
-				logger.Warnf("Could not claim swap %s cooperatively: %s", swap.Id, err)
+			submarinePairs, err := nursery.boltz.GetSubmarinePairs()
+			if err != nil {
+				handleError("Could not get submarine pairs: " + err.Error())
+				return
+			}
+			submarinePair, err := boltz.FindPair(swap.Pair, submarinePairs)
+			if err != nil {
+				handleError("Could not find submarine pair: " + err.Error())
+				return
+			}
+
+			decodedInvoice, err := lightning.DecodeInvoice(swap.Invoice, nursery.network.Btc)
+			if err != nil {
+				handleError("Could not decode invoice: " + err.Error())
+				return
+			}
+			batchOnly = decodedInvoice.AmountSat < submarinePair.Limits.Minimal
+
+			if !batchOnly {
+				if err := nursery.cooperativeSwapClaim(swap, status); err != nil {
+					logger.Warnf("Could not claim swap %s cooperatively: %s", swap.Id, err)
+				}
 			}
 		}
 	}
@@ -293,7 +317,9 @@ func (nursery *Nursery) handleSwapStatus(swap *database.Swap, status boltz.SwapS
 		return
 	}
 
-	if parsedStatus.IsCompletedStatus() {
+	// Don't wait for boltz to claim the swap in case of batchOnly
+	// in which case it will stay at transaction.claim.pending for longer
+	if parsedStatus.IsCompletedStatus() || batchOnly {
 		decodedInvoice, err := lightning.DecodeInvoice(swap.Invoice, nursery.network.Btc)
 		if err != nil {
 			handleError("Could not decode invoice: " + err.Error())
@@ -331,7 +357,6 @@ func (nursery *Nursery) handleSwapStatus(swap *database.Swap, status boltz.SwapS
 			handleError("Could not refund Swap " + swap.Id + ": " + err.Error())
 			return
 		}
-		return
 	}
 	nursery.sendSwapUpdate(*swap)
 }
diff --git a/internal/rpcserver/rpcserver_test.go b/internal/rpcserver/rpcserver_test.go
index ff3ed920..e54faa74 100644
--- a/internal/rpcserver/rpcserver_test.go
+++ b/internal/rpcserver/rpcserver_test.go
@@ -2317,10 +2317,27 @@ func TestSwap(t *testing.T) {
 					defer stop()
 					fundedWallet(t, admin, tc.from)
 
+					submarinePair, err := admin.GetPairInfo(boltzrpc.SwapType_SUBMARINE, pair)
+					require.NoError(t, err)
+
 					_, write, _ := createTenant(t, admin, "test")
 					tenant := client.NewBoltzClient(write)
 
 					t.Run("Normal", func(t *testing.T) {
+						t.Run("Minimal", func(t *testing.T) {
+							swap, err := admin.CreateSwap(&boltzrpc.CreateSwapRequest{
+								Amount:           submarinePair.Limits.Minimal,
+								Pair:             pair,
+								SendFromInternal: true,
+							})
+							require.NoError(t, err)
+
+							stream, _ := swapStream(t, admin, swap.Id)
+							stream(boltzrpc.SwapState_PENDING)
+							test.MineBlock()
+							stream(boltzrpc.SwapState_SUCCESSFUL)
+
+						})
 						t.Run("EnoughBalance", func(t *testing.T) {
 
 							swap, err := admin.CreateSwap(&boltzrpc.CreateSwapRequest{
@@ -2372,10 +2389,6 @@ func TestSwap(t *testing.T) {
 					t.Run("Refund", func(t *testing.T) {
 						cli := tc.cli
 
-						submarinePair, err := admin.GetPairInfo(boltzrpc.SwapType_SUBMARINE, pair)
-
-						require.NoError(t, err)
-
 						createFailed := func(t *testing.T, refundAddress string) (streamFunc, streamStatusFunc) {
 							amount := submarinePair.Limits.Minimal + 100
 							swap, err := admin.CreateSwap(&boltzrpc.CreateSwapRequest{
diff --git a/internal/rpcserver/serializer.go b/internal/rpcserver/serializer.go
index 81944e3d..34e2b591 100644
--- a/internal/rpcserver/serializer.go
+++ b/internal/rpcserver/serializer.go
@@ -177,6 +177,10 @@ func serializeChainSwapData(chainSwap *database.ChainSwapData) *boltzrpc.ChainSw
 }
 
 func serializeSubmarinePair(pair boltz.Pair, submarinePair *boltz.SubmarinePair) *boltzrpc.PairInfo {
+	min := submarinePair.Limits.MinimalBatched
+	if min == 0 {
+		min = submarinePair.Limits.Minimal
+	}
 	return &boltzrpc.PairInfo{
 		Pair: serializePair(pair),
 		Hash: submarinePair.Hash,
@@ -185,7 +189,7 @@ func serializeSubmarinePair(pair boltz.Pair, submarinePair *boltz.SubmarinePair)
 			MinerFees:  submarinePair.Fees.MinerFees,
 		},
 		Limits: &boltzrpc.Limits{
-			Minimal:               submarinePair.Limits.Minimal,
+			Minimal:               min,
 			Maximal:               submarinePair.Limits.Maximal,
 			MaximalZeroConfAmount: submarinePair.Limits.MaximalZeroConfAmount,
 		},
diff --git a/pkg/boltz/api.go b/pkg/boltz/api.go
index 583edaa9..1ce57bb8 100644
--- a/pkg/boltz/api.go
+++ b/pkg/boltz/api.go
@@ -115,6 +115,7 @@ type SubmarinePair struct {
 	Rate   float64 `json:"rate"`
 	Limits struct {
 		Minimal               uint64 `json:"minimal"`
+		MinimalBatched        uint64 `json:"minimalBatched"`
 		Maximal               uint64 `json:"maximal"`
 		MaximalZeroConfAmount uint64 `json:"maximalZeroConf"`
 	} `json:"limits"`


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🔭 Outside diff range comments (1)</summary><blockquote>

<details>
<summary>internal/nursery/swap.go (1)</summary><blockquote>

`19-23`: _🛠️ Refactor suggestion_

**`isFinal` ignores successful swaps that never reach `transaction.claimed`**

For batch-only swaps we mark the state `SUCCESSFUL`, but their status may stay at
`transaction.claim_pending`.  `sendSwapUpdate` will therefore continue to report
`IsFinal = false`, causing open streams never to close.

```diff
- isFinal := swap.Status.String() == boltz.TransactionClaimed.String() || swap.State == boltzrpc.SwapState_REFUNDED
+ isFinal := swap.State == boltzrpc.SwapState_SUCCESSFUL ||
+            swap.State == boltzrpc.SwapState_REFUNDED ||
+            swap.Status.String() == boltz.TransactionClaimed.String()
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>pkg/boltz/api.go (1)</summary><blockquote>

`117-121`: **Consider documenting the semantics of the new `MinimalBatched` limit**

`MinimalBatched` and the existing `Minimal` field can easily be confused by downstream consumers. A short comment that clarifies the difference (e.g. “`MinimalBatched` – below this amount swaps will be executed in the next batch only, `Minimal` – absolute minimum accepted by the server”) would prevent misuse and simplify onboarding for new contributors.

</blockquote></details>
<details>
<summary>internal/nursery/swap.go (1)</summary><blockquote>

`197-199`: **Unused `batchOnly` initialisation**

`batchOnly` is set to `false` here but immediately overwritten a few lines
below.  The declaration can move next to its first real assignment to
remove dead code.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 0b842a1213e58498970cebb8255d17b937afd5e7 and 6ac79401627c3ad664e52954d0daa76a3422c208.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `cmd/boltzcli/commands.go` (1 hunks)
* `internal/nursery/swap.go` (4 hunks)
* `internal/rpcserver/serializer.go` (1 hunks)
* `pkg/boltz/api.go` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (2)</summary>

<details>
<summary>internal/rpcserver/serializer.go (1)</summary><blockquote>

<details>
<summary>pkg/boltzrpc/boltzrpc.pb.go (3)</summary>

* `Limits` (1768-1776)
* `Limits` (1791-1791)
* `Limits` (1806-1808)

</details>

</blockquote></details>
<details>
<summary>cmd/boltzcli/commands.go (1)</summary><blockquote>

<details>
<summary>pkg/boltzrpc/boltzrpc.pb.go (1)</summary>

* `SwapState_SUCCESSFUL` (74-74)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: CI (ubuntu-latest, 1.24.x)

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>internal/rpcserver/serializer.go (1)</summary>

`188-191`: **Serialising `MinimalBatched` into protobuf `Limits.Minimal` can be misleading**

The proto schema still exposes the field as `minimal`, yet we now feed it the value from `MinimalBatched`.  
Clients relying on the previous meaning of `minimal` may silently break.

Possible mitigations:
1. Extend the protobuf with a dedicated `minimal_batched` field and back-fill `minimal` for compatibility, _or_
2. Clearly advertise the semantic change in release notes / API docs.

If option 1 is chosen, the change is binary-incompatible and will require regenerating the gRPC stubs.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T19:25:17Z
user: login: michael1011
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T20:46:59Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>internal/nursery/swap.go (1)</summary><blockquote>

`296-296`: _⚠️ Potential issue_

**Batch-only detection compares against the wrong limit**

The new API field that carries the batch threshold is `MinimalBatched`, but the code compares against `Limits.Minimal`. This will mis-classify swaps and may skip cooperative claiming unintentionally.



```diff
- batchOnly = swap.ExpectedAmount < submarinePair.Limits.Minimal
+ batchOnly = swap.ExpectedAmount < submarinePair.Limits.MinimalBatched
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>internal/nursery/swap.go (2)</summary><blockquote>

`19-19`: **Updated condition for determining final swap state**

The condition for determining if a swap is final has been changed from checking the swap state to checking the status string. This aligns with the new batch-only processing logic, but ensure all code paths correctly set the status string when a swap is successful.

---

`285-295`: **Submarine pair lookup for batch threshold determination**

This code retrieves the submarine pairs and finds the relevant pair to determine if a swap should be batch-only. Consider caching submarine pairs to avoid repeated API calls if multiple swaps are processed in quick succession.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 6ac79401627c3ad664e52954d0daa76a3422c208 and fa319b16193816d98772a0755f8d21c1289ab8ca.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `cmd/boltzcli/commands.go` (1 hunks)
* `internal/nursery/swap.go` (4 hunks)
* `internal/rpcserver/serializer.go` (2 hunks)
* `pkg/boltz/api.go` (1 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* internal/rpcserver/serializer.go

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>

* cmd/boltzcli/commands.go
* pkg/boltz/api.go

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>internal/nursery/swap.go (4)</summary><blockquote>

<details>
<summary>pkg/boltz/events.go (1)</summary>

* `TransactionClaimed` (19-19)

</details>
<details>
<summary>pkg/boltzrpc/boltzrpc.pb.go (7)</summary>

* `SwapState_REFUNDED` (80-80)
* `Pair` (714-721)
* `Pair` (736-736)
* `Pair` (751-753)
* `Limits` (1768-1776)
* `Limits` (1791-1791)
* `Limits` (1806-1808)

</details>
<details>
<summary>pkg/boltz/pair.go (1)</summary>

* `FindPair` (18-28)

</details>
<details>
<summary>internal/logger/logger.go (1)</summary>

* `Warnf` (105-107)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: CI (ubuntu-latest, 1.24.x)

</details>

<details>
<summary>🔇 Additional comments (2)</summary><blockquote>

<details>
<summary>internal/nursery/swap.go (2)</summary>

`197-198`: **Introduction of batch-only processing for small swaps**

The `batchOnly` flag is introduced to identify swaps below a certain threshold that will skip cooperative claiming and be processed in batches.

---

`313-313`: **Extended condition for swap finalization**

The condition for finalizing a swap now includes batch-only swaps. This is consistent with the new batch processing flow, but verify that all necessary finalization steps are performed for batch-only swaps as well.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T18:31:50Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>♻️ Duplicate comments (2)</summary><blockquote>

<details>
<summary>internal/nursery/swap.go (2)</summary><blockquote>

`303-307`: **Add tests to cover batch-only swap processing**

Skipping cooperative claiming for batch-only swaps is sensible, but there appear to be no tests verifying this new path.



Please add unit tests to ensure that when `batchOnly == true`:
- `cooperativeSwapClaim` is indeed skipped  
- The swap is correctly routed through the alternative batch mechanism

```shell
#!/bin/bash
# Check for existing tests covering batch-only processing

echo "Searching for tests of batch-only processing..."
rg -p "test.*batch.*only|test.*minimal.*batched" --type go

echo -e "\nChecking for test files in the nursery package..."
find internal/nursery -name "*_test.go"
```

---

`285-302`: _⚠️ Potential issue_

**Comparison uses incorrect field for batch-only detection**

The code is comparing against `submarinePair.Limits.Minimal` but according to the design, it should be comparing against `submarinePair.Limits.MinimalBatched`. This could cause misclassification of swaps and unintentional skipping of cooperative claiming.


```diff
-			batchOnly = decodedInvoice.AmountSat < submarinePair.Limits.Minimal
+			batchOnly = decodedInvoice.AmountSat < submarinePair.Limits.MinimalBatched
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>internal/nursery/swap.go (3)</summary><blockquote>

`197-197`: **Add documentation for the batchOnly flag**

The new `batchOnly` flag is used to identify swaps that should be processed in batch mode only, skipping cooperative claiming. Add a comment explaining its purpose and implications for better code maintainability.

```diff
-	batchOnly := false
+	// batchOnly indicates if a swap should skip the cooperative claim attempt
+	// and only be processed through batching due to its small amount
+	batchOnly := false
```

---

`296-301`: **Avoid duplicate invoice decoding**

The invoice is decoded in this section and then again at line 319. Store the decoded invoice in a variable to avoid redundant decoding operations.

```diff
+			var decodedInvoice *lightning.InvoiceData
 			decodedInvoice, err := lightning.DecodeInvoice(swap.Invoice, nursery.network.Btc)
 			if err != nil {
 				handleError("Could not decode invoice: " + err.Error())
 				return
 			}
 			batchOnly = decodedInvoice.AmountSat < submarinePair.Limits.MinimalBatched
```

Then reuse this variable later in the function instead of decoding the invoice again.

---

`318-318`: **Clarify the batched swap handling logic**

Add a comment explaining why batched swaps should be processed like completed swaps, even though they're in a different state. This makes the code more maintainable and easier to understand.

```diff
-	if parsedStatus.IsCompletedStatus() || batchOnly {
+	// Process both completed swaps and batch-only swaps (which skip cooperative claiming) through the same finalization logic
+	if parsedStatus.IsCompletedStatus() || batchOnly {
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between fa319b16193816d98772a0755f8d21c1289ab8ca and f1e757b7e670a09b89ac03c747ac4fa34262a988.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `cmd/boltzcli/commands.go` (1 hunks)
* `internal/nursery/swap.go` (3 hunks)
* `internal/rpcserver/serializer.go` (2 hunks)
* `pkg/boltz/api.go` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (3)</summary>

* internal/rpcserver/serializer.go
* pkg/boltz/api.go
* cmd/boltzcli/commands.go

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: CI (ubuntu-latest, 1.24.x)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T18:38:18Z
user: login: michael1011
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-28T20:39:13Z
user: login: michael1011
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-29T20:34:36Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>internal/rpcserver/rpcserver_test.go (1)</summary><blockquote>

`2328-2345`: **Add test for amount below MinimalBatched threshold**

This test only covers the exact minimal batched amount case, but doesn't test the behavior for amounts below this threshold.


Consider adding an additional sub-test that creates a swap with an amount below the MinimalBatched threshold to verify it's rejected or handled appropriately:

```go
t.Run("BelowMinimalBatched", func(t *testing.T) {
    // Calculate an amount below minimal batched threshold
    belowMinAmount := submarinePair.Limits.MinimalBatched - 1
    
    // Create swap and verify appropriate error or behavior
    _, err := admin.CreateSwap(&boltzrpc.CreateSwapRequest{
        Amount:           belowMinAmount,
        Pair:             pair,
        SendFromInternal: true,
    })
    
    // Assert expected outcome (either error or special handling)
})
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between f1e757b7e670a09b89ac03c747ac4fa34262a988 and b2004455d17c2ad0c1fcf6537664622b5e4892c4.

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `cmd/boltzcli/commands.go` (1 hunks)
* `internal/nursery/swap.go` (3 hunks)
* `internal/rpcserver/rpcserver_test.go` (2 hunks)
* `internal/rpcserver/serializer.go` (2 hunks)
* `pkg/boltz/api.go` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (4)</summary>

* pkg/boltz/api.go
* internal/rpcserver/serializer.go
* internal/nursery/swap.go
* cmd/boltzcli/commands.go

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>internal/rpcserver/rpcserver_test.go (3)</summary><blockquote>

<details>
<summary>pkg/boltzrpc/boltzrpc.pb.go (18)</summary>

* `SwapType_SUBMARINE` (181-181)
* `Fees` (2004-2011)
* `Fees` (2026-2026)
* `Fees` (2041-2043)
* `Limits` (1768-1776)
* `Limits` (1791-1791)
* `Limits` (1806-1808)
* `MinerFees` (1949-1956)
* `MinerFees` (1971-1971)
* `MinerFees` (1986-1988)
* `CreateSwapRequest` (3072-3097)
* `CreateSwapRequest` (3112-3112)
* `CreateSwapRequest` (3127-3129)
* `Pair` (714-721)
* `Pair` (736-736)
* `Pair` (751-753)
* `SwapState_PENDING` (73-73)
* `SwapState_SUCCESSFUL` (74-74)

</details>
<details>
<summary>pkg/boltzrpc/client/client.go (1)</summary>

* `NewBoltzClient` (29-34)

</details>
<details>
<summary>internal/test/test.go (1)</summary>

* `MineBlock` (168-172)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: CI (ubuntu-latest, 1.24.x)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-29T20:35:27Z
user: login: michael1011
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-29T20:43:02Z
user: login: kilrau
body: Comments ftw!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-30T08:34:15Z

Review comments:
diff_hunk: @@ -304,7 +304,9 @@ func swapInfoStream(ctx *cli.Context, id string, json bool) error {
 					fmt.Printf("Transaction ID: %s\nAmount: %dsat\n", info.Swap.LockupTransactionId, info.Swap.ExpectedAmount)
 				case boltz.InvoiceSet:
 					fmt.Printf("Invoice: %s\n", info.Swap.Invoice)
-				case boltz.TransactionClaimed:
+				}
+
+				if swap.State == boltzrpc.SwapState_SUCCESSFUL {
 					fmt.Printf("Paid %d sat onchain fee and %d sat boltz fee\n", *info.Swap.OnchainFee, *info.Swap.ServiceFee)
 				}
 			} else if info.ReverseSwap != nil {
path: cmd/boltzcli/commands.go
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Nil-pointer dereference when fees are not yet populated**

`OnchainFee` and `ServiceFee` are optional pointers.  When the swap → `SUCCESSFUL` transition is raced by the CLI before the nursery persisted the fees, dereferencing will panic:

```go
fmt.Printf("Paid %d sat onchain fee and %d sat boltz fee\n", *info.Swap.OnchainFee, *info.Swap.ServiceFee)
```

Guard against `nil`:

```diff
- if swap.State == boltzrpc.SwapState_SUCCESSFUL {
-     fmt.Printf("Paid %d sat onchain fee and %d sat boltz fee\n", *info.Swap.OnchainFee, *info.Swap.ServiceFee)
- }
+ if swap.State == boltzrpc.SwapState_SUCCESSFUL &&
+    info.Swap.OnchainFee != nil && info.Swap.ServiceFee != nil {
+     fmt.Printf("Paid %d sat onchain fee and %d sat boltz fee\n",
+         *info.Swap.OnchainFee, *info.Swap.ServiceFee)
+ }
```

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-24T19:25:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -280,8 +282,23 @@ func (nursery *Nursery) handleSwapStatus(swap *database.Swap, status boltz.SwapS
 		logger.Infof("Swap %s succeeded", swap.Id)
 
 		if parsedStatus == boltz.TransactionClaimPending {
-			if err := nursery.cooperativeSwapClaim(swap, status); err != nil {
-				logger.Warnf("Could not claim swap %s cooperatively: %s", swap.Id, err)
+			submarinePairs, err := nursery.boltz.GetSubmarinePairs()
+			if err != nil {
+				handleError("Could not get submarine pairs: " + err.Error())
+				return
+			}
+			submarinePair, err := boltz.FindPair(swap.Pair, submarinePairs)
+			if err != nil {
+				handleError("Could not find submarine pair: " + err.Error())
+				return
+			}
+
+			batchOnly = swap.ExpectedAmount < submarinePair.Limits.Minimal
+
+			if !batchOnly {
+				if err := nursery.cooperativeSwapClaim(swap, status); err != nil {
path: internal/nursery/swap.go
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Batch-only detection compares against the wrong limit**

The new API field that carries the batch threshold is `MinimalBatched`, but the
code compares against `Limits.Minimal`.  This will mis-classify swaps and may
skip cooperative claiming unintentionally.

```diff
- batchOnly = swap.ExpectedAmount < submarinePair.Limits.Minimal
+ batchOnly = swap.ExpectedAmount < submarinePair.Limits.MinimalBatched
```

Please also update unit tests to cover both sides of the threshold.  



Also applies to: 313-313

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-24T19:25:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -280,8 +282,23 @@ func (nursery *Nursery) handleSwapStatus(swap *database.Swap, status boltz.SwapS
 		logger.Infof("Swap %s succeeded", swap.Id)
 
 		if parsedStatus == boltz.TransactionClaimPending {
-			if err := nursery.cooperativeSwapClaim(swap, status); err != nil {
-				logger.Warnf("Could not claim swap %s cooperatively: %s", swap.Id, err)
+			submarinePairs, err := nursery.boltz.GetSubmarinePairs()
+			if err != nil {
+				handleError("Could not get submarine pairs: " + err.Error())
+				return
+			}
+			submarinePair, err := boltz.FindPair(swap.Pair, submarinePairs)
+			if err != nil {
+				handleError("Could not find submarine pair: " + err.Error())
+				return
+			}
+
+			batchOnly = swap.ExpectedAmount < submarinePair.Limits.Minimal
path: internal/nursery/swap.go
user: login: michael1011
body: This should be invoiceAmount < submarinePair.Limits.Minimal
created_at: 2025-04-24T20:45:36Z
author_association: MEMBER
diff_hunk: @@ -280,8 +282,23 @@ func (nursery *Nursery) handleSwapStatus(swap *database.Swap, status boltz.SwapS
 		logger.Infof("Swap %s succeeded", swap.Id)
 
 		if parsedStatus == boltz.TransactionClaimPending {
-			if err := nursery.cooperativeSwapClaim(swap, status); err != nil {
-				logger.Warnf("Could not claim swap %s cooperatively: %s", swap.Id, err)
+			submarinePairs, err := nursery.boltz.GetSubmarinePairs()
+			if err != nil {
+				handleError("Could not get submarine pairs: " + err.Error())
+				return
+			}
+			submarinePair, err := boltz.FindPair(swap.Pair, submarinePairs)
+			if err != nil {
+				handleError("Could not find submarine pair: " + err.Error())
+				return
+			}
+
+			batchOnly = swap.ExpectedAmount < submarinePair.Limits.Minimal
+
+			if !batchOnly {
+				if err := nursery.cooperativeSwapClaim(swap, status); err != nil {
+					logger.Warnf("Could not claim swap %s cooperatively: %s", swap.Id, err)
+				}
 			}
path: internal/nursery/swap.go
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Conditional cooperative claim for non-batch-only swaps**

Skipping cooperative claiming for batch-only swaps makes sense for efficiency, but ensure proper testing to confirm that batched swaps are processed correctly through the alternative batch mechanism.


---

🏁 Script executed:

```shell
#!/bin/bash
# Verify if there are tests for batch-only swap processing

echo "Searching for test cases related to batch-only swaps..."
rg -p "MinimalBatched|batch.*only" --type go
```

Length of output: 325

---



</details>

**Add tests to cover batch-only swap processing**

Skipping cooperative claiming for batch-only swaps is sensible, but I didn’t find any existing tests that verify the batch-only path. Please add unit tests to ensure that when `batchOnly == true`:
- `cooperativeSwapClaim` is indeed skipped  
- the swap is correctly routed through the alternative batch mechanism  

Suggested locations:
- internal/nursery/swap.go around lines 298–302  
- A new test file under internal/nursery (e.g., `swap_test.go`) covering both `batchOnly = false` and `batchOnly = true` scenarios

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-28T18:31:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -194,6 +194,10 @@ func (nursery *Nursery) handleSwapStatus(swap *database.Swap, status boltz.SwapS
 		}
 	}
 
+	// batchOnly indicates if a swap should skip the cooperative claim attempt
+	// and only be processed through batching due to its small amount
path: internal/nursery/swap.go
user: login: michael1011
body: ```suggestion
	// batchOnly indicates whether the backend is willing to do a 
	// cooperative claim transaction or will batch claim
```
created_at: 2025-04-29T20:33:52Z
author_association: MEMBER
diff_hunk: @@ -293,7 +317,9 @@ func (nursery *Nursery) handleSwapStatus(swap *database.Swap, status boltz.SwapS
 		return
 	}
 
-	if parsedStatus.IsCompletedStatus() {
+	// dont wait for boltz to claim the swap in case of batchOnly
+	// in which case it will stay at transaction.claimed for longer
path: internal/nursery/swap.go
user: login: michael1011
body: ```suggestion
	// Don't wait for boltz to claim the swap in case of batchOnly
	// in which case it will stay at transaction.claim.pending for longer
```
created_at: 2025-04-29T20:34:31Z
author_association: MEMBER
diff_hunk: @@ -2317,10 +2318,31 @@ func TestSwap(t *testing.T) {
 					defer stop()
 					fundedWallet(t, admin, tc.from)
 
+					submarinePair, err := admin.GetPairInfo(boltzrpc.SwapType_SUBMARINE, pair)
+					require.NoError(t, err)
+
 					_, write, _ := createTenant(t, admin, "test")
 					tenant := client.NewBoltzClient(write)
 
 					t.Run("Normal", func(t *testing.T) {
+						t.Run("Minimal", func(t *testing.T) {
+							fees := submarinePair.Fees
+							networkFees := math.Ceil(fees.Percentage * float64(submarinePair.Limits.Minimal) / 100)
+							onchainAmount := submarinePair.Limits.Minimal + uint64(networkFees) + fees.MinerFees
+
+							swap, err := admin.CreateSwap(&boltzrpc.CreateSwapRequest{
+								Amount:           onchainAmount,
+								Pair:             pair,
+								SendFromInternal: true,
+							})
+							require.NoError(t, err)
+
+							stream, _ := swapStream(t, admin, swap.Id)
+							stream(boltzrpc.SwapState_PENDING)
+							test.MineBlock()
+							stream(boltzrpc.SwapState_SUCCESSFUL)
+
+						})
path: internal/rpcserver/rpcserver_test.go
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Enhance test to verify batch-only swap path**

The test creates a swap with minimal amount and verifies it goes through, but doesn't explicitly test that swaps below the MinimalBatched threshold are treated differently (marked as batch-only and skip cooperative claiming).


Enhance this test to verify the batch-only behavior by adding assertions that check:
1. The swap is correctly identified as batch-only when below the minimal batched threshold
2. Cooperative claiming is skipped for these swaps
3. The swap is processed through the batch mechanism

For example:

```go
// After creating the swap, verify it's treated as batch-only
require.True(t, swap.BatchOnly, "Swap should be marked as batch-only for minimal batched amount")

// Verify cooperative claiming was skipped
// This might require accessing internal state or adding debug logs to verify
```

---

_🛠️ Refactor suggestion_

**Test for MinimalBatched threshold instead of Minimal**

This test retrieves the submarine pair and creates a swap with the minimal threshold amount, but it's using `submarinePair.Limits.Minimal` instead of `submarinePair.Limits.MinimalBatched`, which was introduced in this PR.


Please modify this test to use the new `MinimalBatched` threshold to properly test the batched swap behavior:

```diff
-            networkFees := math.Ceil(fees.Percentage * float64(submarinePair.Limits.Minimal) / 100)
-            onchainAmount := submarinePair.Limits.Minimal + uint64(networkFees) + fees.MinerFees
+            networkFees := math.Ceil(fees.Percentage * float64(submarinePair.Limits.MinimalBatched) / 100)
+            onchainAmount := submarinePair.Limits.MinimalBatched + uint64(networkFees) + fees.MinerFees
```

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-29T20:35:27Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-04-24T19:22:12Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

"""
## Walkthrough

The changes introduce a new `MinimalBatched` field to the `Limits` struct within the `SubmarinePair` type, update serialization logic to use this new field, and adjust swap handling logic to account for swaps below this threshold. In the swap processing flow, swaps with an expected amount less than `MinimalBatched` are now identified as "batch-only" and skip cooperative claim attempts. The swap status output logic is also slightly refactored to decouple fee printing from specific transaction events, instead tying it to the swap's successful state.

## Changes

| File(s)                            | Change Summary                                                                                                               |
|----------------------------------|-----------------------------------------------------------------------------------------------------------------------------|
| pkg/boltz/api.go                 | Added `MinimalBatched` field to the `Limits` struct within `SubmarinePair`.                                                  |
| internal/rpcserver/serializer.go | Updated serialization to use `MinimalBatched` instead of `Minimal` for the `Limits.Minimal` field in `serializeSubmarinePair`. |
| internal/nursery/swap.go         | Added logic to detect and handle "batch-only" swaps below the `MinimalBatched` threshold; adjusted swap finalization logic.  |
| cmd/boltzcli/commands.go         | Refactored fee printing to occur when swap is successful, not just on `TransactionClaimed` event.                            |
| internal/rpcserver/rpcserver_test.go | Added a new subtest for minimal amount swaps to verify behavior with the new `MinimalBatched` limit; improved test efficiency. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant CLI
    participant Nursery
    participant RPCServer
    participant API

    User->>CLI: Initiate swap
    CLI->>Nursery: Process swap
    Nursery->>API: Fetch submarine pairs
    API-->>Nursery: Return pair details (including MinimalBatched)
    Nursery->>Nursery: Check if swap amount < MinimalBatched
    alt Batch-only swap
        Nursery->>Nursery: Skip cooperative claim
        Nursery->>Nursery: Finalize as batch-only
    else Not batch-only
        Nursery->>Nursery: Attempt cooperative claim
        Nursery->>Nursery: Finalize if successful
    end
    Nursery->>CLI: Output swap status and fees if successful
```

## Assessment against linked issues

| Objective                                                                                  | Addressed | Explanation                                                                                       |
|--------------------------------------------------------------------------------------------|-----------|-------------------------------------------------------------------------------------------------|
| Apply new `"minimalBatched"` limit for submarine swap pairs (#416)                         | ✅        |                                                                                                 |

## Poem

> A new limit for swaps, so neat and so patched,  
> "MinimalBatched" now carefully matched!  
> Small swaps skip claims, just batch and be done,  
> The code flows more smoothly—oh what fun!  
> With fields and with checks, our logic enhanced,  
> This bunny approves—let swaps be advanced! 🐇✨
"""

<!-- walkthrough_end -->


---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between b2004455d17c2ad0c1fcf6537664622b5e4892c4 and 9e38f1652faa568415963df3a44cfd90df3e8bf2.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `internal/nursery/swap.go` (3 hunks)
* `internal/rpcserver/rpcserver_test.go` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>

* internal/nursery/swap.go
* internal/rpcserver/rpcserver_test.go

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: CI (ubuntu-latest, 1.24.x)

</details>

</details>
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

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.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=BoltzExchange/boltz-client&utm_content=425):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: kilrau
created_at: 2025-04-29T06:53:22Z
body: How about adding the mentioned test and comments? :pray: 

> **Actionable comments posted: 0**
> 
> ♻️ Duplicate comments (2)
> > internal/nursery/swap.go (2)> `303-307`: **Add tests to cover batch-only swap processing**
> > > Skipping cooperative claiming for batch-only swaps is sensible, but there appear to be no tests verifying this new path.
> > > Please add unit tests to ensure that when `batchOnly == true`:
> > > 
> > > * `cooperativeSwapClaim` is indeed skipped
> > > * The swap is correctly routed through the alternative batch mechanism
> > > 
> > > ```shell
> > > #!/bin/bash
> > > # Check for existing tests covering batch-only processing
> > > 
> > > echo "Searching for tests of batch-only processing..."
> > > rg -p "test.*batch.*only|test.*minimal.*batched" --type go
> > > 
> > > echo -e "\nChecking for test files in the nursery package..."
> > > find internal/nursery -name "*_test.go"
> > > ```
> > > 
> > > 
> > >     
> > >       
> > >     
> > > 
> > >       
> > >     
> > > 
> > >     
> > >   
> > > 
> > > `285-302`: _⚠️ Potential issue_
> > > **Comparison uses incorrect field for batch-only detection**
> > > The code is comparing against `submarinePair.Limits.Minimal` but according to the design, it should be comparing against `submarinePair.Limits.MinimalBatched`. This could cause misclassification of swaps and unintentional skipping of cooperative claiming.
> > > ```diff
> > > -			batchOnly = decodedInvoice.AmountSat < submarinePair.Limits.Minimal
> > > +			batchOnly = decodedInvoice.AmountSat < submarinePair.Limits.MinimalBatched
> > > ```
> 
> 🧹 Nitpick comments (3)
> > internal/nursery/swap.go (3)> `197-197`: **Add documentation for the batchOnly flag**
> > > The new `batchOnly` flag is used to identify swaps that should be processed in batch mode only, skipping cooperative claiming. Add a comment explaining its purpose and implications for better code maintainability.
> > > ```diff
> > > -	batchOnly := false
> > > +	// batchOnly indicates if a swap should skip the cooperative claim attempt
> > > +	// and only be processed through batching due to its small amount
> > > +	batchOnly := false
> > > ```
> > > 
> > > 
> > >     
> > >       
> > >     
> > > 
> > >       
> > >     
> > > 
> > >     
> > >   
> > > 
> > > `296-301`: **Avoid duplicate invoice decoding**
> > > The invoice is decoded in this section and then again at line 319. Store the decoded invoice in a variable to avoid redundant decoding operations.
> > > ```diff
> > > +			var decodedInvoice *lightning.InvoiceData
> > >  			decodedInvoice, err := lightning.DecodeInvoice(swap.Invoice, nursery.network.Btc)
> > >  			if err != nil {
> > >  				handleError("Could not decode invoice: " + err.Error())
> > >  				return
> > >  			}
> > >  			batchOnly = decodedInvoice.AmountSat < submarinePair.Limits.MinimalBatched
> > > ```
> > > 
> > > 
> > >     
> > >       
> > >     
> > > 
> > >       
> > >     
> > > 
> > >     
> > >   
> > > Then reuse this variable later in the function instead of decoding the invoice again.
> > > 
> > > `318-318`: **Clarify the batched swap handling logic**
> > > Add a comment explaining why batched swaps should be processed like completed swaps, even though they're in a different state. This makes the code more maintainable and easier to understand.
> > > ```diff
> > > -	if parsedStatus.IsCompletedStatus() || batchOnly {
> > > +	// Process both completed swaps and batch-only swaps (which skip cooperative claiming) through the same finalization logic
> > > +	if parsedStatus.IsCompletedStatus() || batchOnly {
> > > ```
> 
> 📜 Review details
author_association: MEMBER