From 97b826a45d26f43838ca788aec14ff17268f08d4 Mon Sep 17 00:00:00 2001 From: iAmKeralis1131f Date: Thu, 26 Mar 2026 12:41:35 +0530 Subject: [PATCH 1/2] log on initial load, swap layout fix, spendable balance fix --- api1.js | 171 ++++++++- src/components/swap/Coinswap.js | 578 ++++++++++++++--------------- src/components/swap/SwapHistory.js | 91 +++++ src/components/swap/SwapReport.js | 279 +++++++++++--- src/components/wallet/Wallet.js | 3 + src/js/app.js | 170 ++++++++- src/styles/output.css | 107 ++++-- 7 files changed, 1004 insertions(+), 395 deletions(-) diff --git a/api1.js b/api1.js index ca6a979..8442b13 100644 --- a/api1.js +++ b/api1.js @@ -109,6 +109,163 @@ function saveSwapReport(swapId, swapData) { } } +function toNumber(value, fallback = 0) { + const normalized = Number(value); + return Number.isFinite(normalized) ? normalized : fallback; +} + +function getAllSwapReportPaths() { + const reportsRoot = path.join(api1State.DATA_DIR, 'swap_reports'); + const reportPaths = []; + + function walk(dirPath) { + if (!fs.existsSync(dirPath)) return; + + for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) { + const fullPath = path.join(dirPath, entry.name); + if (entry.isDirectory()) { + walk(fullPath); + } else if (entry.isFile() && entry.name.endsWith('.json')) { + reportPaths.push(fullPath); + } + } + } + + walk(reportsRoot); + return reportPaths; +} + +function getHistoricalSwapOutputMap() { + const swapOutputs = new Map(); + + for (const reportPath of getAllSwapReportPaths()) { + try { + const report = JSON.parse(fs.readFileSync(reportPath, 'utf8')); + const outputSwapUtxos = + report.output_swap_utxos || + report.outputSwapUtxos || + report.report?.output_swap_utxos || + report.report?.outputSwapUtxos || + []; + + outputSwapUtxos.forEach((entry) => { + if (!Array.isArray(entry) || entry.length < 2) return; + const [amount, address] = entry; + if (!address) return; + swapOutputs.set(address, toNumber(amount, 0)); + }); + } catch (error) { + console.warn('âš ī¸ Failed to parse swap report for balance derivation:', { + reportPath, + error: error.message, + }); + } + } + + return swapOutputs; +} + +function deriveBalancesFromUtxos(rawUtxos = []) { + const derived = { + spendable: 0, + regular: 0, + swap: 0, + contract: 0, + fidelity: 0, + }; + + rawUtxos.forEach(([utxoEntry, spendInfo]) => { + const amount = toNumber(utxoEntry?.amount?.sats, 0); + const spendType = String(spendInfo?.spendType || '').toLowerCase(); + const isSpendable = Boolean(utxoEntry?.spendable); + + if (isSpendable) { + derived.spendable += amount; + } + + if (spendType.includes('swap')) { + derived.swap += amount; + return; + } + + if (spendType.includes('contract')) { + derived.contract += amount; + return; + } + + if (spendType.includes('fidelity')) { + derived.fidelity += amount; + return; + } + + if (spendType.includes('seed') || spendType.includes('regular')) { + derived.regular += amount; + } + }); + + return derived; +} + +function normalizeBalancePayload(rawBalance = {}, rawUtxos = []) { + const derivedFromUtxos = deriveBalancesFromUtxos(rawUtxos); + const historicalSwapOutputs = getHistoricalSwapOutputMap(); + + const matchedHistoricalSwapUtxos = rawUtxos.filter(([utxoEntry]) => + historicalSwapOutputs.has(utxoEntry?.address) + ); + const historicalSwapBalance = matchedHistoricalSwapUtxos.reduce( + (sum, [utxoEntry]) => sum + toNumber(utxoEntry?.amount?.sats, 0), + 0 + ); + + const normalized = { + spendable: toNumber( + rawBalance.spendable ?? rawBalance.spendable_balance, + derivedFromUtxos.spendable + ), + regular: toNumber( + rawBalance.regular ?? rawBalance.regular_balance, + derivedFromUtxos.regular + ), + swap: toNumber( + rawBalance.swap ?? + rawBalance.swap_balance ?? + rawBalance.swapCoin ?? + rawBalance.swapcoin, + derivedFromUtxos.swap + ), + contract: toNumber( + rawBalance.contract ?? rawBalance.contract_balance, + derivedFromUtxos.contract + ), + fidelity: toNumber( + rawBalance.fidelity ?? rawBalance.fidelity_balance, + derivedFromUtxos.fidelity + ), + }; + + // Completed swap outputs can show up as SeedCoin/regular in the current UTXO API. + // Recover that provenance by matching active UTXOs against saved swap reports. + if (historicalSwapBalance > normalized.swap) { + normalized.swap = historicalSwapBalance; + normalized.regular = Math.max(0, normalized.spendable - normalized.swap); + } + + return { + normalized, + debug: { + rawBalance, + derivedFromUtxos, + historicalSwapBalance, + historicalSwapMatches: matchedHistoricalSwapUtxos.map(([utxoEntry, spendInfo]) => ({ + address: utxoEntry?.address, + amount: toNumber(utxoEntry?.amount?.sats, 0), + spendType: spendInfo?.spendType, + })), + }, + }; +} + async function initNAPI() { try { api1State.coinswapNapi = require('coinswap-napi'); @@ -469,16 +626,16 @@ function registerTakerHandlers() { // Sync removed to prevent UI blocking on page load - relies on background sync // api1State.takerInstance.syncAndSave(); const balance = api1State.takerInstance.getBalances(); + const rawUtxos = api1State.takerInstance.listAllUtxoSpendInfo(); + const { normalized, debug } = normalizeBalancePayload(balance, rawUtxos); + + console.log('💰 Raw taker balance payload:', balance); + console.log('💰 Normalized taker balance payload:', normalized); + console.log('💰 Balance derivation details:', debug); return { success: true, - balance: { - spendable: balance.spendable, - regular: balance.regular, - swap: balance.swap, - contract: balance.contract, - fidelity: balance.fidelity, - }, + balance: normalized, }; } catch (error) { console.error('❌ Failed to get balance:', error); diff --git a/src/components/swap/Coinswap.js b/src/components/swap/Coinswap.js index 4d67a57..850d96d 100644 --- a/src/components/swap/Coinswap.js +++ b/src/components/swap/Coinswap.js @@ -939,273 +939,258 @@ export async function CoinswapComponent(container, swapConfig) { function buildFlowDiagram() { const actualMakers = swapData.makers; // Number of actual makers (e.g., 2) + const totalNodes = actualMakers + 1; + + // Dynamic node sizing based on maker count + const youHalf = actualMakers <= 5 ? 38 : actualMakers <= 10 ? 30 : 22; + const makerHalf = actualMakers <= 5 ? 32 : actualMakers <= 10 ? 25 : 18; + const youFont = actualMakers <= 5 ? 22 : actualMakers <= 10 ? 16 : 13; + const makerFont = actualMakers <= 5 ? 20 : actualMakers <= 10 ? 14 : 10; + const youRx = actualMakers <= 5 ? 14 : 10; + const makerRx = actualMakers <= 5 ? 10 : 7; + const maxNodeHalf = Math.max(youHalf, makerHalf); + const labelPad = youHalf + 62; + + function getRectPoint(distance, width, height) { + const halfW = width / 2; + const halfH = height / 2; + const top = width / 2; + const right = height; + const bottom = width; + const left = height; + const perimeter = top + right + bottom + left + top; + let remaining = ((distance % perimeter) + perimeter) % perimeter; + + const linePoint = (x1, y1, x2, y2, travelled, segmentLength) => { + const ratio = segmentLength === 0 ? 0 : travelled / segmentLength; + return { + x: x1 + (x2 - x1) * ratio, + y: y1 + (y2 - y1) * ratio, + }; + }; - function buildFlowDiagram() { - const actualMakers = swapData.makers; - const radius = 140; - const centerX = 200; - const centerY = 200; - - // Same circular layout for BOTH V1 and V2 - const totalNodes = actualMakers + 1; - const angleStep = (2 * Math.PI) / totalNodes; - - const positions = []; - for (let i = 0; i < totalNodes; i++) { - const angle = angleStep * i - Math.PI / 2; - const x = centerX + radius * Math.cos(angle); - const y = centerY + radius * Math.sin(angle); - positions.push({ x, y }); - } - - return ` -
- - - ${Array.from({ length: actualMakers + 1 }, (_, i) => { - const color = - i < actualMakers - ? makerColors[i % makerColors.length] - : '#10B981'; - return ` - - - - `; - }).join('')} - - - - ${positions - .map((pos, i) => { - const nextPos = positions[(i + 1) % positions.length]; - const color = - i < actualMakers - ? makerColors[i % makerColors.length] - : '#10B981'; - - const dx = nextPos.x - pos.x; - const dy = nextPos.y - pos.y; - const length = Math.sqrt(dx * dx + dy * dy); - const offsetStart = i === 0 ? 40 : 35; - const offsetEnd = (i + 1) % positions.length === 0 ? 40 : 35; - - const startX = pos.x + (dx / length) * offsetStart; - const startY = pos.y + (dy / length) * offsetStart; - const endX = nextPos.x - (dx / length) * offsetEnd; - const endY = nextPos.y - (dy / length) * offsetEnd; + const segments = [ + { + length: top, + point: (travelled) => + linePoint(0, -halfH, halfW, -halfH, travelled, top), + }, + { + length: right, + point: (travelled) => + linePoint(halfW, -halfH, halfW, halfH, travelled, right), + }, + { + length: bottom, + point: (travelled) => + linePoint(halfW, halfH, -halfW, halfH, travelled, bottom), + }, + { + length: left, + point: (travelled) => + linePoint(-halfW, halfH, -halfW, -halfH, travelled, left), + }, + { + length: top, + point: (travelled) => + linePoint(-halfW, -halfH, 0, -halfH, travelled, top), + }, + ]; - return ` - - `; - }) - .join('')} - - - - - You - Send - - - - ${Array.from({ length: actualMakers }, (_, i) => { - const pos = positions[i + 1]; - const color = makerColors[i % makerColors.length]; - return ` - - - M${i + 1} - - ${ - !isV2 - ? ` - - - Pending - - ` - : ` - Off-chain - ` - } - - `; - }).join('')} - - ${ - isV2 - ? ` - - Taproot V2 - MuSig2 - ` - : '' + for (const segment of segments) { + if (remaining <= segment.length) { + return segment.point(remaining); } - -
- `; + remaining -= segment.length; + } + + return { x: 0, y: -halfH }; } - const radius = 140; // Radius of the circle - const centerX = 200; - const centerY = 200; - - // Calculate positions for nodes in a circle - // Total nodes: You + Makers (no duplicate "You") - const totalNodes = actualMakers + 1; // +1 for single "You" - const angleStep = (2 * Math.PI) / totalNodes; - - const positions = []; - for (let i = 0; i < totalNodes; i++) { - const angle = angleStep * i - Math.PI / 2; // Start from top - const x = centerX + radius * Math.cos(angle); - const y = centerY + radius * Math.sin(angle); - positions.push({ x, y }); + + function buildAdaptiveLayout() { + const gap = 14; + + if (actualMakers <= 4) { + const minRadius = + (maxNodeHalf + gap) / Math.sin(Math.PI / totalNodes); + const radius = Math.max(minRadius, 140); + const svgSize = Math.round((radius + labelPad) * 2); + const centerX = svgSize / 2; + const centerY = svgSize / 2; + const angleStep = (2 * Math.PI) / totalNodes; + const positions = Array.from({ length: totalNodes }, (_, i) => { + const angle = angleStep * i - Math.PI / 2; + return { + x: centerX + radius * Math.cos(angle), + y: centerY + radius * Math.sin(angle), + }; + }); + + return { + centerX, + centerY, + svgWidth: svgSize, + svgHeight: svgSize, + positions, + guideMarkup: ``, + }; + } + + if (actualMakers <= 11) { + const safeSin = Math.max(Math.sin(Math.PI / totalNodes), 0.22); + const minRadius = + (maxNodeHalf + gap + Math.max(0, actualMakers - 5) * 2) / safeSin; + const rx = Math.max(minRadius * 1.15, 190 + actualMakers * 10); + const ry = Math.max(minRadius * 0.72, 120 + actualMakers * 5); + const svgWidth = Math.round(rx * 2 + labelPad * 2 + 30); + const svgHeight = Math.round(ry * 2 + labelPad * 2); + const centerX = svgWidth / 2; + const centerY = svgHeight / 2; + const angleStep = (2 * Math.PI) / totalNodes; + const positions = Array.from({ length: totalNodes }, (_, i) => { + const angle = angleStep * i - Math.PI / 2; + return { + x: centerX + rx * Math.cos(angle), + y: centerY + ry * Math.sin(angle), + }; + }); + + return { + centerX, + centerY, + svgWidth, + svgHeight, + positions, + guideMarkup: ``, + }; + } + + const isSquareLayout = actualMakers <= 14; + const width = isSquareLayout + ? Math.max(420, 360 + actualMakers * 16) + : Math.max(640, 430 + actualMakers * 24); + const height = isSquareLayout + ? width + : Math.max(320, 250 + Math.min(actualMakers - 14, 8) * 18); + const halfW = width / 2; + const halfH = height / 2; + const perimeter = width * 2 + height * 2; + const svgWidth = Math.round(width + labelPad * 2); + const svgHeight = Math.round(height + labelPad * 2); + const centerX = svgWidth / 2; + const centerY = svgHeight / 2; + const step = perimeter / totalNodes; + const positions = Array.from({ length: totalNodes }, (_, i) => { + const point = getRectPoint(step * i, width, height); + return { + x: centerX + point.x, + y: centerY + point.y, + }; + }); + + const x = centerX - halfW; + const y = centerY - halfH; + const guideMarkup = ``; + + return { + centerX, + centerY, + svgWidth, + svgHeight, + positions, + guideMarkup, + }; } + const { centerX, centerY, svgWidth, svgHeight, positions, guideMarkup } = + buildAdaptiveLayout(); + + const statusW = Math.max(70, makerHalf * 3.5); + const statusHalfW = statusW / 2; + return ` -
- +
+ - ${Array.from({ length: actualMakers + 1 }, (_, i) => { - const color = - i < actualMakers - ? makerColors[i % makerColors.length] - : '#10B981'; - return ` - - - - `; + ${Array.from({ length: totalNodes }, (_, i) => { + const color = i < actualMakers ? makerColors[i % makerColors.length] : '#10B981'; + return ` + + `; }).join('')} + + + + - - - ${positions - .map((pos, i) => { - const nextPos = positions[(i + 1) % positions.length]; // Wrap around to first node - const color = - i < actualMakers - ? makerColors[i % makerColors.length] - : '#10B981'; - - // Calculate arrow direction - const dx = nextPos.x - pos.x; - const dy = nextPos.y - pos.y; - const length = Math.sqrt(dx * dx + dy * dy); - const offsetStart = i === 0 ? 40 : 35; // Larger offset for "You" node - const offsetEnd = (i + 1) % positions.length === 0 ? 40 : 35; - - const startX = pos.x + (dx / length) * offsetStart; - const startY = pos.y + (dy / length) * offsetStart; - const endX = nextPos.x - (dx / length) * offsetEnd; - const endY = nextPos.y - (dy / length) * offsetEnd; - return ` - - - - `; - }) - .join('')} - - + + ${guideMarkup} + + + ${positions.map((pos, i) => { + const nextPos = positions[(i + 1) % positions.length]; + const color = i < actualMakers ? makerColors[i % makerColors.length] : '#10B981'; + const fromHalf = i === 0 ? youHalf : makerHalf; + const toHalf = (i + 1) % positions.length === 0 ? youHalf : makerHalf; + const dx = nextPos.x - pos.x; + const dy = nextPos.y - pos.y; + const len = Math.sqrt(dx * dx + dy * dy); + const sx = pos.x + (dx / len) * (fromHalf + 4); + const sy = pos.y + (dy / len) * (fromHalf + 4); + const ex = nextPos.x - (dx / len) * (toHalf + 10); + const ey = nextPos.y - (dy / len) * (toHalf + 10); + return ` + + `; + }).join('')} + + - - You - Send - ${(swapData.amount / 100000000).toFixed(4)} + + You + Send + ${(swapData.amount / 100000000).toFixed(4)} - + ${Array.from({ length: actualMakers }, (_, i) => { - const pos = positions[i + 1]; // Offset by 1 because "You" is at position 0 + const pos = positions[i + 1]; const color = makerColors[i % makerColors.length]; - return ` - - - - M${i + 1} - Maker ${i + 1} - - - - - Pending - + return ` + + M${i + 1} + ${actualMakers <= 12 ? ` + Maker ${i + 1} + ` : ''} + + + Pending - `; + `; }).join('')} + + ${isV2 ? ` + Taproot V2 + MuSig2 + ` : ''}
`; @@ -1240,76 +1225,67 @@ export async function CoinswapComponent(container, swapConfig) { } content.innerHTML = ` -
- -

Coinswap in Progress

-

Executing swap through ${swapData.makers} makers...

+ +
+ +
+

Coinswap in Progress

+ + + Active + +
+

Executing swap through ${swapData.makers} makers...

-
+ +
${buildFlowDiagram()}
-
-
-

Progress

-
-
- Amount - ${(swapData.amount / 100000000).toFixed(8)} BTC -
- ${ - !isV2 - ? ` -
- Hops - ${swapData.hops} -
- ` - : ` -
- Protocol - Taproot V2 -
- ` - } -
- Time - 0:00 -
-
+ +
+
+

Amount

+

${(swapData.amount / 100000000).toFixed(8)} BTC

+ ${!isV2 + ? `

Hops: ${swapData.hops}

` + : `

Taproot V2 (MuSig2)

` + }
- ${ - !isV2 - ? ` -
-

Hop Transactions

-
+ ${!isV2 ? ` +
+

Hop Transactions

+
- ` - : '' - } + ` : ''} -
-

Status

+
+
+

Elapsed

+ 0:00 +
-
- â„šī¸ Do not close window +
+ + Do not close window
-
- 🔒 Funds protected by HTLCs +
+ + Funds protected by HTLCs
-
-

Activity Log

-
+ +
+

Activity Log

+
- `; diff --git a/src/components/swap/SwapHistory.js b/src/components/swap/SwapHistory.js index d192138..d1166ad 100644 --- a/src/components/swap/SwapHistory.js +++ b/src/components/swap/SwapHistory.js @@ -130,6 +130,97 @@ export async function loadSwapHistory() { } catch (error) { console.error('Failed to load swap history:', error); } + return swapHistory; +} + +export function summarizeSwapHistory(history) { + const totalSwaps = history.length; + const totalAmount = history.reduce((sum, s) => sum + (s.amount || 0), 0); + const totalFees = history.reduce((sum, s) => sum + (s.totalFee || 0), 0); + const avgFeePaid = totalSwaps > 0 ? Math.round(totalFees / totalSwaps) : 0; + return { totalSwaps, totalAmount, totalFees, avgFeePaid }; +} + +function satsToBtc(sats) { + if (typeof sats !== 'number' || isNaN(sats)) return '0.00000000'; + return (sats / 100000000).toFixed(8); +} + +export function buildSwapHistoryMarkup(history) { + if (history.length === 0) { + return ` +
+
🔄
+

No Swap History

+

You haven't completed any coinswaps yet.

+
+ `; + } + + return ` +
+ ${history + .map((swap) => { + const btcAmount = satsToBtc(swap.amount); + const outputBtc = satsToBtc(swap.totalOutputAmount); + const timeAgo = formatRelativeTime(swap.completedAt); + const dateStr = formatDate(swap.completedAt); + const duration = formatDuration(swap.durationSeconds); + + return ` +
+
+
+ ✓ +
+
+
+ Coinswap + Completed + ${swap.hops} hops +
+
+ ${timeAgo} + â€ĸ + ${dateStr} + â€ĸ + ${duration} +
+
+
+
${btcAmount} BTC
+
${swap.amount.toLocaleString()} sats
+
+
+ + + +
+
+
+
+ Makers +

${swap.makersCount}

+
+
+ Fee +

${swap.feePercentage?.toFixed(2) || '0.00'}%

+
+
+ Total Fee +

${(swap.totalFee || 0).toLocaleString()} sats

+
+
+ Output +

${outputBtc} BTC

+
+
+
+ `; + }) + .join('')} +
+ `; } export async function SwapHistoryComponent(container) { diff --git a/src/components/swap/SwapReport.js b/src/components/swap/SwapReport.js index caab566..60f223b 100644 --- a/src/components/swap/SwapReport.js +++ b/src/components/swap/SwapReport.js @@ -473,54 +473,249 @@ export function SwapReportComponent(container, swapReport) { `; } - // Build swap circuit visualization + // Build swap circuit visualization (circular SVG) function buildCircularFlowHtml() { - const nodes = [ - { label: 'You', sublabel: 'Outgoing', color: '#FF6B35' }, - ...report.makerAddresses.map((addr, index) => ({ - label: `Maker ${index + 1}`, - sublabel: truncateAddress(addr, 10, 6), - color: makerColors[index % makerColors.length], - makerIndex: index, - })), - { label: 'You', sublabel: 'Incoming', color: '#10B981' }, - ]; - const columns = Math.max(nodes.length * 2 - 1, 1); - const flowItems = nodes.flatMap((node, index) => { - const nodeHtml = ` -
-

${node.label}

-

${node.sublabel}

-
- `; + const actualMakers = report.makersCount || report.makerAddresses.length; + const totalNodes = actualMakers + 1; // +1 for You + + // Dynamic node sizing + const youHalf = actualMakers <= 5 ? 38 : actualMakers <= 10 ? 30 : 22; + const makerHalf = actualMakers <= 5 ? 32 : actualMakers <= 10 ? 25 : 18; + const youFont = actualMakers <= 5 ? 22 : actualMakers <= 10 ? 16 : 13; + const makerFont = actualMakers <= 5 ? 20 : actualMakers <= 10 ? 14 : 10; + const youRx = actualMakers <= 5 ? 14 : 10; + const makerRx = actualMakers <= 5 ? 10 : 7; + const maxNodeHalf = Math.max(youHalf, makerHalf); + const labelPad = youHalf + 62; + + function getRectPoint(distance, width, height) { + const halfW = width / 2; + const halfH = height / 2; + const top = width / 2; + const right = height; + const bottom = width; + const left = height; + const perimeter = top + right + bottom + left + top; + let remaining = ((distance % perimeter) + perimeter) % perimeter; + + const linePoint = (x1, y1, x2, y2, travelled, segmentLength) => { + const ratio = segmentLength === 0 ? 0 : travelled / segmentLength; + return { + x: x1 + (x2 - x1) * ratio, + y: y1 + (y2 - y1) * ratio, + }; + }; + + const segments = [ + { + length: top, + point: (travelled) => + linePoint(0, -halfH, halfW, -halfH, travelled, top), + }, + { + length: right, + point: (travelled) => + linePoint(halfW, -halfH, halfW, halfH, travelled, right), + }, + { + length: bottom, + point: (travelled) => + linePoint(halfW, halfH, -halfW, halfH, travelled, bottom), + }, + { + length: left, + point: (travelled) => + linePoint(-halfW, halfH, -halfW, -halfH, travelled, left), + }, + { + length: top, + point: (travelled) => + linePoint(-halfW, -halfH, 0, -halfH, travelled, top), + }, + ]; - if (index === nodes.length - 1) { - return [nodeHtml]; + for (const segment of segments) { + if (remaining <= segment.length) { + return segment.point(remaining); + } + remaining -= segment.length; } - return [ - nodeHtml, - ` -
- - - -
- `, - ]; - }); + return { x: 0, y: -halfH }; + } + + function buildAdaptiveLayout() { + const gap = 14; + + if (actualMakers <= 4) { + const minRadius = + (maxNodeHalf + gap) / Math.sin(Math.PI / totalNodes); + const radius = Math.max(minRadius, 140); + const svgSize = Math.round((radius + labelPad) * 2); + const centerX = svgSize / 2; + const centerY = svgSize / 2; + const angleStep = (2 * Math.PI) / totalNodes; + const positions = Array.from({ length: totalNodes }, (_, i) => { + const angle = angleStep * i - Math.PI / 2; + return { + x: centerX + radius * Math.cos(angle), + y: centerY + radius * Math.sin(angle), + }; + }); + + return { + centerX, + centerY, + svgWidth: svgSize, + svgHeight: svgSize, + positions, + guideMarkup: ``, + }; + } + + if (actualMakers <= 11) { + const safeSin = Math.max(Math.sin(Math.PI / totalNodes), 0.22); + const minRadius = + (maxNodeHalf + gap + Math.max(0, actualMakers - 5) * 2) / safeSin; + const rx = Math.max(minRadius * 1.15, 190 + actualMakers * 10); + const ry = Math.max(minRadius * 0.72, 120 + actualMakers * 5); + const svgWidth = Math.round(rx * 2 + labelPad * 2 + 30); + const svgHeight = Math.round(ry * 2 + labelPad * 2); + const centerX = svgWidth / 2; + const centerY = svgHeight / 2; + const angleStep = (2 * Math.PI) / totalNodes; + const positions = Array.from({ length: totalNodes }, (_, i) => { + const angle = angleStep * i - Math.PI / 2; + return { + x: centerX + rx * Math.cos(angle), + y: centerY + ry * Math.sin(angle), + }; + }); + + return { + centerX, + centerY, + svgWidth, + svgHeight, + positions, + guideMarkup: ``, + }; + } + + const isSquareLayout = actualMakers <= 14; + const width = isSquareLayout + ? Math.max(420, 360 + actualMakers * 16) + : Math.max(640, 430 + actualMakers * 24); + const height = isSquareLayout + ? width + : Math.max(320, 250 + Math.min(actualMakers - 14, 8) * 18); + const halfW = width / 2; + const halfH = height / 2; + const perimeter = width * 2 + height * 2; + const svgWidth = Math.round(width + labelPad * 2); + const svgHeight = Math.round(height + labelPad * 2); + const centerX = svgWidth / 2; + const centerY = svgHeight / 2; + const step = perimeter / totalNodes; + const positions = Array.from({ length: totalNodes }, (_, i) => { + const point = getRectPoint(step * i, width, height); + return { + x: centerX + point.x, + y: centerY + point.y, + }; + }); + + const x = centerX - halfW; + const y = centerY - halfH; + const guideMarkup = ``; + + return { + centerX, + centerY, + svgWidth, + svgHeight, + positions, + guideMarkup, + }; + } + + const { centerX, centerY, svgWidth, svgHeight, positions, guideMarkup } = + buildAdaptiveLayout(); return ` -
-
- ${flowItems.join('')} -
+
+ + + ${Array.from({ length: totalNodes }, (_, i) => { + const color = i < actualMakers ? makerColors[i % makerColors.length] : '#10B981'; + return ` + + `; + }).join('')} + + + + + + + + ${guideMarkup} + + + ${positions.map((pos, i) => { + const nextPos = positions[(i + 1) % positions.length]; + const color = i < actualMakers ? makerColors[i % makerColors.length] : '#10B981'; + const fromHalf = i === 0 ? youHalf : makerHalf; + const toHalf = (i + 1) % positions.length === 0 ? youHalf : makerHalf; + const dx = nextPos.x - pos.x; + const dy = nextPos.y - pos.y; + const len = Math.sqrt(dx * dx + dy * dy); + const sx = pos.x + (dx / len) * (fromHalf + 4); + const sy = pos.y + (dy / len) * (fromHalf + 4); + const ex = nextPos.x - (dx / len) * (toHalf + 10); + const ey = nextPos.y - (dy / len) * (toHalf + 10); + return ``; + }).join('')} + + + + + You + Completed ✓ + + + + ${Array.from({ length: actualMakers }, (_, i) => { + const pos = positions[i + 1]; + const color = makerColors[i % makerColors.length]; + const addr = report.makerAddresses[i] || ''; + const shortAddr = addr ? truncateAddress(addr, 6, 4) : ''; + return ` + + M${i + 1} + ${actualMakers <= 10 ? ` + ${shortAddr} + ` : ''} + `; + }).join('')} + + ${isV2Swap ? ` + Taproot V2 + MuSig2 + ` : ''} +
`; } diff --git a/src/components/wallet/Wallet.js b/src/components/wallet/Wallet.js index ad7fa31..08dd5bc 100644 --- a/src/components/wallet/Wallet.js +++ b/src/components/wallet/Wallet.js @@ -53,6 +53,7 @@ export async function WalletComponent(container) { async function fetchBalance() { try { const data = await window.api.taker.getBalance(); + console.log('đŸ’ŗ Wallet balance API response:', data); if (data.success) { return data.balance; @@ -169,6 +170,7 @@ export async function WalletComponent(container) { try { // Use cached data if requested if (useCache && cached && cached.balance) { + console.log('đŸ’ŗ Wallet balance from cache:', cached.balance); content.querySelector('#regular-balance').textContent = satsToBtc(cached.balance.regular) + ' BTC'; content.querySelector('#swap-balance').textContent = @@ -183,6 +185,7 @@ export async function WalletComponent(container) { // Fetch fresh data const balance = await fetchBalance(); + console.log('đŸ’ŗ Wallet balance used by UI:', balance); content.querySelector('#regular-balance').textContent = satsToBtc(balance.regular) + ' BTC'; diff --git a/src/js/app.js b/src/js/app.js index 1052931..e8849e5 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -322,39 +322,193 @@ async function showPasswordPrompt(config) { * Used on launch so the user sees makers as soon as the app opens. */ async function performLaunchSync(onComplete) { + const escapeHtml = (value) => { + const div = document.createElement('div'); + div.textContent = value || ''; + return div.innerHTML; + }; + + const parseLogLine = (line) => { + const match = line.match( + /^(\d{4}-\d{2}-\d{2}T[\d:.]+)[^\s]*\s+(INFO|WARN|ERROR|DEBUG|TRACE)\s+(.+)$/ + ); + if (!match) { + return { + timestamp: Date.now(), + type: 'info', + message: line, + }; + } + + return { + timestamp: new Date(match[1]).getTime(), + type: match[2].toLowerCase(), + message: match[3], + }; + }; + + const getTypeColor = (type) => { + switch (type) { + case 'error': + return 'text-red-400'; + case 'warn': + return 'text-yellow-400'; + case 'debug': + return 'text-blue-400'; + case 'trace': + return 'text-purple-400'; + default: + return 'text-green-400'; + } + }; + + const formatTime = (timestamp) => + new Date(timestamp).toLocaleTimeString('en-US', { hour12: false }); + const overlay = document.createElement('div'); overlay.id = 'launch-sync-overlay'; overlay.className = 'fixed inset-0 bg-[#0f1419] flex items-center justify-center z-50'; overlay.innerHTML = ` -
-
+
+
+
âŗ +
+

Syncing Market Data

+

Discovering available makers via Tor. This may take a minute...

+

Starting offerbook sync...

+
+ +
+
-

Syncing Market Data

-

Discovering available makers via Tor. This may take a minute...

-
-
+ +
+
+
+

Live sync logs

+

Latest coinswap and backend activity during startup

+
+ 0 lines +
+
`; document.body.appendChild(overlay); + const logOutput = overlay.querySelector('#launch-sync-log-output'); + const logCount = overlay.querySelector('#launch-sync-log-count'); + const statusLabel = overlay.querySelector('#launch-sync-status'); + const progressBar = overlay.querySelector('#launch-sync-progress'); + const syncStartedAt = Date.now(); + let logPoll = null; + + function renderLogs(logs) { + if (!logOutput) return; + + if (logCount) { + logCount.textContent = `${logs.length} ${logs.length === 1 ? 'line' : 'lines'}`; + } + + if (logs.length === 0) { + logOutput.innerHTML = + '
Waiting for startup logs...
'; + return; + } + + logOutput.innerHTML = logs + .map((log) => { + return ` +
+ [${formatTime(log.timestamp)}] + [${log.type.toUpperCase()}] + ${escapeHtml(log.message)} +
+ `; + }) + .join(''); + logOutput.scrollTop = logOutput.scrollHeight; + } + + async function refreshLaunchLogs() { + try { + const data = await window.api.logs.get(120); + if (!data.success || !Array.isArray(data.logs)) return; + + const recentLogs = data.logs + .map(parseLogLine) + .filter((log) => log.timestamp >= syncStartedAt - 5000) + .slice(-50); + + renderLogs(recentLogs); + } catch (error) { + console.error('Failed to refresh launch sync logs:', error); + } + } + try { + await refreshLaunchLogs(); + logPoll = setInterval(refreshLaunchLogs, 1500); + const syncResult = await window.api.taker.syncOfferbookAndWait(); if (syncResult.success) { + if (statusLabel) statusLabel.textContent = 'Offerbook sync in progress...'; + if (progressBar) progressBar.style.width = '52%'; + const syncId = syncResult.syncId; await new Promise((resolve) => { const poll = setInterval(async () => { try { const status = await window.api.taker.getSyncStatus(syncId); - const done = !status.success || status.sync.status === 'completed' || status.sync.status === 'failed'; - if (done) { clearInterval(poll); resolve(); } + const sync = status.sync || {}; + const syncStatus = sync.status || 'syncing'; + const syncMessage = + sync.message || + (syncStatus === 'completed' + ? 'Offerbook ready' + : syncStatus === 'failed' + ? 'Sync failed' + : 'Syncing offerbook...'); + + if (statusLabel) statusLabel.textContent = syncMessage; + if (progressBar) { + const nextWidth = + typeof sync.progress === 'number' + ? Math.max(18, Math.min(100, sync.progress)) + : syncStatus === 'completed' + ? 100 + : syncStatus === 'failed' + ? 100 + : 76; + progressBar.style.width = `${nextWidth}%`; + } + + const done = + !status.success || + syncStatus === 'completed' || + syncStatus === 'failed'; + if (done) { + clearInterval(poll); + resolve(); + } } catch { clearInterval(poll); resolve(); } }, 1000); }); + } else { + if (statusLabel) { + statusLabel.textContent = + syncResult.error || 'Unable to start offerbook sync'; + } + if (progressBar) progressBar.style.width = '100%'; } } catch (err) { console.warn('âš ī¸ Launch offerbook sync error:', err.message); + if (statusLabel) statusLabel.textContent = 'Offerbook sync hit an issue'; + if (progressBar) progressBar.style.width = '100%'; + } finally { + if (logPoll) clearInterval(logPoll); + await refreshLaunchLogs(); } overlay.remove(); diff --git a/src/styles/output.css b/src/styles/output.css index 612b70f..850dc38 100644 --- a/src/styles/output.css +++ b/src/styles/output.css @@ -28,7 +28,6 @@ --color-blue-400: oklch(70.7% 0.165 254.624); --color-blue-500: oklch(62.3% 0.214 259.815); --color-blue-600: oklch(54.6% 0.245 262.881); - --color-purple-200: oklch(90.2% 0.063 306.703); --color-purple-300: oklch(82.7% 0.119 306.383); --color-purple-400: oklch(71.4% 0.203 305.504); --color-purple-500: oklch(62.7% 0.265 303.9); @@ -44,8 +43,8 @@ --spacing: 0.25rem; --container-md: 28rem; --container-2xl: 42rem; + --container-3xl: 48rem; --container-5xl: 64rem; - --container-6xl: 72rem; --container-7xl: 80rem; --text-xs: 0.75rem; --text-xs--line-height: calc(1 / 0.75); @@ -67,13 +66,10 @@ --font-weight-medium: 500; --font-weight-semibold: 600; --font-weight-bold: 700; - --font-weight-black: 900; --tracking-wide: 0.025em; - --tracking-wider: 0.05em; --leading-relaxed: 1.625; --radius-lg: 0.5rem; --radius-xl: 0.75rem; - --radius-2xl: 1rem; --ease-out: cubic-bezier(0, 0, 0.2, 1); --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); --animate-spin: spin 1s linear infinite; @@ -437,8 +433,8 @@ .h-20 { height: calc(var(--spacing) * 20); } - .h-40 { - height: calc(var(--spacing) * 40); + .h-36 { + height: calc(var(--spacing) * 36); } .h-64 { height: calc(var(--spacing) * 64); @@ -452,6 +448,9 @@ .h-screen { height: 100vh; } + .max-h-28 { + max-height: calc(var(--spacing) * 28); + } .max-h-40 { max-height: calc(var(--spacing) * 40); } @@ -515,12 +514,12 @@ .max-w-2xl { max-width: var(--container-2xl); } + .max-w-3xl { + max-width: var(--container-3xl); + } .max-w-5xl { max-width: var(--container-5xl); } - .max-w-6xl { - max-width: var(--container-6xl); - } .max-w-7xl { max-width: var(--container-7xl); } @@ -530,18 +529,15 @@ .max-w-\[200px\] { max-width: 200px; } + .max-w-full { + max-width: 100%; + } .max-w-md { max-width: var(--container-md); } .min-w-0 { min-width: calc(var(--spacing) * 0); } - .min-w-\[180px\] { - min-width: 180px; - } - .min-w-max { - min-width: max-content; - } .flex-1 { flex: 1; } @@ -588,9 +584,6 @@ .grid-cols-8 { grid-template-columns: repeat(8, minmax(0, 1fr)); } - .grid-cols-\[1\.7fr_0\.9fr_1fr_1fr_1fr_1\.25fr\] { - grid-template-columns: 1.7fr 0.9fr 1fr 1fr 1fr 1.25fr; - } .flex-col { flex-direction: column; } @@ -615,6 +608,9 @@ .gap-1 { gap: calc(var(--spacing) * 1); } + .gap-1\.5 { + gap: calc(var(--spacing) * 1.5); + } .gap-2 { gap: calc(var(--spacing) * 2); } @@ -717,11 +713,6 @@ border-color: var(--color-gray-700); } } - .divide-gray-800 { - :where(& > :not(:last-child)) { - border-color: var(--color-gray-800); - } - } .self-center { align-self: center; } @@ -796,6 +787,9 @@ .border-\[\#FF6B35\] { border-color: #FF6B35; } + .border-\[\#FF6B35\]\/10 { + border-color: color-mix(in oklab, #FF6B35 10%, transparent); + } .border-\[\#FF6B35\]\/20 { border-color: color-mix(in oklab, #FF6B35 20%, transparent); } @@ -844,6 +838,12 @@ .border-orange-400 { border-color: var(--color-orange-400); } + .border-orange-500\/20 { + border-color: color-mix(in srgb, oklch(70.5% 0.213 47.604) 20%, transparent); + @supports (color: color-mix(in lab, red, red)) { + border-color: color-mix(in oklab, var(--color-orange-500) 20%, transparent); + } + } .border-orange-500\/30 { border-color: color-mix(in srgb, oklch(70.5% 0.213 47.604) 30%, transparent); @supports (color: color-mix(in lab, red, red)) { @@ -865,12 +865,21 @@ .border-transparent { border-color: transparent; } + .border-white\/5 { + border-color: color-mix(in srgb, #fff 5%, transparent); + @supports (color: color-mix(in lab, red, red)) { + border-color: color-mix(in oklab, var(--color-white) 5%, transparent); + } + } .border-yellow-500\/30 { border-color: color-mix(in srgb, oklch(79.5% 0.184 86.047) 30%, transparent); @supports (color: color-mix(in lab, red, red)) { border-color: color-mix(in oklab, var(--color-yellow-500) 30%, transparent); } } + .bg-\[\#0a0f16\] { + background-color: #0a0f16; + } .bg-\[\#0f1419\] { background-color: #0f1419; } @@ -988,12 +997,21 @@ .bg-orange-500 { background-color: var(--color-orange-500); } + .bg-orange-500\/10 { + background-color: color-mix(in srgb, oklch(70.5% 0.213 47.604) 10%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-orange-500) 10%, transparent); + } + } .bg-orange-500\/20 { background-color: color-mix(in srgb, oklch(70.5% 0.213 47.604) 20%, transparent); @supports (color: color-mix(in lab, red, red)) { background-color: color-mix(in oklab, var(--color-orange-500) 20%, transparent); } } + .bg-purple-400 { + background-color: var(--color-purple-400); + } .bg-purple-500 { background-color: var(--color-purple-500); } @@ -1105,6 +1123,9 @@ .px-2 { padding-inline: calc(var(--spacing) * 2); } + .px-2\.5 { + padding-inline: calc(var(--spacing) * 2.5); + } .px-3 { padding-inline: calc(var(--spacing) * 3); } @@ -1243,6 +1264,10 @@ --tw-font-weight: var(--font-weight-semibold); font-weight: var(--font-weight-semibold); } + .tracking-\[0\.2em\] { + --tw-tracking: 0.2em; + letter-spacing: 0.2em; + } .tracking-wide { --tw-tracking: var(--tracking-wide); letter-spacing: var(--tracking-wide); @@ -1381,6 +1406,10 @@ --tw-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-black) 30%, transparent) var(--tw-shadow-alpha), transparent); } } + .blur { + --tw-blur: blur(8px); + filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); + } .filter { filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); } @@ -1586,6 +1615,16 @@ } } } + .hover\:bg-white\/5 { + &:hover { + @media (hover: hover) { + background-color: color-mix(in srgb, #fff 5%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-white) 5%, transparent); + } + } + } + } .hover\:text-\[\#10B981\] { &:hover { @media (hover: hover) { @@ -1621,6 +1660,13 @@ } } } + .hover\:text-gray-300 { + &:hover { + @media (hover: hover) { + color: var(--color-gray-300); + } + } + } .hover\:text-purple-300 { &:hover { @media (hover: hover) { @@ -1649,13 +1695,6 @@ } } } - .hover\:opacity-80 { - &:hover { - @media (hover: hover) { - opacity: 80%; - } - } - } .hover\:shadow-lg { &:hover { @media (hover: hover) { @@ -1748,12 +1787,6 @@ grid-template-columns: 1.5fr 1fr; } } - .xl\:text-2xl { - @media (width >= 80rem) { - font-size: var(--text-2xl); - line-height: var(--tw-leading, var(--text-2xl--line-height)); - } - } } @font-face { font-family: 'JetBrains Mono'; From cea2ec19212e431ee8dc99105159188a62da0d1b Mon Sep 17 00:00:00 2001 From: iAmKeralis1131f Date: Thu, 26 Mar 2026 13:36:48 +0530 Subject: [PATCH 2/2] swap report fix --- api1.js | 8 +- src/components/swap/Coinswap.js | 2 +- src/components/swap/SwapHistory.js | 56 +++++++--- src/components/swap/SwapReport.js | 162 +++++++++++++++++++++++------ src/js/app.js | 6 +- 5 files changed, 186 insertions(+), 48 deletions(-) diff --git a/api1.js b/api1.js index 8442b13..13ba75e 100644 --- a/api1.js +++ b/api1.js @@ -248,7 +248,13 @@ function normalizeBalancePayload(rawBalance = {}, rawUtxos = []) { // Recover that provenance by matching active UTXOs against saved swap reports. if (historicalSwapBalance > normalized.swap) { normalized.swap = historicalSwapBalance; - normalized.regular = Math.max(0, normalized.spendable - normalized.swap); + normalized.regular = Math.max( + 0, + normalized.spendable - + normalized.swap - + normalized.contract - + normalized.fidelity + ); } return { diff --git a/src/components/swap/Coinswap.js b/src/components/swap/Coinswap.js index 850d96d..7568aa7 100644 --- a/src/components/swap/Coinswap.js +++ b/src/components/swap/Coinswap.js @@ -1143,7 +1143,7 @@ export async function CoinswapComponent(container, swapConfig) { const sy = pos.y + (dy / len) * (fromHalf + 4); const ex = nextPos.x - (dx / len) * (toHalf + 10); const ey = nextPos.y - (dy / len) * (toHalf + 10); - return ` + return ` `; diff --git a/src/components/swap/SwapHistory.js b/src/components/swap/SwapHistory.js index d1166ad..b2cc9de 100644 --- a/src/components/swap/SwapHistory.js +++ b/src/components/swap/SwapHistory.js @@ -86,9 +86,25 @@ function normalizeSwapReport(report) { return { id: report.swapId || nested.swapId || `swap_${Date.now()}`, completedAt, - amount: report.amount || nested.amount || nested.targetAmount || 0, - totalOutputAmount: - nested.totalOutputAmount || nested.total_output_amount || 0, + amount: + toNumber( + report.amount ?? + nested.amount ?? + nested.targetAmount ?? + nested.target_amount ?? + nested.incomingAmount ?? + nested.incoming_amount, + 0 + ), + totalOutputAmount: toNumber( + nested.totalOutputAmount ?? + nested.total_output_amount ?? + nested.outgoingAmount ?? + nested.outgoing_amount ?? + report.totalOutputAmount ?? + report.total_output_amount, + 0 + ), makersCount, hops: makersCount + 1, totalFee, @@ -126,24 +142,36 @@ export async function loadSwapHistory() { report: normalized.report, }; }); + } else { + swapHistory = []; + throw new Error(result.error || 'Failed to load swap history'); } } catch (error) { + swapHistory = []; console.error('Failed to load swap history:', error); + throw error; } return swapHistory; } export function summarizeSwapHistory(history) { const totalSwaps = history.length; - const totalAmount = history.reduce((sum, s) => sum + (s.amount || 0), 0); - const totalFees = history.reduce((sum, s) => sum + (s.totalFee || 0), 0); + const totalAmount = history.reduce( + (sum, s) => sum + (Number(s.amount) || 0), + 0 + ); + const totalFees = history.reduce( + (sum, s) => sum + (Number(s.totalFee) || 0), + 0 + ); const avgFeePaid = totalSwaps > 0 ? Math.round(totalFees / totalSwaps) : 0; return { totalSwaps, totalAmount, totalFees, avgFeePaid }; } function satsToBtc(sats) { - if (typeof sats !== 'number' || isNaN(sats)) return '0.00000000'; - return (sats / 100000000).toFixed(8); + const normalized = Number(sats); + if (!Number.isFinite(normalized)) return '0.00000000'; + return (normalized / 100000000).toFixed(8); } export function buildSwapHistoryMarkup(history) { @@ -161,8 +189,12 @@ export function buildSwapHistoryMarkup(history) {
${history .map((swap) => { - const btcAmount = satsToBtc(swap.amount); - const outputBtc = satsToBtc(swap.totalOutputAmount); + const amount = Number(swap.amount) || 0; + const totalOutputAmount = Number(swap.totalOutputAmount) || 0; + const feePercentage = Number(swap.feePercentage) || 0; + const totalFee = Number(swap.totalFee) || 0; + const btcAmount = satsToBtc(amount); + const outputBtc = satsToBtc(totalOutputAmount); const timeAgo = formatRelativeTime(swap.completedAt); const dateStr = formatDate(swap.completedAt); const duration = formatDuration(swap.durationSeconds); @@ -189,7 +221,7 @@ export function buildSwapHistoryMarkup(history) {
${btcAmount} BTC
-
${swap.amount.toLocaleString()} sats
+
${amount.toLocaleString()} sats
@@ -204,11 +236,11 @@ export function buildSwapHistoryMarkup(history) {
Fee -

${swap.feePercentage?.toFixed(2) || '0.00'}%

+

${feePercentage.toFixed(2)}%

Total Fee -

${(swap.totalFee || 0).toLocaleString()} sats

+

${totalFee.toLocaleString()} sats

Output diff --git a/src/components/swap/SwapReport.js b/src/components/swap/SwapReport.js index 60f223b..d6dc1ac 100644 --- a/src/components/swap/SwapReport.js +++ b/src/components/swap/SwapReport.js @@ -25,21 +25,35 @@ export function SwapReportComponent(container, swapReport) { const normalized = Number(value); return Number.isFinite(normalized) ? normalized : fallback; }; + const nestedReport = swapReport.report || {}; const rawTotalMakerFees = toNumber( - swapReport.totalMakerFees ?? swapReport.total_maker_fees, + swapReport.totalMakerFees ?? + swapReport.total_maker_fees ?? + nestedReport.totalMakerFees ?? + nestedReport.total_maker_fees, 0 ); const rawMiningFee = toNumber( - swapReport.miningFee ?? swapReport.mining_fee, + swapReport.miningFee ?? + swapReport.mining_fee ?? + nestedReport.miningFee ?? + nestedReport.mining_fee, 0 ); const rawFeePaidOrEarned = toNumber( - swapReport.fee_paid_or_earned ?? swapReport.feePaidOrEarned, + swapReport.fee_paid_or_earned ?? + swapReport.feePaidOrEarned ?? + nestedReport.fee_paid_or_earned ?? + nestedReport.feePaidOrEarned ?? + nestedReport.feePaidOrEarned, NaN ); const providedTotalFee = toNumber( - swapReport.totalFee ?? swapReport.total_fee, + swapReport.totalFee ?? + swapReport.total_fee ?? + nestedReport.totalFee ?? + nestedReport.total_fee, NaN ); const derivedTotalFee = Number.isFinite(rawFeePaidOrEarned) @@ -52,39 +66,115 @@ export function SwapReportComponent(container, swapReport) { : derivedTotalFee; // Extract values with safe defaults + const normalizedFundingTxids = + swapReport.fundingTxidsByHop || + swapReport.funding_txids_by_hop || + nestedReport.fundingTxidsByHop || + nestedReport.funding_txids_by_hop || + nestedReport.fundingTxids || + nestedReport.funding_txids || + []; + const normalizedTargetAmount = toNumber( + swapReport.targetAmount ?? + swapReport.target_amount ?? + nestedReport.targetAmount ?? + nestedReport.target_amount ?? + swapReport.amount ?? + nestedReport.incomingAmount ?? + nestedReport.incoming_amount ?? + nestedReport.outgoingAmount ?? + nestedReport.outgoing_amount, + 0 + ); + const normalizedTotalFundingTxs = toNumber( + swapReport.totalFundingTxs ?? + swapReport.total_funding_txs ?? + nestedReport.totalFundingTxs ?? + nestedReport.total_funding_txs, + Array.isArray(normalizedFundingTxids) ? normalizedFundingTxids.length : 0 + ); + const normalizedFeePercentage = toNumber( + swapReport.feePercentage ?? + swapReport.fee_percentage ?? + nestedReport.feePercentage ?? + nestedReport.fee_percentage, + normalizedTargetAmount > 0 ? (rawTotalFee / normalizedTargetAmount) * 100 : 0 + ); + const report = { swapId: swapReport.swapId || swapReport.swap_id || 'unknown', swapDurationSeconds: - swapReport.swapDurationSeconds || swapReport.swap_duration_seconds || 0, - targetAmount: swapReport.targetAmount || swapReport.target_amount || 0, + toNumber( + swapReport.swapDurationSeconds ?? + swapReport.swap_duration_seconds ?? + nestedReport.swapDurationSeconds ?? + nestedReport.swap_duration_seconds, + 0 + ), + targetAmount: normalizedTargetAmount, totalInputAmount: - swapReport.totalInputAmount || swapReport.total_input_amount || 0, + toNumber( + swapReport.totalInputAmount ?? + swapReport.total_input_amount ?? + nestedReport.totalInputAmount ?? + nestedReport.total_input_amount, + normalizedTargetAmount + ), totalOutputAmount: - swapReport.totalOutputAmount || swapReport.total_output_amount || 0, - makersCount: swapReport.makersCount || swapReport.makers_count || 0, + toNumber( + swapReport.totalOutputAmount ?? + swapReport.total_output_amount ?? + nestedReport.totalOutputAmount ?? + nestedReport.total_output_amount ?? + nestedReport.outgoingAmount ?? + nestedReport.outgoing_amount, + 0 + ), + makersCount: toNumber( + swapReport.makersCount ?? + swapReport.makers_count ?? + nestedReport.makersCount ?? + nestedReport.makers_count, + 0 + ), makerAddresses: - swapReport.makerAddresses || swapReport.maker_addresses || [], - totalFundingTxs: - swapReport.totalFundingTxs || swapReport.total_funding_txs || 0, - fundingTxidsByHop: - swapReport.fundingTxidsByHop || swapReport.funding_txids_by_hop || [], + swapReport.makerAddresses || + swapReport.maker_addresses || + nestedReport.makerAddresses || + nestedReport.maker_addresses || + [], + totalFundingTxs: normalizedTotalFundingTxs, + fundingTxidsByHop: normalizedFundingTxids, totalFee: rawTotalFee, totalMakerFees: rawTotalMakerFees, miningFee: rawMiningFee, - feePercentage: - swapReport.feePercentage || - swapReport.fee_percentage || - ((swapReport.targetAmount ?? swapReport.target_amount ?? 0) > 0 - ? (rawTotalFee / - (swapReport.targetAmount ?? swapReport.target_amount ?? 0)) * - 100 - : 0), - makerFeeInfo: swapReport.makerFeeInfo || swapReport.maker_fee_info || [], - inputUtxos: swapReport.inputUtxos || swapReport.input_utxos || [], + feePercentage: normalizedFeePercentage, + makerFeeInfo: + swapReport.makerFeeInfo || + swapReport.maker_fee_info || + nestedReport.makerFeeInfo || + nestedReport.maker_fee_info || + [], + inputUtxos: + swapReport.inputUtxos || + swapReport.input_utxos || + nestedReport.inputUtxos || + nestedReport.input_utxos || + [], outputRegularUtxos: - swapReport.outputRegularUtxos || swapReport.output_regular_utxos || [], + swapReport.outputRegularUtxos || + swapReport.output_regular_utxos || + nestedReport.outputRegularUtxos || + nestedReport.output_regular_utxos || + nestedReport.outputChangeUtxos || + nestedReport.output_change_utxos || + [], outputSwapUtxos: - swapReport.outputSwapUtxos || swapReport.output_swap_utxos || [], + swapReport.outputSwapUtxos || + swapReport.output_swap_utxos || + nestedReport.outputSwapUtxos || + nestedReport.output_swap_utxos || + [], sweepTxid: swapReport.sweep_txid || swapReport.sweepTxid || @@ -475,7 +565,13 @@ export function SwapReportComponent(container, swapReport) { // Build swap circuit visualization (circular SVG) function buildCircularFlowHtml() { - const actualMakers = report.makersCount || report.makerAddresses.length; + const makersCount = Number(report.makersCount); + const actualMakers = Math.max( + 0, + Number.isFinite(makersCount) && makersCount > 0 + ? makersCount + : report.makerAddresses.length + ); const totalNodes = actualMakers + 1; // +1 for You // Dynamic node sizing @@ -738,9 +834,13 @@ export function SwapReportComponent(container, swapReport) { 50% { opacity: 1; } } +.maker-node { + transform-box: fill-box; + transform-origin: center; +} + .maker-node:hover { - z-index: 20 !important; - transform: scale(1.15) !important; + filter: brightness(1.08); } .animate-fade-in-up { animation: fadeInUp 0.6s ease-out forwards; } @@ -754,10 +854,6 @@ export function SwapReportComponent(container, swapReport) { transform: scale(1.02); } - .maker-node:hover { - z-index: 10; - } - /* Tooltip styles */ .tooltip-trigger { position: relative; diff --git a/src/js/app.js b/src/js/app.js index e8849e5..c5ef83e 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -492,7 +492,11 @@ async function performLaunchSync(onComplete) { clearInterval(poll); resolve(); } - } catch { clearInterval(poll); resolve(); } + } catch (err) { + console.error('Sync polling error:', err); + clearInterval(poll); + resolve(); + } }, 1000); }); } else {