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
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Unlike traditional Bitcoin transactions that create an on-chain trail, coinswaps

The Taker app requires the following components to operate:

1. **Bitcoin Core (Mutinynet)** - A fully synced Mutinynet node with proper RPC and ZMQ configuration
1. **Bitcoin Core (Mutinynet)** - A fully synced Mutinynet node with proper RPC, REST, and ZMQ configuration
- See the [Bitcoin Core setup guide](https://github.com/citadel-tech/coinswap/blob/master/docs/bitcoind.md) for detailed instructions

2. **Tor** - Required for anonymous maker discovery and privacy
Expand Down Expand Up @@ -117,30 +117,30 @@ npm run dist
```

This creates production-ready packages in the `dist/` directory:
- `TakerApp-1.0.0.AppImage` - Portable executable for all Linux distributions
- `taker-app_1.0.0_amd64.snap` - Optional snap package
- `CoinswapTaker-0.2.1.AppImage` - Portable executable for Linux distributions
- `coinswaptaker_0.2.1_amd64.snap` - Optional snap package

### Using the AppImage
```bash
Comment on lines 123 to 124
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add blank lines around this heading and the fenced blocks.

markdownlint is still flagging MD022/MD031 in this section, so the README will keep warning until the heading and each code fence are separated by blank lines.

Also applies to: 133-143

🧰 Tools
🪛 markdownlint-cli2 (0.22.0)

[warning] 123-123: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 124-124: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

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

In `@README.md` around lines 123 - 124, Add blank lines immediately before and
after the "Using the AppImage" heading and each surrounding fenced code block so
the heading and code fences are separated by empty lines; update the README's
"Using the AppImage" section (the "Using the AppImage" heading and its
opening/closing ``` blocks) and the other similar fenced blocks in that area
(the blocks flagged around the following section) to satisfy markdownlint
MD022/MD031.

# Make executable (one-time)
chmod +x dist/TakerApp-1.0.0.AppImage
chmod +x dist/CoinswapTaker-0.2.1.AppImage

# Run directly
./dist/TakerApp-1.0.0.AppImage
./dist/CoinswapTaker-0.2.1.AppImage
```

**Optional desktop integration:**
```bash
# Integrate with application menu
./dist/TakerApp-1.0.0.AppImage --appimage-integrate
./dist/CoinswapTaker-0.2.1.AppImage --appimage-integrate

# Remove integration
./dist/TakerApp-1.0.0.AppImage --appimage-unintegrate
./dist/CoinswapTaker-0.2.1.AppImage --appimage-unintegrate
```

**Extract and inspect:**
```bash
./dist/TakerApp-1.0.0.AppImage --appimage-extract
./dist/CoinswapTaker-0.2.1.AppImage --appimage-extract
cd squashfs-root
./TakerApp
Comment on lines +143 to 145
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix the extracted AppImage launch command.

After --appimage-extract, this section still tells users to run ./TakerApp, but the rest of the README has already renamed the unpacked binary to coinswap-taker. Following these steps will fail on the extracted image.

📝 Suggested doc fix
 ./dist/CoinswapTaker-0.2.1.AppImage --appimage-extract
 cd squashfs-root
-./TakerApp
+./coinswap-taker
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
./dist/CoinswapTaker-0.2.1.AppImage --appimage-extract
cd squashfs-root
./TakerApp
./dist/CoinswapTaker-0.2.1.AppImage --appimage-extract
cd squashfs-root
./coinswap-taker
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 143 - 145, The README's AppImage extraction
instructions still tell users to run ./TakerApp which mismatches the renamed
unpacked binary; update the steps after "./dist/CoinswapTaker-0.2.1.AppImage
--appimage-extract" to either rename the extracted binary to "coinswap-taker"
(matching the rest of the doc) or directly instruct running "./coinswap-taker"
instead of "./TakerApp" so the command and references (./TakerApp,
coinswap-taker) are consistent.

```
Expand Down Expand Up @@ -186,8 +186,8 @@ which fusermount
sudo apt install fuse libfuse2

# Or extract and run directly
./TakerApp-1.0.0.AppImage --appimage-extract
./squashfs-root/TakerApp
./CoinswapTaker-0.2.1.AppImage --appimage-extract
./squashfs-root/coinswap-taker
```

**Error: Cannot find module 'coinswap-napi'**
Expand Down Expand Up @@ -228,7 +228,7 @@ Report security issues on our [Discord](https://discord.gg/Wz42hVmrrK) or email

## License

Dual-licensed under MIT or Apache 2.0 at your option.
Licensed under Apache 2.0. See [LICENSE](LICENSE).

## Community

Expand All @@ -238,4 +238,4 @@ Dual-licensed under MIT or Apache 2.0 at your option.

---

**⚠️ Warning**: Experimental software under active development. Mainnet use is **NOT recommended**.
**⚠️ Warning**: Experimental software under active development. Mainnet use is **NOT recommended**.
29 changes: 25 additions & 4 deletions api1.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,18 @@ function toNumber(value, fallback = 0) {
return Number.isFinite(normalized) ? normalized : fallback;
}

function normalizeTimestamp(value, fallback = null) {
if (value == null || value === '') return fallback;

const numeric = Number(value);
if (Number.isFinite(numeric)) {
return numeric < 1e12 ? numeric * 1000 : numeric;
}

const parsed = new Date(value).getTime();
return Number.isFinite(parsed) ? parsed : fallback;
}

function getOfferbookSnapshot() {
const offerbookPath = path.join(api1State.DATA_DIR, 'offerbook.json');
const snapshot = {
Expand Down Expand Up @@ -249,6 +261,7 @@ function inferTaprootFromReport(rawReport = {}) {
}

function buildSwapReportRecord(filePath, rawReport) {
const fileStats = fs.statSync(filePath);
const fileName = path.basename(filePath, '.json');
const nativeSwapId =
rawReport.nativeSwapId ||
Expand Down Expand Up @@ -293,10 +306,16 @@ function buildSwapReportRecord(filePath, rawReport) {
rawReport.report?.endTimestamp ||
rawReport.report?.end_timestamp ||
null;
const completedAt =
Number.isFinite(Number(rawCompletedAt)) && Number(rawCompletedAt) < 1e12
? Number(rawCompletedAt) * 1000
: rawCompletedAt;
const completedAt = normalizeTimestamp(rawCompletedAt, fileStats.mtimeMs);
const startedAt = normalizeTimestamp(
rawReport.startedAt ||
rawReport.started_at ||
rawReport.report?.startedAt ||
rawReport.report?.started_at ||
rawReport.report?.startTimestamp ||
rawReport.report?.start_timestamp,
null
);
const isTaproot = inferTaprootFromReport(rawReport);
const protocol = normalizeSwapProtocol(
rawReport.protocol || nestedReport.protocol,
Expand All @@ -316,7 +335,9 @@ function buildSwapReportRecord(filePath, rawReport) {
nativeSwapId,
appSwapId,
status: normalizedStatus,
startedAt,
completedAt,
fileModifiedAt: fileStats.mtimeMs,
filePath,
fileName,
isCoreReport,
Expand Down
10 changes: 5 additions & 5 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ This guide covers all features and functionality of the Coinswap Taker App.

## Setup and Connection

On each launch, the app loads your configuration:
On launch, the app walks through setup and connection configuration:

1. **Bitcoin Core Connection** - Configure RPC credentials and port
2. **Tor Configuration** - Set control and SOCKS ports (defaults: 9051, 9050)
3. **Wallet Loading** - Opens your existing encrypted wallet, prompts creation, or allows restoration from seed
4. **ZMQ Setup** - Configure real-time block and transaction notifications
3. **Wallet Loading** - Opens your existing encrypted wallet, prompts creation, or allows restoration from backup JSON
4. **ZMQ Setup** - Configure real-time block and transaction notifications as part of the Bitcoin endpoint setup

The setup page allows you to review and update your configuration at any time.
The setup page allows you to review and update your configuration.

## Wallet Page

Expand Down Expand Up @@ -102,7 +102,7 @@ Configure app and Bitcoin Core connection:
- **Wallet Backup** - Create encrypted wallet backups
- **Connection Testing** - Verify Bitcoin Core and Tor connectivity

Settings are persisted locally and can be updated at any time without restarting the app.
Settings can be reviewed and updated from the app, though the current development build may require re-running setup on launch.

## Log Page

Expand Down
18 changes: 17 additions & 1 deletion src/components/market/Market.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@ export function Market(container) {
}
}

function formatTorEndpoint(address, start = 14, end = 16) {
if (!address || typeof address !== 'string') return 'unknown';

const separatorIndex = address.lastIndexOf(':');
if (separatorIndex === -1) return address;

const host = address.slice(0, separatorIndex);
const port = address.slice(separatorIndex + 1);

if (host.length <= start + end + 3) {
return `${host}:${port}`;
}

return `${host.slice(0, start)}...${host.slice(-end)}:${port}`;
}
Comment on lines +39 to +53
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

Extract formatTorEndpoint() into a shared util.

This helper is now identical to the one in src/components/swap/Swap.js, so any truncation tweak has to be kept in sync in two places. Pull it into a shared formatter before the two implementations drift.

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

In `@src/components/market/Market.js` around lines 39 - 53, The formatTorEndpoint
helper in Market.js is duplicated in Swap.js—extract it into a single shared
util named formatTorEndpoint (exported as a named function) and replace the
local implementations in both Market.js and Swap.js with imports of that shared
function; ensure the new util exports formatTorEndpoint, update both components
to import { formatTorEndpoint }, and run the app to confirm behavior is
unchanged.


// Check sync state every second
function startSyncStateMonitor() {
if (syncCheckInterval) return;
Expand Down Expand Up @@ -754,7 +770,7 @@ export function Market(container) {
${protocolBadge.icon} ${protocolBadge.label}
</span>
</div>
<div class="text-gray-300 font-mono text-sm truncate" title="${maker.address}">${maker.address.substring(0, 18)}...</div>
<div class="text-gray-300 font-mono text-sm truncate" title="${maker.address}">${formatTorEndpoint(maker.address)}</div>
<div class="text-green-400">${maker.baseFee}</div>
<div class="text-blue-400">${maker.volumeFee}%</div>
<div class="text-cyan-400">${maker.timeFee}%</div>
Expand Down
25 changes: 9 additions & 16 deletions src/components/receive/Receive.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ export function ReceiveComponent(container) {
<!-- QR Code -->
<div class="bg-white p-4 rounded-lg mb-6 flex items-center justify-center">
<div id="qr-container" class="flex items-center justify-center" style="min-height: 256px; min-width: 256px;">
<div id="qr-loading" class="text-center">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-400 mx-auto mb-2"></div>
<p class="text-sm text-gray-500">Generating address...</p>
<div id="qr-loading" class="text-center text-gray-500">
<p class="text-sm">No address loaded</p>
<p class="text-xs mt-2">Click the button below to generate a receive address.</p>
</div>
</div>
</div>
Expand All @@ -28,7 +28,7 @@ export function ReceiveComponent(container) {
<div class="mb-6">
<div class="bg-[#0f1419] border border-gray-700 rounded-lg p-4 flex items-center justify-between">
<span id="current-address" class="font-mono text-sm text-white break-all flex-1 mr-4">
Loading...
No address loaded
</span>
<button id="copy-address" disabled class="bg-[#FF6B35] hover:bg-[#ff7d4d] disabled:bg-gray-600 disabled:cursor-not-allowed text-white px-4 py-2 rounded text-sm font-semibold text-lg transition-colors whitespace-nowrap">
Copy
Expand All @@ -40,8 +40,8 @@ export function ReceiveComponent(container) {
</div>

<!-- Generate New Address Button -->
<button id="generate-new" disabled class="w-full bg-[#242d3d] hover:bg-[#2d3748] disabled:bg-gray-700 disabled:cursor-not-allowed text-white font-semibold text-lg py-3 rounded-lg transition-colors border border-gray-700">
<span class="generate-text">Generate New Address</span>
<button id="generate-new" class="w-full bg-[#242d3d] hover:bg-[#2d3748] disabled:bg-gray-700 disabled:cursor-not-allowed text-white font-semibold text-lg py-3 rounded-lg transition-colors border border-gray-700">
<span class="generate-text">Generate Address</span>
<span class="generate-loading hidden">
Comment on lines +43 to 45
Copy link
Copy Markdown

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 leak fresh receive addresses to a third-party QR service.

Manual generation is now the primary receive path, and this flow still hands the new address to generateQR(). generateQR() builds an external QR URL, so every fresh receive address is disclosed off-box before the user even copies it. Render the QR locally instead.

Also applies to: 402-405, 419-424

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

In `@src/components/receive/Receive.js` around lines 43 - 45, The Receive
component currently passes fresh receive addresses to the external generateQR()
service (called where the "Generate Address" button handler updates elements
with classes generate-text/generate-loading and in the flows around the other
generate calls), which leaks addresses off-box; replace those calls to
generateQR() so the new address is not sent to any third-party URL and instead
render the QR locally in this component using a client-side QR renderer (e.g., a
local qrcode library) when the button with id "generate-new" is clicked and in
the other flows that call generateQR(); update the handlers that toggle elements
with classes "generate-text" and "generate-loading" to call a local
renderQRCode(address) function (or inline local rendering) and remove any
construction of external URLs or calls to generateQR() so addresses are never
transmitted externally.

<span class="inline-block animate-spin mr-2">⟳</span>
Generating...
Expand Down Expand Up @@ -399,9 +399,9 @@ export function ReceiveComponent(container) {

// Show loading in QR container
qrContainer.innerHTML = `
<div class="text-center">
<div class="text-center">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-400 mx-auto mb-2"></div>
<p class="text-sm text-gray-500">Generating...</p>
<p class="text-sm text-gray-500">Generating address...</p>
</div>
`;

Expand Down Expand Up @@ -457,17 +457,10 @@ export function ReceiveComponent(container) {
}
}

// Initialize - always generate a new address on page load
async function initialize() {
try {
// Always generate a new address
await generateNewAddress();

// Update recent addresses list
await updateRecentAddresses();

// Enable generate button
generateButton.disabled = false;
updateAddressStatus(null);
} catch (error) {
console.error('❌ Initialization failed:', error);
currentAddressEl.textContent = 'Failed to initialize';
Expand Down
25 changes: 22 additions & 3 deletions src/components/swap/Swap.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,22 @@ function saveSwapDataToCache(utxos, makers, balance) {
}
}

function formatTorEndpoint(address, start = 14, end = 16) {
if (!address || typeof address !== 'string') return 'unknown';

const separatorIndex = address.lastIndexOf(':');
if (separatorIndex === -1) return address;

const host = address.slice(0, separatorIndex);
const port = address.slice(separatorIndex + 1);

if (host.length <= start + end + 3) {
return `${host}:${port}`;
}

return `${host.slice(0, start)}...${host.slice(-end)}:${port}`;
}

export async function SwapComponent(container) {
const existingContent = container.querySelector('#swap-content');
if (existingContent) {
Expand Down Expand Up @@ -662,10 +678,13 @@ export async function SwapComponent(container) {
details.hops + ' hop' + (details.hops !== 1 ? 's' : '');
content.querySelector('#estimated-time').textContent =
formatEstimatedTime(details.timeSeconds);
content.querySelector('#selected-makers-display').textContent =
const selectedMakersText =
getTopCandidateMakers()
.map((maker) => maker.address)
.map((maker) => formatTorEndpoint(maker.address))
.join(', ') || 'None selected';
const selectedMakersEl = content.querySelector('#selected-makers-display');
selectedMakersEl.textContent = selectedMakersText;
selectedMakersEl.title = selectedMakersText;
Comment on lines +681 to +687
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Tooltip shows abbreviated value instead of full addresses.

Setting title to selectedMakersText (the abbreviated text) means the tooltip provides no additional information when hovering. The typical UX pattern is to show the full value in the tooltip when text is truncated, allowing users to see complete Tor addresses on hover.

🔧 Proposed fix to show full addresses in tooltip
     const selectedMakersText =
       getTopCandidateMakers()
         .map((maker) => formatTorEndpoint(maker.address))
         .join(', ') || 'None selected';
+    const fullAddressesText =
+      getTopCandidateMakers()
+        .map((maker) => maker.address)
+        .join(', ') || 'None selected';
     const selectedMakersEl = content.querySelector('#selected-makers-display');
     selectedMakersEl.textContent = selectedMakersText;
-    selectedMakersEl.title = selectedMakersText;
+    selectedMakersEl.title = fullAddressesText;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const selectedMakersText =
getTopCandidateMakers()
.map((maker) => maker.address)
.map((maker) => formatTorEndpoint(maker.address))
.join(', ') || 'None selected';
const selectedMakersEl = content.querySelector('#selected-makers-display');
selectedMakersEl.textContent = selectedMakersText;
selectedMakersEl.title = selectedMakersText;
const selectedMakersText =
getTopCandidateMakers()
.map((maker) => formatTorEndpoint(maker.address))
.join(', ') || 'None selected';
const fullAddressesText =
getTopCandidateMakers()
.map((maker) => maker.address)
.join(', ') || 'None selected';
const selectedMakersEl = content.querySelector('#selected-makers-display');
selectedMakersEl.textContent = selectedMakersText;
selectedMakersEl.title = fullAddressesText;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/swap/Swap.js` around lines 681 - 687, The tooltip is currently
set to selectedMakersText (abbreviated output from formatTorEndpoint) so
hovering doesn't reveal full Tor addresses; update the code that sets
selectedMakersEl.title to use the full addresses instead (e.g., map
getTopCandidateMakers() to maker.address and join with ', ' or create a
selectedMakersFull variable) while keeping selectedMakersText for the
displayed/abbreviated content; reference symbols: selectedMakersText,
selectedMakersEl, getTopCandidateMakers, formatTorEndpoint.

content.querySelector('#maker-fee-percent').textContent =
details.makerFeePercent + '%';
content.querySelector('#maker-fee-sats').textContent =
Expand Down Expand Up @@ -1104,7 +1123,7 @@ export async function SwapComponent(container) {
</div>
<div class="flex justify-between mb-2 gap-3">
<span class="text-sm text-gray-400">Top Maker Candidates</span>
<span id="selected-makers-display" class="text-sm text-cyan-400 text-right break-all">None selected</span>
<span id="selected-makers-display" class="text-sm text-cyan-400 text-right whitespace-nowrap overflow-hidden text-ellipsis block max-w-[220px] md:max-w-[280px] lg:max-w-[340px]" title="None selected">None selected</span>
</div>
<div class="flex justify-between mb-2">
<span class="text-sm text-gray-400">Hops</span>
Expand Down
Loading