diff --git a/api1.js b/api1.js index e9b69a3..7f07e9a 100644 --- a/api1.js +++ b/api1.js @@ -1921,6 +1921,23 @@ function registerTorHandlers() { // MAIN REGISTRATION FUNCTION // ============================================================================ +function registerAppHandlers() { + ipcMain.handle('app:getVersionInfo', async () => { + try { + const appVersion = app.getVersion(); + let binaryVersion = 'unknown'; + try { + const napiPkgPath = require.resolve('coinswap-napi/package.json'); + const napiPkg = JSON.parse(fs.readFileSync(napiPkgPath, 'utf8')); + binaryVersion = napiPkg.version || 'unknown'; + } catch (_) {} + return { success: true, appVersion, binaryVersion }; + } catch (error) { + return { success: false, error: error.message }; + } + }); +} + function registerAPI1() { console.log('📦 Registering API v1 handlers...'); @@ -1932,6 +1949,7 @@ function registerAPI1() { registerDialogHandlers(); registerShellHandlers(); registerTorHandlers(); + registerAppHandlers(); console.log('✅ API v1 handlers registered'); } diff --git a/preload.js b/preload.js index 7f1e396..b45af78 100644 --- a/preload.js +++ b/preload.js @@ -68,6 +68,10 @@ contextBridge.exposeInMainWorld('api', { set: (key, value) => ipcRenderer.invoke('preferences:set', key, value), }, + app: { + getVersionInfo: () => ipcRenderer.invoke('app:getVersionInfo'), + }, + // File dialogs - TOP LEVEL, NOT INSIDE TAKER! openFile: (options) => ipcRenderer.invoke('dialog:openFile', options), saveFile: (options) => ipcRenderer.invoke('dialog:saveFile', options), diff --git a/src/components/Nav.js b/src/components/Nav.js index 74c8291..4cb715c 100644 --- a/src/components/Nav.js +++ b/src/components/Nav.js @@ -99,6 +99,13 @@ export async function NavComponent(container) { Settings + + + + + + About + `; diff --git a/src/components/about/About.js b/src/components/about/About.js new file mode 100644 index 0000000..25e5d80 --- /dev/null +++ b/src/components/about/About.js @@ -0,0 +1,50 @@ +export async function AboutComponent(container) { + container.innerHTML = ` +
+ + +
+
+

Coinswap

+

Taker Wallet — Bitcoin Privacy Tool

+
+
+ Version + Loading... +
+
+ + +
+

About Coinswap

+

+ Coinswap is a trustless, self-custodial atomic swap protocol built on Bitcoin. Unlike existing solutions + that rely on centralized servers, Coinswap's marketplace is seeded in the Bitcoin blockchain itself — + no central host required. Sybil resistance is achieved through + Fidelity Bonds: time-locked UTXOs that make Sybil attacks + economically costly while bootstrapping the marketplace on-chain. +

+

+ There are two roles. Makers are swap service providers who + earn fees for supplying liquidity and run in an install-fund-forget mode — no active management needed. + Takers (this app) initiate swaps, pay all fees, and select + makers based on bond validity, available liquidity, and fee rates. +

+

+ Swaps are routed through multiple makers — no single maker sees the full route. The taker relays all + messages between makers over Tor, keeping each maker's view partial. This app is a production-grade + implementation of Chris Belcher's teleport-transactions proof-of-concept. +

+
+ +
+ `; + + try { + const result = await window.api.app.getVersionInfo(); + const version = result.success ? `v${result.appVersion}` : 'unavailable'; + container.querySelector('#app-version').textContent = version; + } catch (_) { + container.querySelector('#app-version').textContent = 'unavailable'; + } +} diff --git a/src/components/connection/ConnectionStatus.js b/src/components/connection/ConnectionStatus.js index 403a5c6..322f4f5 100644 --- a/src/components/connection/ConnectionStatus.js +++ b/src/components/connection/ConnectionStatus.js @@ -228,7 +228,16 @@ export function ConnectionStatusComponent(container, onConnected) { try { const result = await bitcoindConnection.connect(); - if (!result.success) { + if (result.alreadyConnected) { + // Connection existed before our patch was in place — verify and proceed + const check = await bitcoindConnection.testConnection(); + if (check.success) { + showSuccess(check.info); + } else { + bitcoindConnection.disconnect(); + startConnection(); // retry from scratch + } + } else if (!result.success) { showError(result.finalError || result.error); } } catch (error) { diff --git a/src/components/taker/TakerInitialization.js b/src/components/taker/TakerInitialization.js index b27a5be..b60b394 100644 --- a/src/components/taker/TakerInitialization.js +++ b/src/components/taker/TakerInitialization.js @@ -30,12 +30,6 @@ export function TakerInitializationComponent(container, config, onInitialized) { Initializing taker (creates wallet) -
-
- 3 -
- Syncing offerbook -
@@ -269,31 +263,6 @@ export function TakerInitializationComponent(container, config, onInitialized) { } updateStep('step-taker', 'complete', 'Taker ready (wallet loaded)'); - updateProgress(66, 'Syncing offerbook...'); - updateStep('step-offerbook', 'active', 'Syncing offerbook...'); - - const syncResult = await window.api.taker.syncOfferbookAndWait(); - if (!syncResult.success) { - console.warn('⚠️ Offerbook sync failed on launch:', syncResult.error); - updateStep('step-offerbook', 'error', 'Sync failed (will retry later)'); - } else { - 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); - const ok = status.success && status.sync.status === 'completed'; - updateStep('step-offerbook', ok ? 'complete' : 'error', ok ? 'Offerbook ready' : 'Sync failed (will retry later)'); - resolve(); - } - } catch { clearInterval(poll); updateStep('step-offerbook', 'error', 'Sync failed (will retry later)'); resolve(); } - }, 1000); - }); - } - updateProgress(100, 'Initialization complete'); showSuccess(); } catch (error) { diff --git a/src/js/app.js b/src/js/app.js index 7c5e0d4..11d008a 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -7,6 +7,7 @@ import { SwapComponent } from '../components/swap/Swap.js'; import { RecoveryComponent } from '../components/recovery/Recovery.js'; import { LogComponent } from '../components/log/Log.js'; import { SettingsComponent } from '../components/settings/Settings.js'; +import { AboutComponent } from '../components/about/About.js'; import { FirstTimeSetupModal } from '../components/settings/FirstTimeSetup.js'; import { SwapStateManager } from '../components/swap/SwapStateManager.js'; import { ConnectionStatusComponent } from '../components/connection/ConnectionStatus.js'; @@ -23,6 +24,7 @@ const components = { recovery: RecoveryComponent, log: LogComponent, settings: SettingsComponent, + about: AboutComponent, }; // Background swap manager - runs independently of UI components @@ -133,70 +135,17 @@ function setupNavigation() { async function checkBitcoindConnection(config) { console.log('🔌 Checking Bitcoin Core connection...'); - // Update the connection manager with config from setup if (config) { bitcoindConnection.updateConfig(config); } - // Show connection status component const appContainer = document.querySelector('body'); ConnectionStatusComponent(appContainer, (connectionInfo) => { console.log('✅ Bitcoin Core connected, starting app...', connectionInfo); - checkTakerInitialization(config); + startTakerInitWithConfig(config); }); } -async function checkTakerInitialization(config) { - console.log('🔄 Checking Taker initialization...'); - - if (!config || !config.rpc) { - console.log('⚠️ RPC configuration missing, skipping taker initialization'); - startMainApp(); - return; - } - - try { - // Extract wallet name from config - const walletName = - config.wallet?.name || config.wallet?.fileName || 'taker-wallet'; - - console.log('🔍 Checking wallet:', walletName); - - // Store it in config so showPasswordPrompt can use it - if (!config.wallet.name && !config.wallet.fileName) { - config.wallet.name = walletName; - } - - // Check if wallet file is encrypted - const isEncrypted = await window.api.taker.isWalletEncrypted( - null, - walletName - ); - console.log('🔐 Wallet file encrypted:', isEncrypted); - - if (isEncrypted) { - console.log('🔓 Wallet is encrypted, showing password prompt...'); - await showPasswordPrompt(config); // ✅ Config has correct wallet name - } else { - console.log('🔓 Wallet is not encrypted'); - - const result = await window.api.taker.initialize(config); - - if (result.success) { - console.log('✅ Taker initialized'); - startMainApp(); - startBackgroundOfferbookSync(); - } else { - console.error('❌ Taker initialization failed:', result.error); - alert('Failed to initialize: ' + result.error); - } - } - } catch (error) { - console.error('❌ Initialization check failed:', error); - alert('Initialization failed: ' + error.message); - } -} - function startTakerInitWithConfig(config) { const appContainer = document.querySelector('body'); TakerInitializationComponent(appContainer, config, (result) => { @@ -349,6 +298,7 @@ async function startBackgroundOfferbookSync() { } } + // Start the main app after bitcoind connection is established async function startMainApp() { const activeSwap = await SwapStateManager.getActiveSwap(); diff --git a/src/styles/output.css b/src/styles/output.css index cc7042a..da76777 100644 --- a/src/styles/output.css +++ b/src/styles/output.css @@ -45,6 +45,7 @@ --spacing: 0.25rem; --container-md: 28rem; --container-2xl: 42rem; + --container-3xl: 48rem; --container-5xl: 64rem; --container-7xl: 80rem; --text-xs: 0.75rem; @@ -66,6 +67,7 @@ --font-weight-semibold: 600; --font-weight-bold: 700; --tracking-wide: 0.025em; + --leading-relaxed: 1.625; --radius-lg: 0.5rem; --radius-xl: 0.75rem; --ease-out: cubic-bezier(0, 0, 0.2, 1); @@ -515,6 +517,9 @@ .max-w-2xl { max-width: var(--container-2xl); } + .max-w-3xl { + max-width: var(--container-3xl); + } .max-w-5xl { max-width: var(--container-5xl); } @@ -588,9 +593,6 @@ .grid-cols-7 { grid-template-columns: repeat(7, minmax(0, 1fr)); } - .grid-cols-8 { - grid-template-columns: repeat(8, minmax(0, 1fr)); - } .flex-col { flex-direction: column; } @@ -1252,6 +1254,10 @@ --tw-leading: 1; line-height: 1; } + .leading-relaxed { + --tw-leading: var(--leading-relaxed); + line-height: var(--leading-relaxed); + } .font-bold { --tw-font-weight: var(--font-weight-bold); font-weight: var(--font-weight-bold);