Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 59 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,4 +225,62 @@ When reviewing code, pay special attention to:

---

**For Reviewers**: This is a privacy-focused Bitcoin application. Security and correctness are paramount. When in doubt about Bitcoin/Lightning/swap mechanics, ask for clarification rather than making assumptions.
**For Reviewers**: This is a privacy-focused Bitcoin application. Security and correctness are paramount. When in doubt about Bitcoin/Lightning/swap mechanics, ask for clarification rather than making assumptions.


## Orchestration Protocol

When given a task, always follow this pipeline. Do not skip steps.

### Step 1 — Plan first, code never first

Before touching any file, produce a planning doc in this format:
```
## Task: <one line summary>

### Affected files
- `src/api/makers.rs` — reason
- `frontend/app/routes/makers.tsx` — reason

### Agent assignments
- Agent 1: [file list] — [what it does]
- Agent 2: [file list] — [what it does]

### Complexity: small | medium | large
### Agent count: 1 | 2 | 5
```

Rules for agent count:
- **1 agent** — single file, or changes are tightly coupled across files
- **2 agents** — 2-4 files, clearly separable (e.g. backend + frontend split)
- **5 agents** — 5+ files, each agent owns a distinct module/domain

### Step 2 — Spawn agents via Task tool

Each Task agent receives:
- Its assigned files only (no awareness of other agents' files)
- The specific goal for its slice
- The relevant architecture context from this CLAUDE.md

**Hard rule: no two agents may touch the same file. If a file needs changes from two concerns, one agent owns it and handles both.**

### Step 3 — Review

After all agents complete, spawn 1-2 review Tasks that:
- Read all changed files together
- Check for: type mismatches across the API boundary, broken imports, inconsistent naming, logic errors
- If issues found: report back to planner, re-assign fixes to the relevant agent
- If clean: proceed

### Step 4 — Cleanup

Run the appropriate formatters based on what changed:
- Any `.rs` files touched → `cargo fmt`
- Any `.ts` / `.tsx` files touched → `cd frontend && npx prettier --write <changed files>`
- If both → run both

### Step 5 — Verify

- If `.rs` changed → `cargo build` (not release, just check it compiles)
- If `.ts`/`.tsx` changed → `cd frontend && npm run build`
- Report any errors back to planner for a fix cycle
53 changes: 37 additions & 16 deletions api1.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,28 @@ const api1State = {
// HELPER FUNCTIONS
// ============================================================================

/**
* Returns true if a maker from the offerbook is usable for a swap.
* Mirrors the categorization logic in the offerbook handler.
*/
function isUsableMaker(maker) {
const state = maker.state;
const normalizedState =
typeof state === 'string'
? state.toLowerCase()
: state && typeof state === 'object'
? Object.keys(state)[0]?.toLowerCase()
: null;
if (normalizedState === 'unresponsive' || normalizedState === 'bad') {
return false;
}
if (normalizedState === 'good') {
return true;
}
// Fallback for older payloads without an explicit state.
return maker.offer != null;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

function getCurrentWalletName() {
try {
const configPath = path.join(api1State.DATA_DIR, 'config.toml');
Expand Down Expand Up @@ -908,16 +930,23 @@ function registerTakerHandlers() {

makers.forEach((maker) => {
const state = maker.state;

if (state && state.Unresponsive) {
const normalizedState =
typeof state === 'string'
? state.toLowerCase()
: state && typeof state === 'object'
? Object.keys(state)[0]?.toLowerCase()
: null;

if (normalizedState === 'unresponsive') {
unresponsiveMakers.push(maker);
} else if (state && state.Bad) {
} else if (normalizedState === 'bad') {
badMakers.push(maker);
} else if (maker.offer !== null) {
// Good makers have offers
} else if (normalizedState === 'good') {
goodMakers.push(maker);
} else if (maker.offer != null) {
// Fallback for older payloads without an explicit state.
goodMakers.push(maker);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} else {
// Fallback: makers without offers go to unresponsive
unresponsiveMakers.push(maker);
}
});
Expand Down Expand Up @@ -1034,11 +1063,7 @@ function registerCoinswapHandlers() {
const offerbookData = fs.readFileSync(offerbookPath, 'utf8');
const offerbook = JSON.parse(offerbookData);
const makers = offerbook.makers || [];
const goodMakersCount = makers.filter(
(m) =>
m.offer !== null &&
!(m.state && (m.state.Unresponsive || m.state.Bad))
).length;
const goodMakersCount = makers.filter(isUsableMaker).length;

if (goodMakersCount >= makerCount) {
console.log(
Expand Down Expand Up @@ -1069,11 +1094,7 @@ function registerCoinswapHandlers() {
const offerbookData = fs.readFileSync(offerbookPath, 'utf8');
const offerbook = JSON.parse(offerbookData);
const makers = offerbook.makers || [];
const goodMakersCount = makers.filter(
(m) =>
m.offer !== null &&
!(m.state && (m.state.Unresponsive || m.state.Bad))
).length;
const goodMakersCount = makers.filter(isUsableMaker).length;

if (goodMakersCount < makerCount) {
console.error(
Expand Down
21 changes: 17 additions & 4 deletions src/components/swap/Coinswap.js
Original file line number Diff line number Diff line change
Expand Up @@ -723,7 +723,12 @@ export async function CoinswapComponent(container, swapConfig) {
'fundingTxidsByHop',
[]
);
const totalFee = getValue('total_fee', 'totalFee', 0);
const feePaidOrEarned = getValue(
'fee_paid_or_earned',
'feePaidOrEarned',
NaN
);
const totalFee = getValue('total_fee', 'totalFee', NaN);
const miningFee = getValue('mining_fee', 'miningFee', 0);
const inputUtxos = getArrayValue('input_utxos', 'inputUtxos', []);
const outputRegularUtxos = getArrayValue(
Expand Down Expand Up @@ -769,9 +774,17 @@ export async function CoinswapComponent(container, swapConfig) {
};
});

const calculatedMiningFee = miningFee || totalFee - totalMakerFees;
const derivedTotalFee = Number.isFinite(feePaidOrEarned)
? Math.abs(feePaidOrEarned)
: totalMakerFees + miningFee;
const normalizedTotalFee =
Number.isFinite(totalFee) && (totalFee > 0 || derivedTotalFee <= 0)
? totalFee
: derivedTotalFee;
const calculatedMiningFee =
miningFee || normalizedTotalFee - totalMakerFees;
const feePercentage =
targetAmount > 0 ? (totalFee / targetAmount) * 100 : 0;
targetAmount > 0 ? (normalizedTotalFee / targetAmount) * 100 : 0;
Comment on lines +777 to +787
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider adding a brief comment explaining the fee derivation priority.

The fee derivation logic correctly implements a fallback chain, but the conditions are non-obvious. A brief comment would help future maintainers understand the priority:

// Fee derivation priority:
// 1. Use fee_paid_or_earned (absolute value) if available
// 2. Fall back to totalMakerFees + miningFee
// 3. Prefer backend total_fee when positive, else use derived value

As per coding guidelines: "Comment complex Bitcoin/swap logic".

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

In `@src/components/swap/Coinswap.js` around lines 777 - 787, Add a short
clarifying comment above the fee derivation block explaining the priority and
purpose of each computed variable: describe that derivedTotalFee uses the
absolute feePaidOrEarned when finite else falls back to totalMakerFees +
miningFee, that normalizedTotalFee prefers a positive backend totalFee otherwise
uses derivedTotalFee, and that calculatedMiningFee derives from miningFee or the
remainder (normalizedTotalFee - totalMakerFees); also note feePercentage uses
targetAmount to avoid divide-by-zero. Place this comment near the computed
symbols derivedTotalFee, normalizedTotalFee, calculatedMiningFee, and
feePercentage so future maintainers can quickly understand the fallback chain.


return {
swapId,
Expand All @@ -783,7 +796,7 @@ export async function CoinswapComponent(container, swapConfig) {
makerAddresses,
totalFundingTxs,
fundingTxidsByHop,
totalFee,
totalFee: normalizedTotalFee,
totalMakerFees,
miningFee: calculatedMiningFee,
feePercentage,
Expand Down
Loading