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);